diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3f00e1..d4ecb3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: build: name: Build binaries - runs-on: windows-latest + runs-on: windows-2022 strategy: matrix: configuration: @@ -35,11 +35,10 @@ jobs: lfs: false - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v1.1 - name: Generate project files - #run: tools/premake5 vs2019 --ci-build - run: tools/premake5 vs2019 + run: tools/premake5 vs2022 - name: Set up problem matching uses: ammaraskar/msvc-problem-matcher@master diff --git a/README.md b/README.md index 7e53960..f92fafc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Build](https://github.com/momo5502/open-iw5/workflows/Build/badge.svg)](https://github.com/momo5502/open-iw5/actions) ![issues](https://img.shields.io/github/issues/momo5502/open-iw5.svg) ![license](https://img.shields.io/github/license/momo5502/open-iw5.svg) ![forks](https://img.shields.io/github/forks/momo5502/open-iw5.svg) diff --git a/generate.bat b/generate.bat index 33a2a8a..9fe129e 100644 --- a/generate.bat +++ b/generate.bat @@ -1,3 +1,3 @@ @echo off git submodule update --init --recursive -tools\premake5 %* vs2019 \ No newline at end of file +tools\premake5 %* vs2022 diff --git a/premake5.lua b/premake5.lua index 92c86da..36bdf27 100644 --- a/premake5.lua +++ b/premake5.lua @@ -42,15 +42,14 @@ workspace "open-iw5" objdir "%{wks.location}/obj" targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" - configurations { - "Debug", - "Release", - } + configurations { "Debug", "Release" } - architecture "x32" - platforms "x32" + language "C++" + cppdialect "C++20" + + architecture "x86" + platforms "Win32" - buildoptions "/std:c++latest" systemversion "latest" symbols "On" staticruntime "On" @@ -65,33 +64,22 @@ workspace "open-iw5" "No64BitChecks" } - configuration "windows" - defines { - "_WINDOWS", - "WIN32", - } + filter "platforms:Win*" + defines { "_WINDOWS", "WIN32" } + filter {} - configuration "Release" - optimize "Full" - buildoptions "/Os" + filter "configurations:Release" + optimize "Size" + buildoptions { "/GL" } + linkoptions { "/IGNORE:4702", "/LTCG" } + defines { "NDEBUG" } + flags { "FatalCompileWarnings" } + filter {} - defines { - "NDEBUG", - } - - flags { - "FatalCompileWarnings", - } - - configuration "Debug" + filter "configurations:Debug" optimize "Debug" - - defines { - "DEBUG", - "_DEBUG", - } - - configuration {} + defines { "DEBUG", "_DEBUG" } + filter {} project "open-iw5" kind "ConsoleApp" @@ -103,7 +91,6 @@ workspace "open-iw5" linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE" linkoptions "/LAST:.main" - files { "./src/**.rc", "./src/**.hpp", diff --git a/src/game/game.cpp b/src/game/game.cpp index 93e1861..ec4b347 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -23,15 +23,32 @@ namespace game SL_GetStringOfSize_t SL_GetStringOfSize; + Scr_AddEntityNum_t Scr_AddEntityNum; + + Scr_Notify_t Scr_Notify; + Sys_ShowConsole_t Sys_ShowConsole; VM_Notify_t VM_Notify; + BG_NetDataChecksum_t BG_NetDataChecksum; + + LiveStorage_GetPersistentDataDefVersion_t LiveStorage_GetPersistentDataDefVersion; + + LiveStorage_GetPersistentDataDefFormatChecksum_t LiveStorage_GetPersistentDataDefFormatChecksum; + + SV_DirectConnect_t SV_DirectConnect; + + SV_ClientEnterWorld_t SV_ClientEnterWorld; + + SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString; + + SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; + decltype(longjmp)* _longjmp; - int* cmd_args; - int* cmd_argc; - const char*** cmd_argv; + CmdArgs* sv_cmd_args; + CmdArgs* cmd_args; short* scrVarGlob; char** scrMemTreePub; @@ -49,6 +66,18 @@ namespace game scr_classStruct_t* g_classMap; + int* svs_clientCount; + + namespace mp + { + client_t* svs_clients; + } + + namespace dedi + { + client_t* svs_clients; + } + void AddRefToValue(VariableValue* value) { if (value->type == SCRIPT_OBJECT) @@ -280,6 +309,31 @@ namespace game } } + void scr_add_string_dedi(const char* value) + { + static DWORD func = 0x4F1010; + + __asm + { + mov edi, value + call func + retn + } + } + + void Scr_AddString(const char* value) + { + if (is_dedi()) + { + scr_add_string_dedi(value); + } + else if (is_mp()) + { + reinterpret_cast + (0x56AC00)(value); + } + } + const char* SL_ConvertToString(const unsigned int stringValue) { if (!stringValue) return nullptr; @@ -292,6 +346,71 @@ namespace game { return SL_GetStringOfSize(str, user, strlen(str) + 1, 7); } + + __declspec(naked) void sv_send_client_game_state_mp(mp::client_t* client) + { + static DWORD func = 0x570FC0; + + __asm + { + mov esi, client + call func + retn + } + } + + void SV_SendClientGameState(mp::client_t* client) + { + if (is_mp()) + { + sv_send_client_game_state_mp(client); + } + } + + int SV_IsTestClient(int clientNum) + { + assert(clientNum < *svs_clientCount); + + if (is_dedi()) + { + return dedi::svs_clients[clientNum].bIsTestClient; + } + else if (is_mp()) + { + return mp::svs_clients[clientNum].bIsTestClient; + } + + return 0; + } + + void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem) + { + if (is_mp()) + { + reinterpret_cast + (0x570980)(drop, reason, tellThem); + } + } + + void sv_drop_all_bots_mp() + { + for (auto i = 0; i < *svs_clientCount; i++) + { + if (mp::svs_clients[i].header.state != CS_FREE + && mp::svs_clients[i].header.netchan.remoteAddress.type == NA_BOT) + { + SV_DropClient(&mp::svs_clients[i], "EXE_TIMEDOUT", 1); + } + } + } + + void SV_DropAllBots() + { + if (is_mp()) + { + sv_drop_all_bots_mp(); + } + } } launcher::mode mode = launcher::mode::none; @@ -344,15 +463,34 @@ namespace game native::SL_GetStringOfSize = native::SL_GetStringOfSize_t(SELECT_VALUE(0x4E13F0, 0x564650, 0x4E7490)); + native::Scr_AddEntityNum = native::Scr_AddEntityNum_t(SELECT_VALUE(0x0, 0x56ABC0, 0x4EA2F0)); + + native::Scr_Notify = native::Scr_Notify_t(SELECT_VALUE(0x0, 0x52B190, 0x0)); + native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0)); native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720, 0x4EF450)); + native::BG_NetDataChecksum = native::BG_NetDataChecksum_t(SELECT_VALUE(0x0, 0x41BB20, 0x0)); + + native::LiveStorage_GetPersistentDataDefVersion = native::LiveStorage_GetPersistentDataDefVersion_t( + SELECT_VALUE(0x0, 0x548D60, 0x4D0390)); + + native::LiveStorage_GetPersistentDataDefFormatChecksum = native::LiveStorage_GetPersistentDataDefFormatChecksum_t( + SELECT_VALUE(0x0, 0x548D80, 0x4D03D0)); + + native::SV_DirectConnect = native::SV_DirectConnect_t(SELECT_VALUE(0x0, 0x572750, 0x4F74C0)); + + native::SV_ClientEnterWorld = native::SV_ClientEnterWorld_t(SELECT_VALUE(0x0, 0x571100, 0x0)); + + native::SV_Cmd_TokenizeString = native::SV_Cmd_TokenizeString_t(SELECT_VALUE(0x0, 0x545D40, 0x0)); + + native::SV_Cmd_EndTokenizedString = native::SV_Cmd_EndTokenizedString_t(SELECT_VALUE(0x0, 0x545D70, 0x0)); + native::_longjmp = reinterpret_cast(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); - native::cmd_args = reinterpret_cast(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8)); - native::cmd_argc = reinterpret_cast(SELECT_VALUE(0x1750794, 0x1C97914, 0x1B4563C)); - native::cmd_argv = reinterpret_cast(SELECT_VALUE(0x17507B4, 0x1C97934, 0x1B4565C)); + native::sv_cmd_args = reinterpret_cast(SELECT_VALUE(0x0, 0x1CAA998, 0x1B5E7D8)); + native::cmd_args = reinterpret_cast(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8)); native::scrVarGlob = reinterpret_cast(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800)); native::scrMemTreePub = reinterpret_cast(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4)); @@ -371,6 +509,11 @@ namespace game native::g_classMap = reinterpret_cast(SELECT_VALUE(0x92D140, 0x8B4300, 0x7C0408)); + native::svs_clientCount = reinterpret_cast(SELECT_VALUE(0x0, 0x4B5CF8C, 0x4A12E8C)); + native::levelEntityId = reinterpret_cast(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C)); + + native::mp::svs_clients = reinterpret_cast(0x4B5CF90); + native::dedi::svs_clients = reinterpret_cast(0x4A12E90); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 720ca57..b0af38b 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -38,17 +38,43 @@ namespace game typedef unsigned int (*SL_GetStringOfSize_t)(const char* str, unsigned int user, unsigned int len, int type); extern SL_GetStringOfSize_t SL_GetStringOfSize; + typedef void (*Scr_AddEntityNum_t)(int entnum, unsigned int classnum); + extern Scr_AddEntityNum_t Scr_AddEntityNum; + + typedef void (*Scr_Notify_t)(gentity_s* ent, unsigned __int16 stringValue, unsigned int paramcount); + extern Scr_Notify_t Scr_Notify; + typedef void (*Sys_ShowConsole_t)(); extern Sys_ShowConsole_t Sys_ShowConsole; typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); extern VM_Notify_t VM_Notify; + typedef unsigned int (*BG_NetDataChecksum_t)(); + extern BG_NetDataChecksum_t BG_NetDataChecksum; + + typedef int (*LiveStorage_GetPersistentDataDefVersion_t)(); + extern LiveStorage_GetPersistentDataDefVersion_t LiveStorage_GetPersistentDataDefVersion; + + typedef unsigned int (*LiveStorage_GetPersistentDataDefFormatChecksum_t)(); + extern LiveStorage_GetPersistentDataDefFormatChecksum_t LiveStorage_GetPersistentDataDefFormatChecksum; + + typedef void (*SV_DirectConnect_t)(netadr_s from); + extern SV_DirectConnect_t SV_DirectConnect; + + typedef void (*SV_ClientEnterWorld_t)(mp::client_t* client, usercmd_s* cmd); + extern SV_ClientEnterWorld_t SV_ClientEnterWorld; + + typedef void (*SV_Cmd_TokenizeString_t)(const char* text_in); + extern SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString; + + typedef void (*SV_Cmd_EndTokenizedString_t)(); + extern SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; + extern decltype(longjmp)* _longjmp; - extern int* cmd_args; - extern int* cmd_argc; - extern const char*** cmd_argv; + extern CmdArgs* sv_cmd_args; + extern CmdArgs* cmd_args; extern short* scrVarGlob; extern char** scrMemTreePub; @@ -66,6 +92,18 @@ namespace game extern scr_classStruct_t* g_classMap; + extern int* svs_clientCount; + + namespace mp + { + extern client_t* svs_clients; + } + + namespace dedi + { + extern client_t* svs_clients; + } + void AddRefToValue(VariableValue* value); void Conbuf_AppendText(const char* message); @@ -82,9 +120,15 @@ namespace game scr_call_t Scr_GetFunc(unsigned int index); void Scr_NotifyId(unsigned int id, unsigned int stringValue, unsigned int paramcount); int Scr_SetObjectField(unsigned int classnum, int entnum, int offset); + void Scr_AddString(const char* value); const char* SL_ConvertToString(unsigned int stringValue); unsigned int SL_GetString(const char* str, unsigned int user); + + void SV_SendClientGameState(mp::client_t* client); + int SV_IsTestClient(int clientNum); + void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem); + void SV_DropAllBots(); } bool is_mp(); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index b31b48c..5245e34 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -379,6 +379,15 @@ namespace game int flags; }; + struct CmdArgs + { + int nesting; + int localClientNum[8]; + int controllerIndex[8]; + int argc[8]; + const char** argv[8]; + }; + struct msg_t { int overflowed; @@ -546,5 +555,158 @@ namespace game bool (__cdecl *domainFunc)(dvar_t*, DvarValue); dvar_t* hashNext; }; + + struct usercmd_s + { + int serverTime; + int buttons; + int angles[3]; + unsigned int weapon; + unsigned int offHand; + char forwardmove; + char rightmove; + unsigned __int16 airburstMarkDistance; + unsigned __int16 meleeChargeEnt; + unsigned char meleeChargeDist; + char selectedLoc[2]; + unsigned char selectedLocAngle; + char remoteControlAngles[2]; + char remoteControlMove[3]; + }; + + static_assert(sizeof(usercmd_s) == 0x2C); + + struct entityState_s + { + int number; + }; + + struct gentity_s + { + entityState_s s; + }; + + enum clientState_t + { + CS_FREE = 0, + CS_ZOMBIE = 1, + CS_RECONNECTING = 2, + CS_CONNECTED = 3, + CS_CLIENTLOADING = 4, + CS_ACTIVE = 5, + }; + + enum netsrc_t + { + NS_CLIENT1 = 0x0, + NS_CLIENT2 = 0x1, + NS_CLIENT3 = 0x2, + NS_CLIENT4 = 0x3, + NS_MAXCLIENTS = 0x4, + NS_SERVER = 0x4, + NS_PACKET = 0x5, + NS_INVALID_NETSRC = 0x6, + }; + + enum netadrtype_t + { + NA_BOT = 0x0, + NA_BAD = 0x1, + NA_LOOPBACK = 0x2, + NA_BROADCAST = 0x3, + NA_IP = 0x4, + }; + + struct netadr_s + { + netadrtype_t type; + unsigned char ip[4]; + unsigned __int16 port; + unsigned char ipx[10]; + unsigned int addrHandleIndex; + }; + + static_assert(sizeof(netadr_s) == 24); + + struct netProfileInfo_t // Unused + { + unsigned char __pad0[0x5E0]; + }; + + struct netchan_t + { + int outgoingSequence; + netsrc_t sock; + int dropped; + int incomingSequence; + netadr_s remoteAddress; + int qport; + int fragmentSequence; + int fragmentLength; + unsigned char* fragmentBuffer; + int fragmentBufferSize; + int unsentFragments; + int unsentFragmentStart; + int unsentLength; + unsigned char* unsentBuffer; + int unsentBufferSize; + netProfileInfo_t prof; + }; + + static_assert(sizeof(netchan_t) == 0x630); + + struct clientHeader_t + { + clientState_t state; + int sendAsActive; + int deltaMessage; + int rateDealyed; + int hasAckedBaselineData; + int hugeSnapshotSent; + netchan_t netchan; + float predictedOrigin[3]; + int predictedOriginServerTime; + int migrationState; + float predictedVehicleOrigin[3]; + int predictedVehicleServerTime; + }; + + static_assert(sizeof(clientHeader_t) == 0x66C); + + namespace mp + { + struct client_t + { + clientHeader_t header; + const char* dropReason; // 0x66C + char userinfo[1024]; // 0x670 + unsigned char __pad0[0x209B8]; + gentity_s* gentity; // 0x21428 + unsigned char __pad1[0x20886]; + unsigned __int16 scriptId; // 0x41CB2 + int bIsTestClient; // 0x41CB4 + int serverId; // 0x41CB8 + unsigned char __pad2[0x369DC]; + }; + + static_assert(sizeof(mp::client_t) == 0x78698); + } + + namespace dedi + { + struct client_t + { + clientHeader_t header; + const char* dropReason; + char userinfo[1024]; + unsigned char __pad0[0x4123E]; + unsigned __int16 scriptId; + int bIsTestClient; // 0x41CB0 + int serverId; + unsigned char __pad1[0x369D8]; + }; + + static_assert(sizeof(dedi::client_t) == 0x78690); + } } } diff --git a/src/main.cpp b/src/main.cpp index b40e425..92447a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -95,6 +95,8 @@ int main() FARPROC entry_point; enable_dpi_awareness(); + std::srand(uint32_t(time(nullptr))); + { auto premature_shutdown = true; const auto _ = gsl::finally([&premature_shutdown]() diff --git a/src/module/command.cpp b/src/module/command.cpp index 8101b84..d39c30f 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -31,21 +31,21 @@ void command::pre_destroy() void command::dispatcher() { - const auto cmd_index = *game::native::cmd_args; - const auto arg_count = game::native::cmd_argc[cmd_index]; + const auto cmd_index = game::native::cmd_args->nesting; + const auto arg_count = game::native::cmd_args->argc[cmd_index]; if (arg_count < 1) return; - const auto command = utils::string::to_lower(game::native::cmd_argv[cmd_index][0]); + const auto command = utils::string::to_lower(game::native::cmd_args->argv[cmd_index][0]); const auto handler = callbacks_.find(command); if (handler == callbacks_.end()) return; std::vector arguments; arguments.reserve(arg_count); - for (auto i = 0; i < game::native::cmd_argc[cmd_index]; ++i) + for (auto i = 0; i < game::native::cmd_args->argc[cmd_index]; ++i) { - arguments.emplace_back(game::native::cmd_argv[cmd_index][i]); + arguments.emplace_back(game::native::cmd_args->argv[cmd_index][i]); } handler->second(arguments); diff --git a/src/module/test_clients.cpp b/src/module/test_clients.cpp new file mode 100644 index 0000000..14d9c01 --- /dev/null +++ b/src/module/test_clients.cpp @@ -0,0 +1,184 @@ +#include + +#include "test_clients.hpp" +#include "command.hpp" +#include "scheduler.hpp" + +#include "utils/hook.hpp" + +bool test_clients::can_add() +{ + auto i = 0; + while (i < *game::native::svs_clientCount) + { + if (game::native::mp::svs_clients[i].header.state == game::native::clientState_t::CS_FREE) + { + // Free slot was found + break; + } + + ++i; + } + + return i < *game::native::svs_clientCount; +} + +game::native::gentity_s* test_clients::sv_add_test_client() +{ + if (!test_clients::can_add()) + { + return nullptr; + } + + static auto bot_port = 0; + char user_info[1024] = {0}; + + // Most basic string the game will accept. Xuid and Xnaddr can be empty + _snprintf_s(user_info, _TRUNCATE, + "connect bot%d \"\\invited\\0\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\bot%d\\xuid\\%s\\xnaddr\\%s\\natType\\2\\protocol\\%d\\checksum\\%u\\statver\\%d %u\"", + bot_port, bot_port, "", "", 0x507C, game::native::BG_NetDataChecksum(), + game::native::LiveStorage_GetPersistentDataDefVersion(), game::native::LiveStorage_GetPersistentDataDefFormatChecksum()); + + game::native::netadr_s adr; + std::memset(&adr, 0, sizeof(game::native::netadr_s)); + adr.type = game::native::netadrtype_t::NA_BOT; + adr.port = static_cast(bot_port); + ++bot_port; + + game::native::SV_Cmd_TokenizeString(user_info); + game::native::SV_DirectConnect(adr); + game::native::SV_Cmd_EndTokenizedString(); + + // Find the bot + auto idx = 0; + while (idx < *game::native::svs_clientCount) + { + if (game::native::mp::svs_clients[idx].header.state == game::native::clientState_t::CS_FREE) + continue; + + if (game::native::mp::svs_clients[idx].header.netchan.remoteAddress.type == adr.type + && game::native::mp::svs_clients[idx].header.netchan.remoteAddress.port == adr.port) + break; // Found them + } + + if (idx == *game::native::svs_clientCount) + { + // Failed to add test client + return nullptr; + } + + auto client = &game::native::mp::svs_clients[idx]; + client->bIsTestClient = 1; + + game::native::SV_SendClientGameState(client); + + game::native::usercmd_s cmd; + std::memset(&cmd, 0, sizeof(game::native::usercmd_s)); + + game::native::SV_ClientEnterWorld(client, &cmd); + + assert(client->gentity != nullptr); + + return client->gentity; +} + +void test_clients::spawn() +{ + auto* ent = test_clients::sv_add_test_client(); + + if (ent == nullptr) + return; + + game::native::Scr_AddEntityNum(ent->s.number, 0); + + scheduler::once([ent]() + { + game::native::Scr_AddString("autoassign"); + game::native::Scr_AddString("team_marinesopfor"); + game::native::Scr_Notify(ent, static_cast(game::native::SL_GetString("menuresponse", 0)), 2); + + scheduler::once([ent]() + { + const auto string = std::format("class{}", std::to_string(std::rand() % 5)); + game::native::Scr_AddString(string.data()); + game::native::Scr_AddString("changeclass"); + game::native::Scr_Notify(ent, static_cast(game::native::SL_GetString("menuresponse", 0)), 2); + }); + }); +} + +void test_clients::scr_shutdown_system_stub(unsigned char sys) +{ + game::native::SV_DropAllBots(); + reinterpret_cast(0x569E30)(sys); +} + +__declspec(naked) void test_clients::reset_reliable_mp() +{ + // client_t ptr is in esi + __asm + { + cmp [esi + 0x41CB4], 1 // bIsTestClient + jnz resume + + push eax + mov eax, [esi + 0x20E70] // Move reliableSequence to eax + mov [esi + 0x20E74], eax // Move eax to reliableAcknowledge + + resume: + push 0x5CE740 // SV_Netchan_Transmit + retn + } +} + +bool test_clients::check_timeouts(const game::native::mp::client_t* cl) +{ + return (!cl->bIsTestClient || cl->header.state == game::native::clientState_t::CS_ZOMBIE) + && cl->header.netchan.remoteAddress.type != game::native::netadrtype_t::NA_LOOPBACK; +} + +__declspec(naked) void test_clients::check_timeouts_stub_mp() +{ + __asm + { + push eax + pushad + + lea esi, [ebx - 0x2146C] + push esi + call test_clients::check_timeouts + add esp, 4 + + mov [esp + 0x20], eax + popad + pop eax + + test al, al + + push 0x576DD3 + retn + } +} + +void test_clients::post_load() +{ + if (game::is_mp()) this->patch_mp(); + else return; // No sp/dedi bots for now :( + + command::add("spawnBot", [](const std::vector& params) + { + // Because I am unable to expand the scheduler at the moment + // we only get one bot at the time + test_clients::spawn(); + }); +} + +void test_clients::patch_mp() +{ + utils::hook::nop(0x639803, 5); // LiveSteamServer_RunFrame + utils::hook(0x50C147, &test_clients::scr_shutdown_system_stub, HOOK_CALL).install()->quick(); // G_ShutdownGame + utils::hook(0x57BBF9, &test_clients::reset_reliable_mp, HOOK_CALL).install()->quick(); // SV_SendMessageToClient + utils::hook(0x576DCC, &test_clients::check_timeouts_stub_mp, HOOK_JUMP).install()->quick(); // SV_CheckTimeouts +} + +REGISTER_MODULE(test_clients); diff --git a/src/module/test_clients.hpp b/src/module/test_clients.hpp new file mode 100644 index 0000000..50bde01 --- /dev/null +++ b/src/module/test_clients.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "loader/module_loader.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + +class test_clients final : public module +{ +public: + void post_load() override; + +private: + static void patch_mp(); + + static bool can_add(); + static game::native::gentity_s* sv_add_test_client(); + static void spawn(); + + static void scr_shutdown_system_stub(unsigned char sys); + + static void reset_reliable_mp(); + + static bool check_timeouts(const game::native::mp::client_t* cl); + static void check_timeouts_stub_mp(); +}; diff --git a/src/std_include.hpp b/src/std_include.hpp index 675d2e0..3de9928 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -66,7 +66,9 @@ #pragma comment(lib, "ws2_32.lib") #pragma warning(pop) + #pragma warning(disable: 4100) +#pragma warning(disable: 26812) #include "resource.hpp" diff --git a/tools/premake5.exe b/tools/premake5.exe index 9048d51..c73da1f 100644 Binary files a/tools/premake5.exe and b/tools/premake5.exe differ