Skip to content

Hook virtual functions by offset #617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

KillStr3aK
Copy link
Contributor

@KillStr3aK KillStr3aK commented Oct 9, 2024

With these changes, we will be able to hook virtual functions based on offset.

Example

Testing StartTouch, Touch, EndTouch hooks on the soccerball in de_dust2

{
    "StartTouch": {
        "offsets": {
            "windows": 143,
            "linux": 142
        }
    },
    "Touch": {
        "offsets": {
            "windows": 144,
            "linux": 143
        }
    },
    "EndTouch": {
        "offsets": {
            "windows": 145,
            "linux": 144
        }
    }
}
public CPhysicsPropMultiplayer? SoccerBall;

public VirtualFunctionVoid<CBaseEntity, CBaseEntity>? OnStartTouch;
public VirtualFunctionVoid<CBaseEntity, CBaseEntity>? OnTouch;
public VirtualFunctionVoid<CBaseEntity, CBaseEntity>? OnEndTouch;

public override void Load(bool hotReload)
{
    RegisterEventHandler<EventRoundStart>((@event, info) =>
    {
        IEnumerable<CPhysicsPropMultiplayer> list = Utilities.FindAllEntitiesByDesignerName<CPhysicsPropMultiplayer>("prop_physics_multiplayer");

        if (!list.Any())
        {
            return HookResult.Continue;
        }

        SoccerBall = list.First();

        if (OnStartTouch == null) (OnStartTouch = new VirtualFunctionVoid<CBaseEntity, CBaseEntity>(SoccerBall, GameData.GetOffset("StartTouch"))).Hook(HookOnStartTouch, HookMode.Pre);
        if (OnTouch == null) (OnTouch = new VirtualFunctionVoid<CBaseEntity, CBaseEntity>(SoccerBall, GameData.GetOffset("Touch"))).Hook(HookOnTouch, HookMode.Pre);
        if (OnEndTouch == null) (OnEndTouch = new VirtualFunctionVoid<CBaseEntity, CBaseEntity>(SoccerBall, GameData.GetOffset("EndTouch"))).Hook(HookOnEndTouch, HookMode.Pre);

        return HookResult.Continue;
    });
}

public override void Unload(bool hotReload)
{
    OnStartTouch?.Unhook(HookOnStartTouch, HookMode.Pre);
    OnTouch?.Unhook(HookOnTouch, HookMode.Pre);
    OnEndTouch?.Unhook(HookOnEndTouch, HookMode.Pre);
}

private HookResult HookOnStartTouch(DynamicHook h)
{
    CBaseEntity entity = h.GetParam<CBaseEntity>(0);
    CBaseEntity otherEntity = h.GetParam<CBaseEntity>(1);

    if (entity != SoccerBall && otherEntity != SoccerBall)
        return HookResult.Continue;

    Console.WriteLine($"[OnStartTouch] {otherEntity.DesignerName} ({otherEntity.Index}) started touching {entity.DesignerName} ({entity.Index})");
    return HookResult.Continue;
}

private HookResult HookOnTouch(DynamicHook h)
{
    CBaseEntity entity = h.GetParam<CBaseEntity>(0);
    CBaseEntity otherEntity = h.GetParam<CBaseEntity>(1);

    if (entity != SoccerBall && otherEntity != SoccerBall)
        return HookResult.Continue;

    Console.WriteLine($"[OnTouch] {otherEntity.DesignerName} ({otherEntity.Index}) is touching {entity.DesignerName} ({entity.Index})");
    return HookResult.Continue;
}

private HookResult HookOnEndTouch(DynamicHook h)
{
    CBaseEntity entity = h.GetParam<CBaseEntity>(0);
    CBaseEntity otherEntity = h.GetParam<CBaseEntity>(1);

    if (entity != SoccerBall && otherEntity != SoccerBall)
        return HookResult.Continue;

    Console.WriteLine($"[OnEndTouch] {otherEntity.DesignerName} ({otherEntity.Index}) is no longer touching {entity.DesignerName} ({entity.Index})");
    return HookResult.Continue;
}

Debug output:

10:52:04 [INFO] (cssharp:Core) Creating function CBaseEntity_142
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7f66a15159c0
[Info] - Trampoline - Allocated trampoline at 0x7f6721514000 (using 1 attempts)
10:52:04 [INFO] (cssharp:Core) Creating function CBaseEntity_143
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7f66a1050e60
[Info] - Trampoline - Allocated trampoline at 0x7f672104f000 (using 1 attempts)
10:52:04 [INFO] (cssharp:Core) Creating function CBaseEntity_144
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7f66a1050a40
[Info] - Trampoline - Allocated trampoline at 0x7f66d0e97000 (using 1 attempts)
[OnStartTouch] worldent (0) started touching prop_physics_multiplayer (269)
[OnTouch] worldent (0) is touching prop_physics_multiplayer (269)
[OnTouch] prop_physics_multiplayer (269) is touching worldent (0)

Developers should be aware that they are hooking the virtual function, which is shared between instances (same as with signatures)

@KillStr3aK KillStr3aK changed the title Extend Dynamic Extend DynamicHooks to support virtual functions Oct 9, 2024
/// <summary>
/// <b>WARNING:</b> this is only supposed to be used with <see cref="VirtualFunctionVoid"/> and <see cref="VirtualFunctionWithReturn{TResult}"/>
/// </summary>
internal BaseMemoryFunction(IntPtr objectPtr, int offset, DataType returnType, DataType[] parameters) : base(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and related constructors are internal because they should not be used randomly. People should only use the exposed VirtualFunctionVoid and VirtualFunctionWithReturn versions

@KillStr3aK KillStr3aK marked this pull request as ready for review October 9, 2024 09:44
@KillStr3aK KillStr3aK requested a review from roflmuffin as a code owner October 9, 2024 09:44
@Prefix
Copy link

Prefix commented Nov 13, 2024

@roflmuffin +1 on this

@KillStr3aK
Copy link
Contributor Author

@roflmuffin +1 on this

my second review has to be resolved before this pr is ready

@Prefix
Copy link

Prefix commented Jan 10, 2025

@roflmuffin +1 on this

my second review has to be resolved before this pr is ready

what is second pr? can community help you somehow?

@KillStr3aK
Copy link
Contributor Author

@roflmuffin +1 on this

my second review has to be resolved before this pr is ready

what is second pr? can community help you somehow?

#617 (comment)

this is the second review

@KillStr3aK KillStr3aK changed the title Extend DynamicHooks to support virtual functions Hook virtual functions by offset May 10, 2025
@KillStr3aK
Copy link
Contributor Author

KillStr3aK commented May 10, 2025

virtual functions that has been created by an offset are now stored as {typeof(TArg1).Name}_{OffsetNum}, for e.g.:
10:52:04 [INFO] (cssharp:Core) Creating function CBaseEntity_142

TArg1 is meant to represent the originating class of the function

@roflmuffin Unsure of the interaction if someone hooks a CBaseEntity and a CCSPlayerPawn with the same offset.

12:40:05 [INFO] (cssharp:Core) Creating function CBaseEntity_142
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7fc5331159c0
[Info] - Trampoline - Allocated trampoline at 0x7fc5b3114000 (using 1 attempts)

12:40:05 [INFO] (cssharp:Core) Creating function CPhysicsPropMultiplayer_142

12:40:05 [INFO] (cssharp:Core) Creating function CBaseEntity_143
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7fc532c50e60
[Info] - Trampoline - Allocated trampoline at 0x7fc5b2c4f000 (using 1 attempts)

12:40:05 [INFO] (cssharp:Core) Creating function CBaseEntity_144
[Info] - Trampoline - Bounds of relative addresses accessed [0xffffffffffffffff, (nil)]
[Info] - Trampoline - Attempting to allocate trampoline within +-2GB range of 0x7fc532c50a40

the hook itself is reused, but currently only the latter is called, I believe this could also happen if the same function is hooked with 2 different signatures? (tested it and yes)

@KillStr3aK KillStr3aK marked this pull request as draft May 12, 2025 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants