Need help hooking these functions
Posted: Fri Apr 21, 2017 9:21 am
Hey there,
My idea is to make stock TF2 bots work on PLR maps as they would on PL maps. They work perfectly on Payload maps, but on Payload Race maps they just stand still and do nothing. And it's official behavior, as bots are announced to not support PLR maps.
On PL map, there's 1 bomb cart. One team pushes the cart to their enemies, the other one defends their base.
On PLR map, however, there're 2 bomb carts. Each team has to push and defend at the same time.
I want to make the bots forget defending and at least start pushing their cart.
There's a Sourcemod plugin that just spawns a flag (like on CTF maps), makes it invisible, parents it to the carts, hooks its touch method etc., just to make the bots approach their carts. But no, I want to fix the problem in the root.
To start with, I'm trying to hook CTFGameRules::GetPayloadToPush and CTFBotMainAction::Update - just because they seem interesting. No luck so far. Testing on Linux.
CTFGameRules::GetPayloadToPush
This is how I hook it:
Here's what happens:
If I only do a pre-hook - the function crashes (ESP not present) after executing pre-hook;
If I only do a post-hook - the function crashes after executing post-hook;
If I do both hooks - the function crashes after executing post-hook.
Also, as you may have noticed, I print the value of the args[1]. It's junk in a pre-hook, but is set to "3" in a post-hook. Invincible told me that could mean that that "int" argument is passed by reference and is meant to be changed inside of the function.
I tried different return types - VOID, POINTER, INT - same behavior. ESP not present.
CTFBotMainAction::Update
At first I didn't notice this was a virtual, so I hooked it as a regular function - with a symbol. Pre-hook is executed, then "ESP not present". Didn't test post-hooks.
Then I realized it's a virtual, so I decided to obtain an instance of CTFBotMainAction and hook the function properly. Unfortunately, almost all methods of this class are virtual, and I had a hard time trying to get an instance of it.
Finally I've found a non-virtual thunk to CTFBotMainAction::ShouldAttack, but it's only called when bots see an enemy. That means that I will have to run a PL map (or any other map that bots actually support), obtain an instance of CTFBotMainAction, then build a virtual function CTFBotMainAction::Update using the vtable index, and only then I can set a hook and change the level to my PLR map.
Here's the plugin that does all this stuff:
By the way, you may notice that I used args[0] - 4 to get a pointer to the instance. It's because the thunk looks like this:
Eventually I got the instance and set the pre-hook on its Update method which was immediately called, I saw "Pre-Hooked CTFBotMainAction::Update!" in my console, then... ESP not present.
Any ideas?
Thanks.
My idea is to make stock TF2 bots work on PLR maps as they would on PL maps. They work perfectly on Payload maps, but on Payload Race maps they just stand still and do nothing. And it's official behavior, as bots are announced to not support PLR maps.
On PL map, there's 1 bomb cart. One team pushes the cart to their enemies, the other one defends their base.
On PLR map, however, there're 2 bomb carts. Each team has to push and defend at the same time.
I want to make the bots forget defending and at least start pushing their cart.
There's a Sourcemod plugin that just spawns a flag (like on CTF maps), makes it invisible, parents it to the carts, hooks its touch method etc., just to make the bots approach their carts. But no, I want to fix the problem in the root.
To start with, I'm trying to hook CTFGameRules::GetPayloadToPush and CTFBotMainAction::Update - just because they seem interesting. No luck so far. Testing on Linux.
CTFGameRules::GetPayloadToPush
- Regular (non-virtual function)
- Linux symbol: _ZNK12CTFGameRules16GetPayloadToPushEi
- Windows signature: 55 8B EC 56 8B F1 2A 2A 2A 2A 2A 2A 8B 01 2A 2A 2A 2A 2A 2A 83 F8 03
This is how I hook it:
Syntax: Select all
if PLATFORM == "windows":
GET_PAYLOAD_TO_PUSH_IDENTIFIER = b"\x55\x8B\xEC\x56\x8B\xF1\x2A\x2A\x2A\x2A\x2A\x2A\x8B\x01\x2A\x2A\x2A\x2A\x2A\x2A\x83\xF8\x03"
else:
GET_PAYLOAD_TO_PUSH_IDENTIFIER = "_ZNK12CTFGameRules16GetPayloadToPushEi"
server = find_binary('server')
# CTFGameRules::GetPayloadToPush(CTFGameRules *this, int)
get_payload_to_push = server[GET_PAYLOAD_TO_PUSH_IDENTIFIER].make_function(
Convention.THISCALL,
[DataType.POINTER, DataType.INT],
DataType.VOID
)
@PreHook(get_payload_to_push)
def pre_get_payload_to_push(args):
print("Pre-Hooked!", args[1])
@PostHook(get_payload_to_push)
def post_get_payload_to_push(args, ret_val):
print("Post-Hooked!", args[1])
Here's what happens:
If I only do a pre-hook - the function crashes (ESP not present) after executing pre-hook;
If I only do a post-hook - the function crashes after executing post-hook;
If I do both hooks - the function crashes after executing post-hook.
Also, as you may have noticed, I print the value of the args[1]. It's junk in a pre-hook, but is set to "3" in a post-hook. Invincible told me that could mean that that "int" argument is passed by reference and is meant to be changed inside of the function.
I tried different return types - VOID, POINTER, INT - same behavior. ESP not present.
CTFBotMainAction::Update
- Virtual function, linux index is 47, windows index is 46
- Linux symbol: _ZN16CTFBotMainAction6UpdateEP6CTFBotf
- Windows signature: didn't even bother
At first I didn't notice this was a virtual, so I hooked it as a regular function - with a symbol. Pre-hook is executed, then "ESP not present". Didn't test post-hooks.
Then I realized it's a virtual, so I decided to obtain an instance of CTFBotMainAction and hook the function properly. Unfortunately, almost all methods of this class are virtual, and I had a hard time trying to get an instance of it.
Finally I've found a non-virtual thunk to CTFBotMainAction::ShouldAttack, but it's only called when bots see an enemy. That means that I will have to run a PL map (or any other map that bots actually support), obtain an instance of CTFBotMainAction, then build a virtual function CTFBotMainAction::Update using the vtable index, and only then I can set a hook and change the level to my PLR map.
Here's the plugin that does all this stuff:
Syntax: Select all
from core import PLATFORM
from memory import Convention, DataType, find_binary
if PLATFORM == "windows":
SHOULD_ATTACK_THUNK_IDENTIFIER = b""
BOT_MAIN_ACTION_UPDATE_INDEX = 46
else:
SHOULD_ATTACK_THUNK_IDENTIFIER = "_ZThn4_NK16CTFBotMainAction12ShouldAttackEPK8INextBotPK12CKnownEntity"
BOT_MAIN_ACTION_UPDATE_INDEX = 47
should_attack_thunk_hook_set = False
bot_main_action_update = None
server = find_binary('server')
# CTFBotMainAction::ShouldAttack(CTFBotMainAction *this, const INextBot *, const CKnownEntity *)
should_attack_thunk = server[SHOULD_ATTACK_THUNK_IDENTIFIER].make_function(
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER, DataType.POINTER],
DataType.BOOL
)
def create_bot_main_action_update(pointer):
# CTFBotMainAction::Update(CTFBotMainAction *this, CTFBot *, float)
return pointer.make_virtual_function(
BOT_MAIN_ACTION_UPDATE_INDEX,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER, DataType.FLOAT],
DataType.POINTER
)
def pre_bot_main_action_update(args):
print("Pre-Hooked CTFBotMainAction::Update!")
def pre_should_attack_thunk(args):
print("Retrieving CTFBotMainAction instance...")
global bot_main_action_update
bot_main_action_update = create_bot_main_action_update(args[0] - 4)
print("Setting pre-hook on CTFBotMainAction::Update...")
bot_main_action_update.add_pre_hook(pre_bot_main_action_update)
print("Removing pre-hook from CTFBotMainAction::ShouldAttack thunk...")
should_attack_thunk.remove_pre_hook(pre_should_attack_thunk)
global should_attack_thunk_hook_set
should_attack_thunk_hook_set = False
def load():
global should_attack_thunk_hook_set
should_attack_thunk.add_pre_hook(pre_should_attack_thunk)
should_attack_thunk_hook_set = True
def unload():
global should_attack_thunk_hook_set
if should_attack_thunk_hook_set:
should_attack_thunk.remove_pre_hook(pre_should_attack_thunk)
should_attack_thunk_hook_set = False
global bot_main_action_update
if bot_main_action_update is not None:
bot_main_action_update.remove_pre_hook(pre_bot_main_action_update)
bot_main_action_update = None
By the way, you may notice that I used args[0] - 4 to get a pointer to the instance. It's because the thunk looks like this:
Eventually I got the instance and set the pre-hook on its Update method which was immediately called, I saw "Pre-Hooked CTFBotMainAction::Update!" in my console, then... ESP not present.
Any ideas?
Thanks.