Kingdom Hearts II - MDLX (MoDeL eXtended)
The MDLX format is an extension of the MDLS format from Kingdom Hearts 1. It is a BAR encapsulating the 3D data of the model(0x04), textures(0x07), object definition(0x17) and potentially other additional informations such as the AI. This page will mostly define the 3D data of the file as other informations will be documented separately.
3D Data Structure
Kingdom Hearts II splits models in packets of up to a theoretical limit of 16KB due to the memory limit of the PS2 CPU VU1, processing each of those parts, which we will subsequently call subparts.
An MDLX has at least a bone structure and a model(several models can be present in the case of, ie, a model for the shadows). Bone entries use an old technique for skeletal animation called Scale-Rotate-Translate or SRT. All bones SRT values are derived from their parents.
Each subpart contains one or more VIF packet which contains The VIF packets must not be larger than 100 QWC or else the game will begin to slow down dramatically.
3D Data Structure Header
Offset | Variable Type | Description |
---|---|---|
0 | void | A memory buffer used by the game as Lookup Tables |
0x90 | uint32_t | The version of the format(currently 3) |
0x94 | uint32_t | Reserved |
0x98 | uint32_t | Reserved |
0x9C | uint32_t | Next model header |
0xA0 | uint16_t | Bone count |
0xA2 | uint16_t | Unknown |
0xA4 | uint32_t | Bone offset |
0xA8 | uint32_t | Unknown offset |
0xAC | uint16_t | Model Subpart Count |
0xAE | uint16_t | Unknown |
3D Data Subpart Header
Offset | Variable Type | Description |
---|---|---|
0 | uint32_t | Unknown |
4 | uint32_t | Texture index |
8 | uint32_t | Unknown |
0xC | uint32_t | Unknown |
0x10 | uint32_t | DMA Offset |
0x14 | uint16_t | MAT Offset |
0x18 | uint16_t | DMA Size |
0x1C | uint32_t | Unknown |
3D Data Bone Entry
Offset | Variable Type | Description |
---|---|---|
0 | uint16_t | Index |
2 | uint16_t | Reserved |
4 | int32_t | Parent |
8 | uint32_t | Unknown |
0xC | uint32_t | Unknown |
0x10 | f32 | Scale X |
0x14 | f32 | Scale Y |
0x18 | f32 | Scale Z |
0x1C | f32 | Scale W |
0x20 | f32 | Rotation X |
0x24 | f32 | Rotation Y |
0x28 | f32 | Rotation Z |
0x2C | f32 | Rotation W |
0x30 | f32 | Translation X |
0x34 | f32 | Translation Y |
0x38 | f32 | Translation Z |
0x3C | f32 | Translation W |
VIF Data Structure
Each vif packet is VU1 assembly code writing to the processor memory the 3D Data encapsulated through unpack commands. As this is not really a file format per se a valid example file is available below instead of the file format table. It can be assembled using the tool dsp-as from the open source ps2sdk.
.align 0
;test.dae_mp1_pkt1.obj
;Automatically generated by kh2vif
;DO NOT EDIT IF YOU DON'T KNOW WHAT YOU ARE DOING
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[r] V4_32, 0, * ;Model Part Header
.int 1, 0, 0, 0 ;type 1 Model
.int 36, 4, 54, 56; Number of u+v+flag+index, their offset, offset of vertex affiliation header, offset of mat definition(end)
.int 0, 0, 0, 0; Nobody care about vertices merging and colors
.int 14, 40, 0, 5; Number of vertices, their offset, reserved and number of array attribution
.EndUnpack
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[r] V2_16, 4, *; UV definition
.short 2048, 0
.short 1024, 1024
.short 1024, 0
.short 1024, 3071
.short 2048, 2048
.short 2048, 3071
.short 3071, 2048
.short 2048, 1024
.short 3071, 1024
.short 2048, 2048
.short 1024, 1024
.short 2048, 1024
.short 1024, 1024
.short 0, 2048
.short 0, 1024
.short 2048, 4095
.short 1024, 3071
.short 2048, 3071
.short 2048, 0
.short 2048, 1024
.short 1024, 1024
.short 1024, 3071
.short 1024, 2048
.short 2048, 2048
.short 3071, 2048
.short 2048, 2048
.short 2048, 1024
.short 2048, 2048
.short 1024, 2048
.short 1024, 1024
.short 1024, 1024
.short 1024, 2048
.short 0, 2048
.short 2048, 4095
.short 1024, 4095
.short 1024, 3071
.EndUnpack
stmask 0xcfcfcfcf; Sets mask register(3303, check EEUSER_E)
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[mru] S_8, 4, *; Vertex indices
.byte 0
.byte 1
.byte 2
.byte 9
.byte 8
.byte 11
.byte 12
.byte 3
.byte 4
.byte 8
.byte 1
.byte 3
.byte 1
.byte 10
.byte 5
.byte 6
.byte 9
.byte 11
.byte 0
.byte 3
.byte 1
.byte 9
.byte 13
.byte 8
.byte 12
.byte 8
.byte 3
.byte 8
.byte 13
.byte 1
.byte 1
.byte 13
.byte 10
.byte 6
.byte 7
.byte 9
.EndUnpack
stmask 0x3f3f3f3f; Sets mask register(3330, check EEUSER_E)
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[mru] S_8, 4, *; Flags
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.byte 0x10; stock
.byte 0x10; stock
.byte 0x20; draw triangle
.EndUnpack
stcol 0x3f800000, 0x3f800000, 0x3f800000, 0x3f800000; We set garbage data to 1(float) so even if nothing is referenced game doesn't go crazy
stmask 0x80808080; Sets mask register(0002, check EEUSER_E)
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[mr] V3_32, 40, *; Vertex definition
.float 1.000000, 1.000000, 0.000000
.float -1.000000, -1.000000, 0.000000
.float -1.000000, 1.000000, 0.000000
.float 1.000000, -1.000000, 0.000000
.float 1.000000, 1.000000, 0.000000
.float -1.000000, 1.000000, 0.000000
.float 1.000000, 1.000000, 0.000000
.float -1.000000, 1.000000, 0.000000
.float 0.999999, -1.000001, 2.000000
.float -1.000000, 1.000000, 2.000000
.float -1.000000, 1.000000, 2.000000
.float 1.000000, 1.000000, 2.000000
.float 1.000000, 1.000000, 2.000000
.float -1.000000, -1.000000, 2.000000
.EndUnpack
stcycl 01, 01; We write code to memory without skips/overwrite
unpack[r] V4_32, 54 ,*; Vertex affiliation header
.int 8, 1, 2, 2
.int 1, 0, 0, 0
.EndUnpack
vifnop
vifnop; We wait for data to be kicked in
Kaitai file structure
Due to the complexity of the file format a Kaitai parser has not been written yet. Should you be in need of an emitter/parser you can look at those two tools in the meanwhile.