package data import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "os" "path/filepath" "sort" "strings" ) var ( // FIXME: My poor understanding of the .obj format prevents me from // successfully loading these files objBlacklist = []string{ /* Lots of unexpected magic values, disable the check for now "15_rocks.obj", // Unexpected magic value: 19726564 (expected 21102801) "2_cath.obj", // Unexpected magic value: 21364973 (expected 21102801) "2nd_flor.obj", // Unexpected magic value: 22151377 (expected 21102801) "3_cath.obj", // Unexpected magic value: 21102809 (expected 21102801) "4_cath.obj", // Unexpected magic value: 21496017 (expected 21102801) "BODIES.obj", // Unexpected magic value: 21627103 (expected 21102801) "BRDG_TIL.OBJ", // Unexpected magic value: 17957074 (expected 21102801) "Cheveron.obj", // Unexpected magic value: 10879118 (expected 21102801) "Heavy_Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Heavy_Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Heavy_Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Heavy_Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Ht_drt.obj", // Unexpected magic value: 22806737 (expected 21102801) "Ht_grs.obj", // Unexpected magic value: 22806737 (expected 21102801) "IVY02.OBJ", // Unexpected magic value: 18481399 (expected 21102801) "J_top2.obj", // Unexpected magic value: 22347993 (expected 21102801) "Lascannon_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Lascannon_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Lascannon_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Lascannon_Pain_Mask.obj", // Unexpected magic value: 14811136 (expected 21102801) "Man_Shadow.obj", // Unexpected magic value: 22479091 (expected 21102801) "Melta_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801) "Melta_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Melta_Pain_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801) "Melta_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Multi_Melta_Effect.obj", "Multi_Melta_Mask.obj", "Multi_Melta_Pain_Effect.obj", "Multi_Melta_Pain_Mask.obj", "Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801) "Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801) "TZEENTCH.OBJ", // Unexpected magic value: 18088209 (expected 21102801) "altar.obj", // Unexpected magic value: 18219222 (expected 21102801) */ "j_tree2.obj", // ObjectHeader is completely empty "inven.obj", // Main header padding contains unknown values: [134744072 134744072 134744072] } objFrameMagic = uint32(0x014200D1) ) func init() { sort.Strings(objBlacklist) } type FrameHeader struct { Magic uint32 Width uint16 // FIXME: I'm not certain this is what these are. If they are, they may be the wrong way around Height uint16 Padding1 uint32 // I don't think this is used. Could be wrong. PixelSize uint32 // Size of PixelData, excluding this frame header Padding2 uint64 // I don't think this is used either. Could be wrong. } func (f FrameHeader) Check(expectedSize uint32) error { // There seem to be different frame types, keyed by the magic value? // if f.Magic != objFrameMagic { // return fmt.Errorf("Unexpected magic value: %d (expected %d)", f.Magic, objFrameMagic) // } if f.Padding1 != 0 || f.Padding2 != 0 { return fmt.Errorf("Frame header padding contains unknown values: %d %d", f.Padding1, f.Padding2) } // Remove 24 bytes from passed-in size to account for the header if f.PixelSize != expectedSize-24 { return fmt.Errorf("Advertised pixel size: %d differs from expected: %v", f.PixelSize, expectedSize-24) } return nil } type Frame struct { FrameHeader PixelData []byte } type frameInfoHeader struct { Offset uint32 // Offset of the frame relative to the frame data segment Size uint32 // Size of the frame in bytes, including the header } func (f frameInfoHeader) Check() error { if f.Size < 24 { return fmt.Errorf("Unexpected frame size: %d (expected >= 24)", f.Size) } return nil } // ObjectHeader totals 32 bytes on disk type ObjectHeader struct { NumFrames uint32 // How many frames does this object have? MainHeaderSize uint32 // Number of bytes taken by this header. Should always be `32` FrameInfoSize uint32 // Number of bytes taken up by the next block. 8 * NumFrames FrameDataOffset uint32 // The starting point of the frame data FrameDataSize uint32 // frame data should take up this many bytes Padding [3]uint32 // Unused, as far as I can see } func (h ObjectHeader) ExpectedFrameInfoSize() uint32 { return h.NumFrames * 8 } func (h ObjectHeader) Check() error { if h.MainHeaderSize != 32 { return fmt.Errorf("Unexpected main header size: %d (expected 32)", h.MainHeaderSize) } if h.ExpectedFrameInfoSize() != h.FrameInfoSize { return fmt.Errorf("Unexpected frame info size: %d (expected %d)", h.FrameInfoSize, h.ExpectedFrameInfoSize()) } if h.Padding[0] != 0 || h.Padding[1] != 0 || h.Padding[2] != 0 { return fmt.Errorf("Main header padding contains unknown values: %+v", h.Padding) } return nil } type Object struct { ObjectHeader Filename string Frames []*Frame } 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 } // Now load all frames into memory framesInfo := make([]frameInfoHeader, out.NumFrames) if err := binary.Read(f, binary.LittleEndian, &framesInfo); err != nil { return nil, err } if _, err := f.Seek(int64(out.FrameDataOffset), 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) for _, frameInfo := range framesInfo { if err := frameInfo.Check(); err != nil { return nil, err } if _, err := buf.Seek(int64(frameInfo.Offset), io.SeekStart); err != nil { return nil, err } frame := &Frame{} if err := binary.Read(buf, binary.LittleEndian, &frame.FrameHeader); err != nil { return nil, err } if err := frame.Check(frameInfo.Size); err != nil { return nil, err } // It's safe to assume that a `bytes.Reader` will always satisfy the // requested read size. frame.PixelData = make([]byte, frame.PixelSize) if _, err := buf.Read(frame.PixelData); err != nil { return nil, err } out.Frames = append(out.Frames, frame) } return out, nil } func readObjFrame(f io.Reader, obj *Object) error { return 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 } i := sort.SearchStrings(objBlacklist, basename) if i >= len(objBlacklist) || objBlacklist[i] == basename { continue } obj, err := LoadObject(filename) if err != nil { return nil, fmt.Errorf("%s: %v", filename, err) } out[basename] = obj } return out, nil }