georgeto / gothic3sdk

An inofficial SDK for Gothic 3.
Other
27 stars 6 forks source link

Gui & Items architecture #8

Open LionBlazer opened 1 year ago

LionBlazer commented 1 year ago

Hello I have some questions about the architecture of gui classes and item classes. About Gui How can I get an instance of an opened gui in random place? I found a class of several gui manager classes, the first one is an eCGUIManager::AccessGUIManager(), but apparently it provides access to low-level windows. The second one is gCSession::getInstance().GetGUIManager() but I didn't find any useful methods here either.

The need to get a gui instance arose when I realized that CFFGFCStatic (in particular gCHUDStaticMap) doesn't call the onChar method. As far as I understand, this is due to the fact that some parent gui overrides it. I decided to check which class captured the mouse processing while clicking on documents/maps/recipes. It turned out to be some kind of gCHUDDialogMain2 about which nothing is known.


About Items In the game, some items have their own internal information. For example, weapons have information about blessing/sharpening/poisoning, etc. Can you tell where they are stored, and how I can write my data to the item? Are the heirs of eCEntityPropertySet classes that store information about a specific item, or about the type of items?

georgeto commented 1 year ago

Hello I have some questions about the architecture of gui classes and item classes. About Gui How can I get an instance of an opened gui in random place? I found a class of several gui manager classes, the first one is an eCGUIManager::AccessGUIManager(), but apparently it provides access to low-level windows. The second one is gCSession::getInstance().GetGUIManager() but I didn't find any useful methods here either.

The need to get a gui instance arose when I realized that CFFGFCStatic (in particular gCHUDStaticMap) doesn't call the onChar method. As far as I understand, this is due to the fact that some parent gui overrides it. I decided to check which class captured the mouse processing while clicking on documents/maps/recipes. It turned out to be some kind of gCHUDDialogMain2 about which nothing is known.

Well you question is a little bit to vague I am afraid, what do you want to achieve? Intercept user input, if the map view is currently open, to trigger some actions on the map?

The hierarchy from gCGUIManager to gCHUDStaticMap is the following: gCGUIManager -> gCHUDDialogMain2 (-> gCHUDPageMain2) -> gCHUDPageDocuments2 -> gCHUDViewDocument2 -> gCHUDViewMap2 -> gCHUDStaticMap

About Items In the game, some items have their own internal information. For example, weapons have information about blessing/sharpening/poisoning, etc. Can you tell where they are stored, and how I can write my data to the item?

Each items, like any "object" in Gothic 3, is represented by a so called Entity (eCEntity is the common base class). An entity has some fixed properties (name, flags, location, scaling, ...) and an array of property sets (instances of classes deriving from eCEntityPropertySet). Entities can have (almost) any combination of property sets, depending on what kind of object they represent. An item for example has an gCItem_PS property set (besides others), an NPC however has gCInventory_PS, gCNPC_PS, gCNavigation_PS, and many more. The blessing/sharpening/poisoning of weapons for example is stored in the Quality property of gCItem_PS.

Are the heirs of eCEntityPropertySet classes that store information about a specific item, or about the type of items?

Actual both, or rather it depends whether the property set is part of a template entity (eCTemplateEntity, type/blueprint of item) or a instance entity (eCSpatialEntity/eCDynamicEntity, instance of entity).

Item instances (instance entities) are mostly stored in the gCInventory_PS property set of the player, NPCs and containers such as chests. When the player receives items in dialogues or the game generates plunder/merchant inventories, template (entities) are used as blueprints to instantiate item instances (entities).

The same happens when you for example spawn objects via the ingame console via the spawn <template name>command. The game searches for the template entity that corresponds to the name, and then uses it as a blueprint to create an instance of that template, a instance entity.

LionBlazer commented 1 year ago

Thanks for providing the hierarchy! I was able to reach gCHUDStaticMap with the following code image (Ghidra doesn't seem to provide an acceptable way to view class fields, so I wrote such function to trace inside of objects, is this a good way to view fields?) image

Well you question is a little bit to vague I am afraid, what do you want to achieve? Intercept user input, if the map view is currently open, to trigger some actions on the map?

Yes, I'm intercepting the basic input processing method with the signature OnLButtonDblClk@CFFGFCWnd@@UAEXIVbCPoint@@@Z


Each items, like any "object" in Gothic 3, is represented by a so called Entity (eCEntity is the common base class). An entity has some fixed properties (name, flags, location, scaling, ...) and an array of property sets (instances of classes deriving from eCEntityPropertySet). Entities can have (almost) any combination of property sets, depending on what kind of object they represent. An item for example has an gCItem_PS property set (besides others), an NPC however has gCInventory_PS, gCNPC_PS, gCNavigation_PS, and many more. The blessing/sharpening/poisoning of weapons for example is stored in the Quality property of gCItem_PS.

Does this mean that I have to create a new class inheriting eCEntityPropertySet that will store all my information? It seems that all possible property set type IDs are described in the eEPropertySetType enum. Will I have to change this enum in the game code?

georgeto commented 1 year ago

Does this mean that I have to create a new class inheriting eCEntityPropertySet that will store all my information? It seems that all possible property set type IDs are described in the eEPropertySetType enum. Will I have to change this enum in the game code?

Adding a custom eCEntityPropertySet would be one possibility, although I never tried this, so I can't tell what challenges that raises. Maybe it is as easy as inheriting from eCEntityPropertySet, you have to try.

Alternatively, an existing field of a property set, can be repurposed.

Alternatively, you can add a field to an existing property set (I already did that, could provide an example).

Or as a fundamentally different approach you decide to not store the data in the entity/item, but instead inside a global variable, for example a map, that holds the data for each entity. Than if you are interested in the data of the entity, you can make a lookup in that map.

LionBlazer commented 1 year ago

(I already did that, could provide an example).

Yes, please provide it


I will try to implement my own property set and share my results in the future. (an attempt to just create your own class and call AddPropertySet failed - the function returns 0)

georgeto commented 1 year ago

Yes, please provide it

Here I added two additional properties to the gCInteraction_PS property set.

ge_interaction_ps_ext.h

#pragma once

#include "Game.h"

class gCInteraction_PS_Ext :
    public gCInteraction_PS
{
    public:
        GE_DEFINE_PROPERTY( gCQuest_PS, bTObjArray<bCString>, m_arrDestinationPath, DestinationPath )
        GE_DEFINE_PROPERTY( gCInteraction_PS, GEBool, m_bRoutineExclusive, RoutineExclusive )
        GE_PADDING( 3 )

    public:
        gCInteraction_PS_Ext( void );

    protected:
        void Invalidate( void );
};

GE_ASSERT_SIZEOF( gCInteraction_PS_Ext, 0xF0 )

ge_interaction_ps_ext.cpp

#include "ge_interaction_ps_ext.h"

#include "Game.h"

#include "me_propertymacros.h"

ME_DEFINE_PROPERTY_TYPE(gCInteraction_PS_Ext, gCInteraction_PS, DestinationPath, m_arrDestinationPath)
ME_DEFINE_PROPERTY_TYPE(gCInteraction_PS_Ext, gCInteraction_PS, RoutineExclusive, m_bRoutineExclusive)

gCInteraction_PS_Ext::gCInteraction_PS_Ext(void)
    : m_bRoutineExclusive(GEFalse)
{
}

void gCInteraction_PS_Ext::Invalidate()
{
    gCInteraction_PS::Invalidate();
    this->m_bRoutineExclusive = GEFalse;
}

ME_MODULE(gCInteraction_PS_Ext)
{
    ME_PATCH_PROPERTY_SET( "Game.dll", "<TODO: Name of your DLL>.dll", gCInteraction_PS, gCInteraction_PS_Ext, RVA_Game( 0x1B14EC ), GEU32 )
}

me_propertymacros.h

#pragma once

#define __ME_DEFINE_PROPERTY_TYPE( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME, READ_ONLY ) \
    decltype(EXTOBJECTCLASS::ms_PropertyMember_ ## MEMBERNAME) EXTOBJECTCLASS::ms_PropertyMember_ ## MEMBERNAME ( *bCPropertyObjectSingleton::GetInstance().FindTemplate( bTClassName<OBJECTCLASS>::GetUnmangled()), offsetof( EXTOBJECTCLASS, MEMBERNAME ), #PROPERTYNAME, bCString(), bEPropertyType_Normal, READ_ONLY );

#define ME_DEFINE_PROPERTY_TYPE( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME ) \
    __ME_DEFINE_PROPERTY_TYPE( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME, GEFalse )

#define ME_DEFINE_PROPERTY_TYPE_READ_ONLY( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME ) \
    __ME_DEFINE_PROPERTY_TYPE( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME, GETrue )

#define ME_DEFINE_ENUMPROP_TYPE( EXTOBJECTCLASS, OBJECTCLASS, PROPERTYNAME, MEMBERNAME ) \
    decltype(EXTOBJECTCLASS::ms_PropertyMember_ ## MEMBERNAME) EXTOBJECTCLASS::ms_PropertyMember_ ## MEMBERNAME ( *bCPropertyObjectSingleton::GetInstance().FindTemplate( bTClassName<OBJECTCLASS>::GetUnmangled()), offsetof( EXTOBJECTCLASS, MEMBERNAME ), #PROPERTYNAME, bEPropertyType_PropertyContainer );

#define ME_REWRITE_MEMBER_FUNCTION( HOOKNAME, SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, PREFIX, SUFFIX ) \
    static mCFunctionHook Hook_ ## HOOKNAME;                                                         \
    Hook_ ## HOOKNAME.Prepare(                                                                       \
        GetProcAddress( SOURCE_MODULE, PREFIX + bTClassName<SOURCE_TYPE>::GetUnmangled() + SUFFIX ), \
        GetProcAddress( TARGET_MODULE, PREFIX + bTClassName<TARGET_TYPE>::GetUnmangled() + SUFFIX ), \
        mCBaseHook::mEHookType_ThisCall ).Transparent().Hook();

#define ME_REWRITE_CONSTRUCTOR( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE ) \
    ME_REWRITE_MEMBER_FUNCTION( Constructor, SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, "??0",          "@@QAE@XZ" )

#define ME_REWRITE_DESTRUCTOR( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE ) \
    ME_REWRITE_MEMBER_FUNCTION( Destructor,  SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, "??1",          "@@UAE@XZ" )

#define ME_REWRITE_INVALIDATE( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE ) \
    ME_REWRITE_MEMBER_FUNCTION( Invalidate,  SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, "?Invalidate@", "@@IAEXXZ" )

#define ME_PATCH_PROPERTY_SET_SIZE( TYPE, SIZE_OFFSET, SIZE_TYPE ) \
    static mCDataPatch Patch_CreatePropertySetSize( SIZE_OFFSET, static_cast<SIZE_TYPE>(sizeof(TYPE)) );

#define __ME_PATCH_PROPERTY_SET( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE, REWRITE_INVALIDATE ) \
    __pragma( warning ( push ))                                                              \
    __pragma( warning( disable: 4127 ))                                                      \
    do {                                                                                     \
        ME_PATCH_PROPERTY_SET_SIZE( TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE )                    \
        ME_REWRITE_CONSTRUCTOR( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE )     \
        ME_REWRITE_DESTRUCTOR( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE )      \
        if ( REWRITE_INVALIDATE )                                                            \
        { ME_REWRITE_INVALIDATE( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE ) }  \
    } while (0);                                                                             \
    __pragma( warning ( pop ))

#define ME_PATCH_PROPERTY_SET( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE ) \
    __ME_PATCH_PROPERTY_SET( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE, GETrue )

#define ME_PATCH_PROPERTY_SET_NO_INVALIDATE( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE ) \
    __ME_PATCH_PROPERTY_SET( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, SIZE_OFFSET, SIZE_TYPE, GEFalse )

The following should be noted regarding the persistence of properties in save games:

Important for the question of whether the data is persisted in the save game, is the SaveGameBehavior of a property set (eEPSSaveGameRelevance GetSaveGameRelevance()), and whether the entity is in a dynamic context (lrentdat) or a static context (node).

When saving/loading SaveGames, only entities in .lrendats (eCDynamicEntity) are included. Entities in .nodes (eCSpatialEntity) are only included if their .node, or rather the associated .lrgeodat (eCGeometrySpatialContext ) is marked as HybridContext [0]. But even then, only their gCInteraction_PS property set is saved, and only if the entity is an interactive object that grants a one-time bonus [1].

So you have to ensure that all entities that have state that needs to be persisted in the SaveGame are in a .lrendat (dynamic context).

[0] Rule of thumb: The .nodes of the 5000x5000 grid that spans the world, i.e. G3_World_01_x???y0z???_CStat.node, have HybridContext = false, all other HybridContext = true.

[1] For this, the UseType of the entity is considered: gEUseType_Bookstand, gEUseType_PickOre, gEUseType_Stoneplate, gEUseType_OrcBoulder.


Regarding the creation of completely own property sets, I do have kind of an example, even though I have never tried it with eCEntityPropertySet as a base class, but only property objects in general.

#include "property/me_defaultproperty.h"

ME_PROPERTY_SET( mCCGEConditionEquipped, mCCGECondition )
    public:
        ME_DEFINE_DEFAULT_STRPROP(Wearer)
        ME_DEFINE_DEFAULT_PROPERTY(bTObjArray<bCString>, m_arrItems, Items)
};
GE_DEFINE_PROPERTY_OBJECT( mCCGEConditionEquipped, mCCGECondition )
ME_DEFINE_DEFAULT_STRPROP_TYPE(mCCGEConditionEquipped, Wearer, 0)
ME_DEFINE_DEFAULT_PROPERTY_TYPE(mCCGEConditionEquipped, m_arrItems, Items, 0)
LionBlazer commented 1 year ago

Thank you for such a detailed example. To begin with, I tried to simply copy it to my project. However, the game crashes at the patch injection stage. Crashes because of this line: ME_PATCH_PROPERTY_SET("Game.dll", "Script_LearnPoints.dll", gCInteraction_PS, gCInteraction_PS_Ext, RVA_Game(0x1B14EC), GEU32)

I couldn't find any normal logs, but here's what's written in g3debug.log:

Description: Invalid call ! Gothic3 engine FATAL ERROR: Module: AsmJit error! --> (Invalid instruction) Filename:<>, Line:#0 Debug Assertion Failed!

As far as I understand it falls on the injection of the constructor hook

georgeto commented 1 year ago

As far as I understand it falls on the injection of the constructor hook

Were you able to resolve the error?

LionBlazer commented 1 year ago

I added the property to the eEPropertySetType_Item:

class __declspec( dllexport ) gCItem_PS_Ext :
    public gCItem_PS {
public:
        GE_DEFINE_PROPERTY(gCItem_PS, GEU32, m_svmapValMap, ValMap)
public:
    gCItem_PS_Ext(void);

protected:
    void Invalidate(void);
};

GE_ASSERT_SIZEOF(gCItem_PS_Ext, 0x18)
ME_DEFINE_PROPERTY_TYPE(gCItem_PS_Ext, gCItem_PS, ValMap, m_svmapValMap)

gCItem_PS_Ext::gCItem_PS_Ext() : m_svmapValMap() {}

void gCItem_PS_Ext::Invalidate() {
    gCItem_PS::Invalidate();
    m_svmapValMap = 0;
}

ME_MODULE(gCItem_PS_Ext) {
    ME_PATCH_PROPERTY_SET("Game.dll", "Script_LearnPoints.dll", gCItem_PS, gCItem_PS_Ext, RVA_Game( 0xbaa27 ), GEU32)
}

Usage:

auto item = dynamic_cast<gCItem_PS_Ext*>(mapEntity->GetPropertySet(eEPropertySetType_Item));
item->SetValMap(item->GetValMap() + 1);
println(item->GetValMap());

(it works)


But in reality, it was added to all items of this type, and not to a specific stack. I looked at some methods in the inventory, and I got the feeling that the quality property has a special status. It is passed to a method that creates a new stack in the inventory:

GEInt CreateItems(bCPropertyID const &, GEU32, GEU32);
GEInt CreateItems(eCTemplateEntity const *, GEU32, GEU32);
georgeto commented 1 year ago
auto item = dynamic_cast<gCItem_PS_Ext*>(mapEntity->GetPropertySet(eEPropertySetType_Item));
item->SetValMap(item->GetValMap() + 1);
println(item->GetValMap());

(it works)

What is mapEntity in your example, so how do you obtain that entity??

georgeto commented 1 year ago

But in reality, it was added to all items of this type, and not to a specific stack. I looked at some methods in the inventory, and I got the feeling that the quality property has a special status. It is passed to a method that creates a new stack in the inventory:

Ah, so your goal is to store additional properties on each inventory stack. Then it is a bit different, because the items in the inventory are not entities of their own, but are managed as a list of gCInventoryStacks in the gCInventory_PS of the entity the inventory belongs to (for example PC_Hero).

Sou you have to extend the gCInventoryStack (like you have done with gCItem_PS above). Then you can use the gCInventory_PS::FindStack/GetStack() functions that return the gCInventoryStack for a given item stack in the inventory.

LionBlazer commented 1 year ago
auto item = dynamic_cast<gCItem_PS_Ext*>(mapEntity->GetPropertySet(eEPropertySetType_Item));
item->SetValMap(item->GetValMap() + 1);
println(item->GetValMap());

(it works)

What is mapEntity in your example, so how do you obtain that entity??

I get mapEntity of the active map directly from gCHUDStaticMap https://github.com/georgeto/gothic3sdk/issues/8#issuecomment-1611100895


Sou you have to extend the gCInventoryStack (like you have done with gCItem_PS above). Then you can use the gCInventory_PS::FindStack/GetStack() functions that return the gCInventoryStack for a given item stack in the inventory.

Thanks, I'll try

LionBlazer commented 10 months ago

Oh, it's been a long time.

I have implemented an extension for gCInventoryStack:

class __declspec( dllexport ) gCInventoryStack_Ext : public gCInventoryStack {
public:
    GEU32 m_svmapValMap;
public:
    gCInventoryStack_Ext(void);
    GEBool CanMerge( gCInventoryStack_Ext const & ) const;
protected:
    void Invalidate(void);
};
gCInventoryStack_Ext::gCInventoryStack_Ext() {}//: m_svmapValMap() {}

void gCInventoryStack_Ext::Invalidate() {
    gCInventoryStack::Invalidate();
    m_svmapValMap = 0;
}

GEBool gCInventoryStack_Ext::CanMerge( gCInventoryStack_Ext const & another) const {
    auto item = another.GetTemplate();
    if (item) {
        if (item->HasPropertySet(eEPropertySetType_Map)) {
            return GEFalse;
        }
    }
    return gCInventoryStack::CanMerge(another);
}

ME_MODULE(gCInventoryStack_Ext) {
    __ME_PATCH_PROPERTY_SET("Game.dll", "Script_LearnPoints.dll", gCInventoryStack, gCInventoryStack_Ext, RVA_Game( 0xc0e07 ), GEU32, GETrue, GETrue)
}

#define ME_REWRITE_CAN_MERGE( SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE ) \
ME_REWRITE_MEMBER_FUNCTION( CanMerge,  SOURCE_MODULE, TARGET_MODULE, SOURCE_TYPE, TARGET_TYPE, "?CanMerge@", "@@QBE_NABV1@@Z" )

And it works, but I ran into the problem of "indistinguishability" of gCInventoryStacks. The fact is that if I have several non-merging stacks with the same item templates inside, then they will have the same gCMap_PS and eCEntity instances. Therefore, I cannot understand from the gCHUDStaticMap object for which stack the map is currently open:

class gCHUDStaticMap : public CFFGFCStatic {
public:
    gCMap_PS* map;
    eCEntity* entity;
};

I had an idea to add to gCHUDStaticMap (just like with gCInventoryStack, via extension) a pointer to the current open Stack, or to some uid of the open stack, so that it can be found later.

As far as I understand, the gCHUDStaticMap object is not recreated every time a player opens a new map. But I could not find where the creation of this object takes place.

Could you tell me where the initialization of the gCHUDStaticMap object takes place and where gCMap_PS is written to it when switching the map?

georgeto commented 10 months ago

As far as I understand, the gCHUDStaticMap object is not recreated every time a player opens a new map. But I could not find where the creation of this object takes place.

As far as I know the gCHUDStaticMap is only created once when the gCGUIManager is initialized in gCGUIManager::Create(), and I think that happens once on startup of the game.

The nesting of GUI components is as follows: gCGUIManager -> gCHUDDialogMain2 -> gCHUDPageDocuments2 -> gCHUDViewDocument2 -> gCHUDViewMap2 -> gCHUDStaticMap

Could you tell me where the initialization of the gCHUDStaticMap object takes place and where gCMap_PS is written to it when switching the map?

So gCHUDStaticMap is initialized in the constructor of gCHUDViewMap2 (RVA 0x82650). (Hint for one way to figure this out: It is easy to find once you got hold of the vftable of gCHUDStaticMap. I would expect that Ghidra should auto-detect and name the vftable. Then just check the cross-references for the vftable, i.e. where it is used. That should give you both the constructor (so RVA 0x82650 like mentioned above and the destructor).

Assuming that there is only one instance of gCHUDStaticMap / gCHUDViewMap2 (I pretty sure that is indeed the case), you can take a shortcut and just store the currently selected state in a global variable. No need for all the hassle of providing additional storage inside gCHUDStaticMap.

Switching of the map is done via gCHUDViewDocument2::SetStack(), VA 0x81E20. The gCInventoryStack *` for the stack that gets selected is passed in the EAX register. The function checks whether the selected stack has one of the following property sets; gCRecipe_PS, gCLetter_PS, gCBook_PS, gCMap_PS Then depending on which property set is present, it calls the corresponding gCHUDViewDocument2::DisplayRecipe (RVA 0x7f500) / Letter (RVA 0x82550) / Book (RVA 0x818b0) / Map (RVA 0x0x826c0) function.

LionBlazer commented 10 months ago

Thanks! 0x81E20 works pretty good.

I have a couple more questions related to the extension for gCInventoryStack.

In the example above, I added GEU32 m_svmapValMap to gCInventoryStack_Ext. However, after changing it (even for non-stackable stacks), the game "sometimes" crashes. For example, always after "teach all"/"give all". Or rarely when opening inventory. What could this be related to? Maybe I implemented the extension incorrectly? Another strange thing is that if you initialize m_svmapValMap in the constructor, the game crashes when loading / starting a new game.

I am also interested in the ability to save/load my information in the stack. Is it possible to do this through the Read(bCIStream&) and Write(bCOStream&) methods in bCObjectRefBase? They are virtual and in theory they can simply be overridden

Jackydima commented 2 months ago

For anyone that tries to add new Attributes to existing Classes with these information here:

Some Classes of the Game, dont have the correct size, that represent the ingame size. The size of classes like "gCDamageReceiver_PS" or "gCNPC_PS" have the default class size of 0x14, because they are stated like empty classes: class gCDamageReceiver_PS;

You can check if the class has a default size, by hovering over the classname with you mouse.

There will be issues if you try to add extra attributes to a class -> like the extra attributes share the same data location as the first attributes of the vanilla class!

So extending classes like that you should use the Padding function and then your Attributes: GE_PADDING (<RealSize> - 0x14) GE_DEFINE_PROPERTY(...) ...

The real size can be found by looking into the Game.dll disassembled file. There will be a reference to classes that uses only an adress for the constructor: image image

In the second one you can see the size is 0x208 of the gCNPC_PS class.

So you would need to write first GE_PADDING (0x208 - 0x14) in the coresponding header! The correct place can be found in the examples above :)

Also in for the cpp the adress of the real size is shown in the second picture:

ME_MODULE(gCNPC_PS_Ext) { ME_PATCH_PROPERTY_SET( "Game.dll", "<TODO: Name of your DLL>.dll", gCNPC_PS, gCNPC_PS_Ext, RVA_Game( 0x14523c ), GEU32 ) }