Changes

Jump to navigation Jump to search
123 bytes removed ,  17:10, 5 June 2017
update formatting, add cat
Line 1: Line 1: −
This page details the technical inner workings of ManiaPlanet (and the original TrackMania). It describes the game's classes, data structures and functions, and is as such geared towards reverse engineers and programmers.
+
This page details the technical '''inner workings of ManiaPlanet''' (and the original TrackMania). It describes the game's classes, data structures and functions, and is as such geared towards reverse engineers and programmers.
   −
This documentation goes together well with [http://forum.mania-creative.com/thread-2983.html TM2Unlimiter], which is an open source tool that contains all the code listed below and is kept up to date with the newest ManiaPlanet function addresses.
+
This documentation goes together well with [http://forum.mania-creative.com/thread-2983.html TM2Unlimiter], which is an open source tool that contains all the code listed below and is kept up to date with the newest ManiaPlanet function addresses.{{deadlink}}
   −
=Basic data structures=
+
== Basic data structures ==
 
This section describes the basic primitive data structures used in ManiaPlanet.
 
This section describes the basic primitive data structures used in ManiaPlanet.
   Line 27: Line 27:     
===String===
 
===String===
Zero-terminated string of ASCII characters. An empty string is denoted by a size of 0 *and* psz pointing to a specific empty ASCII string (a 0 byte).
+
Zero-terminated string of ASCII characters. An empty string is denoted by a size of 0 ''and'' psz pointing to a specific empty ASCII string (a 0 byte).
    
  struct String
 
  struct String
Line 50: Line 50:     
===StringInt===
 
===StringInt===
"International" string: zero-terminated string of UTF16 characters (wchar_t). As with String, an empty string is denoted by a size of 0 *and* pwsz pointing to a specific empty UTF16 string (a 0 word).
+
"International" string: zero-terminated string of UTF16 characters (wchar_t). As with String, an empty string is denoted by a size of 0 ''and'' pwsz pointing to a specific empty UTF16 string (a 0 word).
    
  struct StringInt
 
  struct StringInt
Line 72: Line 72:  
Stores a numerical or textual identifier.
 
Stores a numerical or textual identifier.
   −
Everytime a string is assigned to an Id, this string is placed into a global 2-dimensional table with its position depending on its hash (if it wasn't already in the table). This position is then stored in the Id. Because of this mechanism, Id's can be used for very fast string comparisons (like a hash) while still being reversible.
+
Everytime a string is assigned to an Id, this string is placed into a global 2-dimensional table with its position depending on its hash (if it wasn't already in the table). This position is then stored in the Id. Because of this mechanism, Ids can be used for very fast string comparisons (like a hash) while still being reversible.
    
  struct Id
 
  struct Id
Line 158: Line 158:  
  };
 
  };
   −
=Object system=
+
==Object system==
 
ManiaPlanet has its own custom object system that supports the following features:
 
ManiaPlanet has its own custom object system that supports the following features:
 
* Reflection
 
* Reflection
Line 217: Line 217:  
GetPropertyIndirect() and CallMethodIndirect() should not be used directly. These only exist for handling calculated properties (where the value doesn't come directly from a field in the class but is calculated). Instead, use the nonvirtual wrapper methods GetProperty() and CallMethod() which handle both field properties and calculated properties.
 
GetPropertyIndirect() and CallMethodIndirect() should not be used directly. These only exist for handling calculated properties (where the value doesn't come directly from a field in the class but is calculated). Instead, use the nonvirtual wrapper methods GetProperty() and CallMethod() which handle both field properties and calculated properties.
   −
==Runtime class inspection==
+
===Runtime class inspection===
Each CMwNod-derived class is described by a CMwClassInfo instance, which contains the [[Class ID's|class ID]], pointer to the parent class, and list of members (properties/methods). The classes are grouped into "engines" (CMwEngineInfo) which can be thought of as namespaces in other languages. Finally, all available engines are listed in a singleton CMwEngineManager instance.
+
Each CMwNod-derived class is described by a CMwClassInfo instance, which contains the [[Class IDs|class ID]], pointer to the parent class, and list of members (properties/methods). The classes are grouped into "engines" (CMwEngineInfo) which can be thought of as namespaces in other languages. Finally, all available engines are listed in a singleton CMwEngineManager instance.
    
  class CMwEngineManager
 
  class CMwEngineManager
Line 384: Line 384:  
  };
 
  };
   −
==Late binding==
+
===Late binding===
 
Once you have the CMwMemberInfo of a property of a class, you can access this property without knowing the class's internal structure. Similarly, if you have a CMwMethodInfo, you can call the method without knowing its address. In effect, this lets you use ManiaPlanet functionality as easily from C++ as from ManiaScript; except that by directly accessing the class system, a lot more functionality is available (most methods are actually blocked for use from ManiaScript).
 
Once you have the CMwMemberInfo of a property of a class, you can access this property without knowing the class's internal structure. Similarly, if you have a CMwMethodInfo, you can call the method without knowing its address. In effect, this lets you use ManiaPlanet functionality as easily from C++ as from ManiaScript; except that by directly accessing the class system, a lot more functionality is available (most methods are actually blocked for use from ManiaScript).
    
Performing late binding in ManiaPlanet requires three objects:
 
Performing late binding in ManiaPlanet requires three objects:
* A CMwNod: the object you want to perform the action on.
+
* A CMwNod: the object on which you want to perform the action.
 
* A CMwMemberInfo: the object that describes the member you want to access. This can be retrieved by getting the CMwNod's CMwClassInfo (pNod->GetClassInfo()) and looping over the class's members until you find the one with the name you are looking for.
 
* A CMwMemberInfo: the object that describes the member you want to access. This can be retrieved by getting the CMwNod's CMwClassInfo (pNod->GetClassInfo()) and looping over the class's members until you find the one with the name you are looking for.
 
* A CMwStack: wraps the CMwMemberInfo. In addition, when calling a method, this contains any method arguments.
 
* A CMwStack: wraps the CMwMemberInfo. In addition, when calling a method, this contains any method arguments.
Line 404: Line 404:  
Example:
 
Example:
   −
    List < class CPlugSolid* >& CFuncClouds::GetSolidFids () const
+
List < class CPlugSolid* >& CFuncClouds::GetSolidFids () const
    {
+
{
        static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "SolidFids" );
+
    static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "SolidFids" );
        struct
+
    struct
        {
+
    {
            List < class CPlugSolid* >* pResult;
+
        List < class CPlugSolid* >* pResult;
            List < class CPlugSolid* > storage;
+
        List < class CPlugSolid* > storage;
        } result;
+
    } result;
        CMwStack stack;
+
    CMwStack stack;
        stack.Push ( pMemberInfo );
+
    stack.Push ( pMemberInfo );
        GetProperty ( &stack, &result );
+
    GetProperty ( &stack, &result );
        return *result.pResult;
+
    return *result.pResult;
    }
+
}
    
'''Setting properties'''
 
'''Setting properties'''
Line 425: Line 425:  
Example:
 
Example:
   −
    void CFuncClouds::SetSolidFids ( List < class CPlugSolid* >& value )
+
void CFuncClouds::SetSolidFids ( List < class CPlugSolid* >& value )
    {
+
{
        static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "SolidFids" );
+
    static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "SolidFids" );
        CMwStack stack;
+
    CMwStack stack;
        stack.Push ( pMemberInfo );
+
    stack.Push ( pMemberInfo );
        CallMethod ( &stack, &value );
+
    CallMethod ( &stack, &value );
    }
+
}
    
'''Calling methods'''
 
'''Calling methods'''
 
* Create a CMwStack.
 
* Create a CMwStack.
 
* Push the CMwMemberInfo* of the method you want to call.
 
* Push the CMwMemberInfo* of the method you want to call.
* Push any method arguments in the *reverse* order as they appear in the signature (right to left).
+
* Push any method arguments in the ''reverse'' order as they appear in the signature (right to left).
 
* If the method has a return value, allocate a variable for it and push this variable.
 
* If the method has a return value, allocate a variable for it and push this variable.
 
* Call pNod->CallMethod(&stack, NULL).
 
* Call pNod->CallMethod(&stack, NULL).
Line 442: Line 442:  
Example:
 
Example:
   −
    uint CGameCtnEditorPluginScriptHandler::CanPlaceBlock ( class CGameCtnBlockInfo* pBlockModel, int3 coord, uint dir )
+
uint CGameCtnEditorPluginScriptHandler::CanPlaceBlock ( class CGameCtnBlockInfo* pBlockModel, int3 coord, uint dir )
    {
+
{
        static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "CanPlaceBlock" );
+
    static CMwMemberInfo* pMemberInfo = GetClassInfo ()->GetMemberInfo ( "CanPlaceBlock" );
        uint uiVariantIndex;
+
    uint uiVariantIndex;
        CMwStack stack;
+
    CMwStack stack;
        stack.Push ( pMemberInfo );
+
    stack.Push ( pMemberInfo );
        stack.Push ( dir );
+
    stack.Push ( dir );
        stack.Push ( coord );
+
    stack.Push ( coord );
        stack.Push ( *reinterpret_cast < CMwNod** > ( &pBlockModel ) );
+
    stack.Push ( *reinterpret_cast < CMwNod** > ( &pBlockModel ) );
        stack.Push ( uiVariantIndex );
+
    stack.Push ( uiVariantIndex );
        CallMethod ( &stack, NULL );
+
    CallMethod ( &stack, NULL );
        return uiVariantIndex;
+
    return uiVariantIndex;
    }
+
}
    
Internally, a CMwStack has some optimizations for efficient list storage. The first two items are always stored inside the object itself (m_ContainedItems member). If more items are pushed after these two, a buffer is allocated on the heap and the additional items are stored in there. "Pushing" an item consists of appending it to the back of the list. After the stack is set up, it is passed to GetProperty/CallMethod which processes the items in the same order in which they were pushed.
 
Internally, a CMwStack has some optimizations for efficient list storage. The first two items are always stored inside the object itself (m_ContainedItems member). If more items are pushed after these two, a buffer is allocated on the heap and the additional items are stored in there. "Pushing" an item consists of appending it to the back of the list. After the stack is set up, it is passed to GetProperty/CallMethod which processes the items in the same order in which they were pushed.
   −
    class CMwStack
+
class CMwStack
 +
{
 +
public:
 +
    enum eItemType
 +
    {
 +
        ITEM_MEMBER    = 0,            // CMwMemberInfo*
 +
        ITEM_BOOL      = 0x10000001,
 +
        ITEM_OBJECT    = 0x10000002,  // CMwNod*
 +
        ITEM_ENUM      = 0x10000003,
 +
        ITEM_ISO4      = 0x10000004,  // Matrix34*
 +
        ITEM_VEC2      = 0x10000005,
 +
        ITEM_VEC3      = 0x10000006,
 +
        ITEM_INT3      = 0x10000007,
 +
        ITEM_UINT3      = 0x10000008,
 +
        ITEM_INT        = 0x10000009,
 +
        ITEM_UINT      = 0x1000000A,
 +
        ITEM_FLOAT      = 0x1000000B,
 +
        ITEM_STRING    = 0x1000000C,  // String*
 +
        ITEM_WSTRING    = 0x1000000D    // StringInt*
 +
    };
 +
 +
    struct Item
 
     {
 
     {
    public:
+
         void*          m_pValue;      // Always a pointer, even for primitives
        enum eItemType
+
        eItemType      m_Type;
        {
  −
            ITEM_MEMBER    = 0,            // CMwMemberInfo*
  −
            ITEM_BOOL      = 0x10000001,
  −
            ITEM_OBJECT    = 0x10000002,  // CMwNod*
  −
            ITEM_ENUM      = 0x10000003,
  −
            ITEM_ISO4      = 0x10000004,  // Matrix34*
  −
            ITEM_VEC2      = 0x10000005,
  −
            ITEM_VEC3      = 0x10000006,
  −
            ITEM_INT3      = 0x10000007,
  −
            ITEM_UINT3      = 0x10000008,
  −
            ITEM_INT        = 0x10000009,
  −
            ITEM_UINT      = 0x1000000A,
  −
            ITEM_FLOAT      = 0x1000000B,
  −
            ITEM_STRING    = 0x1000000C,  // String*
  −
            ITEM_WSTRING    = 0x1000000D    // StringInt*
  −
         };
  −
   
  −
        struct Item
  −
        {
  −
            void*          m_pValue;      // Always a pointer, even for primitives
  −
            eItemType      m_Type;
  −
        };
  −
   
  −
    private:
  −
        Item                m_ContainedItems[2];    // The first two items in the queue
  −
        Item*              m_pExtraItems;          // Pointer to an array of additional items (item 3 and above)
  −
        short              m_sSize;                // The total number of items in the queue (contained + extra)
  −
        short              m_sExtraItemsCapacity;  // The capacity of the "extra items" array (*not* counting m_ContainedItems)
  −
        int                m_iCurrentPos;          // Current reading position in the item queue (starts at 0 and increases)
   
     };
 
     };
 +
 +
private:
 +
    Item                m_ContainedItems[2];    // The first two items in the queue
 +
    Item*              m_pExtraItems;          // Pointer to an array of additional items (item 3 and above)
 +
    short              m_sSize;                // The total number of items in the queue (contained + extra)
 +
    short              m_sExtraItemsCapacity;  // The capacity of the "extra items" array (''not'' counting m_ContainedItems)
 +
    int                m_iCurrentPos;          // Current reading position in the item queue (starts at 0 and increases)
 +
};
   −
==Serialization==
+
===Serialization===
Every CMwNod can be read from and written to a file. Typically this is a .gbx file, but it's also possible to "deserialize" a .png file into a CPlugFilePng instance. Serializing and deserializing is easy to do with the following methods:
+
Every CMwNod can be read from and written to a file. Typically this is a [[GBX|.gbx]] file, but it's also possible to "deserialize" a .png file into a CPlugFilePng instance. Serializing and deserializing is easy to do with the following methods:
    
  void CreateNodFromFid(CMwNod** ppResultNod, CSystemFid* pFid, int flags = 7);
 
  void CreateNodFromFid(CMwNod** ppResultNod, CSystemFid* pFid, int flags = 7);
Line 500: Line 500:  
  void WriteNodToFile(StringInt* pwstrFilePath, CMwNod* pNod, CSystemFids* pFolder, int flags);
 
  void WriteNodToFile(StringInt* pwstrFilePath, CMwNod* pNod, CSystemFids* pFolder, int flags);
   −
=I/O=
+
== I/O ==
==File and folder structure==
+
 
 +
===File and folder structure===
 
ManiaPlanet maintains several filesystem trees in its memory. The files it represents can come from the actual file system, or from archives (.zip/.pak/.Pack.Gbx).
 
ManiaPlanet maintains several filesystem trees in its memory. The files it represents can come from the actual file system, or from archives (.zip/.pak/.Pack.Gbx).
   −
At the root of each file hierarchy, there is a "drive". This should not be confused with a PC harddrive or partition like C:\; instead, it is an isolated ManiaPlanet folder. The following drives exist:
+
At the root of each file hierarchy, there is a "drive". This should not be confused with a PC harddrive or partition like {{c|C:\}}; instead, it is an isolated ManiaPlanet folder. The following drives exist:
 
* Resource: contents of ManiaPlanet\Packs\Resource.pak. Contains common fonts, shaders, textures etc.
 
* Resource: contents of ManiaPlanet\Packs\Resource.pak. Contains common fonts, shaders, textures etc.
 
* Personal: the "Personal" folder containing the user's maps, settings etc.
 
* Personal: the "Personal" folder containing the user's maps, settings etc.
Line 576: Line 577:  
  };
 
  };
   −
  class CSystemFid : public CMwNod           // Base class for files
+
  class CSystemFid : public CMwNod         // Base class for files
 
  {
 
  {
 
  private:
 
  private:
Line 634: Line 635:  
  class CSystemFidsDrive : public CSystemFidsFolder {};
 
  class CSystemFidsDrive : public CSystemFidsFolder {};
   −
==Loading files==
+
===Loading files===
 
Loading a file in ManiaPlanet is quite easy: you get the CSystemFid's pLoader and call its GetBuffer() method, which returns a stream object ready for reading (see Streams section). Once you're done reading, call the loader's FreeBuffer() method to release the stream.
 
Loading a file in ManiaPlanet is quite easy: you get the CSystemFid's pLoader and call its GetBuffer() method, which returns a stream object ready for reading (see Streams section). Once you're done reading, call the loader's FreeBuffer() method to release the stream.
   Line 644: Line 645:  
  };
 
  };
   −
==Streams==
+
===Streams===
 
ManiaPlanet uses various stream-like objects for reading and writing files. This means that each stream is associated with an array of bytes (like a file), has a certain position within that array, and can either read from or write to the array (optionally making it larger).
 
ManiaPlanet uses various stream-like objects for reading and writing files. This means that each stream is associated with an array of bytes (like a file), has a certain position within that array, and can either read from or write to the array (optionally making it larger).
   −
===CClassicBuffer===
+
====CClassicBuffer====
Abstract base class that represents a stream. Comparable to C#'s Stream class. Contrary to what the name might suggest, this class does *not* necessarily contain a memory buffer.
+
Abstract base class that represents a stream. Comparable to C#'s Stream class. Contrary to what the name might suggest, this class does ''not'' necessarily contain a memory buffer.
    
  class CClassicBuffer
 
  class CClassicBuffer
Line 674: Line 675:  
  };
 
  };
   −
===CClassicBufferPart===
+
====CClassicBufferPart====
 
Exposes a subsection of an existing stream as a new stream.
 
Exposes a subsection of an existing stream as a new stream.
   Line 685: Line 686:  
  };
 
  };
   −
===CClassicBufferMemory===
+
====CClassicBufferMemory====
 
In-memory data stream, comparable to C#'s MemoryStream.
 
In-memory data stream, comparable to C#'s MemoryStream.
   Line 698: Line 699:  
  };
 
  };
   −
===CSystemFile===
+
====CSystemFile====
 
Buffered file stream. The class reads 4 KB from the designated file into memory and then services read requests from that buffer, until the buffer is exhausted and the next 4 KB is read.
 
Buffered file stream. The class reads 4 KB from the designated file into memory and then services read requests from that buffer, until the buffer is exhausted and the next 4 KB is read.
   Line 713: Line 714:  
  };
 
  };
   −
===CSystemFileMemMapped===
+
====CSystemFileMemMapped====
 
Memory-mapped file stream.
 
Memory-mapped file stream.
   Line 727: Line 728:  
  };
 
  };
   −
===CClassicBufferZlib===
+
====CClassicBufferZlib====
 
Compresses (when writing) or decompresses (when reading) data from an underlying stream. Comparable to C#'s DeflateStream.
 
Compresses (when writing) or decompresses (when reading) data from an underlying stream. Comparable to C#'s DeflateStream.
   Line 745: Line 746:  
  };
 
  };
   −
===CClassicBufferCrypted===
+
====CClassicBufferCrypted====
 
Encrypts (when writing) or decrypts (when reading) data from an underlying stream.
 
Encrypts (when writing) or decrypts (when reading) data from an underlying stream.
   Line 772: Line 773:  
There are some nasty details in the way TrackMania/ManiaPlanet handle encryption. For details, see the page on [[PAK|.pak files]] (for TrackMania). ManiaPlanet further complicates things by adding custom, non-standard behaviour to the CBC mode.
 
There are some nasty details in the way TrackMania/ManiaPlanet handle encryption. For details, see the page on [[PAK|.pak files]] (for TrackMania). ManiaPlanet further complicates things by adding custom, non-standard behaviour to the CBC mode.
   −
===CClassicArchive===
+
====CClassicArchive====
 
Wraps a CClassicBuffer and exposes handy methods for reading/writing bytes, words, dwords, floats etc. in one go. It's like a C# BinaryReader and BinaryWriter in one: there are both reading and writing methods, and also ReadWrite methods that call either the Read or Write method depending on the bWriting field.
 
Wraps a CClassicBuffer and exposes handy methods for reading/writing bytes, words, dwords, floats etc. in one go. It's like a C# BinaryReader and BinaryWriter in one: there are both reading and writing methods, and also ReadWrite methods that call either the Read or Write method depending on the bWriting field.
   Line 796: Line 797:  
  };
 
  };
   −
For details on ReadWriteNodRef, see the definition of "noderef" on the [[GBX|GBX page]].
+
For details on ReadWriteNodRef, see the definition of "noderef" on the [[GBX#Primitives|GBX page]].
   −
===CSystemArchiveNod===
+
====CSystemArchiveNod====
CClassicArchive-derived class used specifically for reading and writing [[GBX|.gbx files]].
+
CClassicArchive-derived class used specifically for reading and writing .gbx files.
    
  class CSystemArchiveNod : public CClassicArchive
 
  class CSystemArchiveNod : public CClassicArchive
Line 837: Line 838:     
All objects, both from the current .gbx file and from external files, are stored in the nodEntries list. The main object of the .gbx file is always at index 0. Every time a subobject is read from the .gbx file, or an external file is referenced, the nodEntries index is specified where the object should be stored. The process of reading .gbx files is explained in detail on the [[GBX|GBX page]].
 
All objects, both from the current .gbx file and from external files, are stored in the nodEntries list. The main object of the .gbx file is always at index 0. Every time a subobject is read from the .gbx file, or an external file is referenced, the nodEntries index is specified where the object should be stored. The process of reading .gbx files is explained in detail on the [[GBX|GBX page]].
 +
 +
[[Category:Internals]]

Navigation menu