OpenKh

This is a project centralizes all the technical knowledge of Kingdom Hearts series in one place, providing documentation, tools, code libraries and the foundation for modding the commercial games.

View on GitHub

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.