Files
ordoor/doc/formats/maps.md

19 KiB

Map format information

Overview

The Map/ directory contains linked pairs of files: a .TXT and a .MAP. The former appears to be structured text, with tilde-separated sections. The latter is a custom format of some kind, GZip-compressed, with a WHMAP constant and character names embedded

WH40K_TD.exe can read in and write out maps for us, for investigation \o/.

I update structure with details as I work them out, and put the working in appropriately-named subsections below that section.

Directory listing

total 1.5M
-rw-r--r-- 1  65K Nov  4  1998 Chapter01.MAP
-rw-r--r-- 1  429 Nov  7  1998 Chapter01.TXT
-rw-r--r-- 1  81K Nov  4  1998 Chapter02.MAP
-rw-r--r-- 1  587 Nov  7  1998 Chapter02.TXT
-rw-r--r-- 1  88K Nov  4  1998 Chapter03.MAP
-rw-r--r-- 1  597 Nov  7  1998 Chapter03.TXT
-rw-r--r-- 1 100K Jan 26  1999 Chapter04.MAP
-rw-r--r-- 1  538 Jan 26  1999 Chapter04.TXT
-rw-r--r-- 1  42K Jan 26  1999 Chapter05.MAP
-rw-r--r-- 1 1.3K Nov  7  1998 Chapter05.TXT
-rw-r--r-- 1  56K Nov  4  1998 Chapter06.MAP
-rw-r--r-- 1  572 Nov  7  1998 Chapter06.TXT
-rw-r--r-- 1  63K Jan 26  1999 Chapter07.MAP
-rw-r--r-- 1  750 Jan 26  1999 Chapter07.TXT
-rw-r--r-- 1  66K Nov 20  1998 Chapter08.MAP
-rw-r--r-- 1  638 Nov  7  1998 Chapter08.TXT
-rw-r--r-- 1  98K Jan 26  1999 Chapter09.MAP
-rw-r--r-- 1  656 Jan 26  1999 Chapter09.TXT
-rw-r--r-- 1 100K Nov  5  1998 Chapter10.MAP
-rw-r--r-- 1  622 Nov  7  1998 Chapter10.TXT
-rw-r--r-- 1 110K Nov  4  1998 Chapter11.MAP
-rw-r--r-- 1  396 Nov  7  1998 Chapter11.TXT
-rw-r--r-- 1 118K Jan 26  1999 Chapter12.MAP
-rw-r--r-- 1  593 Nov  7  1998 Chapter12.TXT
-rw-r--r-- 1 133K Nov  4  1998 Chapter13.MAP
-rw-r--r-- 1 1.1K Nov  7  1998 Chapter13.TXT
-rw-r--r-- 1 137K Jan 26  1999 Chapter14.MAP
-rw-r--r-- 1  641 Nov  7  1998 Chapter14.TXT
-rw-r--r-- 1 140K Nov  5  1998 Chapter15.MAP
-rw-r--r-- 1  338 Nov  7  1998 Chapter15.TXT

Multimaps

There are also maps in the MultiMaps/ directory. I guess they're reworks of the Maps/ entries for multiplayer with differing numbers of players - Chapter1_3P.MAP would be the chapter1 map, just modified for 3-player multiplayer. Ignore for now, but here's the directory listing:

total 1.4M
-rw-r--r-- 1  93K Nov  5  1998 Chapter10_2P.MAP
-rw-r--r-- 1   21 Nov  4  1998 Chapter10_2P.TXT
-rw-r--r-- 1 103K Nov  5  1998 Chapter11_4P.MAP
-rw-r--r-- 1   36 Nov  4  1998 Chapter11_4P.TXT
-rw-r--r-- 1 111K Jan 26  1999 Chapter12_2P.MAP
-rw-r--r-- 1  198 Nov  4  1998 Chapter12_2P.TXT
-rw-r--r-- 1 125K Jan 26  1999 Chapter13_4P.MAP
-rw-r--r-- 1  394 Nov  4  1998 Chapter13_4P.TXT
-rw-r--r-- 1  64K Nov  5  1998 Chapter1_3P.MAP
-rw-r--r-- 1  168 Nov  4  1998 Chapter1_3P.txt
-rw-r--r-- 1 127K Nov  4  1998 Chapter14_4P.MAP
-rw-r--r-- 1   21 Nov  4  1998 Chapter14_4P.TXT
-rw-r--r-- 1 127K Nov  7  1998 Chapter15_2P.MAP
-rw-r--r-- 1   21 Nov  4  1998 Chapter15_2P.TXT
-rw-r--r-- 1  75K Nov  4  1998 Chapter2_4P.MAP
-rw-r--r-- 1  109 Nov  4  1998 Chapter2_4P.txt
-rw-r--r-- 1  83K Nov  5  1998 Chapter3_2P.MAP
-rw-r--r-- 1  119 Nov  4  1998 Chapter3_2P.txt
-rw-r--r-- 1  92K Jan 26  1999 Chapter4_2P.MAP
-rw-r--r-- 1   21 Nov  4  1998 Chapter4_2P.txt
-rw-r--r-- 1  35K Nov  4  1998 Chapter5_3P.MAP
-rw-r--r-- 1  416 Nov  4  1998 Chapter5_3P.txt
-rw-r--r-- 1  48K Nov  5  1998 Chapter6_2P.MAP
-rw-r--r-- 1  357 Nov  4  1998 Chapter6_2P.txt
-rw-r--r-- 1  56K Nov  7  1998 Chapter7_2P.MAP
-rw-r--r-- 1  233 Nov  4  1998 Chapter7_2P.txt
-rw-r--r-- 1  59K Nov  4  1998 Chapter8_2P.MAP
-rw-r--r-- 1  240 Nov  4  1998 Chapter8_2P.txt
-rw-r--r-- 1  91K Nov  4  1998 Chapter9_4P.MAP
-rw-r--r-- 1  367 Nov  4  1998 Chapter9_4P.TXT

Structure

The .MAP files are compressed with gzip. All offsets are on the uncompressed size.

(Incomplete) parser can be found here

The start of Chapter01.MAP. It's header then per-map-coordinate data. Must remember that maps have 7 height levels (7), so the volume we're describing is actually WxLxH in all cases

00000000   01 00 00 00  .... # IsCampaignMap
00000004   1A 00 00 00  .... # MinWidth
00000008   14 00 00 00  .... # MinLength
0000000C   68 00 00 00  h... # MaxWidth
00000010   50 00 00 00  P... # MaxLength
00000014   01 00 00 00  .... # Unknown1
00000018   00 00 00 00  .... # Unknown2
0000001C   00 00 00 00  .... # Unknown3
00000020   00 00 00 00  .... # Unknown4
00000024   08 00 57 48  ..WH # Magic value
00000028   4D 41 50 00  MAP. # ...
0000002C   00 00 00 00  .... # Unknown
00000030   91 09 00 00  .... # Unknown - varies between maps of the same size
00000034   6D 61 70 30  map0 # Name of object set to load from `Sets/`
00000038   31 00 00 00  1... # ...
0000003C   00 00 00 00  .... # Unknown from here....
00000040   00 00 00 00  ....
00000044   00 00 00 00  ....
00000048   00 00 00 00  ....
0000004C   00 00 00 00  ....
00000050   00 00 00 00  ....
00000054   00 00 00 00  ....
00000058   00 00 00 00  ....
0000005C   00 00 00 00  ....
00000060   00 00 00 00  ....
00000064   00 00 00 00  ....
00000068   00 00 00 00  ....
0000006C   00 00 00 00  ....
00000070   00 00 00 00  ....
00000074   00 00 00 00  ....
00000078   00 00 00 00  ....
0000007C   00 00 00 00  ....
00000080   00 00 00 00  ....
00000084   00 00 00 00  ....
00000088   00 00 00 00  ....
0000008C   00 00 00 00  ....
00000090   00 00 00 00  ....
00000094   00 00 00 00  ....
00000098   00 00 00 00  ....
0000009C   00 00 00 00  ....
000000A0   00 00 00 00  ....
000000A4   00 00 00 00  ....
000000A8   00 00 00 00  ....
000000AC   00 00 00 00  ....
000000B0   00 00 00 00  ....
000000B4   38 00 30 00  8.0.
000000B8   00 00 3F 00  ..?.
000000BC   32 00 00 00  2...
000000C0   00 00 00 00  ....
000000C4   00 00 00 00  ....
000000C8   00 00 00 00  ....
000000CC   00 00 00 00  ....
000000D0   00 00 3F 00  ..?.
000000D4   32 00 01 00  2...
000000D8   00 00 0F 00  ....
000000DC   05 00 00 00  ....
000000E0   01 00 00 00  ....
000000E4   A2 FF 80 00  ....
000000E8   17 00 00 00  ....
000000EC   FF FF FF FF  ....
000000F0   FF FF FF FF  ....
000000F4   FF FF FF FF  ....
000000F8   FF FF FF FF  ....
000000FC   FF FF FF FF  ....
00000100   FB 00 00 00  .... # It's plausible that per-coord data begins here

Comparing some values across all maps bundled with the game:

  • Maps/
    • Chapter10.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map10
    • Chapter12.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map12
    • Chapter15.MAP: IsCampaignMap=1 W=0:130 L=0:100 SetName=map15
    • Chapter04.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map04
    • Chapter08.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map08
    • Chapter09.MAP: IsCampaignMap=1 W=13:117 L=10:90 SetName=map09
    • Chapter03.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map03
    • Chapter11.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map11
    • Chapter13.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map13
    • Chapter02.MAP: IsCampaignMap=1 W=13:117 L=10:90 SetName=map02
    • Chapter06.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map06
    • Chapter07.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map07
    • Chapter14.MAP: IsCampaignMap=1 W=13:117 L=10:90 SetName=map14
    • Chapter01.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map01
    • Chapter05.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map05
  • MultiMaps/
    • Chapter13_4P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map13
    • Chapter1_3P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map01
    • Chapter5_3P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map05
    • Chapter8_2P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map08
    • Chapter11_4P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map11
    • Chapter9_4P.MAP: IsCampaignMap=0 W=13:117 L=10:90 SetName=map09
    • Chapter12_2P.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map12
    • Chapter15_2P.MAP: IsCampaignMap=0 W=0:130 L=0:100 SetName=map15
    • Chapter2_4P.MAP: IsCampaignMap=0 W=13:117 L=10:90 SetName=map02
    • Chapter4_2P.MAP: IsCampaignMap=1 W=26:104 L=20:80 SetName=map04
    • Chapter10_2P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map10
    • Chapter14_4P.MAP: IsCampaignMap=0 W=13:117 L=10:90 SetName=map14
    • Chapter3_2P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map03
    • Chapter6_2P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map06
    • Chapter7_2P.MAP: IsCampaignMap=0 W=26:104 L=20:80 SetName=map07

IsCampaignMap

This is just a theory, but every map that forms part of the single-player campaign has a value of 0x01 for 0x0000 - 0x0003.

Most of the MP maps and the maps I generate for myself have the value of 0x0 instead. Perhaps Chapter4_2P.MAP and Chapter12_2P.MAP have it set by accident? Or maybe it's another thing entirely.

TODO: try to trace what WH40K.exe does with this flag, if anything.

Width and Length

Maximum allowed size by WH40K_TD.exe is 130x100. Minimum is 20x20.

To investigate this, I created maps of different sizes only, and observed what differences this made in the putative header:

Offset 20x20 20x21 21x20 21x21 100x100 130x100
0x04 0x37 0x37 0x36 0x36 0x0F 0x00
0x08 0x28 0x27 0x28 0x27 0x00 0x00
0x0C 0x4B 0x4B 0x4B 0x4B 0x73 0x82
0x10 0x3C 0x3C 0x3C 0x3C 0x64 0x64

WH40K_TD.exe gives us coordinates for each point on each of these maps:

Size North East South West 0x04 0x08 0x0c 0x10
20x20 55,40 74,40 74,59 55,59 55 40 75 60
20x21 55,39 74,39 74,59 55,59 55 39 75 60
21x20 54,40 74,40 74,59 54,59 54 40 75 60
100x100 15, 0 114, 0 114,99 15,99 15 0 115 100
130x100 0, 0 129, 0 129,99 0,99 0 0 130 100

So our valid coordinates range in width from (*0x04, (*0x0c)-1), and for length, from (*0x08, (*0x10)-1)

We're specifying a viewport on a statically-defined area with a width of 130, and a length of 100. The viewport is centered on the middle of that area and anything outside it is clipped away.

Per-coordinate data

All .MAP files are the same size once uncompressed, regardles of width and length parameters, suggesting that they each have fixed-sized records for every possible coordinate, regardless.

Skipping the header we know about, we have data like this:

00000060   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000070   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000080   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000090   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000A0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000B0   00 00 00 00  1F 00 2C 00  00 00 00 00  00 00 00 00  ......,.........
000000C0   00 00 00 00  00 00 01 00  00 00 00 00  00 00 00 00  ................
000000D0   00 00 00 00  00 00 01 00  00 00 00 00  00 00 00 00  ................
000000E0   01 00 FC FF  F3 FF 1C 00  10 00 00 00  FF FF FF FF  ................
000000F0   FF FF FF FF  FF FF FF FF  FF FF FF FF  FF FF FF FF  ................
00000100   FB 00 00 00  24 FF FF FF  51 02 00 00  00 06 00 FF  ....$...Q.......
00000110   3F 00 00 00  82 01 00 00  00 00 00 FF  00 00 00 00  ?...............
00000120   39 00 00 00  87 01 00 00  00 00 00 FF  00 00 00 00  9...............
00000130   38 00 00 00  89 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000140   38 00 00 00  85 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000150   38 00 00 00  88 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000160   38 00 00 00  89 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000170   38 00 00 00  84 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000180   38 00 00 00  89 01 00 00  00 00 00 FF  00 00 00 00  8...............
00000190   38 00 00 00  85 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001A0   38 00 00 00  8A 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001B0   38 00 00 00  82 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001C0   38 00 00 00  8A 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001D0   38 00 00 00  83 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001E0   38 00 00 00  83 01 00 00  00 00 00 FF  00 00 00 00  8...............
000001F0   38 00 00 00  88 01 00 00  00 00 00 FF  00 00 00 00  8...............

It would be very neat if the per-coordinate data started at 0x100 and took 16 bytes per coordinate.

Total number of possible coordinates is 100x130x7 = 91,000 = 1,456,000 bytes.

Uncompressed file size is always 1,458,915 bytes, leaving 2,915 bytes over for header and any trailing data.

How are the rows structured? [z][y][x]?

Looking at the data around 0x163890, we see:

00163800   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163810   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163820   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163830   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163840   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163850   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163860   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163870   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163880   38 00 00 00  00 01 00 00  00 00 00 FF  00 00 00 00  8...............
00163890   38 00 00 68  00 00 00 50  00 00 00 1A  00 00 00 14  8..h...P........
001638A0   00 00 00 3A  00 00 00 00  38 25 00 04  00 00 00 00  ...:....8%......
001638B0   00 00 00 1A  00 00 00 00  00 00 00 00  00 00 00 00  ................
001638C0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
001638D0   00 00 00 00  00 00 00 00  00 00 00 02  00 00 00 00  ................
001638E0   00 00 00 32  00 00 00 00  00 00 00 00  00 00 00 00  ...2............
001638F0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163900   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163910   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163920   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163930   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163940   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163950   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163960   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00163970   00 00 00 00  00 00 00 00  00 00 00 00  00 17 4D 61  ..............Ma
00163980   6E 69 61 00  00 00 00 00  00 00 00 00  00 00 00 00  nia.............

So this looks like a good working theory.

Wherever they start, these rows must refer to the object sets somehow. We also need to work out how they're arranged.

Disassembly of WH40K_TD.EXE suggests a file Engine::CellInfo at 0x4226F0. It dumps the following information:

  • Mission exit: true / false
  • Smoke: true / false
  • Sprite: true / false
  • Vehicle: true / false
  • Net start: true / false
  • Spell: true / false
  • Trigger: true / false
  • Breadcrumb: true / false
  • Door: true / false
  • Lock: true / false
  • Reactor: true / false
  • Objective: true / false
  • Canister number %d
  • Cell visible %d
  • Tile visible %d
  • N0 - N7: true / false (seems to be a bitfield)
  • Object 0 SURFACE: true / false
  • Object area %d
  • Curr sprite %d
  • Object 1 LEFT: true / false
  • Object area %d
  • Curr sprite %d
  • Object 2 RIGHT: true / false
  • Object area %d
  • Curr sprite %d
  • Object 3 CENTRE: true / false
  • Object area %d
  • Curr sprite %d

Presumably at least some of these attributes come from the records identified above. Will need to experiment to establish correlations. Items of interest:

  • A cell can have 4 objects in it - a surface, a left, a right, and a centre.
  • Presumably a surface is what we show on the ground, and it will be set for "ground level" data.

Press "U" to get to the screen!

Debug screen

I've added a view-map command to explore the data graphically. Each of the 16 bytes in a cell row must have a function; comparing a known map to how it looks in WH40K_TD.exe can help me to unravel that function.

Here's a side-by-side comparison of Chapter3.MAP, investigating CellIndex=3 and Z index = 0

Map comparison

Incrementing the Z index shows the building and terrain progressively changing as expected, so this is a good hint that the data is actually arranged in [z][y][x][16] blocks and that I either have the right offset, or I'm out by a well-aligned amount.

Trying to constrict to the viewport currently cuts off much of this displayed map, suggesting that I've got something wrong in the calculation somewhere. Need to dig into that more.

Trailer

Assuming the theory above is correct, we have trailer data starting at 0x163890. Browsing through the Chapter01.MAP data, I see:

  • Names:
    • 0x163970: Mania
    • Dagon
    • Nihasa
    • Samnu
    • Gigamen
    • Valefor
    • Apollyon
    • Vassago
    • Diabolus
    • Asmodee
    • Gamigin
    • Beherit
    • Marbas
    • Lucifer
    • Ahpuch
    • Gorgon
    • Loki
    • Lucifer
    • Mammon
    • Baphomet
    • Magot
    • Belial
    • Mamon
    • ...

So we must have enemy characters placed on the map, although I don't remember Chapter01 having this many...

At 0017C9DF we see the start of scenario text:

0017C9A0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
0017C9B0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
0017C9C0   00 00 00 02  00 00 00 00  00 00 00 32  00 00 00 01  ...........2....
0017C9D0   03 FF FF 00  00 00 00 00  00 00 00 00  00 00 00 59  ...............Y
0017C9E0   6F 75 20 68  61 76 65 20  73 70 6F 74  74 65 64 20  ou have spotted
0017C9F0   61 6D 6D 75  6E 69 74 69  6F 6E 20 63  72 61 74 65  ammunition crate
0017CA00   73 20 77 69  74 68 20 49  6D 70 65 72  69 61 6C 20  s with Imperial
0017CA10   6D 61 72 6B  69 6E 67 73  2E 00 00 00  00 00 00 00  markings........
0017CA20   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................

Several records like this.

Around 001841A0: mission objectives!

00184190   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
001841A0   00 00 00 00  00 00 4D 69  73 73 69 6F  6E 20 4F 62  ......Mission Ob
001841B0   6A 65 63 74  69 76 65 3A  0A 0A 45 6E  74 65 72 20  jective:..Enter
001841C0   74 68 65 20  74 65 6D 70  6C 65 20 6F  66 20 74 68  the temple of th
001841D0   65 20 43 68  61 6F 73 20  43 75 6C 74  69 73 74 20  e Chaos Cultist
001841E0   61 6E 64 20  72 65 74 72  69 65 76 65  20 74 68 65  and retrieve the
001841F0   20 72 65 6C  69 63 2E 20  0A 0A 52 65  63 6F 6D 6D   relic. ..Recomm
00184200   65 6E 64 65  64 20 45 71  75 69 70 6D  65 6E 74 3A  ended Equipment:
00184210   20 0A 0A 4B  72 61 6B 20  47 72 65 6E  61 64 65 73   ..Krak Grenades
00184220   20 0A 0A 41  6E 74 69 2D  50 6C 61 6E  74 20 47 72   ..Anti-Plant Gr
00184230   65 6E 61 64  65 73 0A 00  00 00 00 00  00 00 00 00  enades..........
00184240   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................

Since all the files are exactly the same length uncompressed, I'm going to assume these are all a fixed number of fixed-size records when looking into it.