From 55f12e3313be46aecb6119176388d2483600d108 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 2 Jan 2023 13:57:00 +0100 Subject: [PATCH] Basic working dedicated server support --- src/client/component/auth.cpp | 71 ++++++++++++++++++++++++------ src/client/component/dedicated.cpp | 5 +++ src/client/component/demonware.cpp | 13 +++--- src/client/component/getinfo.cpp | 60 +++++++++++++++++++++++++ src/client/component/network.cpp | 29 ++++++------ src/client/component/party.cpp | 39 ---------------- src/client/component/scheduler.cpp | 13 +++--- src/client/game/game.cpp | 6 +-- src/client/game/game.hpp | 28 ++++++++++-- src/client/game/structs.hpp | 2 +- src/client/game/symbols.cpp | 2 +- src/client/game/symbols.hpp | 13 +++--- src/client/main.cpp | 21 +++++++-- 13 files changed, 206 insertions(+), 96 deletions(-) create mode 100644 src/client/component/getinfo.cpp diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index 19beb07c..a4eb6cfd 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -79,24 +79,24 @@ namespace auth static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); return key; } - } - bool is_second_instance() - { - static const auto is_first = [] + bool is_second_instance() { - static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex"); - return mutex && GetLastError() != ERROR_ALREADY_EXISTS; - }(); + static const auto is_first = [] + { + static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex"); + return mutex && GetLastError() != ERROR_ALREADY_EXISTS; + }(); - return !is_first; + return !is_first; + } } uint64_t get_guid() { static const auto guid = []() -> uint64_t { - if (is_second_instance()) + if (game::is_server() || is_second_instance()) { return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000); } @@ -112,10 +112,55 @@ namespace auth void post_unpack() override { // Patch steam id bit check - utils::hook::jump(0x141E19D7D_g, 0x141E19DCB_g); - utils::hook::jump(0x141EB2D06_g, 0x141EB2D46_g); - utils::hook::jump(0x141EB2E3D_g, 0x141EB2E82_g); - utils::hook::jump(0x141EB3CC5_g, 0x141EB3D06_g); + std::vector> patches{}; + const auto p = [&patches](const size_t a, const size_t b) + { + patches.emplace_back(a, b); + }; + + if (game::is_server()) + { + p(0x1404747C6_g, 0x140474806_g); + p(0x140474A24_g, 0x140474A68_g); + p(0x140474A85_g, 0x140474AC6_g); + p(0x140457ED0_g, 0x140457F26_g); + p(0x140473DD8_g, 0x140473E19_g); + p(0x1404743D5_g, 0x140474423_g); + p(0x1404744FD_g, 0x140474553_g); + p(0x14047462D_g, 0x140474677_g); + p(0x140475057_g, 0x14047509F_g); // ? + p(0x140475672_g, 0x1404756B5_g); + p(0x140477322_g, 0x140477365_g); // ? + } + else + { + p(0x141E19D7D_g, 0x141E19DCB_g); + p(0x141EB2D06_g, 0x141EB2D46_g); + p(0x141EB2E3D_g, 0x141EB2E82_g); + p(0x141EB3CC5_g, 0x141EB3D06_g); + p(0x141E19B60_g, 0x141E19BB6_g); + // + p(0x141EB0F78_g, 0x141EB0FB9_g); + p(0x141EB1038_g, 0x141EB1079_g); + p(0x141EB25B5_g, 0x141EB2603_g); + p(0x141EB26DD_g, 0x141EB2733_g); + p(0x141EB280D_g, 0x141EB2857_g); + + p(0x141EB2B7A_g, 0x141EB2B8A_g); + p(0x141EB2B91_g, 0x141EB2BC3_g); + + p(0x141EB31C7_g, 0x141EB31D7_g); + p(0x141EB31DE_g, 0x141EB320F_g); + + p(0x141EB5407_g, 0x141EB544F_g); // ? + p(0x141EB5A22_g, 0x141EB5A65_g); + p(0x141EB7562_g, 0x141EB75A5_g); // ? + } + + for (const auto& patch : patches) + { + utils::hook::jump(patch.first, patch.second); + } } }; } diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 2650c416..8925c063 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -1,6 +1,8 @@ #include #include "loader/component_loader.hpp" +#include "game/game.hpp" + #include namespace dedicated @@ -13,6 +15,9 @@ namespace dedicated { void post_unpack() override { + // Ignore "bad stats" + utils::hook::set(0x14052D523_g, 0xEB); + utils::hook::nop(0x14052D4E4_g, 2); } }; } diff --git a/src/client/component/demonware.cpp b/src/client/component/demonware.cpp index f50ccc15..cecc28b8 100644 --- a/src/client/component/demonware.cpp +++ b/src/client/component/demonware.cpp @@ -476,17 +476,20 @@ namespace demonware { server_thread = utils::thread::create_named_thread("Demonware", server_main); + + utils::hook::set(game::select(0x14293E829, 0x1407D5879), 0x0); // CURLOPT_SSL_VERIFYPEER + utils::hook::set(game::select(0x15F3CCFED, 0x1407D5865), 0xAF); // CURLOPT_SSL_VERIFYHOST + + utils::hook::copy_string(game::select(0x1430B96E0, 0x140EE4C68), "http://prod.umbrella.demonware.net"); + if (game::is_server()) { return; } - utils::hook::set(0x14293E829_g, 0x0); // CURLOPT_SSL_VERIFYPEER - utils::hook::set(0x15F3CCFED_g, 0xAF); // CURLOPT_SSL_VERIFYHOST - utils::hook::set(0x1430B9810_g, 0x0); // HTTPS -> HTTP - - utils::hook::copy_string(0x1430B96E0_g, "http://prod.umbrella.demonware.net"); utils::hook::copy_string(0x1430B9BE0_g, "http://prod.uno.demonware.net/v1.0"); + + utils::hook::set(0x1430B9810_g, 0x0); // HTTPS -> HTTP utils::hook::copy_string(0x1430B93C8_g, "http://%s:%d/auth/"); utils::hook::set(0x141EC4B50_g, 0xC3D08948); // Skip publisher file signature stuff diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp new file mode 100644 index 00000000..6ad3bf68 --- /dev/null +++ b/src/client/component/getinfo.cpp @@ -0,0 +1,60 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "steam/steam.hpp" + +#include "network.hpp" + +#include +#include + +#include + +namespace getinfo +{ + namespace + { + std::string get_dvar_string(const char* dvar_name) + { + const auto dvar = game::Dvar_FindVar(dvar_name); + if (!dvar) + { + return {}; + } + + return game::Dvar_GetString(dvar); + } + } + + struct component final : generic_component + { + void post_unpack() override + { + network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) + { + utils::info_string info{}; + info.set("challenge", std::string(data.begin(), data.end())); + info.set("gamename", "T7"); + info.set("hostname", get_dvar_string("sv_hostname")); + info.set("gametype", get_dvar_string("g_gametype")); + //info.set("sv_motd", get_dvar_string("sv_motd")); + info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); + info.set("mapname", get_dvar_string("mapname")); + //info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1"); + //info.set("clients", utils::string::va("%i", get_client_count())); + //info.set("bots", utils::string::va("%i", get_bot_count())); + //info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients)); + info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/)); + info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); + //info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); + info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0)); + info.set("shortversion", SHORTVERSION); + + network::send(target, "infoResponse", info.build(), '\n'); + }); + } + }; +} + +REGISTER_COMPONENT(getinfo::component) diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index d89b6954..59a73cb0 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -39,11 +39,13 @@ namespace network void handle_command_stub(utils::hook::assembler& a) { + const auto sv = game::is_server(); + a.pushad64(); - a.mov(r8, r12); // msg - a.mov(rdx, rcx); // command - a.mov(rcx, r15); // address + a.mov(rdx, rcx); // command + a.mov(r8, sv ? r15 : r12); // msg + a.mov(rcx, sv ? r14 : r15); // address a.call_aligned(handle_command); @@ -51,7 +53,7 @@ namespace network a.popad64(); - a.jmp(0x14134D14B_g); + a.ret(); } bool socket_set_blocking(const SOCKET s, const bool blocking) @@ -210,24 +212,21 @@ namespace network return 2; } - struct component final : client_component + struct component final : generic_component { void post_unpack() override { - //utils::hook::jump(0x14143CAB0_g, ret2); // patch dwGetConnectionStatus - //utils::hook::jump(0x14233307E_g, 0x1423330C7_g); + utils::hook::nop(game::select(0x142332E76, 0x140596DF6), 4); // don't increment data pointer to optionally skip socket byte + utils::hook::call(game::select(0x142332E43, 0x140596DC3), read_socket_byte_stub); // optionally read socket byte + utils::hook::call(game::select(0x142332E81, 0x140596E01), verify_checksum_stub); // skip checksum verification + utils::hook::set(game::select(0x14233305E, 0x140596F2E), 0); // don't add checksum to packet - utils::hook::nop(0x142332E76_g, 4); // don't increment data pointer to optionally skip socket byte - utils::hook::call(0x142332E43_g, read_socket_byte_stub); // optionally read socket byte - utils::hook::call(0x142332E81_g, verify_checksum_stub); // skip checksum verification - utils::hook::set(0x14233305E_g, 0); // don't add checksum to packet - - utils::hook::set(0x14134C6E0_g, 5); // set initial connection state to challenging + utils::hook::set(game::select(0x14134C6E0, 0x14018E574), 5); // set initial connection state to challenging // intercept command handling - utils::hook::jump(0x14134D146_g, utils::hook::assemble(handle_command_stub)); + utils::hook::call(game::select(0x14134D146, 0x14018EED0), utils::hook::assemble(handle_command_stub)); - utils::hook::set(0x14224E90D_g, 0xEB); // don't kick clients without dw handle + utils::hook::set(game::select(0x14224E90D, 0x1405315F9), 0xEB); // don't kick clients without dw handle // TODO: Fix that scheduler::once(create_ip_socket, scheduler::main); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 6fa0ee0c..2e0c9f98 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -1,20 +1,16 @@ #include #include "loader/component_loader.hpp" #include "game/game.hpp" -#include "steam/steam.hpp" #include "party.hpp" #include "network.hpp" #include "scheduler.hpp" #include -#include #include #include #include -#include - namespace party { namespace @@ -186,17 +182,6 @@ namespace party connect_host = target; query_server(target, handle_connect_query_response); } - - std::string get_dvar_string(const char* dvar_name) - { - const auto dvar = game::Dvar_FindVar(dvar_name); - if (!dvar) - { - return {}; - } - - return game::Dvar_GetString(dvar); - } } void query_server(const game::netadr_t& host, query_callback callback) @@ -221,32 +206,8 @@ namespace party { void post_unpack() override { - utils::hook::jump(0x141E19B60_g, 0x141E19BB6_g); // patch steam id validity check utils::hook::jump(0x141EE6030_g, connect_stub); - network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) - { - utils::info_string info{}; - info.set("challenge", std::string(data.begin(), data.end())); - info.set("gamename", "T7"); - info.set("hostname", get_dvar_string("sv_hostname")); - info.set("gametype", get_dvar_string("g_gametype")); - //info.set("sv_motd", get_dvar_string("sv_motd")); - info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); - info.set("mapname", get_dvar_string("mapname")); - //info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1"); - //info.set("clients", utils::string::va("%i", get_client_count())); - //info.set("bots", utils::string::va("%i", get_bot_count())); - //info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients)); - info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/)); - info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); - //info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); - //info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated"))); - info.set("shortversion", SHORTVERSION); - - network::send(target, "infoResponse", info.build(), '\n'); - }); - network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data) { bool found_query = false; diff --git a/src/client/component/scheduler.cpp b/src/client/component/scheduler.cpp index f23e25b6..ce8995dd 100644 --- a/src/client/component/scheduler.cpp +++ b/src/client/component/scheduler.cpp @@ -75,8 +75,8 @@ namespace scheduler { new_callbacks_.access([&](task_list& new_tasks) { - tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), - std::move_iterator(new_tasks.end())); + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), + std::move_iterator(new_tasks.end())); new_tasks = {}; }); }); @@ -167,10 +167,13 @@ namespace scheduler { if (!game::is_server()) { - r_end_frame_hook.create(0x142273560_g, r_end_frame_stub); // some func called before R_EndFrame, maybe SND_EndFrame? - g_run_frame_hook.create(0x14065C360_g, server_frame_stub); // GlassSv_Update - main_frame_hook.create(0x1420F9860_g, main_frame_stub); // Com_Frame_Try_Block_Function + r_end_frame_hook.create(0x142273560_g, r_end_frame_stub); + // some func called before R_EndFrame, maybe SND_EndFrame? } + + //g_run_frame_hook.create(0x14065C360_g, server_frame_stub); // GlassSv_Update + main_frame_hook.create(game::select(0x1420F9860, 0x1405020E0), main_frame_stub); + // Com_Frame_Try_Block_Function } void pre_destroy() override diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index 052af5e0..76aba355 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -9,7 +9,7 @@ namespace game { const utils::nt::library& get_host_library() { - static auto host_library = [] + static const auto host_library = [] { utils::nt::library host{}; if (!host || host == utils::nt::library::get_by_address(get_base)) @@ -26,13 +26,13 @@ namespace game size_t get_base() { - static auto base = reinterpret_cast(get_host_library().get_ptr()); + static const auto base = reinterpret_cast(get_host_library().get_ptr()); return base; } bool is_server() { - static auto server = get_host_library().get_optional_header()->CheckSum == 0x14C28B4; + static const auto server = get_host_library().get_optional_header()->CheckSum == 0x14C28B4; return server; } } diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 83cd194c..34b480c1 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -1,7 +1,5 @@ #pragma once -#include "structs.hpp" - namespace game { size_t get_base(); @@ -9,12 +7,16 @@ namespace game inline size_t relocate(const size_t val) { + if (!val) return 0; + const auto base = get_base(); return base + (val - 0x140000000); } inline size_t derelocate(const size_t val) { + if (!val) return 0; + const auto base = get_base(); return (val - base) + 0x140000000; } @@ -24,6 +26,16 @@ namespace game return derelocate(reinterpret_cast(val)); } + inline size_t select(const size_t client_val, const size_t server_val) + { + return relocate(is_server() ? server_val : client_val); + } + + inline size_t select(const void* client_val, const void* server_val) + { + return select(reinterpret_cast(client_val), reinterpret_cast(server_val)); + } + template class symbol { @@ -33,9 +45,15 @@ namespace game { } + symbol(const size_t address, const size_t server_address) + : address_(address) + , server_address_(server_address) + { + } + T* get() const { - return reinterpret_cast(relocate(this->address_)); + return reinterpret_cast(select(this->address_, this->server_address_)); } operator T*() const @@ -49,7 +67,8 @@ namespace game } private: - size_t address_; + size_t address_{}; + size_t server_address_{}; }; } @@ -58,4 +77,5 @@ inline size_t operator"" _g(const size_t val) return game::relocate(val); } +#include "structs.hpp" #include "symbols.hpp" diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index c787841d..7bf18d98 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -828,7 +828,7 @@ namespace game JOIN_RESPONSE_COUNT = 0x1E, }; - typedef void (*joinCompleteCallback)(const int, JoinResult); + typedef void (*joinCompleteCallback)(int, JoinResult); struct AgreementStatus { diff --git a/src/client/game/symbols.cpp b/src/client/game/symbols.cpp index 2edede21..12275c3c 100644 --- a/src/client/game/symbols.cpp +++ b/src/client/game/symbols.cpp @@ -6,6 +6,6 @@ namespace game { eModes Com_SessionMode_GetMode() { - return eModes(*reinterpret_cast(0x1568EF7F4_g) << 28 >> 28); + return eModes(*reinterpret_cast(game::select(0x1568EF7F4, 0x14948DB04)) << 28 >> 28); } } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 27971fbf..f8ad7b45 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -40,12 +40,11 @@ namespace game WEAK symbol Live_GetConnectivityInformation{0x141E0C410}; // MSG - WEAK symbol MSG_ReadByte{0x142155EB0}; + WEAK symbol MSG_ReadByte{0x142155EB0, 0x14050D1B0}; // NET - WEAK symbol NET_SendPacket{0x142332F70}; - WEAK symbol NET_StringToAdr{0x1421731E0}; - WEAK symbol NetAdr_InitFromString{0x142332F70}; + WEAK symbol NET_SendPacket{0x142332F70, 0x140596E40}; + WEAK symbol NET_StringToAdr{0x1421731E0, 0x140515110}; // Sys WEAK symbol Sys_Milliseconds{0x142333430}; @@ -53,11 +52,11 @@ namespace game WEAK symbol Sys_GetTLS{0x142184210}; // Dvar - WEAK symbol Dvar_FindVar{0x1422BD730}; + WEAK symbol Dvar_FindVar{0x1422BD730, 0x140575540}; WEAK symbol Dvar_GenerateHash{0x14133DBF0}; WEAK symbol Dvar_FindMalleableVar{0x1422BD6A0}; WEAK symbol Dvar_GetDebugName{0x1422BDCB0}; - WEAK symbol Dvar_GetString{0x1422BFFF0}; + WEAK symbol Dvar_GetString{0x1422BFFF0, 0x140575E30}; WEAK symbol Dvar_SetFromStringByName{ 0x1422C7F60 }; @@ -76,7 +75,7 @@ namespace game WEAK symbol cmd_functions{0x15689FF58}; WEAK symbol sv_cmd_args{0x15689CE30}; - WEAK symbol ip_socket{0x157E77818}; + WEAK symbol ip_socket{0x157E77818, 0x14A640988}; WEAK symbol s_join{0x15574C640}; diff --git a/src/client/main.cpp b/src/client/main.cpp index 8d0e297f..6e5e30a1 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -7,9 +7,12 @@ #include #include #include +#include #include +#include "game/game.hpp" + namespace { volatile bool g_call_tls_callbacks = false; @@ -55,6 +58,12 @@ namespace return steam::SteamAPI_RestartAppIfNecessary(); } + BOOL set_process_dpi_aware_stub() + { + component_loader::post_unpack(); + return SetProcessDPIAware(); + } + void patch_imports() { patch_steam_import("SteamAPI_RegisterCallback", steam::SteamAPI_RegisterCallback); @@ -74,7 +83,9 @@ namespace //patch_steam_import("SteamAPI_Shutdown", steam::SteamAPI_Shutdown); g_original_import = patch_steam_import("SteamAPI_RestartAppIfNecessary", restart_app_if_necessary_stub); - utils::hook::set(utils::nt::library{}.get_iat_entry("kernel32.dll", "ExitProcess"), exit_hook); + const utils::nt::library game{}; + utils::hook::set(game.get_iat_entry("kernel32.dll", "ExitProcess"), exit_hook); + utils::hook::set(game.get_iat_entry("user32.dll", "SetProcessDPIAware"), set_process_dpi_aware_stub); } void remove_crash_file() @@ -163,7 +174,6 @@ namespace { if (handle_process_runner()) { - TerminateProcess(GetCurrentProcess(), 0); return 0; } @@ -186,7 +196,7 @@ namespace const auto client_binary = "BlackOps3.exe"s; const auto server_binary = "BlackOps3_UnrankedDedicatedServer.exe"s; - const auto has_server = utils::io::file_exists(server_binary); + const auto has_server = !utils::io::file_exists(client_binary) || utils::flags::has_flag("dedicated"); if (!component_loader::activate(has_server)) { @@ -199,6 +209,11 @@ namespace throw std::runtime_error("Unable to load binary into memory"); } + if (has_server != game::is_server()) + { + throw std::runtime_error("Bad binary loaded into memory"); + } + patch_imports(); if (!component_loader::post_load())