From b690c763bbb0a316c2b3eef000b8b0c9bc1ac05b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 16 Apr 2020 03:03:51 +0100 Subject: [PATCH] A few more .idx realisations, and some parsing code --- cmd/loader/main.go | 6 ++- doc/formats/ani.md | 22 +++++--- internal/idx/idx.go | 120 ++++++++++++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 50 deletions(-) diff --git a/cmd/loader/main.go b/cmd/loader/main.go index ade179e..6157e9d 100644 --- a/cmd/loader/main.go +++ b/cmd/loader/main.go @@ -201,8 +201,12 @@ func loadFonts() { func loadIdx() { idxPath := filepath.Join(*gamePath, "Idx", "WarHammer.idx") - _, err := idx.Load(idxPath) + idx, err := idx.Load(idxPath) if err != nil { log.Fatalf("Failed to parse %s as idx: %v", idxPath, err) } + + for i, group := range idx.Groups { + log.Printf("Group %2d: %4d records, start sprite is %6d", i, len(group.Records), group.Spec.SpriteIdx) + } } diff --git a/doc/formats/ani.md b/doc/formats/ani.md index ce299e3..ffd236b 100644 --- a/doc/formats/ani.md +++ b/doc/formats/ani.md @@ -309,11 +309,14 @@ matches the value at offset 8. This, then, must be the link. If the first sprite is 0, the displayed sprite is 64 (`0x40`)... +We still need to know how to go from "librarian" to "index 11", though. The +`CTYPE_LIBRARIAN` value in `HasAction.ani` gives librarians an 8... + | Offset | Size | Meaning | | ------ | ---- | ------- | -| 0 | 4 | Offset of type 2 record(s) | -| 4 | 4 | Unknown | +| 0 | 4 | Offset of type 2 records | +| 4 | 4 | Number of type 2 records | | 8 | 4 | First sprite in `WarHammer.ani` for this record | ### Type 2 record(s) @@ -334,7 +337,8 @@ Next, we read 5x 12-byte records - 60 bytes total - from that offset in the type the reads of type 2 records stop - so we were searching for it. In the first 12-byte record, we have a close offset: `0x098350`. So we have -1,824 bytes available in this block of type 2 records - enough for 152 of them. +1,824 bytes available in this block of type 2 records - enough for 152 of them, +which is the number specified in the second position of the type 1 header. What is the significance of the fifth 12-byte read? Why do we move onto type 3 records when we reach it? When we place the librarian, he is **facing** south, @@ -354,7 +358,7 @@ total, but I haven't counted the actions yet. | Offset | Size | Meaning | | ------ | ---- | ------- | -| 0 | 2? | Static per each group of 8 type-2 records? | +| 0 | 2? | ActionID? Static per each group of 8 type-2 records? | | 2 | 1? | Counts up from `01` to `08` in each group of 8 type-2 records? | | 3 | 1? | Is `0x33` for all but the last 4 groups of 8 type-2 records? | | 4 | 4 | Position of type 3 record | @@ -386,9 +390,13 @@ offsets of the frames we load. | ------ | ---- | ------- | | 0 | 2 | First sprite in animation (relative offset) | | 2 | 2 | Last sprite in animation (relative offset)? | -| 4 | 2? | Could also be last sprite in animation? | -| 6 | 2? | ??? | -| 8 | 12? | ??? - unset in this case | +| 4 | 2? | Could also be last sprite in animation? | +| 6 | 2? | ??? | +| 8 | 12? | ??? - unset in this case | The remaining 78-byte chunk is impenetrable so far, but we should now have the information we need to display all the animated sequences in `WarHammer.ani`! + +How do we know it needs to be 78 bytes? One option is multiplying the final +field of the type 2 record by 6. Maybe we have 6 bytes of description per frame, +or maybe it's unrelated to frames? diff --git a/internal/idx/idx.go b/internal/idx/idx.go index e9405dd..4a98535 100644 --- a/internal/idx/idx.go +++ b/internal/idx/idx.go @@ -1,39 +1,58 @@ -// package idx parses the Idx/WarHammer.idx file. -// -// No, I don't know what it's for yet. +// 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" + "fmt" + //"log" "os" //"strings" ) -type Type1Record struct { - Offset1 uint32 // Where the type2 for this type1 is to be found - Unknown1 uint32 // ??? - Offset2 uint32 // Another offset? But what to? -} - -type Type2Record struct { - Unknown1 [4]byte // ??? - Offset uint32 // Where the type3 for this type2 is to be found - Unknown2 [4]byte // ?? -} - -type Type3Record struct { - First20 [20]byte - Last78 [78]byte -} +const ( + NumGroups = 512 // Experimentally determined +) type Idx struct { Filename string + Groups []Group +} - Type1Records [512]Type1Record // Experimentally, there seem to be 512 of these - Type2Records []Type2Record - Type3Records []Type3Record +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) { @@ -44,29 +63,46 @@ func Load(filename string) (*Idx, error) { defer f.Close() - out := &Idx{Filename: filename} - - if err := binary.Read(f, binary.LittleEndian, &out.Type1Records); err != nil { - return nil, err + out := &Idx{ + Filename: filename, + Groups: make([]Group, NumGroups), } - for i, rec := range out.Type1Records { - var off1diff uint32 - var off2diff uint32 + var specs [NumGroups]Spec - if i > 0 && rec.Offset1 > 0 { - lastRec := out.Type1Records[i-1] - off1diff = rec.Offset1 - lastRec.Offset1 - off2diff = rec.Offset2 - lastRec.Offset2 + 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) } - log.Printf( - "%.3d: 0x%.6x diff=%.6d %.4d 0x%.6x diff=%.6d", - i, rec.Offset1, off1diff, rec.Unknown1, rec.Offset2, off2diff, - // i, - // rec.Unknown1[0], rec.Unknown1[1], rec.Unknown1[2], rec.Unknown1[3], - // rec.Unknown1[4], rec.Unknown1[5], rec.Unknown1[6], rec.Unknown1[7], - ) + // 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