Files
ordoor/internal/data/object.go

185 lines
4.1 KiB
Go
Raw Normal View History

package data
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
2018-03-21 05:08:24 +00:00
type SpriteHeader struct {
Unknown0 uint32
Width uint16 // FIXME: I'm not certain this is what these are.
Height uint16 // FIXME: If they are, they may be the wrong way around
Padding1 uint32 // I don't think this is used. Could be wrong.
2018-03-21 05:08:24 +00:00
PixelSize uint32 // Size of PixelData, excluding this sprite header
Padding2 uint64 // I don't think this is used either. Could be wrong.
}
2018-03-21 05:08:24 +00:00
func (s SpriteHeader) Check(expectedSize uint32) error {
if s.Padding1 != 0 || s.Padding2 != 0 {
return fmt.Errorf("Sprite header padding contains unknown values: %d %d", s.Padding1, s.Padding2)
}
// Remove 24 bytes from passed-in size to account for the header
2018-03-21 05:08:24 +00:00
if s.PixelSize != expectedSize-24 {
return fmt.Errorf("Advertised pixel size: %d differs from expected: %v", s.PixelSize, expectedSize-24)
}
return nil
}
2018-03-21 05:08:24 +00:00
type Sprite struct {
SpriteHeader
PixelData []byte
}
2018-03-21 05:08:24 +00:00
type dirEntry struct {
Offset uint32 // Offset of the sprite relative to the data block
Size uint32 // Size of the sprite in bytes, including any header
}
2018-03-21 05:08:24 +00:00
func (d dirEntry) Check() error {
if d.Size < 24 {
return fmt.Errorf("Unexpected sprite size: %d (expected >= 24)", d.Size)
}
return nil
}
2018-03-21 05:08:24 +00:00
// ObjectHeader totals 24 bytes on disk
type ObjectHeader struct {
NumSprites uint32 // How many sprites does this object have?
DirOffset uint32 // Offset of the directory block
DirSize uint32 // Size of the directory block. 8 * NumSprites
DataOffset uint32 // Offset of the sprite data block
DataSize uint32 // Size of the sprite data block
}
2018-03-21 05:08:24 +00:00
func (h ObjectHeader) ExpectedDirSize() uint32 {
return h.NumSprites * 8
}
func (h ObjectHeader) Check() error {
2018-03-21 05:08:24 +00:00
// TODO: check for overlaps
2018-03-21 05:08:24 +00:00
if h.ExpectedDirSize() != h.DirSize {
return fmt.Errorf("Unexpected sprite directory size: %d (expected %d)", h.DirSize, h.ExpectedDirSize())
}
return nil
}
type Object struct {
ObjectHeader
Filename string
Sprites []*Sprite
}
func LoadObject(filename string) (*Object, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
out := &Object{Filename: filename}
if err := binary.Read(f, binary.LittleEndian, &out.ObjectHeader); err != nil {
return nil, err
}
if err := out.ObjectHeader.Check(); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
// Now load all sprites into memory
dir := make([]dirEntry, out.NumSprites)
if _, err := f.Seek(int64(out.DirOffset), io.SeekStart); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
if err := binary.Read(f, binary.LittleEndian, &dir); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
if _, err := f.Seek(int64(out.DataOffset), io.SeekStart); err != nil {
return nil, err
}
// FIXME: this might - *might* - load interstitial data we don't really
// need, so wasting memory.
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
buf := bytes.NewReader(data)
2018-03-21 05:08:24 +00:00
for _, dirEntry := range dir {
if err := dirEntry.Check(); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
if _, err := buf.Seek(int64(dirEntry.Offset), io.SeekStart); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
sprite := &Sprite{}
2018-03-21 05:08:24 +00:00
if err := binary.Read(buf, binary.LittleEndian, &sprite.SpriteHeader); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
if err := sprite.Check(dirEntry.Size); err != nil {
return nil, err
}
// It's safe to assume that a `bytes.Reader` will always satisfy the
// requested read size.
2018-03-21 05:08:24 +00:00
sprite.PixelData = make([]byte, sprite.PixelSize)
if _, err := buf.Read(sprite.PixelData); err != nil {
return nil, err
}
2018-03-21 05:08:24 +00:00
out.Sprites = append(out.Sprites, sprite)
}
return out, nil
}
func LoadObjects(dir string) (map[string]*Object, error) {
fis, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
out := make(map[string]*Object, len(fis))
for _, fi := range fis {
filename := filepath.Join(dir, fi.Name())
basename := filepath.Base(filename)
extname := filepath.Ext(filename)
// Don't try to load non-.obj files
if !strings.EqualFold(extname, ".obj") {
continue
}
obj, err := LoadObject(filename)
if err != nil {
return nil, fmt.Errorf("%s: %v", filename, err)
}
out[basename] = obj
}
return out, nil
}