2019-05-05 22:12:15 +00:00
|
|
|
package filesystem
|
2019-05-05 21:03:20 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-06-27 22:07:34 +00:00
|
|
|
"runtime"
|
2019-05-05 21:03:20 +00:00
|
|
|
"strings"
|
|
|
|
|
2019-06-27 22:07:34 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/bindata"
|
2019-05-05 21:03:20 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/enum"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Binary file format headers for Levels and Doodads.
|
|
|
|
//
|
|
|
|
// The header is 8 bytes long: "DOODLE" + file format version + file type number.
|
|
|
|
const (
|
|
|
|
BinMagic = "DOODLE"
|
|
|
|
BinVersion uint8 = 1 // version of the file format we support
|
|
|
|
BinLevelType uint8 = 1
|
|
|
|
BinDoodadType uint8 = 2
|
|
|
|
)
|
|
|
|
|
2019-05-05 22:12:15 +00:00
|
|
|
// Paths to system-level assets bundled with the application.
|
|
|
|
var (
|
2020-04-22 06:50:45 +00:00
|
|
|
SystemDoodadsPath = filepath.Join("assets", "doodads")
|
|
|
|
SystemLevelsPath = filepath.Join("assets", "levels")
|
|
|
|
SystemCampaignsPath = filepath.Join("assets", "campaigns")
|
2019-05-05 22:12:15 +00:00
|
|
|
)
|
|
|
|
|
2019-05-05 21:03:20 +00:00
|
|
|
// MakeHeader creates the binary file header.
|
|
|
|
func MakeHeader(filetype uint8) []byte {
|
|
|
|
header := make([]byte, len(BinMagic)+2)
|
|
|
|
for i := 0; i < len(BinMagic); i++ {
|
|
|
|
header[i] = BinMagic[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
header[len(header)-2] = BinVersion
|
|
|
|
header[len(header)-1] = filetype
|
|
|
|
|
|
|
|
return header
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadHeader reads and verifies a header from a filehandle.
|
|
|
|
func ReadHeader(filetype uint8, fh io.Reader) error {
|
|
|
|
header := make([]byte, len(BinMagic)+2)
|
|
|
|
_, err := fh.Read(header)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("ReadHeader: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if string(header[:len(BinMagic)]) != BinMagic {
|
|
|
|
return errors.New("not a doodle drawing (no magic number in header)")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the file format version and type.
|
|
|
|
var (
|
|
|
|
fileVersion = header[len(header)-2]
|
|
|
|
fileType = header[len(header)-1]
|
|
|
|
)
|
|
|
|
|
|
|
|
if fileVersion == 0 || fileVersion > BinVersion {
|
|
|
|
return errors.New("binary format was created using a newer version of the game")
|
|
|
|
} else if fileType != filetype {
|
|
|
|
return errors.New("drawing type is not the type we expected")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
FindFile looks for a file (level or doodad) in a few places.
|
|
|
|
|
|
|
|
The filename should already have a ".level" or ".doodad" file extension. If
|
|
|
|
neither is given, the exact filename will be searched in all places.
|
|
|
|
|
|
|
|
1. Check in the files built into the program binary.
|
|
|
|
2. Check for system files in the binary's assets/ folder.
|
|
|
|
3. Check the user folders.
|
|
|
|
|
|
|
|
Returns the file path and an error if not found anywhere.
|
|
|
|
*/
|
|
|
|
func FindFile(filename string) (string, error) {
|
|
|
|
var filetype string
|
|
|
|
|
2019-07-07 03:31:50 +00:00
|
|
|
// If the filename has path separators, return it as-is.
|
2021-07-11 19:31:39 +00:00
|
|
|
if strings.ContainsRune(filename, filepath.Separator) ||
|
|
|
|
strings.ContainsRune(filename, '/') {
|
2019-07-07 03:31:50 +00:00
|
|
|
return filename, nil
|
|
|
|
}
|
|
|
|
|
2019-05-05 21:03:20 +00:00
|
|
|
// Any hint on what type of file we're looking for?
|
|
|
|
if strings.HasSuffix(filename, enum.LevelExt) {
|
|
|
|
filetype = enum.LevelExt
|
|
|
|
} else if strings.HasSuffix(filename, enum.DoodadExt) {
|
|
|
|
filetype = enum.DoodadExt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search level directories.
|
|
|
|
if filetype == enum.LevelExt || filetype == "" {
|
|
|
|
// system levels
|
2019-05-05 22:12:15 +00:00
|
|
|
candidate := filepath.Join(SystemLevelsPath, filename)
|
2019-06-27 22:07:34 +00:00
|
|
|
|
|
|
|
// embedded system doodad?
|
|
|
|
if _, err := bindata.Asset(candidate); err == nil {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WASM: can't check the filesystem. Let the caller go ahead and try
|
|
|
|
// loading via ajax request.
|
|
|
|
if runtime.GOOS == "js" {
|
2019-06-27 22:59:18 +00:00
|
|
|
return filename, nil
|
2019-06-27 22:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// external system level?
|
2019-05-05 21:03:20 +00:00
|
|
|
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// user levels
|
|
|
|
candidate = userdir.LevelPath(filename)
|
|
|
|
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search doodad directories.
|
|
|
|
if filetype == enum.DoodadExt || filetype == "" {
|
2019-06-27 22:07:34 +00:00
|
|
|
// system doodads path
|
2019-05-05 22:12:15 +00:00
|
|
|
candidate := filepath.Join(SystemDoodadsPath, filename)
|
2019-06-27 22:07:34 +00:00
|
|
|
|
|
|
|
// embedded system doodad?
|
|
|
|
if _, err := bindata.Asset(candidate); err == nil {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WASM: can't check the filesystem. Let the caller go ahead and try
|
|
|
|
// loading via ajax request.
|
|
|
|
if runtime.GOOS == "js" {
|
2019-06-27 22:59:18 +00:00
|
|
|
return filename, nil
|
2019-06-27 22:07:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// external system doodad?
|
2019-05-05 21:03:20 +00:00
|
|
|
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// user doodads
|
|
|
|
candidate = userdir.DoodadPath(filename)
|
|
|
|
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-05 22:12:15 +00:00
|
|
|
return filename, errors.New("file not found")
|
2019-05-05 21:03:20 +00:00
|
|
|
}
|