This wiki article was created largely as an extended answer to someone who asked about the format. It is still very incomplete, with no imminent plans to expand it. Talking to senior Ironfist team members remains the best way to learn more.
The main data structures in question are fullMap, SMapHeader, mapCell, and mapCellExtra. The raw reverse-engineered versions of the data structures are available in HEROES2W.h and EDITOR2W.h, but prettier versions are available in map.h . Some data structures are slightly different between the editor and the game; the editor's versions have extra data to support deleting.
The data structures make heavy use of bitfields. E.g.: there is a 1-bit field in mapCell declared "unsigned int isShadow : 1;". However, in the raw reverse-engineered data structure, it appears as part of a larger
field_4_1_1_isShadow_1_13_extraInfo field ("1 bit unknown, 1 bit isShadow, 1 bit unknown, 13 bits extraInfo"). All decompiled code that accesses the isShadow field will do so using bitops.
The mapCell field contains places to store the info of one object on that cell. However, cells may contain many overlapping objects. A single cell may contain a sulfur mine, the mountain it sits on, a visiting hero, the shadow of an object next to it, and the top part of a tall building on a cell beneath it. To support this, each cell also contains an embedded linked list of "cell extras," which store this information for other objects.
Some cells have extra gameplay information associated with them, e.g.: the cell with the entrance to a town is linked to the town object, a sign has text, etc. These are stored in an additional object, confusingly also called an "extra." (Yes, these names were used by the original game developers.) These are stored in the field extraInfo as an offset into the global array ppMapExtra, which contains these Extra objects. You can browse map.h to see a list of the various Extra objects (e.g.: TownExtra, SignExtra).
mapCell decompiled | mapCell ironfist | |||||
Name | Size (bits) | Bit indexes | Name | Size (bits) | Bit indexes | |
groundIndex | 16 | 1-16 | groundIndex | 16 | 1-16 | |
objTileset | 8 | 17-24 | hasObject | 1 | 17 | |
objectIndex | 8 | 25-32 | isRoad | 1 | 18 | |
extraInfo | 16 | 33-48 | objTileset | 6 | 19-24 | |
overlayTileset | 8 | 49-56 | objectIndex | 8 | 25-32 | |
overlayIndex | 8 | 57-64 | field_4_1 | 1 | 33 | |
displayFlags | 8 | 65-72 | isShadow | 1 | 34 | |
objType | 8 | 73-80 | field_4_3 | 1 | 35 | |
extraIdx | 16 | 81-96 | extraInfo | 13 | 36-48 | |
hasOverlay | 1 | 49 | ||||
hasLateOverlay | 1 | 50 | ||||
overlayTileset | 6 | 51-56 | ||||
overlayIndex | 8 | 57-64 | ||||
flags | 8 | 65-72 | ||||
objType | 8 | 73-80 | ||||
extraIdx | 16 | 81-96 |
Conversion table
mapCell decompiled | mapCell ironfist |
extraInfo & 1 | field_4_1 |
(extraInfo >> 1) & 1 | isShadow |
(extraInfo >> 2) & 1 | field_4_3 |
(extraInfo >> 8) >> -5 | extraInfo |
objTileset & 1 | hasObject |
(objTileset >> 2) & 0x3F | objTileset |
overlayTileset & 1 | hasOverlay |
(overlayTileset >> 1) & 1 | hasLateOverlay |
(overlayTileset >> 2) & 0x3F | overlayTileset |
It's also very important to use casts for these fields
(unsigned char)overlayIndex
(unsigned char)objectIndex
mapCellExtra decompiled | mapCellExtra ironfist | |||||
Name | Size (bits) | Bit indexes | Name | Size (bits) | Bit indexes | |
nextIdx | 16 | 1-16 | nextIdx | 16 | 1-16 | |
_1_q_7_objTileset | 8 | 17-24 | animatedObject | 1 | 17 | |
objectIndex | 8 | 25-32 | objTileset | 7 | 18-24 | |
field_4_1_1_1_isShadow_5 | 8 | 33-40 | objectIndex | 8 | 25-32 | |
_1_q_1_hasLateOverlay_6_q | 8 | 41-48 | field_4_1 | 1 | 33 | |
field_6 | 8 | 49-56 | field_4_2 | 1 | 34 | |
field_4_3 | 1 | 35 | ||||
field_4_4 | 5 | 36-40 | ||||
animatedLateOverlay | 1 | 41 | ||||
hasLateOverlay | 1 | 42 | ||||
tileset | 6 | 43-48 | ||||
overlayIndex | 8 | 49-56 |
Conversion table
mapCellExtra decompiled | mapCellExtra ironfist |
_1_q_7_objTileset & 1 | animatedObject |
(_1_q_7_objTileset >> 1) & 0x7F | objTileset |
_1_q_1_hasLateOverlay_6_q & 1 | animatedLateOverlay |
(_1_q_1_hasLateOverlay_6_q >> 1) & 1 | hasLateOverlay |
(_1_q_1_hasLateOverlay_6_q >> 2) & 0x3F | tileset |
field_4_1_1_1_isShadow_5 & 1 | field_4_1 |
(field_4_1_1_1_isShadow_5 >> 1) & 1 | field_4_2 |
(field_4_1_1_1_isShadow_5 >> 2) & 1 | field_4_3 |