Last active
September 4, 2016 08:39
-
-
Save YaLTeR/b6cb8f800cbd010a62231abd333f2f98 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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