Decode WarHammer.ani
This commit is contained in:
@@ -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:
|
||||
|
||||
<details>
|
||||
|
||||
```
|
||||
# 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
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
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:
|
||||
|
||||
<details>
|
||||
|
||||
@@ -366,69 +272,123 @@ read(<WarHammer.ani>, "\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`!
|
||||
|
Reference in New Issue
Block a user