In this tutorial we'll be looking at CreateMove - the function of choice for anyone looking to manipulate player input. Specifically, we'll be looking at the CreateMove function defined in the IClientMode interface. I'll be going through one of the simpler methods of finding it, hooking it and finally creating a signature that should persist through game updates.
Once again, we'll be targeting Counter-Strike: Global Offensive. This should also work on other recent Source games but some of the later instructions will be slightly different for 32-bit games. Keep in mind there are dozens of similar ways to get to the end result and this is just one of them; not necessarily the 'best' one either.
Finding the right CreateMove
If you look for Source networking information on the Valve Developer Wiki you'll eventually find this page about user commands. Catching these commands and editing them before they're sent to the server opens up a whole world of possibilities.
The first stop for a client user command is at the CreateMove function defined in the IBaseClientDLL interface. You're probably wondering why there's a whole section for this if we found it so quickly. Well, that's because we haven't found it yet. Sure, you could hook this function but I promise there's a much easier way that involves 100% less manual validation.
void CreateMove(int sequence_number, float input_sample_frametime, bool active)— CreateMove as defined in the IBaseClientDLL and IInput interface.
Turns out there are quite a few CreateMove functions! The one from IBaseClientDLL sets up a few things before calling the CreateMove from IInput. The user command is created here and populated with the initial values. Other CreateMove functions are then called allowing for some values to be changed before the user command is finally validated and placed into a circular buffer ready to be sent to the server.
bool CreateMove(float flInputSampleTime, CUserCmd* cmd)— CreateMove as defined in the IClientMode interface.
So let's look at where CreateMove from IInput calls CreateMove from IClientMode. You'll notice the function prototype is a little different; now including a pointer to the current user command. You should know why we're hooking this one now.
// Let the move manager override anything it wants to.
if ( g_pClientMode->CreateMove( input_sample_frametime, cmd ) )
It's perfect. If we hooked this function we could make all the changes we wanted and it'll be validated for us when we're done. Needless to say, if we find where g_pClientMode
is defined then the rest should be fairly straightfoward. Luckily, Valve has us covered! One string search in the repository later and we're taken straight to a certain hl2_clientmode.cpp
file.
IClientMode* g_pClientMode = NULL;
#define SCREEN_FILE "scripts/vgui_screens.txt"
void CHLModeManager::Init(void) {
g_pClientMode = GetClientModeNormal();
PanelMetaClassMgr()->LoadMetaClassDefinitionFile(SCREEN_FILE);
}
Wow, it really was that easy, huh? Only one thing left to do now. Search for the scripts/vgui_screens.txt
string in IDA, follow the reference and you should find yourself in the CHLModeManager::Init
function.
Double-click on the variable I've named g_ClientModeNormal
and copy the address.
.bss:5A69360 ?? ?? ?? ?? ?? ?? ?? g_ClientModeNormal dq ?
Adding the offset 0x5A69360
to the base address of client_client.so
will now give us the pointer to IClientMode.
Hooking CreateMove
For now we'll move on to the exciting part where we hook CreateMove and make some changes. First, we'll need the CUserCmd structure. Take another look at CreateMove from IInput if you're curious as to what some of these variables do.
struct CUserCmd {
virtual ~CUserCmd() {};
int command_number;
int tick_count;
Vector viewangles;
Vector aimdirection;
float forwardmove;
float sidemove;
float upmove;
int buttons;
unsigned char impulse;
int weaponselect;
int weaponsubtype;
int random_seed;
short mousedx;
short mousedy;
bool hasbeenpredicted;
Vector headangles;
Vector headoffset;
};
Just to save you some time the CreateMove index is 25. If you really want to find it yourself you can search for the LevelInit
string, follow the cross-reference to the virtual table and look at the function directly above.
Add the prototype for CreateMove to your project and create the replacement function. I'll be using my basic hooking class in the following example code but you're free to use whatever you like. We'll create a primitive auto-bunnyhop in our hook using the buttons variable in the user command and the flags from our local player entity. Make sure you have a GetFlags function in your C_BasePlayer class that returns the m_fFlags network variable as an integer.
typedef bool (*CreateMoveFn) (void*, float, CUserCmd*);
bool hkCreateMove(void* thisptr, float flInputSampleTime, CUserCmd* cmd) {
C_BasePlayer* localplayer = reinterpret_cast<C_BasePlayer*>(entitylist->GetClientEntity(engine->GetLocalPlayer()));
if (cmd->buttons & IN_JUMP && !(localplayer->GetFlags() & FL_ONGROUND)) {
// remove the jump button from the user command when we are in the air
cmd->buttons &= ~IN_JUMP;
}
return clientmode_hook->GetOriginalFunction<CreateMoveFn>(25)(thisptr, flInputSampleTime, cmd);
}
An interesting note about hooking CreateMove here is that you're not required to call the original function. However, I've noticed that returning without calling the original allows you to keep moving the mouse when it should usually be locked, for example, when warmup ends or after the final kill of a game. This can be useful in some cases where you don't want the client view angles to reflect the ones sent to the server.
Before we can hook anything we'll need the base address of the client_client.so
library. There are a lot of different ways to find this including the method below that uses the dl_iterate_phdr
function. If you decide to use this method make sure you're including the link.h
header somewhere.
// store the base address here
uintptr_t client_client = 0;
// enumerate through loaded shared libraries
dl_iterate_phdr([] (struct dl_phdr_info* info, size_t size, void* data) {
// check for 'client_client.so' in name
if (strcasestr(info->dlpi_name, "client_client.so")) {
// write the address and break out of the loop
*reinterpret_cast<uintptr_t*>(data) = info->dlpi_addr + info->dlpi_phdr[0].p_vaddr;
return 1;
}
// haven't found it yet, keep going..
return 0;
}, &client_client);
IClientMode* clientmode = reinterpret_cast<IClientMode*>(client_client + 0x5A69360);
clientmode_hook = std::make_unique<VMTHook>(clientmode);
clientmode_hook->HookFunction(reinterpret_cast<void*>(hkCreateMove), 25);
Getting ClientMode dynamically
Not quite finished just yet! If we want to minimize the amount of time we spend fixing our projects every time the game updates we can use a technique called signature scanning. Check the AlliedModders wiki for a great explanation.
Let's switch back to IDA and take another look at where we found our pointer to IClientMode.
.text:951430 55 push rbp
.text:951431 48 8D 05 28 7F 11 05 lea rax, g_ClientModeNormal
.text:951438 48 89 E5 mov rbp, rsp
The position of this function will vary after game updates but most of the opcodes will remain the same. We can create a signature by copying the bytes that won't change and adding the unique bytes that frequently change as wildcard characters. During runtime we will search through the memory of the process - specifically a range of memory assigned to the client_client.so
library. If our signature is matched, the address of where the first byte was found will be returned.
I'll leave it up to you to find a suitable signature scanning function. You can find plenty of different implementations over here. Depending on the implementation you might need to convert the signature into a 'code-style' signature with a mask. I've included my signature below in both styles, so far it's survived three updates and hopefully it'll last a while longer.
48 8D 05 ? ? ? ? 48 89 E5 48 89 05 ? ? ? ? E8 ? ? ? ? 5D 48— Regular or 'IDA-style' signature.\x48\x8D\x05\x00\x00\x00\x00\x48\x89\xE5\x48\x89\x05\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x5D\x48— Code-style signature.xxx????xxxxxx????x????xx— Code-style mask.
Alright, time to implement this in our project. We want to get to client_client.so+951431
here, so if your pattern starts anywhere else make sure you increment or decrement the return address accordingly.
uintptr_t init_address = FindPattern(client_client, signature_goes_here);
uint32_t offset = *reinterpret_cast<uint32_t*>(init_address + 3);
IClientMode* clientmode = reinterpret_cast<IClientMode*>(init_address + offset + 7);
Are you wondering why we needed to do that? It eluded me for a while but it's actually quite simple.
Instructions can now reference data relative to the instruction pointer (RIP register). This makes position independent code, as is often used in shared libraries and code loaded at run time, more efficient.— x86-64 from Wikipedia, the free encyclopedia.
Take another look at the CCSModeManager::Init
disassembly. The instruction pointer is 0x951431
and the bytes 28 7F 11 05
are relative to the instruction pointer. Once read into the offset variable it becomes 0x5117F28
due to endianness.
.text:951431 48 8D 05 28 7F 11 05 lea rax, g_ClientModeNormal
Take the instruction pointer, add the offset and finally add the size of the instruction and you'll have the absolute address.
0x951431 + 0x5117F28 + 7 = 0x5A69360
And with that.. we're all finished! As always, you can find the final product over here. I've recently also added a second implementation that uses the GetClientMode
function from CHLClient::HudProcessInput
so check that out too.