2020-04-15 00:27:43 +01:00
|
|
|
|
# `Anim/WarHammer.ani`
|
|
|
|
|
|
2020-04-15 22:18:53 +01:00
|
|
|
|
This turns out to simply be an [`obj`](obj.md) file.
|
|
|
|
|
|
|
|
|
|
The first 1,064 sprites are all of the same Ultramarine, carryng a bolter. There
|
|
|
|
|
are eight "facing" orientations:
|
|
|
|
|
|
|
|
|
|
* North
|
|
|
|
|
* Northeast
|
|
|
|
|
* East
|
|
|
|
|
* Southeast
|
|
|
|
|
* South
|
|
|
|
|
* Southwest
|
|
|
|
|
* West
|
|
|
|
|
* Northwest
|
|
|
|
|
|
|
|
|
|
For each orientation, an action is pictured in a variable number of frames. The
|
|
|
|
|
final frame for each action appears to be "stationary".
|
|
|
|
|
|
|
|
|
|
* Walk (13 frames)
|
|
|
|
|
* Run (9 frames)
|
|
|
|
|
* Crouch down (8 frames)
|
|
|
|
|
* Stand up (8 frames)
|
|
|
|
|
* Take aim (standing) (6 frames)
|
|
|
|
|
* Fire (standing) (6 frames)
|
|
|
|
|
* Relax aim (standing) (6 frames)
|
|
|
|
|
* Throw grenade (standing) (18 frames)
|
|
|
|
|
* Take aim (crouched) (5 frames)
|
|
|
|
|
* Fire (crouched) (5 frames)
|
|
|
|
|
* Relax aim (crouched) (5 frames)
|
|
|
|
|
* Throw grenade (crouched) (17 frames)
|
|
|
|
|
* Draw melee weapon (standing) (10 frames)
|
|
|
|
|
* Strike down with melee weapon (standing) (8 frames)
|
|
|
|
|
* Stab with melee weapon (standing) (9 frames)
|
|
|
|
|
|
|
|
|
|
Added together and multiplied by 87, that's 1064.
|
|
|
|
|
|
|
|
|
|
The next sprite is a walking-north action for an ultramarine with a flamer. The
|
|
|
|
|
total number of frames for this character is 1120 - 56 additional frames, or 7
|
|
|
|
|
per orientation. Could be an extra action, or an extra frame per action.
|
|
|
|
|
|
|
|
|
|
Also notable is that while the bolter showed muzzle flash in the animation, the
|
|
|
|
|
flamer only showed a tiny hint of fire. I think the animation for spewing flame
|
|
|
|
|
is held elsewhere.
|
|
|
|
|
|
|
|
|
|
I strongly suspect the actions and the number of frames in each action are
|
|
|
|
|
configurable. So, what other files are implicated in its interpretation? Here's
|
|
|
|
|
a few possibilities:
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-15 22:18:53 +01:00
|
|
|
|
* `Data/AniObDef.dat`
|
|
|
|
|
* `Data/Coordinates.dat`
|
|
|
|
|
* `Data/HasAction.dat`
|
|
|
|
|
* `Data/VehicDef.dat`
|
|
|
|
|
* `Data/WeapDef.dat`
|
2020-04-15 00:27:43 +01:00
|
|
|
|
- `Idx/WarHammer.idx`
|
|
|
|
|
|
2020-04-15 22:18:53 +01:00
|
|
|
|
## `Data/AniObDef.dat`
|
|
|
|
|
|
|
|
|
|
Including comments, this is 4098 lines, giving approx. 45 lines for each
|
2020-04-15 00:27:43 +01:00
|
|
|
|
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.
|
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
## `Data/HasAction.dat`
|
2020-04-15 22:18:53 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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.
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
Fortunately, it's commented extensively. For each "Character Type", there are
|
|
|
|
|
36 different possible animations.
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
Here's a table representation of the data:
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
|
|
|
|
```
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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
|
2020-04-15 00:27:43 +01:00
|
|
|
|
```
|
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
`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.
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
I think we still need the data in `.idx` for a full picture, though. Things we
|
|
|
|
|
still need:
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
* Mapping of character type to sprite directory index in `WarHammer.ani`
|
|
|
|
|
* Number of frames in each AnimAction
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
Either of these could be hardcoded, or dynamic.
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
## `Idx/WarHammer.idx`
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
`WarHammer.idx` (1,880,078 bytes, binary, so around 10KiB per character, in
|
|
|
|
|
theory) is more reasonable.
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
Here's a list of operations on the file when `WH40K_TD.EXE` is instructed to
|
|
|
|
|
place a single Librarian:
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
|
|
|
|
<details>
|
|
|
|
|
|
|
|
|
|
```
|
2020-04-15 21:11:01 +01:00
|
|
|
|
_llseek(<WarHammer.idx>, 132, [132], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x30\x7c\x09\x00\x98\x00\x00\x00\x88\xf8\x00\x00", 12) = 12
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
2020-04-15 21:11:01 +01:00
|
|
|
|
_llseek(<WarHammer.idx>, 132, [132], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x30\x7c\x09\x00\x98\x00\x00\x00\x88\xf8\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 621616, [621616], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x02\x01\x01\x33\x50\x83\x09\x00\x0d\x00\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 621628, [621628], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x02\x01\x02\x33\xb2\x83\x09\x00\x0d\x00\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 621640, [621640], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x02\x01\x03\x33\x14\x84\x09\x00\x0d\x00\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 621652, [621652], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x02\x01\x04\x33\x76\x84\x09\x00\x0d\x00\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 621664, [621664], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x02\x01\x05\x33\xd8\x84\x09\x00\x0d\x00\x00\x00", 12) = 12
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 623832, [623832], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.idx>, "\x34\x00\x40\x00\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 20) = 20
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.idx>, 0, [623852], SEEK_CUR) = 0
|
|
|
|
|
_llseek(<WarHammer.idx>, 623852, [623852], SEEK_SET) = 0
|
|
|
|
|
_llseek(<WarHammer.idx>, 623930, [623930], SEEK_SET) = 0
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509440, [509440], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x0c\x4d\xb6\x09\x68\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509448, [509448], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x74\x5b\xb6\x09\xfe\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509456, [509456], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x72\x6a\xb6\x09\x67\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509464, [509464], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xd9\x79\xb6\x09\xa9\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509472, [509472], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x82\x89\xb6\x09\xbb\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509480, [509480], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x3d\x99\xb6\x09\x08\x10\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509488, [509488], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x45\xa9\xb6\x09\xd1\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509496, [509496], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x16\xb9\xb6\x09\x01\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509504, [509504], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x17\xc8\xb6\x09\xc4\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509512, [509512], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xdb\xd6\xb6\x09\xe3\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509520, [509520], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xbe\xe5\xb6\x09\x0c\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509528, [509528], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xca\xf4\xb6\x09\x41\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509536, [509536], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x0b\x04\xb7\x09\xa6\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509440, [509440], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x0c\x4d\xb6\x09\x68\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164448540, [164448540], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf7\x00\x0a\x01\x35\x00\x54\x00\x00\x00\x00\x00\x50\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x13\x87\x2a\x2a\x2b\x2a\x2b"..., 3688) = 3688
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509448, [509448], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x74\x5b\xb6\x09\xfe\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164452228, [164452228], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf5\x00\x08\x01\x37\x00\x57\x00\x00\x00\x00\x00\xe6\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x15\x03\x2a\x8a\x2b\x2a\x2c"..., 3838) = 3838
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509456, [509456], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x72\x6a\xb6\x09\x67\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164456066, [164456066], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf4\x00\x08\x01\x39\x00\x5a\x00\x00\x00\x00\x00\x4f\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x18\x81\x2a\x05\x2b\x80\x1b"..., 3943) = 3943
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509464, [509464], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xd9\x79\xb6\x09\xa9\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164460009, [164460009], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xee\x00\x07\x01\x42\x00\x5b\x00\x00\x00\x00\x00\x91\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x22\x81\x2a\x80\x1f\x00\x80"..., 4009) = 4009
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509472, [509472], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x82\x89\xb6\x09\xbb\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164464018, [164464018], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xee\x00\x0a\x01\x43\x00\x5c\x00\x00\x00\x00\x00\xa3\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x23\x03\x29\x03\x2b\x80\x1a"..., 4027) = 4027
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509480, [509480], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x3d\x99\xb6\x09\x08\x10\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164468045, [164468045], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xec\x00\x09\x01\x43\x00\x5a\x00\x00\x00\x00\x00\xf0\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x24\x81\x2a\x80\x1e\x00\x80"..., 4104) = 4104
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509488, [509488], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x45\xa9\xb6\x09\xd1\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164472149, [164472149], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xee\x00\x09\x01\x3f\x00\x56\x00\x00\x00\x00\x00\xb9\x0f\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1d\x8a\x26\x26\x48\x29\x2a"..., 4049) = 4049
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509496, [509496], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x16\xb9\xb6\x09\x01\x0f\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164476198, [164476198], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf6\x00\x08\x01\x35\x00\x5b\x00\x00\x00\x00\x00\xe9\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x10\x81\x2a\x04\x2b\x86\x2a"..., 3841) = 3841
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509504, [509504], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\x17\xc8\xb6\x09\xc4\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164480039, [164480039], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf7\x00\x07\x01\x33\x00\x5b\x00\x00\x00\x00\x00\xac\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1b\x88\x2b\x2c\x2b\x2c\x2c"..., 3780) = 3780
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509512, [509512], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xdb\xd6\xb6\x09\xe3\x0e\x00\x00", 8) = 8
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 164483819, [164483819], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xf8\x00\x07\x01\x41\x00\x5f\x00\x00\x00\x00\x00\xcb\x0e\x00\x00\xd4\x71\x3b\x01\x00\x00\x00\x00\x80\x1c\x83\x2c\x2c\x2a\x80\x22"..., 3811) = 3811
|
|
|
|
|
|
|
|
|
|
_llseek(<WarHammer.ani>, 509520, [509520], SEEK_SET) = 0
|
|
|
|
|
read(<WarHammer.ani>, "\xbe\xe5\xb6\x09\x0c\x0f\x00\x00", 8) = 8
|
2020-04-15 00:27:43 +01:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
</details>
|
|
|
|
|
|
2020-04-15 22:18:53 +01:00
|
|
|
|
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.
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
So what are we doing here? What did we read? Here's what I get:
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
### Type 1 record
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
From `0x84`:
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
```
|
|
|
|
|
# xxd -s 0x84 -c 12 -e -l 12 -u orig/Idx/WarHammer.idx
|
|
|
|
|
00000084: 00097C30 00000098 0000F888 0|..........
|
|
|
|
|
```
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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`)...
|
|
|
|
|
|
2020-04-16 03:03:51 +01:00
|
|
|
|
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...
|
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
|
|
|
|
|
| Offset | Size | Meaning |
|
|
|
|
|
| ------ | ---- | ------- |
|
2020-04-16 03:03:51 +01:00
|
|
|
|
| 0 | 4 | Offset of type 2 records |
|
|
|
|
|
| 4 | 4 | Number of type 2 records |
|
2020-04-16 01:48:44 +01:00
|
|
|
|
| 8 | 4 | First sprite in `WarHammer.ani` for this record |
|
|
|
|
|
|
|
|
|
|
### Type 2 record(s)
|
|
|
|
|
|
|
|
|
|
From `0x097C30`:
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
|
|
|
|
```
|
2020-04-16 01:48:44 +01:00
|
|
|
|
# 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........
|
2020-04-15 21:11:01 +01:00
|
|
|
|
```
|
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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.
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
In the first 12-byte record, we have a close offset: `0x098350`. So we have
|
2020-04-16 03:03:51 +01:00
|
|
|
|
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.
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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.
|
2020-04-15 21:11:01 +01:00
|
|
|
|
|
2020-04-16 01:48:44 +01:00
|
|
|
|
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 |
|
|
|
|
|
| ------ | ---- | ------- |
|
2020-04-16 03:03:51 +01:00
|
|
|
|
| 0 | 2? | ActionID? Static per each group of 8 type-2 records? |
|
2020-04-16 01:48:44 +01:00
|
|
|
|
| 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)? |
|
2020-04-16 03:03:51 +01:00
|
|
|
|
| 4 | 2? | Could also be last sprite in animation? |
|
|
|
|
|
| 6 | 2? | ??? |
|
|
|
|
|
| 8 | 12? | ??? - unset in this case |
|
2020-04-16 01:48:44 +01:00
|
|
|
|
|
|
|
|
|
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`!
|
2020-04-16 03:03:51 +01:00
|
|
|
|
|
|
|
|
|
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?
|