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