neverlosecc / source2gen

Source2 games SDK generator
http://s2gen.com
Apache License 2.0
193 stars 31 forks source link

Embedding classes for non-pointer props #26

Closed DucaRii closed 5 months ago

DucaRii commented 6 months ago

this is an idea ive had for a while and would, for me at least, increase productivity while reversing

the general idea is that for props that are instances of classes rather than pointers we could directly embed the class implementation into the source and then add the offsets together.

the goal of this would be to make it easier to find out what variable the game is referencing. for example: cs2 frequently accesses CSkeletonInstance::m_modelState.m_hModel with offset 0x200, now if you look up the offset 0x200 in the sdk nothing comes up because m_modelState is embedded and starts at offset 0x160 with m_hModel being at offset 0xa0.

ive whipped up a quick and (very) dirty POC to show what i mean. i didnt want to make too many changes to the code base incase this is not something that should be within the scope of this project.

if this was to be properly implemented, the class dumping could be put into its own function to allow recursively calling it or fields and their offset could be cached in some sort of list?

for my POC i decided to add a 'cacheddump' variable to the class type which is filled up after a class is dumped with that section of the dump

// [class dumping...]
builder.end_block();

class_dump.cached_dump_ = builder.str().substr(pre_string_size);

and heres the code that modifies the actual prop dumping, hardcoded for CModelState in a proper solution we definitley shouldnt work with regex, or any string replacing but it worked for the POC with minimal codebase changes

if (type.compare("CModelState") == 0) {
    auto prop_class = std::ranges::find_if(classes_to_dump, [type](const class_t& cls) { return cls.target_->GetName().compare(type) == 0; });

    if (prop_class != classes_to_dump.end()) {

        auto cached_dump = prop_class->cached_dump_;
        // add the prop name before the classes finishing semicolon and add offset
        cached_dump.replace(cached_dump.find_last_of(';'), 1, std::format(" {}; // {:#x}", var_info.formatted_name(), field.m_single_inheritance_offset));

        // finds all comments for offsets '// 0x??'
        std::regex offset_comment_regex("\\/\\/ 0[xX][0-9a-fA-F]+");
        std::smatch matcher;
        std::string reformatted_dump;

        while (std::regex_search(cached_dump, matcher, offset_comment_regex)) {
            reformatted_dump += matcher.prefix().str();

            reformatted_dump += [field](std::smatch matcher) -> std::string {
                auto offset = std::stoul(matcher.str().substr(3), nullptr, 16);
                return std::format("// {:#x} ({:#x})", offset, offset + field.m_single_inheritance_offset);
            }(matcher);

            cached_dump = matcher.suffix().str();
        }

        // add tail of string
        reformatted_dump += cached_dump;

        builder.push_line(reformatted_dump, false);
    }
} else {
    // @note: @es3n1n: push prop
    //
    builder.prop(var_info.m_type, var_info.formatted_name(), false);
    if (!var_info.is_bitfield())
        builder.reset_tabs_count().comment(std::format("{:#x}", field.m_single_inheritance_offset), false).restore_tabs_count();
    builder.next_line();
}

using this, the output changes from:

class CSkeletonInstance : public CGameSceneNode
{
private:
    [[maybe_unused]] uint8_t __pad0150[0x10]; // 0x150
public:
    // MNetworkEnable
    CModelState m_modelState; // 0x160  

to: (class metadata removed and indentation fixed for cosmetic purposes)

class CSkeletonInstance : public CGameSceneNode
{
private:
    [[maybe_unused]] uint8_t __pad0150[0x10]; // 0x150
public:
    // MNetworkEnable
    class CModelState
    {
    private:
        [[maybe_unused]] uint8_t __pad0000[0xa0]; // 0x0 (0x160)
    public:
        // MNetworkEnable
        // MNetworkChangeCallback "skeletonModelChanged"
        CStrongHandle< InfoForResourceTypeCModel > m_hModel; // 0xa0 (0x200)    
        // MNetworkDisable
        CUtlSymbolLarge m_ModelName; // 0xa8 (0x208)    
    private:
        [[maybe_unused]] uint8_t __pad00b0[0x38]; // 0xb0 (0x210)
    public:
        // MNetworkEnable
        bool m_bClientClothCreationSuppressed; // 0xe8 (0x248)  
    private:
        [[maybe_unused]] uint8_t __pad00e9[0x97]; // 0xe9 (0x249)
    public:
        // MNetworkEnable
        // MNetworkChangeCallback "skeletonMeshGroupMaskChanged"
        uint64_t m_MeshGroupMask; // 0x180 (0x2e0)  
    private:
        [[maybe_unused]] uint8_t __pad0188[0x9a]; // 0x188 (0x2e8)
    public:
        // MNetworkEnable
        // MNetworkChangeCallback "skeletonMotionTypeChanged"
        int8_t m_nIdealMotionType; // 0x222 (0x382) 
        // MNetworkDisable
        int8_t m_nForceLOD; // 0x223 (0x383)    
        // MNetworkDisable
        int8_t m_nClothUpdateFlags; // 0x224 (0x384)    
    } m_modelState; // 0x160

maybe an alternative solution to embedding the whole class would be to just add a few comments above the prop just like metadata? like so

// MNetworkEnable
// m_hModel (0x200)
// m_ModelName (0x208)
CModelState m_modelState; // 0x160  
cpz commented 6 months ago

Looks like a good idea, I'll mark it as an improvement, as soon as someone is available from us we'll try to make it happen, well or if there's a desire you can make a pull request.

DucaRii commented 6 months ago

yeah im down to pr if i find the time, which of the 2 options i listed do you think would be the better choice i think for readability and for the sake of not having to make too many changes to the codebase the 2nd option i layed out would be better

// MNetworkEnable
// m_hModel (0x200)
// m_ModelName (0x208)
CModelState m_modelState; // 0x160  
cpz commented 5 months ago

Thanks to DucaRii, it was added, commit there: Link