The DTI and MtFramework 2.0 - AniBullet/MonsterHunterWorldModding GitHub Wiki
This guide covers the DTI and general information about MtFramework 2.0 (The Engine the game runs on). The information is mostly focused on memory layout of classes.
"DTI" stands for Data Type Information and is data embedded into the exe that contains information about the different class types in the framework/game.
The DTI can contain information about:
- The Class itself
- Name
- Size
- Parent Classes
- Class members
- Name
- Offset
- Type
The DTI does not provide all information about all classes as this information has to be added manually for each class by the devs. So it only has that information which the devs bothered to add. Also of note is that, if a class A has some fields listed in its DTI, then any class X that inherits from A will also have those fields listed. (And possibly some extra fields)
Now of course having to look in the exe for all of this information would be a pain. Fortunately Ando was so nice as to publish his DTI dumping code, and has a github repo for storing the DTI Dump (+ a couple of other things) for the current game version.
You can find them here: https://github.com/Andoryuuta/MHW-DTI-Dumps/
And the dumper can be found here: https://github.com/Andoryuuta/MHW-ClassPropDump
The DTI Dump is in the form of a C++ header file, with pseudo class declarations for each object type.
(Almost) all classes in the game have some sort of one-letter prefix to their name.
For example: uEm001
, sMhCamera
, rEmDtTune
, cEmFecesManager
(Yes, that's a thing).
The meanings of each of those prefixes are as follows:
u is Unit Resource (inherits from cUnit)
c is Class
n is Namespace
r is Resource (inherits from cResource)
s is Singleton
a is Area (inherits from cArea)
All files are loaded into r
classes.
As mentioned before, the DTI holds information about the classes itself:
- Name: The full official name of the class
- Size: The size in bytes that the class occupies when instantiated
- The location of the Virtual Function Table
- Its CRC32 hash (Which is used by the game for quick identification of types)
- The classes it inherits from
As an example let's pick rArmorData
:
// rArmorData vftable:0x143436A80, Size:0xD0, CRC32:0x7501E3C1
class rArmorData /*: cResource, MtObject*/ {
// ...
Immediately we can tell that the name of the class is rArmorData
, the virtual function table is located at 0x143436A80
, and the class is 0xD0
(208) bytes large. We can also tell that the class inherits from cResource
and MtObject
. As you might have guessed, MtObject
is the base-class for all classes in MtFramework. The exception being small structs and classes that don't inherit from anything at all.
The DTI also holds information about specific fields in certain classes:
- The data type
- The full name of the field
- The offset from the base of the class
If the offset of a field is not given, there are addresses to a Getter and a Setter respectively, usually the actual offset can be deduced from those. For example, let's take rArmorData
again. The dumped information for this class looks like so:
// rArmorData vftable:0x143436A80, Size:0xD0, CRC32:0x7501E3C1
class rArmorData /*: cResource, MtObject*/ {
s32 'mRefCount' ; // Offset:0x5C, Var, CRC32:0x26C06A67, Flags:0x3000
u32 'mAttr' ; // Offset:0x60, Var, CRC32:0xDD77E828, Flags:0x3000
u64 'mSize' ; // Offset:0x70, Var, CRC32:0x2EC9EF56, Flags:0x3000
u64 'mID' ; // Offset:0x78, Var, CRC32:0x10BB0F5B, Flags:0x3000
u64 'mCreateTime' ; // Offset:0x80, Var, CRC32:0x3EA974DF, Flags:0x3000
u32 'mQuality' ; // Offset:0x7FFFFFFFFFFFFFFF, PSEUDO-PROP, Getter:0x1402B7C90, Setter:0x1402B8DF0, CRC32:0xFF50780E, Flags:0x91000
u32 'mState' ; // Offset:0x7FFFFFFFFFFFFFFF, PSEUDO-PROP, Getter:0x1404521A0, Setter:0x140452380, CRC32:0x5E27483A, Flags:0x83000
u32 'mTag' ; // Offset:0x7FFFFFFFFFFFFFFF, PSEUDO-PROP, Getter:0x1404521B0, Setter:0x140452390, CRC32:0xD372274C, Flags:0x83000
cstring 'mPath' ; // Offset:0x7FFFFFFFFFFFFFFF, PSEUDO-PROP, Getter:0x141986F00, Setter:0x14023D5D0, CRC32:0xD25D4033, Flags:0x83000
};
As you can see the offset for the mPath
member is listed as 0x7FFFFFFFFFFFFFFF
, however the class size is only 0xD0
. Basically if the offset is unknown it will be displayed as 0x7FFFFFFFFFFFFFFF
(INT64 Max) in the dump. However we can see that in the comment there are Getter
and Setter
addresses. Let's take a look at the Getter
.
The function looks like this
lea rax,[rcx+0xc]
ret
This tells us that the mPath
member is at offset 0xC
. However some of these Getters/Setters might point to "unimplemented" functions in which case there is unfortunately no way of knowing the offset of that field.
These "unimplemented" functions usually look something like
xor eax,eax
ret
or
mov al,0x1
ret
MtFramework, specifically 2.0, is the engine the game runs on. It has a lot of interesting traits and features to take advantage of when reverse engineering.
MTF has a big list of basic types that are used in the DTI dump, some of them like u32
, s8
, f32
, or even vector3
might be obvious, but some others like classref
, color
, sphere
, or string
might not be so obvious. Well it's obvious what they are but not what the underlying types might be.
classref
for example can be a pointer to any class, color
is an unsigned 32-bit integer storing colors in the usual RGBA format.
Here is a header file that I put together which typedef
s all of the types used in the DTI dump. I am still not 100% sure on all of the types, especially the geometric ones, so if you have any corrections please let me know at Fexty#4696 on discord.
Each class has its own DTI instance somewhere in the exe. Yes, the instances themselves are in the exe, not just pointers to them. Each instance looks roughly like this:
+0x00: VFT
+0x08: Class Name
+0x10: Next
+0x18: Child
+0x20: Parent
+0x28: Link
+0x30: Flags and Size/4
+0x38: CRC32 Hash
And as an example, let's say we're looking at the DTI instance of class cIconData
, then the VFT of that DTI instance would look like so:
+0x00: ~MtDTI<cIconData>();
+0x08: cIconData* NewInstance(); // Allocates and initializes a new object of type cIconData
+0x10: cIconData* CtorInstance(cIconData* obj); // Initializes the passed object
+0x18: cIconData** CtorInstanceArray(cIconData** arr, u32 count); // Initializes an array of cIconData with a given size
With this information we can declare our own MtDTI
type to layer on-top of the game's instances:
class MtDTI {
public:
const char* class_name;
MtDTI* next;
MtDTI* child;
MtDTI* parent;
MtDTI* link;
u32 flags_and_size_div_4;
u32 crc_hash;
virtual ~MtDTI() = 0;
virtual void* NewInstance() = 0;
virtual void* CtorInstance(void* object) = 0;
virtual void** CtorInstanceArray(void** objects, u32 count) = 0;
};
This means that using the header file I linked above (which has this MtDTI
declaration as well), you can declare a variable of the correct type and instantiate objects from the game that way:
struct rEmAngry {}; // Empty declaration of type so we can use it
MtDTI& dti = *(MtDTI*)0x145069738; // declare reference to DTI
rEmAngry* agr = (rEmAngry*)dti.NewInstance(); // Use DTI to create new instance of rEmAngry
Interestingly, the virtual function tables ("VFT"s) of the MTF classes are one of the most useful things available.
The first 5 functions in the VFT of any class that inherits from MtObject
look like so:
+0x00: ~Class(); // Destructor
+0x08: void CreateGUI(MtProperty&); // Useless function, mostly unimplemented
+0x10: bool IsInitialized(); // Only implemented for cUnit inheriting classes
+0x18: MtDTI& GetDTI(); // Returns a reference/pointer to the DTI instance of the class
+0x20: void PopulatePropertyList(MtPropertyList&); // Fills out the PropertyList (if implemented)
These five functions will always be there, whether they're implemented or not, they will always be there. This also means that class-specific virtual functions will always start at offset 0x28
, i.e. entry 6 and higher.
Now you may ask: "But how can I find this virtual function table?"
If a class has any virtual functions at all, then a pointer to its VFT can always be found at the very start of the object, i.e. offset 0x0
. For example:
class A : MtObject {
int number;
float decimal;
uint64_t big_number;
virtual ~A() {}
};
Will look like this in memory:
+0x00: VFT
+0x08: number
+0x0C: decimal
+0x10: big_number
Also, A
's VFT will look like this:
+0x00: A::~A();
+0x08: void MtObject::CreateGUI(MtProperty&);
+0x10: bool MtObject::IsInitialized();
+0x18: MtDTI& MtObject::GetDTI();
+0x20: void MtObject::PopulatePropertyList(MtPropertyList&);
So if you need to know what an object you're looking at is called, you can simply look for its VFT in the DTI dump.
If you can't find the VFT in the DTI dump, there is a chance that the dumper failed to retrieve this class, in that case you can follow these steps and if you're lucky you'll find the class name:
- Head to the VFT in Cheat Engine/Ghidra/IDA
- Go to entry number 5
(+0x18)
- Find the
lea rax,[blah]
instruction - Navigate to
blah + 0x8
- Navigate to the pointer at this address
- If the class has any DTI then you'll find the class name here