diff --git a/doc/formats/ani.md b/doc/formats/ani.md
index fdde390..ce299e3 100644
--- a/doc/formats/ani.md
+++ b/doc/formats/ani.md
@@ -61,6 +61,68 @@ of the ~188 characters in the `ani`. That doesn't seem many, and there's no
obvious correspondence between the commented-on names (`SMOKE01`?) and the
viewed frames... but then, I've not viewed all the frames.
+## `Data/HasAction.dat`
+
+This file seems relevant as it says whether or not particular animations exist
+for the different types of character, which maps directly to what is stored in
+the .ani file - and so must affect lookups thereof.
+
+Fortunately, it's commented extensively. For each "Character Type", there are
+36 different possible animations.
+
+Here's a table representation of the data:
+
+```
+ Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult
+00 x x x x x x x x x x x x x x x x x x x x x x x
+01 x x x x x x x x x x x x x x x x x x x x x x x
+02 x x x x x x x x x x x x x x x x x x x x x x x
+03
+04
+05
+06 x x x x x x x x x x x x x x x x x x x x
+07 x x x x x x x x x x x x x x x x x x x x x x x
+08 x x x x x x x x x x x x x x x x
+09
+10
+11
+12
+13
+14 x x x x x x x x x x x x x
+15 x x x x x x x x x x x x x
+16 x x x x x x x x x x x x x
+17 x x x x x x x x x x x x x
+18 x x x x x x x x x x x x x
+19 x x x x x x x x x x x x x
+20 x x x x x x x x x x x x x
+21 x x x x x x x x x x x x x
+22 x x x x x x x x x x x x x x
+23 x x x x x x x x x x x x x
+24 x x x x x x x x x x x x x x x x
+25 x x x x x x x x x x x x x x x x x x x x x x
+26 x x x x x x x x x x x x x x x x x
+27 x x x x x x x x x x x x x x x x x x x x x
+28 x x x x x x x x x x x x x x
+29 x x x
+30 x
+31 x
+32 x x x
+33 x x x x x x x x x x x x x x x x x x x x x
+34 x x x x x x x x x x x x x x x x x x x x x x x
+35 x x x x x x x
+```
+
+`WarHammer.ani` doesn't have blank sprites for the unchecked cells, so this must
+surely be used to map between set-of-sprites and `AnimAction`. The names map
+very well to the descriptions I came up with when observing the sprites.
+
+I think we still need the data in `.idx` for a full picture, though. Things we
+still need:
+
+* Mapping of character type to sprite directory index in `WarHammer.ani`
+* Number of frames in each AnimAction
+
+Either of these could be hardcoded, or dynamic.
## `Idx/WarHammer.idx`
@@ -68,163 +130,7 @@ viewed frames... but then, I've not viewed all the frames.
theory) is more reasonable.
Here's a list of operations on the file when `WH40K_TD.EXE` is instructed to
-place an Ultramarine squad:
-
-
-
-```
-# Operation 1
-_llseek(34, 12, [12], SEEK_SET) = 0
-read(34, "\0\30\0\0X\4\0\0\0\0\0\0", 12) = 12
-
-# Operation 2
-_llseek(34, 6144, [6144], SEEK_SET) = 0
-read(34, "\2\6\1\0 L\0\0\r\0\0\0", 12) = 12
-_llseek(34, 6156, [6156], SEEK_SET) = 0
-read(34, "\2\6\2\0\202L\0\0\r\0\0\0", 12) = 12
-_llseek(34, 6168, [6168], SEEK_SET) = 0
-read(34, "\2\6\3\0\344L\0\0\r\0\0\0", 12) = 12
-_llseek(34, 6180, [6180], SEEK_SET) = 0
-read(34, "\2\6\4\0FM\0\0\r\0\0\0", 12) = 12
-_llseek(34, 6192, [6192], SEEK_SET) = 0
-read(34, "\2\6\5\0\250M\0\0\r\0\0\0", 12) = 12
-
-# Operation 3
-_llseek(34, 19880, [19880], SEEK_SET) = 0
-read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20
-read(34, "\0\0\6\0\4\0\0\0\6\0\4\0\0\0\6\0\4\0\0\0\5\0\4\0\0\0\5\0\4\0\0\0"..., 78) = 78
-
-# Operation 4: repeat 1
-# Operation 5: repeat 2
-# Operation 6: repeat 3
-
-# Operation 7
-_llseek(34, 24, [24], SEEK_SET) = 0
-read(34, "0\212\1\0X\4\0\0\210&\0\0", 12) = 12
-
-# Operation 8
-_llseek(34, 100912, [100912], SEEK_SET) = 0
-read(34, "\2\6\1\0P\276\1\0\r\0\0\0", 12) = 12
-_llseek(34, 100924, [100924], SEEK_SET) = 0
-read(34, "\2\6\2\0\262\276\1\0\r\0\0\0", 12) = 12
-_llseek(34, 100936, [100936], SEEK_SET) = 0
-read(34, "\2\6\3\0\24\277\1\0\r\0\0\0", 12) = 12
-_llseek(34, 100948, [100948], SEEK_SET) = 0
-read(34, "\2\6\4\0v\277\1\0\r\0\0\0", 12) = 12
-_llseek(34, 100960, [100960], SEEK_SET) = 0
-read(34, "\2\6\5\0\330\277\1\0\r\0\0\0", 12) = 12
-
-# Operation 9
-_llseek(34, 114648, [114648], SEEK_SET) = 0
-read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20
-
-# Operation 10
-_llseek(34, 114648, [114648], SEEK_SET) = 0
-read(34, "4\0@\0@\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20
-read(34, "\0\0\6\0\4\0\0\0\6\0\4\0\0\0\6\0\4\0\0\0\5\0\4\0\0\0\5\0\4\0\0\0"..., 78) = 78
-
-
-# Operation 11: repeat 7
-# Operation 12: repeat 8
-# Operation 13: repeat 9
-
-# lots of repeats
-```
-
-
-
-So what are we doing here? What did we read? Here's another view of it:
-
-```
-# Operation 1: read 12 bytes from 0x0C
-
-0000000C 00 18 00 00 58 04 00 00 00 00 00 00 ....X.......
-
-# Operation 7: read 12 bytes from offset 0x18
-
-00000018 30 8A 01 00 58 04 00 00 88 26 00 00 0...X....&..
-
-# Operation 2: 5 times, read 12 bytes from offset 0x1800
-
-00001800 02 06 01 00 20 4C 00 00 0D 00 00 00 .... L......
-0000180C 02 06 02 00 82 4C 00 00 0D 00 00 00 .....L......
-00001818 02 06 03 00 E4 4C 00 00 0D 00 00 00 .....L......
-00001824 02 06 04 00 46 4D 00 00 0D 00 00 00 ....FM......
-00001830 02 06 05 00 A8 4D 00 00 0D 00 00 00 .....M......
-
-# Operation 3a: read 20 bytes from offset 0x4DA8
-
-00004DA8 34 00 40 00 40 00 01 00 00 00 00 00 4.@.@.......
-00004DB4 00 00 00 00 00 00 00 00 ........
-
-# Operation 3b: read 78 more bytes
-
-00004DBC 00 00 06 00 04 00 00 00 06 00 04 00
-00004DC8 00 00 06 00 04 00 00 00 05 00 04 00
-00004DD4 00 00 05 00 04 00 00 00 03 00 04 00
-00004DE0 00 00 04 00 FC FF 00 00 05 00 FC FF
-00004DEC 00 00 06 00 FC FF 00 00 05 00 FC FF
-00004DF8 00 00 06 00 FC FF 00 00 06 00 FC FF
-00004E04 00 00 00 00 00 00
-
-
-# Operation 8: 5 times, read 12 bytes from offset 0x18A30
-
-00018A30 02 06 01 00 50 BE 01 00 0D 00 00 00 ....P.......
-00018A3C 02 06 02 00 B2 BE 01 00 0D 00 00 00 ............
-00018A48 02 06 03 00 14 BF 01 00 0D 00 00 00 ............
-00018A54 02 06 04 00 76 BF 01 00 0D 00 00 00 ....v.......
-00018A60 02 06 05 00 D8 BF 01 00 0D 00 00 00 ............
-
-# Operation 9a: read 20 bytes from offset 0x1BFD8
-
-0001BFD8 34 00 40 00 40 00 01 00 00 00 00 00 4.@.@.......
-0001BFE4 00 00 00 00 00 00 00 00 ........
-
-# Operation 9b: read 78 more bytes
-
-0001BFEC 00 00 06 00 04 00 00 00 06 00 04 00
-0001BFF8 00 00 06 00 04 00 00 00 05 00 04 00
-0001C004 00 00 05 00 04 00 00 00 03 00 04 00
-0001C010 00 00 04 00 FC FF 00 00 05 00 FC FF
-0001C01C 00 00 06 00 FC FF 00 00 05 00 FC FF
-0001C028 00 00 06 00 FC FF 00 00 06 00 FC FF
-0001C034 00 00 00 00 00 00
-```
-
-Operation 1 contains `[18 00]`. Operation 2, at `0x1800`, contains `[A8 4D]` -
-and operation 3 starts at `0x4DA8`. The pattern repeats for operations
-7 -> 8 -> 9.
-
-Assumimg operations 1 & 7 represent one type of record, while 2 & 8 represent
-another, that would give us a 511-entry directory starting at 0xC. We know the
-first 4 bytes represent an offset to find the type of record represented by
-2 & 8. We don't know about the other 8 bytes.
-
-| Offset | Size | Meaning |
-| ------ | ---- | ------- |
-| 0 | 4 | Position of type 2 record(s) |
-| 4 | ? | Unknown |
-
-The records represented by operations 2 & 8 both read 5x 12-byte records, but it
-looks like they're arranged in blocks of 8. Nothing in type 1 records indicates
-how many type 2 records there are.
-
-| Offset | Size | Meaning |
-| ------ | ---- | ------- |
-| 0 | 2? | `02 06` - ??? |
-| 2 | 2? | `n xx` - ??? `n` counts from 1 to 8 |
-| 4 | 4 | Position of type 3 record |
-| 8 | 4 | ??? |
-
-The two type 3a and 3b records are identical to each other, it's hard to know
-what's what.
-
-None of the unknown numbers, however sliced, seem to be sprite indices for
-`Anim/WarHammer.ani`.
-
-Here's a trace of both `idx` and `ani` files when placing a single librarian for
-me to dig into in more detail. No other files are touched when doing this.
+place a single Librarian:
@@ -366,69 +272,123 @@ read(, "\xbe\xe5\xb6\x09\x0c\x0f\x00\x00", 8) = 8
Notable is that we read from `idx` **before** we read from `ani` - so it does
seem like the former should tell us where to pull from the latter.
-We still load 5 type 2 records, even though there's just a single librarian, and
-8 compass points. Why 5? After looking in `idx`, we load 10 sprites from `ani`,
-which is at least a multiple. Do any of the records we load from `idx` specify
-sprite directory offsets that match?
+So what are we doing here? What did we read? Here's what I get:
-## `Data/HasAction.dat`
+### Type 1 record
-This file seems relevant as it says whether or not particular animations exist
-for the different types of character, which maps directly to what is stored in
-the .ani file - and so must affect lookups thereof.
-
-Fortunately, it's commented extensively. For each "Character Type", there are
-36 different possible animations.
-
-Here's a table representation of the data:
+From `0x84`:
```
- Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult
-00 x x x x x x x x x x x x x x x x x x x x x x x
-01 x x x x x x x x x x x x x x x x x x x x x x x
-02 x x x x x x x x x x x x x x x x x x x x x x x
-03
-04
-05
-06 x x x x x x x x x x x x x x x x x x x x
-07 x x x x x x x x x x x x x x x x x x x x x x x
-08 x x x x x x x x x x x x x x x x
-09
-10
-11
-12
-13
-14 x x x x x x x x x x x x x
-15 x x x x x x x x x x x x x
-16 x x x x x x x x x x x x x
-17 x x x x x x x x x x x x x
-18 x x x x x x x x x x x x x
-19 x x x x x x x x x x x x x
-20 x x x x x x x x x x x x x
-21 x x x x x x x x x x x x x
-22 x x x x x x x x x x x x x x
-23 x x x x x x x x x x x x x
-24 x x x x x x x x x x x x x x x x
-25 x x x x x x x x x x x x x x x x x x x x x x
-26 x x x x x x x x x x x x x x x x x
-27 x x x x x x x x x x x x x x x x x x x x x
-28 x x x x x x x x x x x x x x
-29 x x x
-30 x
-31 x
-32 x x x
-33 x x x x x x x x x x x x x x x x x x x x x
-34 x x x x x x x x x x x x x x x x x x x x x x x
-35 x x x x x x x
+# xxd -s 0x84 -c 12 -e -l 12 -u orig/Idx/WarHammer.idx
+00000084: 00097C30 00000098 0000F888 0|..........
```
-`WarHammer.ani` doesn't have blank sprites for the unchecked cells, so this must
-surely be used to map between set-of-sprites and `AnimAction`.
+The first read contains `0x097C30`. The second (+5) read, at `0x097C30`,
+contains `0x0984D8`. We then read 20, followed by 78, bytes, and go on to read
+from the `.ani` file.
-I think we still need the data in `.idx` for a full picture, though. Things we
-still need:
+The whole start of the file looks like a directory of the same kind of records
+(call them type 1). The record at offset 0 is empty, as are the last few, but
+the rest have always-increasing offsets in the first and third position. The
+first appears to be for a "tactical marine", or at least, it is read (similarly
+to the librarian) when placing a "tactical squad". That has an offset of
+0x1800` in the first position, which gives us space for 512 of these 12-byte
+records. We can say they look like:
-* Mapping of character type to sprite directory index in `WarHammer.ani`
-* Number of frames in each AnimAction
+Is there anything in here that can link us to what we're reading from the `.ani`
+file? From it, we read 14 entries from the sprite directory, starting at
+byte offset `0x07C600` and direntry offset 63676 (`0xF8BC`). We then load 10
+sprites. The first is at byte offset `0x9CD491C`, and is 3688 bytes.
-Either of these could be hardcoded, or dynamic.
+Looking at that sprite in the object viewer, it is the librarian \o/ - facing
+south \o/. However, it's not the sprite we see in `WH40K_TD.exe`. That one is,
+I think, number 63688 (`0xF8C8`) - 12 sprites on. Nothing matches these numbers.
+
+However, the **first** librarian sprite is at index 63624 (`0xF888`), which
+matches the value at offset 8. This, then, must be the link.
+
+If the first sprite is 0, the displayed sprite is 64 (`0x40`)...
+
+
+| Offset | Size | Meaning |
+| ------ | ---- | ------- |
+| 0 | 4 | Offset of type 2 record(s) |
+| 4 | 4 | Unknown |
+| 8 | 4 | First sprite in `WarHammer.ani` for this record |
+
+### Type 2 record(s)
+
+From `0x097C30`:
+
+```
+# xxd -s 0x00097C30 -g 1 -c 12 -l 60 -u orig/Idx/WarHammer.idx
+00097c30: 02 01 01 33 50 83 09 00 0D 00 00 00 ...3P.......
+00097c3c: 02 01 02 33 B2 83 09 00 0D 00 00 00 ...3........
+00097c48: 02 01 03 33 14 84 09 00 0D 00 00 00 ...3........
+00097c54: 02 01 04 33 76 84 09 00 0D 00 00 00 ...3v.......
+00097c60: 02 01 05 33 D8 84 09 00 0D 00 00 00 ...3........
+```
+
+Next, we read 5x 12-byte records - 60 bytes total - from that offset in the type
+1 record. The address of the next read is embedded in the fifth, which is where
+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.
+
+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,
+and that facing is the fifth one in the listing (N, NE, E, SE, S). It's all I
+can come up with.
+
+Perhaps this is the fifth facing of the first action? Looking ahead in the file,
+we can see that the third byte counts from 1 to 8 and falls again, so this is a
+tempting idea.
+
+If so, since we know the librarian has 23 actions, we'd expect room for 23 * 8
+type 2 records in this block. That would need 2208 bytes, and we only have 1824
+- enough for 19 animations, which is quite close.
+
+Looking at the librarian in the `ani` file, we see they have 1055 sprites in
+total, but I haven't counted the actions yet.
+
+| Offset | Size | Meaning |
+| ------ | ---- | ------- |
+| 0 | 2? | 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 |
+| 8 | 4? | ??? - small values though. Count of frames? |
+
+### Type 3 record
+
+From `0x0984D8`:
+
+```
+# xxd -s 0x984D8 -g 1 -c 12 -l 20 -u orig/Idx/WarHammer.idx
+000984d8: 34 00 40 00 40 00 01 00 00 00 00 00 4.@.@.......
+000984e4: 00 00 00 00 00 00 00 00
+
+# xxd -s 0x984EC -g 1 -c 12 -l 78 -u orig/Idx/WarHammer.idx
+000984ec: 00 00 06 00 04 00 00 00 06 00 04 00 ............
+000984f8: 00 00 06 00 04 00 00 00 05 00 04 00 ............
+00098504: 00 00 05 00 04 00 00 00 03 00 04 00 ............
+00098510: 00 00 04 00 FC FF 00 00 05 00 FC FF ............
+0009851c: 00 00 06 00 FC FF 00 00 05 00 FC FF ............
+00098528: 00 00 06 00 FC FF 00 00 06 00 FC FF ............
+00098534: 00 00 00 00 00 00
+```
+
+Here, in the first read, we see `34 00` and `40 00`. These are the **relative**
+offsets of the frames we load.
+
+| Offset | Size | Meaning |
+| ------ | ---- | ------- |
+| 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 |
+
+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`!
diff --git a/scripts/try-uncompress b/scripts/try-uncompress
index be2b362..308145e 100755
--- a/scripts/try-uncompress
+++ b/scripts/try-uncompress
@@ -19,7 +19,7 @@ module Obj
def self.parse(data)
hdr = new(*data[0..SIZE - 1].unpack("VVVVV"))
- pp hdr
+ # pp hdr
hdr.validate!(data.bytes.size)
hdr
end
@@ -96,7 +96,7 @@ module Obj
DirEntry.parse(rel_data.byteslice(rel_offset, DirEntry::SIZE))
end
- pp entries
+ # pp entries
new(entries)
end
@@ -380,6 +380,18 @@ def correlate(filenames)
pp results
end
+def directory(filename, num)
+ data = File.read(filename).force_encoding("BINARY")
+
+ hdr = Obj::Header.parse(data)
+ dir = Obj::SpriteDir.parse(data[hdr.dir_range])
+ entry = dir.entries[num]
+
+ puts "Sprite directory starts at 0x#{hdr.dir_offset.to_s(16)}"
+ puts "Directory entry for sprite #{num} is at 0x#{(hdr.dir_offset + (Obj::DirEntry::SIZE * num)).to_s(16)}"
+ puts "Sprite #{num} is at 0x#{(hdr.data_offset + entry.rel_offset).to_s(16)} and is #{entry.sprite_size} bytes"
+end
+
def sprites(filename)
obj = load_obj(filename)
@@ -514,6 +526,8 @@ def unknown16(filenames)
end
case command = ARGV.shift
+when "directory" then
+ directory(ARGV[0], ARGV[1].to_i)
when "unknown16" then
unknown16(ARGV)
when "sprites" then