File formats
The "Size" column in each table should be interpreted in bytes, unless specified otherwise.
The Type[N] notation represents a sequence of N structures of
type Type, but it doesn't necessarily mean that all Types have the same
length. It just means that you have a Type, then another, and another, until
you reach N occurrences.
IT
General structure of the .it format (everything is in MSB):
| Type | Size | Name | Description |
|---|---|---|---|
| IT_Item[X] | sizeof(IT_Item)*X | Items | Sequence of all pickable items in the game. |
The X represents any natural number, this format doesn't have a specific number of minimal or maximal items. So this file is just a sequence of IT_Items.
Structure of IT_Item:
| Type | Size | Name | Description |
|---|---|---|---|
| ITEM_TYPE | 4 | Type | Type of item. |
| uint32 | 4 | UID | UID of item. |
| EXTRA_INFO | 4 | Extra info | Specifies the document, photo, map or statuette if needed. |
| uint32 | 4 | Multiplier | Multiplier for the ammunition. |
| DIFF_MODE | 4 | Diff mode | Tells in which difficulty and mode the item shows up. |
For more info on each type, check these links:
TM
General structure of the .tm format (everything is in MSB):
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | ??? | ??? usually 0x05 or 0x04 |
| TM_Section[X] | sizeof(TM_Section)*X | Sections | All sections of the TM file. |
Structure of TM_Section:
| Type | Size | Name | Description |
|---|---|---|---|
| TM_SECTION_TYPE | 4 | Section type | Depending on this type, the content will be different. |
| uint32 | 4 | Size | Size 'L' of the content, including these 4 bytes. |
| TM_Generic_Content | L - 4 | Content | Content of the section. |
TM_SECTION_TYPE can have the following values:
| Value | Meaning |
|---|---|
| 0x04 | Door coordinates (?) |
| 0x06 | NPC section (?) |
| 0x07 | Description section (?) |
| 0x08 | Item section |
TM_Generic_Content is just a union of these structures:
- TM_Coord_Content
- TM_Item_Content
Depending on the TM_SECTION_TYPE, the content must be interpreted in different
ways. For example, if TM_SECTION_TYPE is 0x08, then the content of the section
must be interpreted as a TM_Item_Content structure. Here are the formats of each
of the previous structures.
TM_Coord_Content:
| Type | Size | Name | Description |
|---|---|---|---|
| ??? | 4 | ??? | ??? |
| ??? | 4 | ??? | ??? |
| uint32 | 4 | X pos 1 | Coordinate along the X axis of player. |
| uint32 | 4 | Y pos 1 | Coordinate along the Y axis of player. |
| uint32 | 4 | Z pos 1 | Coordinate along the Z axis of player. |
| float | 4 | ??? | ??? |
| float | 4 | ??? | ??? |
| ??? | 4 | ??? | ??? 0x00 00 00 00 ? |
| uint32 | 4 | X pos 2 | Coordinate along the X axis of teammate. |
| uint32 | 4 | Y pos 2 | Coordinate along the Y axis of teammate. |
| uint32 | 4 | Z pos 2 | Coordinate along the Z axis of teammate. |
| float | 4 | ??? | ??? |
| float | 4 | ??? | ??? |
| ??? | 4 | ??? | ??? 0x00 00 00 00 ? |
These TM_Coord_Content seem to hold info on the position and rotation of both characters (player and teammate, or Player 1 and Player 2) when they enter the room through a specific door. The first 2 fields probably specify the door.
TM_Item_Content:
| Type | Size | Name | Description |
|---|---|---|---|
| ITEM_TYPE | 4 | Type | The type of the item. Same as in allitems.it. |
| uint32 | 4 | UID | The UID of the item. Same as in allitems.it. |
| float | 4 | X pos | The position along the X axis. |
| float | 4 | Y pos | The position along the Y axis. |
| float | 4 | X pos | The position along the Z axis. |
| float[9] | 36 | Rotation | The rotation of the item, a rotation matrix encoded as an array of 9 floats. |
| uint32 | 4 | Item info length | The length of the following uint8 array. The value is L2. |
| uint8[L2] | L2 | Item info | Gives information like the multiplier, the DIFF_MODE and the EXTRA_INFO. |
The Item info is an array of printable characters (like a string that doesn't
end with 0x00), it can take several shapes and each has a slighly different
meaning. This array shows a bunch of numbers (sometimes letters) separated by
slashes, for example: "1/1/7", "1/1", or "1/B". When there are three
numbers/letters, the first one is the multiplier in decimal, and the third one
is the diff_mode in hexadecimal. When there are two, the first one is probably
the multiplier (which is always "1"), and the second one is the extra_info, but
left padded with zeros to occupy 8 characters (because it represents a 4 bytes
integer) ONLY WHEN the item corresponds to a document, photo or statuette (for
example, "1/00020834"), not with maps. With maps we only have the letter itself,
like for example "1/C". When it's just one number, it's usually 1 and it
probably represents the multiplier, the diff_mode is probably "7" by default and
the extra_info "1" (which corresponds to an EXTRA_INFO of 0x00000000 in the
allitems.it file) by default.
More research on this last field is needed.
SAV (ObsCure)
General structure of the .sav files of the first game (everything is in LSB):
| Type | Size | Name | Description |
|---|---|---|---|
| SAV_Header | 22 | Header | Gives general info on the game. |
| SAV_Items | ItemsAndInfo | Sequence of picked up items. | |
| SAV_Characters | Characters | Information on each playable character. | |
| SAV_Progress | Progress | Progress in the game and in each room. |
SAV_Header
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | CRC32 | CRC32 checksum of the rest of the contents of the file. |
| uint8 | 1 | ??? | ??? 0x06 |
| uint32 | 4 | Index | Index of the savefile. It's 'N' in "gameN.sav". |
| ROOM_ID_SHORT | 1 | Room | Room where the game was saved. |
| uint32 | 4 | Play time | Duration of the gameplay in milliseconds. |
| uint8 | 1 | Times saved | Number of times the player saved the game. |
| ??? | 2 | ??? | ??? |
| DIFF_MODE | 1 | Diff mode | Difficulty and mode of the game. |
| ??? | 2 | ??? | ??? Usually 0x6400 |
| uint16 | 2 | Length of rest | Length of SAV_Items + SAV_Characters (excluding these 2 bytes). |
For more info on the ROOM_ID_SHORT type, check this link.
SAV_Items
| Type | Size | Name | Description |
|---|---|---|---|
| uint16 | 2 | Length of SAV_Items | Length of this SAV_Items section (including these 2 bytes). |
| uint8 | 1 | Inventory capacity | Let's call the value 'I'. I * sizeof(SAV_ItemOrAmmo) = Length of SAV_Items - 3. |
| SAV_ItemOrAmmo[I] | I*9 | Items | Sequence of items, the inventory itself. |
Sav_ItemOrAmmo is the union of the following types:
- SAV_Item
- SAV_Ammo
SAV_Item:
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Item UID | Unique identifier for the item. |
| uint8 | 1 | Number | How many examples of this item the player has. |
| EXTRA_INFO | 4 | Extra info | Extra info on the item. |
SAV_Ammo:
| Type | Size | Name | Description |
|---|---|---|---|
| AMMO_TYPE | 1 | Ammo type | 0x06 for shotgun and 0x07 for handgun. |
| uint32 | 4 | ??? | 0x00 00 00 XX where 0xXX is the least valuable byte of Quantity. |
| uint32 | 4 | Quantity | Quantity of ammunition of said type. |
Despite being "Quantity" in LSB (like everything in SAV files), "???" is in MSB.
So the first 3 bytes are zeros and the 4th byte is this 0xXX described above.
At this point you might wonder how the game knows if the SAV_ItemOrAmmo is a
SAV_Item or a SAV_Ammo. The game checks the first byte, if it's a 0x06 or a
0x07, then it's a SAV_Ammo, otherwise it's a SAV_Item. That's because there
is no item UID in the game ending with 0x06 or 0x07 (note how I wrote
"ending" instead of "starting" because it's in LSB).
SAV_Characters
| Type | Size | Name | Description |
|---|---|---|---|
| SAV_Character[5] | Characters | An array of 5 SAV_Character structures. |
In this sequence of 5 SAV_Character, the order of the characters is Stan, Josh, Kenny, Ashley and Shannon.
SAV_Character:
| Type | Size | Name | Description |
|---|---|---|---|
| uint16 | 2 | Length of SAV_Character | Length of SAV_Character (including these 2 bytes). |
| uint8 | 1 | Capacity | Weapons inventory capacity. Let's call it 'W'. |
| uint32 | 4 | Door | Represents the door next to which the character is standing. |
| uint8 | 1 | Room | Room where the character is located. |
| bool | 1 | IsTeammate? | ??? 0x01 if this character is the teammate, 0x00 otherwise. |
| float | 4 | X pos? | Coordinate of the character along the X axis. |
| float | 4 | Y pos? | Coordinate of the character along the Y axis. |
| float | 4 | Z pos? | Coordinate of the character along the Z axis. |
| uint8 | 1 | Rotation? | Rotation of the character. |
| uint32 | 4 | Current weapon UID | The Item UID of the weapon the character is holding. |
| ??? | 29 | ??? | ??? |
| float | 4 | Health | Health of the character. |
| SAV_Weapon[W] | W*9 | Weapons | Weapons inventory of the character. |
SAV_Weapon:
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Weapon item UID | Unique identifier of the weapon (which is also a pickable item). |
| uint8 | 1 | Quantity | Quantity of said weapon (always 0x01). |
| Ammo_Flash_Info | 4 | AmmoFlashlight | Ammo in the chamber of the weapon and attached flashlight. |
Ammo_Flash_Info has a weird structure, let's imagine this as a uint32 in its
MSB notation (even though it's stored in LSB in the .sav file):
0xXX XX XY YY, the least valuable byte is 0xYY, the next one is 0xXY, then
the two 0xXX. 0xXX XX X stores information on the flashlight that is
attached to the gun, while 0xY YY stores the ammunition the gun holds in its
chamber. Usually the game only uses 8bits (or even 4bits, since it's usually a
number between 0 and 15), so the least valuable byte 0xYY, but it actually
reads the 12bits 0xY YY, so you can actually have up until 16^3 - 1 bullets in
the chamber if you modify the .sav file manually.
SAV_Progress
| Type | Size | Name | Description |
|---|---|---|---|
| SAV_Progress_Chunk[] | Chunks | Each of these generic chunks give information on the progress in the game. |
SAV_Progress_Chunk:
| Type | Size | Name | Description |
|---|---|---|---|
| uint16 | 2 | Length | Length of the chunk (excluding these 2 bytes). Let's call it 'C'. |
| SAV_Chunk_Content | C | Content | The content of the chunk, which can have different structures. |
The first 3 SAV_Progress_Chunks give general information on the progress in the game, like, for example, the drawings in the map. While the other chunks give information on each specific room. The SAV_Chunk_Content structure is a union of the following structures:
- SAV_General1_Chunk_Content
- SAV_Room_Chunk_Content
SAV_General1_Chunk_Content
This is the first Progress Chunk, it gives general information on the progress in the game. Structure of the SAV_General1_Chunk_Content:
| Type | Size | Name | Description |
|---|---|---|---|
| bool02 | 1 | isKennysBagTaken | Has Kenny's bag been stolen? |
| ??? | 29 | ??? | ??? |
| bool | 1 | friedLibKin | Has Friedman's Kinematic at the library been triggered? |
| ??? | 11 | ??? | ??? |
| bool02 | 1 | diningHallLightsOn | Are the lights in the Dining Hall on? |
| ??? | 77 | ??? | ??? |
| bool | 1 | stanFound | Is Stan in the team? Has his cutscene been triggered? |
| MAP_DRAWING | 1 | mapDrawing | What drawing can be seen in the map? |
| ??? | 38 | ??? | ??? |
Bool is 0x00 if false and 0x01 if true. Bool02 is 0x00 if false and 0x02
if true. The size of this structure is always 0xA0.
MAP_DRAWING can take these hexadecimal values:
| MAP_DRAWING | Description |
|---|---|
| 01 | “Friedman” on the classrooms building |
| 02 | “Friedman” on the library building |
| 03 | “Janitor” |
| 04 | “Nurse” and arrow |
| 05 | Wheel on the Administration building |
| 06 | Stairs on amphitheatre |
| 07 | “Nurse” again (and lockers?) |
| 08 | Keyhole on the library building |
| 09 | Projector on amphitheatre |
| 0A | Keypad on the big door |
SAV_Room_Chunk_Content
This structure gives information on a specific room like the monsters and their health, the position of props, the items that have been picked up, etc. Structure of the SAV_Room_Chunk_Content:
| Type | Size | Name | Description |
|---|---|---|---|
| ROOM_ID_SHORT | 1 | Room | The chunk gives information on this room. |
| ??? | 1 | ??? | ??? Gives information on the kinematics that have been played in the room? |
| ??? | 1 | ??? | ??? 0x00? |
| ??? | ? | ??? | ??? |
HOE (ObsCure)
Here's the general structure of HOE files (everything is in MSB):
| Type | Size | Name | Description |
|---|---|---|---|
| float? | 4 | ??? | ??? |
| HOE_Chunk[] | ? | Chunks | A sequence of chunks, there are different types. |
| uint32 | 4 | End | 0x00 00 00 04 indicating the end of the file. |
The HOE_Chunk structure is the following:
| Type | Size | Name | Description |
|---|---|---|---|
| HOE_CHUNK_TYPE | 4 | Chunk type | Specifies the type of chunk, and how its content must be intepreted |
| HOE_Content | ? | Content | The content of the chunk. |
HOE_CHUNK_TYPE can have the following values:
| Value | Meaning |
|---|---|
| 0x02 | Collisions |
| 0x03 | Imported functions ??? |
| 0x05 | Event definition |
| 0x06 | Event Instances |
Chunks of the same type are always contiguous, and the order in which the
types of chunks are found in the file is the following: 0x03 (if there is
any), 0x02, 0x05, and finally 0x06.
HOE_Content is just the union of the following structures:
- HOE_Collision
- HOE_Imports
- HOE_Event
- HOE_Instance
Depending on the chunk type, the content must be interpreted as one of the following structures.
HOE_Collision
This structure defines the collisions of the room. Thanks to it, characters don't pass through tables, walls, etc. Here's its format:
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Length of chunk | Length of the chunk, including these 4 bytes. Let's call it L. |
| ??? | L-4 | ??? | ??? Unknown format |
HOE_Imports
This structure seems to mention the name of some functions. Here's its format:
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Length of chunk | Length of the chunk, including these 4 bytes. Let's call it L. |
| ??? | L-4 | ??? | ??? Unknown format |
HOE_Event
There's still a lot of research to do, especially regarding the scripting language, but this is more or less the structure of the event definitions (most of the time):
| Type | Size | Name | Description |
|---|---|---|---|
| float | 4 | ??? | Always 4.0?? |
| LString | ? | Name | Name of the event. |
| ??? | ? | ??? | ??? |
| uint32 | 4 | Number of strings | Number of following strings, let's call it N. |
| LString[N] | ?*N | Strings? | ??? |
| uint32 | 4 | Number of vars | Number of following variables, let's call it V. |
| HOE_Var[V] | 8*V | Vars | Variables used in HOE scripts. |
| uint32 | 4 | Number of -1s | Number of following 0xFF FF FF FF??? Let's call it M. |
| uint32[M] | 4*M | Minus ones??? | An array of 0xFF FF FF FFs??? |
| ??? | ??? | Script | This is a script in some bytecode language. Unknown format. |
For more information on LStrings, check this link.
The scripting language includes a lot of strings that seem to be function names,
it also includes what seems to be a bunch of OP codes, like 0x65, 0xC9,
0xD0, etc. It also includes sometimes 32bits integers that represent a
HOE_Var, to be precise they are the index of a specific HOE_Var in the Vars
array. The scripts sometimes end with 0x00 00 00 07.
The HOE_Var structure has the following format:
| Type | Size | Name | Description |
|---|---|---|---|
| HOE_VAR_TYPE | 4 | Type | The type of the value. |
| uint32/float | 4 | Value | The value. |
HOE_VAR_TYPE dictates how the value must be intepreted. It can have the following values:
| Value | Meaning |
|---|---|
| 0x01 | Unsigned Integer |
| 0x02 | Float |
HOE_Instance
| Type | Size | Name | Description |
|---|---|---|---|
| uint8 | 1 | ??? | ??? |
| uint32 | 4 | Length of Instance chunk | The length of the rest of this chunk, including these 4 bytes. |
| ??? | ??? | Unknown content | ??? |
Among other things, in the "Unknown content" you can find the coordinates of monsters (when the HOE_Instance represents a monster, which is not always the case) in the form of an array of 3 floats (X, Y and Z), usually after the "gIsAttacking" string.
HVP (ObsCure)
.hvp is the archive container used by the engine to ship the game's
data. The game reads from four of them: datapack.hvp, cachpack.hvp,
kinepack.hvp, and strmpack.hvp. Internally a .hvp is a header,
followed by a tree-structured table of contents, followed by a region
of zlib-compressed file blobs.
General structure of the .hvp format (everything is in MSB):
| Type | Size | Name | Description |
|---|---|---|---|
| HVP_Header | 40 | Header | File-wide header, including the two CRC32s and the root entry count. |
| HVP_Entry[] | ? | TOC entries | A tree of directory and file entries describing every packed file. |
| uint8[] | ? | Data | Concatenation of all packed file blobs. Each blob is referenced by offset. |
HVP_Header
| Type | Size | Name | Description |
|---|---|---|---|
| ??? | 28 | ??? | Magic and reserved engine fields. Always identical across all four archives. |
| uint32 | 4 | Data offset | Offset (relative to byte 40) where the data section starts. Also = TOC byte size. |
| uint32 | 4 | Header CRC32 | CRC32 of bytes [0..31] (i.e. everything before the two CRC fields). |
| uint32 | 4 | Entries CRC32 | CRC32 of bytes [40..40+Data_offset], i.e. of the entire TOC section. |
| uint32 | 4 | Root count | Number of top-level entries in the TOC. |
After the header (at file offset 40), the TOC begins. Both CRC32s use
the standard polynomial 0xEDB88320. They are validated on read; if
either fails the engine refuses to mount the archive.
HVP_Entry
The TOC is a tree of HVP_Entry records. Each record is either a
directory (which holds a child count and a list of children that
follow it sequentially) or a file (which holds metadata for a packed
blob). The two variants share a leading 4-byte size field but
otherwise have different layouts:
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Entry size | Total size of this entry in bytes. The next entry begins immediately after. |
| HVP_Content | E-4 | Content | Either an HVP_Dir_Content or an HVP_File_Content, depending on the layout of these bytes. |
To tell directories from files: compute name_len = Entry_size - 17.
If name_len > 0, and the byte at offset +16 from the entry start
equals name_len, and the name_len bytes after it are all printable
ASCII, the entry is a directory. Otherwise it's a file.
HVP_Dir_Content
| Type | Size | Name | Description |
|---|---|---|---|
| ??? | 5 | ??? | ??? |
| uint32 | 4 | Child count | Number of HVP_Entry records that belong to this directory. Let's call it C. |
| ??? | 3 | ??? | ??? |
| uint8 | 1 | Name length | Length of the directory name. Let's call it N. Equal to Entry_size - 17. |
| char[N] | N | Name | Directory name in ASCII. No null terminator. |
| HVP_Entry[C] | ? | Children | The C entries immediately following this one are the directory's children. |
HVP_File_Content
| Type | Size | Name | Description |
|---|---|---|---|
| HVP_VARIANT | 4 | Variant tag | 0x00000001 for File entries. |
| uint8 | 1 | Is compressed | 0x01 if the blob is zlib-compressed, 0x00 if stored raw. |
| uint32 | 4 | Compressed size | Size of the blob in the data section. |
| uint32 | 4 | Decompressed size | Size of the file after decompression (equal to Compressed size if not compressed). |
| int32 | 4 | Checksum | bytes_sum of the compressed blob (see below). |
| uint32 | 4 | Offset | Absolute file offset where the compressed blob starts. |
| ??? | 3 | ??? | Padding / reserved. |
| uint8 | 1 | Name length | Length of the file name. Let's call it N. |
| char[N] | N | Name | File name (just the basename, not the full path). The full path is reconstructed by walking up through the parent directory entries. |
For more info on HVP_VARIANT, check
this link.
The bytes_sum checksum is not a CRC. It is computed over the
compressed bytes as follows:
sum = 0 (signed int32)
for each 4-byte chunk in the blob, read as LE uint32:
sum = sum + chunk // wrapping signed addition
for each remaining byte (if length not a multiple of 4):
sum = sum + byte // wrapping signed addition
The blob itself, when Is compressed == 0x01, is a standard zlib
stream: a 2-byte header (0x78 0xDA for best compression — 0x78
0x9C is also accepted), followed by the deflate-compressed data,
followed by a 4-byte adler32 in big-endian.
Modifying an HVP
To replace a file inside an HVP without rewriting the whole archive:
- Walk the TOC to find the target
HVP_File_Contententry. - Compress the new file with zlib and compute its
bytes_sumchecksum. - Append the new compressed blob to the end of the
.hvpfile. - Overwrite the four fields in the TOC entry:
Compressed size,Decompressed size,Checksum, andOffset. The newOffsetis the position where you appended the blob. - Recompute and write the
Entries CRC32(bytes[40..40+Data offset]) and then theHeader CRC32(bytes[0..31]).
The original blob's bytes remain in the file but become unreferenced. This append-at-EOF strategy works because the engine resolves files only through the TOC offset, never by scanning the data section.
LNG (ObsCure)
.lng is the localized-text format. Each language ships its own
copy in _texts/ (en.lng, fr.lng, de.lng, etc.). The format is
a flat sequence of length-prefixed entries indexed by a 32-bit ID;
the rest of the engine looks up strings by passing this ID.
General structure of the .lng format (headers and lengths are in
MSB; UTF-16 payloads are in LSB):
| Type | Size | Name | Description |
|---|---|---|---|
| ??? | 4 | ??? | Padding / version field, usually zero. |
| uint32 | 4 | Entry count | Number of LNG_Entry records that follow. |
| LNG_Entry[] | ? | Entries | Sequence of entries. |
LNG_Entry
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | ID | Unique 32-bit identifier the engine uses to look up this string. |
| uint32 | 4 | Size | Size of the payload that follows (the Content field). Let's call it L. |
| LNG_Content | L | Content | The string payload, either Windows-1252 or UTF-16 LE. |
LNG_Content is a union of two encodings, distinguished by the first
byte:
- If the first byte is
0x00, the rest of the payload is a Windows-1252 (8-bit Latin) string terminated by0x00. - If the first byte is
0x01, the second byte is a marker (0x1C) and the rest of the payload is a UTF-16 LE string terminated by0x00 0x00.
For more info on LNG_ENCODING, check
this link.
LNG_Win1252_Content
| Type | Size | Name | Description |
|---|---|---|---|
| LNG_ENCODING | 1 | Encoding flag | Always 0x00. |
| char[?] | ? | String | Windows-1252 encoded text, null-terminated. |
LNG_UTF16_Content
| Type | Size | Name | Description |
|---|---|---|---|
| LNG_ENCODING | 1 | Encoding flag | Always 0x01. |
| uint8 | 1 | Marker | Always 0x1C. |
| uint16[?] | ? | String | UTF-16 LE codepoints, terminated by 0x0000. |
Notes:
- A single
.lngfile may mix both encodings entry-by-entry. - IDs are not necessarily contiguous and may be reused across files for the same logical string.
- The engine renders strings through the bitmap font glyph table (see below), so any codepoint used in a UTF-16 string must have a corresponding glyph entry in the EXE's glyph table, otherwise it renders as a blank or fallback glyph.
MAP (ObsCure)
.map files live in _common/ (e.g. mapgen.map, mapb000.map,
mapd100.map). They describe room regions for navigation/level
purposes. Each level group has its own .map file; mapgen.map
covers the general overview map.
General structure of the .map format (everything is in MSB):
| Type | Size | Name | Description |
|---|---|---|---|
| uint32 | 4 | Entry count | Number of MAP_Entry records that follow. Let's call it N. |
| ??? | 4 | ??? | Usually 0x00000000. |
| MAP_Entry[N] | 36*N | Entries | Per-room bounding box entries. |
| ??? | ? | ??? | Trailing data not yet decoded; size varies per file. |
MAP_Entry
| Type | Size | Name | Description |
|---|---|---|---|
| char[4] | 4 | Name | Up to 4 ASCII characters, null-padded if shorter (e.g. "M1\0\0", "M001"). |
| float | 4 | X1 | Normalized left edge in [0..1]. |
| float | 4 | Y1 | Normalized top edge in [0..1]. |
| float | 4 | Z1 | Sentinel. +Inf (0x7F800000) for standard rooms; -Inf (0xFF800000) for container/group entries that visually overlap with other rooms. |
| float | 4 | X2 | Normalized right edge in [0..1]. |
| float | 4 | Y2 | Normalized bottom edge in [0..1]. |
| float | 4 | Z2 | Sentinel. Always +Inf in the files examined. |
| ??? | 8 | ??? | Trailing zeros; possibly reserved. |
Example entries from mapgen.map:
| Name | X1 | Y1 | X2 | Y2 | Z1 sentinel | Likely meaning |
|---|---|---|---|---|---|---|
| M001 | 0.5657 | 0.1437 | 0.7884 | 0.3007 | +Inf |
A room. |
| M100 | 0.2886 | 0.2435 | 0.6290 | 0.6249 | -Inf |
Container that groups several rooms. |
| C0 | 0.1656 | 0.1404 | 0.2633 | 0.2706 | +Inf |
A room. |
Note that these bounding boxes do not control the room highlight
rectangles shown on the in-game map screen (changing them has no
visible effect on the highlights), so they are probably used for
some other purpose — pathfinding, mini-map drawing, or
player-position-to-room lookup. The semantic meaning of the short
names (M0, B0, C0, etc.) and the layout of the trailing data
have not yet been decoded.