#include #include #include "game/game.hpp" #include #include namespace { std::vector custom_binds; utils::hook::detour key_get_binding_for_cmd_hook; utils::hook::detour cl_execute_key_hook; game::native::KeyState* keys; int key_binding_offset; int key_write_bindings_to_buffer([[maybe_unused]] int local_client_num, char* buffer, int buffer_size) { buffer_size = buffer_size - 4; auto bytes_used = 0; for (auto keyIndex = 0; keyIndex < 256; ++keyIndex) { if (keys[keyIndex].binding && keys[keyIndex].binding < key_binding_offset) { auto len = sprintf_s(&buffer[bytes_used], buffer_size - bytes_used, "bind %s \"%s\"\n", game::native::Key_KeynumToString(keyIndex, false), game::native::command_whitelist[keys[keyIndex].binding]); if (len < 0) { return bytes_used; } bytes_used += len; } else if (keys[keyIndex].binding >= key_binding_offset) { auto value = keys[keyIndex].binding - key_binding_offset; if (static_cast(value) < custom_binds.size() && !custom_binds[value].empty()) { auto len = sprintf_s(&buffer[bytes_used], buffer_size - bytes_used, "bind %s \"%s\"\n", game::native::Key_KeynumToString(keyIndex, false), custom_binds[value].data()); if (len < 0) { return bytes_used; } bytes_used += len; } } } buffer[bytes_used] = '\0'; return bytes_used; } __declspec(naked) void key_write_bindings_to_buffer_stub() { __asm { pushad push [esp + 0x20 + 0x8] // bufferSize push [esp + 0x20 + 0x8] // buffer push eax // localClientNum call key_write_bindings_to_buffer add esp, 0xC popad ret } } int get_binding_for_custom_command(const char* command) { auto index = 0; for (const auto& bind : custom_binds) { if (bind == command) { return index; } ++index; } custom_binds.emplace_back(command); index = static_cast(custom_binds.size()) - 1; return index; } int key_get_binding_for_cmd_stub(const char* command) { for (auto i = 0; i <= key_binding_offset; i++) { if (game::native::command_whitelist[i] && !std::strcmp(command, game::native::command_whitelist[i])) { return i; } } return key_binding_offset + get_binding_for_custom_command(command); } void cl_execute_key_stub(game::native::LocalClientNum_t local_client_num, int key, int down) { if (key >= key_binding_offset) { key -= key_binding_offset; if (static_cast(key) < custom_binds.size() && !custom_binds[key].empty()) { game::native::Cbuf_AddText(local_client_num, utils::string::va("%s\n", custom_binds[key].data())); } return; } cl_execute_key_hook.invoke(local_client_num, key, down); } } class binding final : public module { public: void post_load() override { keys = reinterpret_cast(SELECT_VALUE(0xA98E44, 0xB3C760)); key_binding_offset = SELECT_VALUE(0x51, 0x5B); utils::hook(SELECT_VALUE(0x54BB9A, 0x48C53A), key_write_bindings_to_buffer_stub, HOOK_CALL).install()->quick(); key_get_binding_for_cmd_hook.create(SELECT_VALUE(0x5330A0, 0x48C1C0), &key_get_binding_for_cmd_stub); cl_execute_key_hook.create(SELECT_VALUE(0x438710, 0x48AF00), cl_execute_key_stub); } }; REGISTER_MODULE(binding)