Model libraries (MLP, MMP, MGP)
A level’s meshes are not stored one by one. A PAK holds a quartet: four entries with one base name and four extensions, .scp (the scene: placements and more, see Placements), .mlp (the model library: a table of
meshes and their metadata), .mmp (the MAIN buffer) and .mgp (the LOCAL buffer). The names of the sections they sit in (MAIN, LOCAL) follow the PS3’s main and video memory, and the split is real: the MMP holds
positions and indices and the MGP the other attributes of the same vertices. Observed
That the two buffers are literally loaded into main and video memory is inferred from the section names. Inferred
The disc has 657 quartets in 324 of the 336 PAKs, which together hold 21,309 mesh resources and 35,114 submeshes. Proven · all 657 quartets 512 of the quartets hold only rigid meshes; the other 145 (103 character PAKs and 21 layer PAKs) hold meshes with an embedded skinning stream (Skinned streams). Proven · all 657 quartets The 21,309 mesh resources are 17,897 rigid and 3,412 skinned; counts are of occurrences across PAKs, not of distinct meshes. Proven · all 657 quartets
All integers and floats are big-endian. MMP and MGP are raw buffers with no header of their own.
Offsets
Section titled “Offsets”Three bases are in use. Proven · all 657 quartets
- The tables of an SCP point at absolute offsets inside the decoded SCP.
- A record of the MLP’s resource table gives the offsets and sizes of one mesh’s slices: its metadata is relative to the MLP’s data base, its position and index bytes to the MMP’s start, its attribute bytes to the MGP’s start.
- Offsets inside a mesh’s metadata (its stream descriptors and submeshes) are relative to that metadata slice, and the descriptors’ buffer offsets are relative to that mesh’s own MMP or MGP slice.
No relocation table is needed for rigid meshes. The executable’s loader turns the offsets of an SCP and of an MLP into pointers when it loads the file and records that in the u16 at offset 10 (bit 4); a mesh’s metadata carries that bit already set on disc, so it is used as stored. Observed · static reading of the executable
Followed by resource_count records of 28 bytes from table_offset, then padding to data_base.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 = 0xFAFA0003 | magic | Proven | |
| 0x4 | 4 | u32 | unknown | Unknown | 0 in the CHARLIE sample |
| 0x8 | 2 | u16 = 0x001C | header_size | Observed | equals the table offset |
| 0xA | 2 | u16 | load_flags | Observed | 0 on disc; the loader sets bit 1 for storage it only borrows and bit 4 once relocated |
| 0xC | 4 | u32 | unknown | Unknown | |
| 0x10 | 4 | u32 | resource_count | Proven | 54 in CHARLIE, 750 in MIKE |
| 0x14 | 4 | u32 | data_base | Proven | where the metadata blob starts; 0x610 in CHARLIE |
| 0x18 | 4 | u32 = 28 | table_offset | Proven |
One per mesh. The metadata of a mesh starts at data_base + metadata_offset.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | mesh_id | Proven | equals the id in the mesh's own metadata at 0x80; placements refer to it |
| 0x4 | 4 | u32 | metadata_offset | Proven | |
| 0x8 | 4 | u32 | mmp_offset | Proven | |
| 0xC | 4 | u32 | mgp_offset | Proven | |
| 0x10 | 4 | u32 | metadata_size | Proven | |
| 0x14 | 4 | u32 | mmp_size | Proven | |
| 0x18 | 4 | u32 | mgp_size | Proven | 0 when the mesh has no MGP data |
The id in the record equals the id inside the mesh in all 21,309 resource occurrences. Proven · all 657 quartets
The slices of the MMP and MGP follow one another with zero padding of at most 14 and 12 bytes between them, and in the 512 rigid-only quartets 131,907,800 of the 131,996,256 MMP bytes and 98,794,820 of the 98,833,888 MGP bytes are inside slices; every unconsumed byte is zero. Proven · 512 rigid-only quartets
The first CHARLIE record is 296f09da, 0, 0, 0, 0x180, 0x1208, 0x840. Observed
Mesh metadata (BABA0031)
Section titled “Mesh metadata (BABA0031)”Followed at 0xC0 by the submesh records. The descriptors it points at sit after them.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 = 0xBABA0031 | magic | Proven | |
| 0x4 | 4 | u32 | unknown | Unknown | sentinel |
| 0x8 | 2 | u16 = 0x00C0 | header_size | Observed | |
| 0xA | 2 | u16 = 0xFFFF | load_flags | Observed | the loader relocates the offsets at 0x84 and 0x90 to 0xA0 only when bit 4 is clear |
| 0xC | 4 | u32 | unknown | Unknown | sentinel |
| 0x10 | 64 | f32[16] | local_matrix | Proven | the identity in all 21,309 resources |
| 0x50 | 16 | f32[4] | bounding_sphere | Inferred | centre xyz and radius; not needed for decoding |
| 0x60 | 16 | f32[4] | aabb_min | Proven | local box minimum xyz; the fourth value is 1 |
| 0x70 | 16 | f32[4] | aabb_max | Proven | local box maximum xyz; the fourth value is 1 |
| 0x80 | 4 | u32 | mesh_id | Proven | |
| 0x84 | 8 | u32[2] | unknown | Unknown | sentinels |
| 0x8C | 4 | u32 | vertex_format_mask | Proven | which attributes the vertices hold; see Vertex formats |
| 0x90 | 4 | u32 | primary_stream | Proven | offset of the primary stream descriptor |
| 0x94 | 4 | u32 | attribute_stream | Proven | offset of the MGP stream descriptor, or 0xFFFFFFFF |
| 0x98 | 4 | u32 | index_stream | Proven | offset of the index descriptor |
| 0x9C | 4 | u32 | embedded_stream | Proven | offset of the skinning stream, or 0xFFFFFFFF for rigid meshes |
| 0xA0 | 4 | u32 = 0xC0 | submesh_offset | Proven | |
| 0xA4 | 4 | u32 | submesh_count | Proven | |
| 0xA8 | 4 | u32 | secondary_count | Unknown | may differ from the submesh count |
| 0xAC | 8 | u32[2] | runtime_buffers | Observed | the loader writes the MMP and MGP base pointers here |
| 0xB4 | 4 | u32 | flags | Observed | bit 0: the skinning stream is already processed (all 3,412 skinned meshes) |
| 0xB8 | 8 | u8[8] | unknown | Unknown |
Every position of every decoded rigid mesh is finite and inside the stored box (to 0.05 units). Proven · all 17,897 rigid meshes
Stream descriptors
Section titled “Stream descriptors”The primary stream (positions and the packed normal, in the MMP) and the MGP stream share a 24-byte layout; the index descriptor has its own.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | vertex_count | Proven | |
| 0x4 | 4 | u32 | second_count | Unknown | equals vertex_count in every sample |
| 0x8 | 4 | u32 | stride | Proven | bytes per vertex |
| 0xC | 4 | u32 | unknown | Unknown | address-like or small values; not an offset into the file |
| 0x10 | 4 | u32 | offset | Proven | into this resource's slice of the MMP or MGP |
| 0x14 | 4 | u32 | size | Proven | count × stride |
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | index_count | Proven | |
| 0x4 | 4 | u32 = 5 | primitive | Proven | a triangle list |
| 0x8 | 4 | u32 | offset | Proven | into this resource's MMP slice |
| 0xC | 4 | u32 | size | Proven | index_count × 2 |
| 0x10 | 4 | u32 = 1 | index_type | Proven | big-endian u16 |
| 0x14 | 4 | u32 | unknown | Unknown | 0 |
For a rigid mesh the primary stream is 16 bytes a vertex: three big-endian f32 (x, y, z) and one packed word, the normal (Vertex formats). Proven · all 17,897 rigid meshes
The MMP slice holds the vertices then the indices, and the two descriptors together consume it exactly; the MGP stream consumes the MGP slice exactly. Proven · all 17,897 rigid meshes
In CHARLIE’s first mesh 176 vertices × 16 = 0xB00 bytes precede 900 indices (0x708 bytes), 0xB00 + 0x708 = 0x1208 = the MMP size, and 300 triangles result. Observed
Submeshes
Section titled “Submeshes”submesh_count records from submesh_offset; they partition the index buffer.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 16 | f32[4] | bounding_sphere | Inferred | |
| 0x10 | 16 | u8[16] | unknown | Unknown | address-like values |
| 0x20 | 4 | u32 | first_index | Proven | in indices, not bytes; a multiple of 3 |
| 0x24 | 4 | u32 | index_count | Proven | a multiple of 3 |
| 0x28 | 4 | u32 | base_vertex | Observed | 0 in every rigid mesh |
| 0x2C | 4 | u32 | unknown | Unknown | sentinel |
| 0x30 | 4 | u32 | unknown | Unknown | |
| 0x34 | 4 | u32 | material_id | Proven | a global id looked up in the material banks |
| 0x38 | 4 | u8[4] | uv_selectors | Proven | which stored UV set feeds each logical UV; see Vertex formats |
| 0x3C | 36 | u8[36] | unknown | Unknown | padding and sentinels |
The submeshes’ index spans are an exact, disjoint partition of the mesh’s index buffer and every index is below the vertex count. Proven · all 17,897 rigid meshes Every one of the 35,114 submesh occurrences, rigid or skinned, names a material id present in the material banks. Proven · all 657 quartets
The words at metadata offsets 4, 12, 132, 136, 168 and 184 and submesh offsets 16 to 31, 44, 48 and 60 to 95 hold sentinels, address-like values or zero. Nothing was found that reads them during loading.