Material banks (MBP and MDP)
A submesh carries a material id, a number that is looked up in a material bank. A bank is a pair of files: an .mbp index and an .mdp of material records. A material names its vertex and pixel shader and its textures by path
string, holds the shader’s parameters as float4 values, and lists its texture slots. There are three banks, all in the archive MATERIALS.PAK (in database). Observed
| Bank | Files | Materials |
|---|---|---|
| environments | environments\env_materials.mbp, .mdp |
3,020 |
| characters | characters\char_materials.mbp, .mdp |
1,065 |
| actors | actors\actor_materials.mbp, .mdp |
17 |
All 4,102 records of the three banks were read to their last byte with no byte left over. Proven · all 4,102 materials
All numbers are big-endian.
MBP index
Section titled “MBP index”Followed by count entries of 12 bytes, which end exactly at the end of the file.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 = 0xFADD0001 | magic | Proven | |
| 0x4 | 4 | u32 | file_size | Proven | |
| 0x8 | 4 | u32 = 0x00180000 | header_words | Observed | a u16 24 (the table offset) and a zero u16 |
| 0xC | 4 | u32 = 0xBEEFFACE | sentinel | Proven | |
| 0x10 | 4 | u32 | count | Proven | |
| 0x14 | 4 | u32 = 24 | table_offset | Proven |
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | material_id | Proven | unique within a bank; 0 occurs in all three banks |
| 0x4 | 4 | u32 | mdp_offset | Proven | |
| 0x8 | 4 | u32 | packed_size | Proven | the byte length in the low 22 bits; the upper 10 bits are 0x004 in every entry |
The records start at offset 0 of the MDP, follow one another without gap or overlap and end at its last byte; the id in each record (at 0x10) equals the id of its index entry. Proven · all 4,102 materials
MDP record
Section titled “MDP record”The record starts with a fixed header; every pointer in it is an offset from record + 0x40. After the header come the two shader paths, the parameter descriptors, the parameter values, the sampler descriptors, one texture-path pointer per sampler and the texture paths, padded to 16 bytes with zeros (0 to 15 of them). Proven · all 4,102 materials
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 = 0xAAAA0003 | magic | Proven | |
| 0x4 | 4 | u32 = 0x40 | header_size | Proven | |
| 0x8 | 4 | u32 = 0x00400000 | unknown | Unknown | |
| 0xC | 4 | u32 = 0xBEEFFACE | sentinel | Proven | |
| 0x10 | 4 | u32 | material_id | Proven | |
| 0x14 | 4 | u32 = 0xFFFF000E | unknown | Unknown | |
| 0x18 | 4 | u32 = 0xFFFFFFFF | unknown | Unknown | |
| 0x1C | 4 | u32 | identifier | Unknown | 3,578 distinct values over 4,102 records |
| 0x20 | 8 | u32[2] = 0xFFFFFFFF | unknown | Unknown | |
| 0x28 | 4 | u32 | unknown | Unknown | 110 distinct values, 0 to 0x25 |
| 0x2C | 4 | u32 | unknown | Unknown | 45 distinct values |
| 0x30 | 8 | u32[2] = 0xFFFFFFFF | unknown | Unknown | |
| 0x38 | 4 | u32 | zero | Unknown | |
| 0x3C | 4 | u32 | state_flags | Unknown | 34 distinct values; 0x200 in 3,255 records, 0 in 491 |
| 0x40 | 4 | u32 = 0xFFFFFFFF | unknown | Unknown | |
| 0x44 | 4 | u32 = 0x20 | vertex_shader_offset | Proven | the path starts at 0x60 |
| 0x48 | 4 | u32 | pixel_shader_offset | Proven | points just past the vertex shader's NUL; not a length |
| 0x4C | 4 | u32 | parameter_count | Proven | |
| 0x50 | 4 | u32 | parameter_offset | Proven | just past the pixel shader's NUL |
| 0x54 | 4 | u32 | sampler_count | Proven | 0 to 10 |
| 0x58 | 4 | u32 | sampler_offset | Proven | just past the last parameter value |
| 0x5C | 4 | u32 | texture_pointer_offset | Proven | just past the sampler descriptors |
The shader paths are ASCII strings without an extension, the stem of the .shp, .vsp and .psp entries of the SHADERS sections. One material names a pixel program that does not exist on the disc, shaders\pixel\environment\env_challenge_outline. Observed
Parameters
Section titled “Parameters”parameter_count descriptors, then one float4 value for each, in the same order.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | name_hash | Proven | sdbm of the shader uniform's name |
| 0x4 | 4 | u32 | encoding | Unknown | 0x000100AD in 72,115 descriptors, 0x020100AD in 121 |
| 0x8 | 4 | u32 = 0xFFFFFFFF | sentinel | Observed | |
| 0xC | 4 | u32 | value_offset | Proven | of a float4 (four f32), right after the descriptors |
The materials hold 72,236 parameter descriptors with a float4 each (all finite, inactive lanes included). A hash is matched to a name by taking the sdbm of the uniform names in the material’s own pixel program: 28,121 descriptors match and 44,115 do not. Proven · all 4,102 materials A hash in the same material may repeat; the order is meaningful and is kept. Observed The vertex programs’ own parameters have no names, so a vertex-stage uniform cannot be matched. Observed
Samplers and textures
Section titled “Samplers and textures”sampler_count descriptors, then one u32 offset per sampler to its texture path.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | state | Unknown | 0 in 30,413 descriptors, 0x01010100 in one, 0x02020200 in seven |
| 0x4 | 4 | u32 | state | Unknown | 2 in 30,416 descriptors, 1 in four, 3 in one |
| 0x8 | 4 | u32 | packed_state | Unknown | 0x00020C02 in 30,416 descriptors |
| 0xC | 4 | u32 | zero | Observed | |
| 0x10 | 4 | u32 | binding_hash | Observed | 21 distinct values; four are named, see below |
| 0x14 | 4 | u32 | state | Unknown | 0x200 in 30,408 descriptors; 0, 0x201, 0x202 and 0x300 in the rest |
| 0x18 | 4 | u32 = 0xFFFF000E | sentinel | Observed |
The texture path of a sampler is the name of a .thp entry without its extension. Of the 30,421 sampler slots, 15,319 have an empty path, 14,117 name a texture that exists on the disc (case-insensitively) and 985 name one that does not (91 in the character bank, 894 in the environment bank). Proven · all 4,102 materials
A material’s textures are therefore not all present: a reader has to keep an unresolved path as it is. Observed
The binding hash is an sdbm. Four of the 21 values equal the sdbm of a string in the executable: 95591c40 is Color_Map (3,995 slots), d79d6be4 Normal_Map (3,948), ffc34d98 Spec_Map (2,998) and 2538db2e Effect_Map (2,816), 13,757 slots in all. The other 17 values (16,664 slots) match no string in the executable, the scripts or the shader files. Proven · 2,661,053 candidate strings
That a slot is named Color_Map shows the identity of the slot, not what the pixel program does with it. The shaders’ parameter tables list no samplers, so which texture unit a slot feeds is not established, and “slot i feeds unit i” is not assumed. Unknown
Which bank
Section titled “Which bank”A submesh’s id is looked up in the bank of its own quartet’s namespace: the path of the quartet decides. Every nonzero id under levels\ is found in the environment bank, under characters\ in the character bank and under actors\ in the actor bank, and none is missing. The 34,469 nonzero references (2,885 distinct ids) are 26,669 environment, 1,521 character and 6,279 actor. Proven · all 35,114 submeshes
A PAK’s directory does not decide: level PAKs hold both environment and actor materials. Proven · all 35,114 submeshes
Material id 0 occurs in all three banks, and 645 submeshes use it (514 in levels, 5 in characters, 126 in actors). The namespace rule picks the bank for it as well, which is reasoning, not something the loader was seen to do. Inferred With that rule the 35,114 submeshes use 27,183 environment, 1,526 character and 6,405 actor materials. Inferred
The loader’s lookup (the function at 0x00415b84 that the model finalizer calls with the submesh’s id) was not traced to a bank choice.
The header words at 28, 40, 44 and 60 and the sampler state words are kept as integers; their values (blend, depth and sampler state are the obvious candidates) are not tied to anything in the engine.