I’ve not updated the blog in a long time. I’ve been working on SBOL on and off in my spare time and have lost track of changes I’ve implemented. I’ll try to retroactively create posts at a later date, but for now I’ll post about my findings with the Model (MMDL) format.

Recently my interest in the MMDL format was reignited by a user on the TXR discord. I was also invited to a discord server that appears to focus on extracting the models from TXR games. So with my very limited experience with 3D models I was sure to find help there.

It looks like Genki, like with all other aspects of SBOL chose to use a unique way to store the model data so users’ experienced with the TXR models of other games doesn’t quite apply with the MMDL files. Which is a shame as it means extracting the models from another TXR game and converting them to MMDL would require a decent understanding on the MMDL file structure. So I got to work.

My plan was first to log the file structure to a txt file so I can visualize the structure better. I usually write in C/C++ but this time I opted for python. As many model viewers and importer plugins support python. Also many model researchers work with python more than C/C++. Having no experience with python but having a good understanding of programming it’s generally just a matter of syntax and when I get stuck… Google :D.

So looking at the header of the file it’s 0x24 bytes. As I’ve mentioned before the first 4 bytes are 0x4C, 0x44, 0x4D, 0x4D (‘LDMM’). Like with most intel x86 based formats it’s little endian. Making the first 4 bytes ‘MMDL’. The structure is as follows:

0x0000: "LDMM"
0x0004: Object Table size
0x0008: Vertices Table Size
0x000C: Faces Table Size
0x0010: Unknown Value (Cars tend to be 0x152, everything else is 0x142)
0x0014: Vertices Size
0x0018: Vertices Count
0x001C: Face Size
0x0020: Face Count

After the header there are entries that vary in size. The first entry always appears to be the same which totals 52bytes in size. Before I get into it I’ll need a class that can process the MMDL file and store the file in a structure. I can then iterate through the structure to convert it to another format. The file (like most model formats) contains the Material data, vertices data and face data. There’s going to be many “unknown” values but once I can see the values in a txt file the values may make the value name obvious. The first entry structure is as follows:

0x0000: UnknownValue1
0x0004: UnknownValue2
0x0008: UnknownValue3
0x000C: UnknownValue4
0x0010: UnknownValue5
0x0014: UnknownValue6
0x0018: UnknownValue7
0x001C: UnknownValue8
0x0020: UnknownValue9
0x0024: UnknownValue10
0x0028: UnknownValue11
0x002C: SubEntryCount
0x0030: ID

The value I’ve labelled as SubEntryCount is the count of the sub-entries after the initial entry. The entry directly after the initial entry appears to be 40bytes in size and found that there are as many of these entries as the value I’ve labelled as SubEntryCount. Not only the initial entry has a count so does the child of this entry so it looks like we’re working with nested structures but these structures are static in size. The entries I’m going to be calling “objects” . So I’ve chosen to call the initial entry the object base.

Working my way down the structures of the child objects of each object there are 4 types including the base. I’ve settled on the following structures for the MMDL class:

@dataclass
class ObjectBase:
    UnknownValue1: float = 0.0
    UnknownValue2: float = 0.0
    UnknownValue3: float = 0.0
    UnknownValue4: float = 0.0
    UnknownValue5: float = 0.0
    UnknownValue6: float = 0.0
    UnknownValue7: float = 0.0
    UnknownValue8: int = 0
    UnknownValue9: int = 0
    UnknownValue10: int = 0
    UnknownValue11: int = 0
    SubEntryCount: int = 0
    ID: int = 0
    Entries: list = field(default_factory=list)

@dataclass
class Object1:
    UnknownValue1: float = 0.0
    UnknownValue2: float = 0.0
    UnknownValue3: float = 0.0
    UnknownValue4: float = 0.0
    UnknownValue5: float = 0.0
    UnknownValue6: float = 0.0
    UnknownValue7: float = 0.0
    UnknownValue8: float = 0.0
    SubEntryCount: int = 0
    ID: int = 0
    Entries: list = field(default_factory=list)

@dataclass
class Object2:
    UnknownValue1: float = 0.0
    Name: str = str()
    UnknownValue2: float = 0.0
    UnknownValue3: float = 0.0
    UnknownValue4: float = 0.0
    UnknownValue5: float = 0.0
    UnknownValue6: float = 0.0
    UnknownValue7: float = 0.0
    UnknownValue8: float = 0.0
    UnknownValue9: float = 0.0
    UnknownValue10: float = 0.0
    UnknownValue11: float = 0.0
    UnknownValue12: float = 0.0
    UnknownValue13: float = 0.0
    UnknownValue14: float = 0.0
    UnknownValue15: float = 0.0
    UnknownValue16: float = 0.0
    UnknownValue17: float = 0.0
    UnknownValue18: float = 0.0
    UnknownValue19: float = 0.0
    UnknownValue20: float = 0.0
    UnknownValue21: float = 0.0
    UnknownValue22: float = 0.0
    UnknownValue23: float = 0.0
    UnknownValue24: float = 0.0
    UnknownValue25: float = 0.0
    SubEntryCount: int = 0
    ID: int = 0
    Entries: list = field(default_factory=list)

@dataclass
class Object3:
    UnknownValue1: int = 0
    VerticeOffset: int = 0
    VerticeCount: int = 0
    FaceOffset: int = 0
    FaceCount: int = 0
    Ka_r: float = 0.0
    Ka_g: float = 0.0
    Ka_b: float = 0.0
    d: float = 0.0
    Kd_r: float = 0.0
    Kd_g: float = 0.0
    Kd_b: float = 0.0
    Ks_r: float = 0.0
    Ks_g: float = 0.0
    Ks_b: float = 0.0
    Ni: float = 0.0
    UnknownValue2: float = 0.0
    UnknownValue3: float = 0.0
    UnknownValue4: float = 0.0
    UnknownValue5: float = 0.0
    UnknownValue6: float = 0.0
    Ns: float = 0.0
    MaterialName: str = str()
    UnknownValue7: float = 0.0
    TextureName: str = str()
    ID: int = 0

The structure I’ve called Object3 has many of the values labelled and that is because these entries appear to be the material entries. As I plan to convert to OBJ the labels I’ve used are attribute names in the OBJ format.

I’ll go into more detail in Part 2.

Categories: Game Assets