Skip to content

Instantly share code, notes, and snippets.

@YaLTeR
Last active September 4, 2016 08:39
Show Gist options
  • Save YaLTeR/b6cb8f800cbd010a62231abd333f2f98 to your computer and use it in GitHub Desktop.
Save YaLTeR/b6cb8f800cbd010a62231abd333f2f98 to your computer and use it in GitHub Desktop.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <psapi.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
using std::uint8_t;
using std::size_t;
using std::uintptr_t;
#include <MinHook.h>
#define UNUSED(expr) (void)(expr)
struct module_info
{
HMODULE handle;
LPVOID base;
DWORD size;
};
struct pattern
{
const uint8_t* bytes;
const char* mask;
};
constexpr uint8_t bytes_CHL1GameMovement__CheckJumpButton[] = { 0x83, 0xEC, 0x14, 0x53, 0x56, 0x8B, 0xF1, 0x57, 0x8B, 0x7E, 0x08, 0x85, 0xFF, 0x74, 0x12, 0x8B, 0x07, 0x8B, 0xCF, 0xFF, 0x90, 0x60, 0x01, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x04, 0x8B, 0xCF, 0xEB };
constexpr pattern pattern_CHL1GameMovement__CheckJumpButton = {
bytes_CHL1GameMovement__CheckJumpButton,
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
constexpr uint8_t bytes_CGameMovement__FinishGravity[] = { 0x8B, 0x51, 0x08, 0xD9, 0x82, 0xB0, 0x0B, 0x00, 0x00, 0xD8, 0x1D, '?', '?', '?', '?', 0xDF, 0xE0, 0xF6, 0xC4, 0x44, 0x7A, 0x4D, 0xD9, 0x82, 0x08, 0x02, 0x00, 0x00, 0xD8, 0x1D };
constexpr pattern pattern_CGameMovement__FinishGravity = {
bytes_CGameMovement__FinishGravity,
"xxxxxxxxxxx????xxxxxxxxxxxxxxx"
};
constexpr uint8_t bytes_ConCommand_constructor[] = { 0x8B, 0x44, 0x24, 0x08, 0x33, 0xD2, 0x56, 0x8B, 0xF1, 0x89, 0x46, 0x18, 0x8B, 0x44, 0x24, 0x18, 0x3B, 0xC2, 0x88, 0x56, 0x08, 0x89, 0x56, 0x0C, 0x89, 0x56, 0x10, 0x89, 0x56, 0x14, 0x89, 0x56, 0x04, 0xC7, 0x06 };
constexpr pattern pattern_ConCommand_constructor = {
bytes_ConCommand_constructor,
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
constexpr uint8_t bytes__Host_RunFrame[] = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0xA0, '?', '?', '?', '?', 0x84, 0xC0, 0x53, 0x56, 0x57, 0x74, 0x17, 0xE8, '?', '?', '?', '?', 0x83, 0xF8, 0xFE, 0x74, 0x0D, 0x68 };
constexpr pattern pattern__Host_RunFrame = {
bytes__Host_RunFrame,
"xxxxxxx????xxxxxxxx????xxxxxx"
};
constexpr uint8_t bytes_Cbuf_InsertText[] = { 0x8B, 0x15, '?', '?', '?', '?', 0x85, 0xD2, 0x53, 0x55, 0x56, 0x57, 0x8B, 0xEA, 0x74, 0x35, 0x52, 0xE8 };
constexpr pattern pattern_Cbuf_InsertText = {
bytes_Cbuf_InsertText,
"xx????xxxxxxxxxxxx"
};
static module_info get_module_info(LPCWSTR name)
{
module_info rv;
rv.handle = GetModuleHandleW(name);
if (rv.handle) {
MODULEINFO info;
if (GetModuleInformation(GetCurrentProcess(), rv.handle, &info, sizeof(info))) {
rv.base = info.lpBaseOfDll;
rv.size = info.SizeOfImage;
}
}
return rv;
}
static bool compare(const uint8_t* data, const pattern& pattern)
{
auto bytes = pattern.bytes;
auto mask = pattern.mask;
for (; *mask != '\0'; ++data, ++bytes, ++mask)
if (*mask == 'x' && *data != *bytes)
return false;
return true;
}
static uintptr_t find_pattern(const module_info& module, const pattern& pattern)
{
auto length = std::strlen(pattern.mask);
auto p = reinterpret_cast<const uint8_t*>(module.base);
auto end = p + module.size - length;
for (; p <= end; ++p)
if (compare(p, pattern))
return reinterpret_cast<uintptr_t>(p);
return 0;
}
static uintptr_t relative_call_offset(uintptr_t address)
{
return *reinterpret_cast<uintptr_t*>(address) + address + 4;
}
template<typename Object, typename Function>
Function* patch_vtable(Object* object, size_t index, Function* new_value)
{
auto vtable = *reinterpret_cast<Function***>(object);
auto address = &vtable[index];
DWORD old_protect;
auto result = VirtualProtect(address, sizeof(new_value), PAGE_EXECUTE_READWRITE, &old_protect);
auto rv = *address;
*address = new_value;
// The first call might have failed, but the target might have still been accessible.
if (result)
VirtualProtect(address, sizeof(new_value), old_protect, &old_protect);
return rv;
}
#define PATCH_VTABLE(object, function, new_value) \
patch_vtable(object, offsetof(std::remove_pointer_t<decltype(object->vtable)>, function) / sizeof(void (*)()), new_value)
using CreateInterface_t = void* (*)(const char* name, int* return_code);
namespace server_dll
{
using SendPropType = int;
struct SendTable;
// Not declared.
using RecvProp = void;
using ArrayLengthSendProxyFn = void (*)();
using SendVarProxyFn = void (*)();
using SendTableProxyFn = void (*)();
struct SendProp
{
void* vtable;
RecvProp* matching_recv_prop;
SendPropType type;
int bits;
int string_buffer_len;
float low_value;
float high_value;
SendProp* array_prop;
ArrayLengthSendProxyFn array_length_proxy;
int elements;
int element_stride;
char* var_name;
char* exclude_DT_name;
float high_low_mul;
int flags;
SendVarProxyFn proxy_fn;
SendTableProxyFn data_table_proxy_fn;
SendTable* data_table;
int offset;
unsigned char data_table_proxy_index;
};
// Not declared.
using CSendTablePrecalc = void;
struct SendTable
{
SendProp* props;
int prop_count;
char* net_table_name;
CSendTablePrecalc* precalc;
bool initialized;
int write_spawn_count;
int data_table_proxies;
};
struct ServerClass
{
char* network_name;
SendTable* table;
ServerClass* next;
int class_id;
int instance_baseline_index;
};
// Not declared.
using edict_t = void;
struct IServerGameDLL
{
struct {
void (*unused0)();
void (*unused1)();
void (*unused2)();
void (*unused3)();
void (*unused4)();
void (__fastcall *ServerActivate)(edict_t* pEdictList, int edictCount, int clientMax);
void (*unused6)();
void (*unused7)();
void (*unused8)();
void (*unused9)();
void (*unused10)();
ServerClass* (__fastcall *GetAllServerClasses)(IServerGameDLL* thisptr);
// There are more functions.
} *vtable;
};
constexpr auto INTERFACEVERSION_SERVERGAMEDLL = "ServerGameDLL002";
static CreateInterface_t CreateInterface;
using CHL1GameMovement__CheckJumpButton_t = void(__fastcall *)(void* thisptr);
static CHL1GameMovement__CheckJumpButton_t CHL1GameMovement__CheckJumpButton;
using CGameMovement__FinishGravity_t = void(__fastcall *)(void* thisptr);
static CGameMovement__FinishGravity_t CGameMovement__FinishGravity;
using IServerGameDLL__ServerActivate_t = void (__fastcall *)(IServerGameDLL* thisptr, int edx, edict_t* pEdictList, int edictCount, int clientMax);
static IServerGameDLL__ServerActivate_t IServerGameDLL__ServerActivate;
static int* health_bits = nullptr;
static bool inside_CheckJumpButton = false;
static bool jumped_last_tick = false;
static void __fastcall MyCHL1GameMovement__CheckJumpButton(void* thisptr)
{
constexpr int IN_JUMP = (1 << 1);
auto pM_nOldButtons = reinterpret_cast<int*>(*reinterpret_cast<uintptr_t*>(reinterpret_cast<uintptr_t>(thisptr) + 4) + 40);
auto origM_nOldButtons = *pM_nOldButtons;
if (!jumped_last_tick) // Do not do anything if we jumped on the previous tick.
{
*pM_nOldButtons &= ~IN_JUMP; // Reset the jump button state as if it wasn't pressed.
}
jumped_last_tick = false;
inside_CheckJumpButton = true;
CHL1GameMovement__CheckJumpButton(thisptr);
inside_CheckJumpButton = false;
if (!jumped_last_tick)
{
*pM_nOldButtons = origM_nOldButtons; // Restore the old jump button state.
}
}
static void __fastcall MyCGameMovement__FinishGravity(void* thisptr)
{
if (inside_CheckJumpButton)
jumped_last_tick = true;
CGameMovement__FinishGravity(thisptr);
}
static void __fastcall MyIServerGameDLL__ServerActivate(IServerGameDLL* thisptr, int edx, edict_t* pEdictList, int edictCount, int clientMax)
{
PATCH_VTABLE(thisptr, ServerActivate, IServerGameDLL__ServerActivate);
*health_bits = 32;
IServerGameDLL__ServerActivate(thisptr, edx, pEdictList, edictCount, clientMax);
}
static const wchar_t* setup_health_bits_patch()
{
auto iServerGameDLL = reinterpret_cast<IServerGameDLL*>(CreateInterface(INTERFACEVERSION_SERVERGAMEDLL, nullptr));
if (!iServerGameDLL)
return L"Couldn't get the IServerGameDLL interface from the engine.";
bool found_table = false;
for (auto server_class = iServerGameDLL->vtable->GetAllServerClasses(iServerGameDLL); server_class; server_class = server_class->next) {
if (!std::strcmp(server_class->network_name, "CBasePlayer")) {
found_table = true;
bool found_prop = false;
auto send_table = server_class->table;
for (int i = 0; i < send_table->prop_count; ++i) {
auto& prop = send_table->props[i];
if (!std::strcmp(prop.var_name, "m_iHealth")) {
found_prop = true;
health_bits = &prop.bits;
IServerGameDLL__ServerActivate = PATCH_VTABLE(iServerGameDLL, ServerActivate, MyIServerGameDLL__ServerActivate);
break;
}
}
if (!found_prop)
return L"Couldn't find the \"m_iHealth\" send property.";
break;
}
}
if (!found_table)
return L"Couldn't find the \"CBasePlayer\" send table.";
return nullptr;
}
}
namespace engine_dll {
constexpr size_t COMMAND_COMPLETION_MAX_ITEMS = 64;
constexpr size_t COMMAND_COMPLETION_ITEM_LENGTH = 64;
using FnCommandCallback = void (*)();
using FnCommandCompletionCallback = int (*)(const char* partial, char commands[COMMAND_COMPLETION_MAX_ITEMS][COMMAND_COMPLETION_ITEM_LENGTH]);
static int default_completion_callback(const char* partial, char commands[COMMAND_COMPLETION_MAX_ITEMS][COMMAND_COMPLETION_ITEM_LENGTH])
{
UNUSED(partial);
UNUSED(commands);
return 0;
}
struct ConCommandBase {
void* vtable;
ConCommandBase* next;
bool registered;
const char* name;
const char* help_string;
int flags;
ConCommandBase(const char* name, const char* help_string = nullptr, int flags = 0)
: vtable(nullptr)
, next(nullptr)
, registered(false)
, name(name)
, help_string(help_string)
, flags(flags)
{
}
};
struct ConCommand : ConCommandBase {
FnCommandCallback callback;
FnCommandCompletionCallback completion_callback;
bool has_completion_callback;
ConCommand(const char* name, FnCommandCallback callback, const char* help_string = nullptr, int flags = 0, FnCommandCompletionCallback completion_callback = nullptr)
: ConCommandBase(name, help_string, flags)
, callback(callback)
, completion_callback(completion_callback ? completion_callback : default_completion_callback)
, has_completion_callback(!!completion_callback)
{
}
};
// Not declared.
using ConVar = ConCommandBase;
class ICVar {
public:
virtual void* RegisterConCommandBase(ConCommandBase* variable) = 0;
virtual const char* GetCommandLineValue(const char* variable_name) = 0;
virtual const ConVar* FindVar(const char* name) = 0;
virtual ConCommandBase* GetCommands() = 0;
};
constexpr auto VENGINE_CVAR_INTERFACE_VERSION = "VEngineCvar001";
static CreateInterface_t CreateInterface;
using _Host_RunFrame_Input_t = void (*)(float accumulated_extra_samples);
static _Host_RunFrame_Input_t _Host_RunFrame_Input;
using Cbuf_Execute_t = void (*)();
static Cbuf_Execute_t Cbuf_Execute;
using Cbuf_InsertText_t = void (*)(const char* text);
static Cbuf_InsertText_t Cbuf_InsertText;
static bool duckspam_is_active = false;
static bool pausefloat_is_active = false;
static enum class DuckPauseSpamState {
NONE,
DUCK_IS_PRESSED,
PAUSE_IS_ACTIVE
} duck_pause_spam_state;
static ConCommand duckspam_down("+duckspam", []() {
duckspam_is_active = true;
});
static ConCommand duckspam_up("-duckspam", []() {
duckspam_is_active = false;
});
static ConCommand pausefloat_down("+pausefloat", []() {
pausefloat_is_active = true;
});
static ConCommand pausefloat_up("-pausefloat", []() {
pausefloat_is_active = false;
});
// Returns nullptr on success; an error message on failure.
static const wchar_t* try_registering_commands()
{
auto icvar = reinterpret_cast<ICVar*>(CreateInterface(VENGINE_CVAR_INTERFACE_VERSION, nullptr));
if (!icvar)
return L"Couldn't get the ICVar interface from the engine.";
icvar->RegisterConCommandBase(&duckspam_down);
icvar->RegisterConCommandBase(&duckspam_up);
icvar->RegisterConCommandBase(&pausefloat_down);
icvar->RegisterConCommandBase(&pausefloat_up);
return nullptr;
}
static void My_Host_RunFrame_Input(float accumulated_extra_samples)
{
bool execute = true;
switch (duck_pause_spam_state) {
case DuckPauseSpamState::PAUSE_IS_ACTIVE:
if (duckspam_is_active || pausefloat_is_active) {
Cbuf_InsertText("unpause;+duck;wait;");
duck_pause_spam_state = DuckPauseSpamState::DUCK_IS_PRESSED;
} else {
Cbuf_InsertText("unpause;wait;");
duck_pause_spam_state = DuckPauseSpamState::NONE;
}
break;
case DuckPauseSpamState::DUCK_IS_PRESSED:
if (pausefloat_is_active) {
Cbuf_InsertText("setpause;-duck;wait;");
duck_pause_spam_state = DuckPauseSpamState::PAUSE_IS_ACTIVE;
} else {
Cbuf_InsertText("-duck;wait;");
duck_pause_spam_state = DuckPauseSpamState::NONE;
}
break;
case DuckPauseSpamState::NONE:
if (duckspam_is_active || pausefloat_is_active) {
Cbuf_InsertText("+duck;wait;");
duck_pause_spam_state = DuckPauseSpamState::DUCK_IS_PRESSED;
} else {
execute = false;
}
break;
}
if (execute)
Cbuf_Execute();
_Host_RunFrame_Input(accumulated_extra_samples);
}
}
static unsigned __stdcall main_thread(void* args)
{
UNUSED(args);
auto server = get_module_info(L"server.dll");
if (!server.handle) {
MessageBoxW(0, L"Could not get server.dll module info.", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto CHL1GameMovement__CheckJumpButton = find_pattern(server, pattern_CHL1GameMovement__CheckJumpButton);
if (!CHL1GameMovement__CheckJumpButton) {
MessageBoxW(0, L"Could not find CHL1GameMovement::CheckJumpButton().", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto CGameMovement__FinishGravity = find_pattern(server, pattern_CGameMovement__FinishGravity);
if (!CGameMovement__FinishGravity) {
MessageBoxW(0, L"Could not find CGameMovement::FinishGravity().", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
server_dll::CGameMovement__FinishGravity = reinterpret_cast<server_dll::CGameMovement__FinishGravity_t>(CGameMovement__FinishGravity);
server_dll::CreateInterface = reinterpret_cast<CreateInterface_t>(GetProcAddress(server.handle, "CreateInterface"));
if (!server_dll::CreateInterface) {
MessageBoxW(0, L"Could not get CreateInterface from server.dll.", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto error_message = server_dll::setup_health_bits_patch();
if (error_message) {
MessageBoxW(0, error_message, L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto engine = get_module_info(L"engine.dll");
if (!engine.handle) {
MessageBoxW(0, L"Could not get engine.dll module info.", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto _Host_RunFrame = find_pattern(engine, pattern__Host_RunFrame);
if (!_Host_RunFrame) {
MessageBoxW(0, L"Could not find _Host_RunFrame().", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
engine_dll::_Host_RunFrame_Input = *reinterpret_cast<engine_dll::_Host_RunFrame_Input_t>(relative_call_offset(_Host_RunFrame + 0x1D3));
engine_dll::Cbuf_Execute = *reinterpret_cast<engine_dll::Cbuf_Execute_t>(relative_call_offset(_Host_RunFrame + 0x14B));
// For some reason there's an exact copy of Cbuf_InsertText right after the actual function.
// Keep find first pattern or extend the pattern considerably.
engine_dll::Cbuf_InsertText = reinterpret_cast<engine_dll::Cbuf_InsertText_t>(find_pattern(engine, pattern_Cbuf_InsertText));
if (!engine_dll::Cbuf_InsertText) {
MessageBoxW(0, L"Could not find Cbuf_InsertText().", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
engine_dll::CreateInterface = reinterpret_cast<CreateInterface_t>(GetProcAddress(engine.handle, "CreateInterface"));
if (!engine_dll::CreateInterface) {
MessageBoxW(0, L"Could not get CreateInterface from engine.dll.", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto ConCommand_constructor = find_pattern(engine, pattern_ConCommand_constructor);
if (!ConCommand_constructor) {
MessageBoxW(0, L"Could not find the ConCommand constructor.", L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto ConCommand_vtable = *reinterpret_cast<void**>(ConCommand_constructor + 35);
engine_dll::duckspam_down.vtable = ConCommand_vtable;
engine_dll::duckspam_up.vtable = ConCommand_vtable;
engine_dll::pausefloat_down.vtable = ConCommand_vtable;
engine_dll::pausefloat_up.vtable = ConCommand_vtable;
error_message = engine_dll::try_registering_commands();
if (error_message) {
MessageBoxW(0, error_message, L"HL:S OOE Tools", MB_ICONERROR);
return 1;
}
auto err = MH_Initialize();
if (err != MH_OK) {
char buf[256];
std::strcpy(buf, "MH_Initialize error: ");
std::strncat(buf, MH_StatusToString(err), ARRAYSIZE(buf) - ARRAYSIZE("MH_Initialize error: "));
MessageBoxA(0, buf, "HL:S OOE Tools", MB_ICONERROR);
return 1;
}
err = MH_CreateHook(reinterpret_cast<LPVOID>(CHL1GameMovement__CheckJumpButton), reinterpret_cast<LPVOID>(server_dll::MyCHL1GameMovement__CheckJumpButton), reinterpret_cast<LPVOID*>(&server_dll::CHL1GameMovement__CheckJumpButton));
if (err != MH_OK) {
char buf[256];
std::strcpy(buf, "MH_CreateHook error: ");
std::strncat(buf, MH_StatusToString(err), ARRAYSIZE(buf) - ARRAYSIZE("MH_CreateHook error: "));
MessageBoxA(0, buf, "HL:S OOE Tools", MB_ICONERROR);
return 1;
}
err = MH_CreateHook(reinterpret_cast<LPVOID>(CGameMovement__FinishGravity), reinterpret_cast<LPVOID>(server_dll::MyCGameMovement__FinishGravity), reinterpret_cast<LPVOID*>(&server_dll::CGameMovement__FinishGravity));
if (err != MH_OK) {
char buf[256];
std::strcpy(buf, "MH_CreateHook error: ");
std::strncat(buf, MH_StatusToString(err), ARRAYSIZE(buf) - ARRAYSIZE("MH_CreateHook error: "));
MessageBoxA(0, buf, "HL:S OOE Tools", MB_ICONERROR);
return 1;
}
err = MH_CreateHook(reinterpret_cast<LPVOID>(engine_dll::_Host_RunFrame_Input), reinterpret_cast<LPVOID>(engine_dll::My_Host_RunFrame_Input), reinterpret_cast<LPVOID*>(&engine_dll::_Host_RunFrame_Input));
if (err != MH_OK) {
char buf[256];
std::strcpy(buf, "MH_CreateHook error: ");
std::strncat(buf, MH_StatusToString(err), ARRAYSIZE(buf) - ARRAYSIZE("MH_CreateHook error: "));
MessageBoxA(0, buf, "HL:S OOE Tools", MB_ICONERROR);
return 1;
}
err = MH_EnableHook(MH_ALL_HOOKS);
if (err != MH_OK) {
char buf[256];
std::strcpy(buf, "MH_CreateHook error: ");
std::strncat(buf, MH_StatusToString(err), ARRAYSIZE(buf) - ARRAYSIZE("MH_CreateHook error: "));
MessageBoxA(0, buf, "HL:S OOE Tools", MB_ICONERROR);
return 1;
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
UNUSED(reserved);
switch (reason) {
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(module);
_beginthreadex(nullptr, 0, main_thread, nullptr, 0, nullptr);
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment