doodle/pkg/balance/filesystem.go
Noah Petherbridge f76ba6fbb7 WIP: MsgPack stubs, Level Filesystem Module
* Add some encoding/decoding functions for binary msgpack format for
  levels and doodads. Currently it writes msgpack files that can be
  decoded and printed by Python (mp2json.py) but it can't re-read from
  the binary format. For now, levels will continue to write in JSON
  format.
* Add filesystem abstraction functions to the balance/ package to search
  multiple paths to find Levels and Doodads, to make way for
  system-level doodads.
2019-05-06 12:41:46 -07:00

119 lines
3.0 KiB
Go

package balance
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"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
)
// 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
// 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
candidate := filepath.Join(".", "assets", "levels", filename)
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 == "" {
// system doodads
candidate := filepath.Join(".", "assets", "doodads", filename)
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
}
}
return "", errors.New("file not found")
}