// package idx parses the Idx/WarHammer.idx file. It groups the sprites in // Anim/WarHammer.ani into playable animations. package idx import ( "encoding/binary" "fmt" //"log" "os" //"strings" ) const ( NumGroups = 512 // Experimentally determined ) type Idx struct { Filename string Groups []Group } type Group struct { Spec Spec Records []Record Details []Detail // Records and details are correlated by index } // type Spec links a set of animations to a starting sprite in WarHammer.ani type Spec struct { Offset uint32 // Where the Records for this Spec are to be found Count uint32 // Number of Records for this Spec SpriteIdx uint32 // Index of the first sprite in } type Record struct { // A guess, but each group of 8 records with increasing compass points share // this value. ActionID uint16 Compass byte // It's odd to only have one byte. Maybe Unknown1 belongs to this too? Unknown1 byte // ??? Only see values 0x33 and 0x00 for librarian. Offset uint32 // Where the Detail for this Record is to be found. NumFrames uint32 // A guess, but seems to fit. Number of frames for this action + compass. } type Detail struct { FirstSprite uint16 // Relative offset from the group's SpriteIdx LastSprite uint16 // Relative offset from the group's SpriteIdx Unknown1 uint16 // Could also be LastSprite? Something else? AtRestSprite? Unknown2 uint16 // Number of resting sprites, if we're AtRestSprite? Padding [12]byte // Set to zero in the cases I've looked at so far. // Remainder []byte // FIXME: no idea what this is yet, but we seem to have NumFrames*6 of them } func Load(filename string) (*Idx, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() out := &Idx{ Filename: filename, Groups: make([]Group, NumGroups), } var specs [NumGroups]Spec if err := binary.Read(f, binary.LittleEndian, &specs); err != nil { return nil, fmt.Errorf("reading specs: %v", err) } for i, spec := range specs { group := &out.Groups[i] group.Spec = spec group.Records = make([]Record, spec.Count) group.Details = make([]Detail, spec.Count) if _, err := f.Seek(int64(spec.Offset), 0); err != nil { return nil, fmt.Errorf("spec %v: seeking: %v", i, err) } // We can read all records at once if err := binary.Read(f, binary.LittleEndian, &group.Records); err != nil { return nil, fmt.Errorf("spec %v: reading records: %v", i, err) } // But we need to step through the records to learn where to read details for j, rec := range group.Records { // group.Details[j].Remainder = make([]byte, rec.NumFrames*6) if _, err := f.Seek(int64(rec.Offset), 0); err != nil { return nil, fmt.Errorf("spec %v, record %v: seeking to detail: %v", i, j, err) } if err := binary.Read(f, binary.LittleEndian, &group.Details[j]); err != nil { return nil, fmt.Errorf("spec %v, record %v: reading detail: %v", i, j, err) } } out.Groups[i] = *group } return out, nil }