From 6ac0dd57e6c85439e44767042a26e2dc5e820aed Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Fri, 3 Feb 2023 19:19:47 +0100 Subject: [PATCH 01/29] Fix client --- src/client/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/main.cpp b/src/client/main.cpp index b7231496..1dfdb9ae 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -262,7 +262,7 @@ namespace throw std::runtime_error("Unable to load binary into memory"); } - if (has_server != game::is_server()) + if (is_server != game::is_server()) { throw std::runtime_error("Bad binary loaded into memory"); } From e88ad62b4dc33b091587f56b2c3c3183c3c4dd67 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sat, 4 Feb 2023 15:53:54 +0100 Subject: [PATCH 02/29] Experiments --- src/client/component/getinfo.cpp | 202 +++++++++++++++++++++++++++++++ src/client/game/symbols.hpp | 2 + 2 files changed, 204 insertions(+) diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index 10972990..ba0f41dc 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -12,6 +12,8 @@ #include +#include "command.hpp" + namespace getinfo { namespace @@ -26,6 +28,12 @@ namespace getinfo return game::Dvar_GetString(dvar); } + + + int Com_SessionMode_GetGameMode() + { + return *reinterpret_cast(game::select(0x1568EF7F4, 0x14948DB04)) << 14 >> 28; + } } int get_assigned_team() @@ -33,10 +41,203 @@ namespace getinfo return (rand() % 2) + 1; } + + utils::hook::detour xxx; + + struct DDLDef + { + char* name; + uint16_t version; + uint32_t checksum; + byte flags; + int bitSize; + int byteSize; + void* structList; + int structCount; + void* enumList; + int enumCount; + DDLDef* next; + int headerBitSize; + int headerByteSize; + int reserveSize; + int userFlagsSize; + bool paddingUsed; + }; + + struct DDLRoot + { + const char* name; + DDLDef* ddlDef; + }; + + void p__rint(const char* fileName, int version, DDLRoot* ddlRoot) + { + if (ddlRoot) + { + printf("DDL: %s\n", ddlRoot->name); + DDLDef* currDef; + for (currDef = ddlRoot->ddlDef; currDef; currDef = currDef->next) + { + //if (currDef->version == version) + // return currDef; + printf("Version: %d\n", currDef->version); + } + } + + + MessageBoxA(0, fileName, std::to_string(version).data(), 0); + } + + void* DDL_LoadAssetWithVersion(const char* fileName, int version) + { + auto ddlRoot = (DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, fileName, 1, -1).data; + p__rint(fileName, version, ddlRoot); + + return xxx.invoke(fileName, version); + } + + enum StorageFileType + { + STORAGE_COMMON_SETTINGS = 0x0, + STORAGE_PROFILE_SHOUTCASTER = 0x1, + STORAGE_CP_STATS_ONLINE = 0x2, + STORAGE_CP_STATS_OFFLINE = 0x3, + STORAGE_CP_STATS_NIGHTMARE = 0x4, + STORAGE_CP_LOADOUTS = 0x5, + STORAGE_CP_LOADOUTS_OFFLINE = 0x6, + STORAGE_MP_STATS_ONLINE = 0x7, + STORAGE_MP_STATS_OFFLINE = 0x8, + STORAGE_MP_LOADOUTS = 0x9, + STORAGE_MP_LOADOUTS_OFFLINE = 0xA, + STORAGE_ZM_STATS_ONLINE = 0xB, + STORAGE_ZM_STATS_OFFLINE = 0xC, + STORAGE_ZM_LOADOUTS = 0xD, + STORAGE_ZM_LOADOUTS_OFFLINE = 0xE, + STORAGE_PAINTSHOP_DATA = 0xF, + STORAGE_GUNSMITH = 0x10, + STORAGE_PAINTJOBS = 0x11, + STORAGE_EMBLEMS = 0x12, + STORAGE_EXTERNAL_DATA = 0x13, + STORAGE_FILE_COUNT = 0x14, + STORAGE_FILE_FIRST = 0x0, + STORAGE_FILE_INVALID = 0xFFFFFFFF, + }; + + struct StorageFileInfo + { + const char* name; + const char* ddlPath; + const char** fileNames; + void* files; + int size; + int slots; + StorageFileType fileType; + /*StorageTargetType targetType; + game::eModes sessionMode; + CampaignMode campaignMode; + eNetworkModes networkMode; + eGameModes gameMode; + bool readOnLogin; + bool readOnly; + bool sendToServer; + bool useScratch; + bool optional;*/ + }; + + struct StorageFileMap + { + StorageFileInfo* info; + /*StorageFileCallbacks callbacks; + StorageFileDDL ddl; + UIModelIndex uiFileTypeModel;*/ + }; + + + bool should_transfer_stub(void* storage_file_info) + { + auto should_transfer = game::ShouldTransfer(storage_file_info); + + if(i >= 12 && i <= 15) + { + def = !def; + } + + return should_transfer; + } + + DDLDef* core_get_ddl(int a) + { + auto def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(a); + //MessageBoxA(0, (def->name + (" - core - " + std::to_string(a))).data(), std::to_string(def->version).data(), 0); + + if (a == 1) + { + def = ((DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, "gamedata/ddl/mp/mp_stats.ddl", true, -1).data)->ddlDef; + //for (int i = 0;; ++i) { + //def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(i); + // MessageBoxA(0, (def->name + (" - core - " + std::to_string(0))).data(), std::to_string(def->version).data(), 0); + //} + } + return def; + } + + DDLDef* Loadouts_get_ddl(uint64_t a) + { + auto def = reinterpret_cast(game::select(0x141EAF250, 0x140472AA0))(a); + //MessageBoxA(0, (def->name + (" - load - " + std::to_string(a))).data(), std::to_string(def->version).data(), 0); + + if (a == 3) + { + def = ((DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, "gamedata/ddl/loadouts/mp_loadouts.ddl", true, -1).data)->ddlDef; + //for (int i = 0;; ++i) { + //def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(i); + //MessageBoxA(0, (def->name + (" - load - " + std::to_string(0))).data(), std::to_string(def->version).data(), 0); + //} + } + + return def; + } + struct component final : generic_component { void post_unpack() override { + //xxx.create(game::select(0x142522EE0, 0x140618AB0), DDL_LoadAssetWithVersion); + + + command::add("dlll", []() + { + game::DB_EnumXAssets(game::ASSET_TYPE_DDL, [](game::XAssetHeader h, void*) + { + auto* ddlr = ((DDLRoot*)h.data); + p__rint(ddlr->name, 0, ddlr); + }, nullptr, true); + }); + + command::add("lel", []() + { + for(int i = 0; i < 16;++i) + { + core_get_ddl(i); + } + }); + + if (game::is_server()) + { + /*utils::hook::call(0x140467F54_g, core_get_ddl); + utils::hook::call(0x14052D518_g, core_get_ddl); + utils::hook::call(0x14052E62E_g, core_get_ddl); + utils::hook::call(0x14054B5E1_g, core_get_ddl); + + utils::hook::call(game::select(0, 0x14052E66D), Loadouts_get_ddl); + utils::hook::call(game::select(0x142277447, 0x14054B607), Loadouts_get_ddl);*/ + } + else + { + + utils::hook::call(0x1422781E3_g, ShouldTransfer); + } + //utils::hook::jump(game::select(0x142254EF0, 0x140537730), get_assigned_team); network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) @@ -55,6 +256,7 @@ namespace getinfo //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("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); //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); diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 5c949be1..8b2f7cc9 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -94,6 +94,8 @@ namespace game WEAK symbol Cinematic_StopPlayback{0x1412BEA70}; + WEAK symbol ShouldTransfer{0x142276E10}; + // Rendering WEAK symbol R_AddCmdDrawText{ From ba1b66294cbe628739510250427d6ac56d175101 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sat, 4 Feb 2023 16:02:20 +0100 Subject: [PATCH 03/29] Remove experiments --- src/client/component/getinfo.cpp | 195 +------------------------------ 1 file changed, 1 insertion(+), 194 deletions(-) diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index ba0f41dc..f53684a8 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -40,204 +40,11 @@ namespace getinfo { return (rand() % 2) + 1; } - - - utils::hook::detour xxx; - - struct DDLDef - { - char* name; - uint16_t version; - uint32_t checksum; - byte flags; - int bitSize; - int byteSize; - void* structList; - int structCount; - void* enumList; - int enumCount; - DDLDef* next; - int headerBitSize; - int headerByteSize; - int reserveSize; - int userFlagsSize; - bool paddingUsed; - }; - - struct DDLRoot - { - const char* name; - DDLDef* ddlDef; - }; - - void p__rint(const char* fileName, int version, DDLRoot* ddlRoot) - { - if (ddlRoot) - { - printf("DDL: %s\n", ddlRoot->name); - DDLDef* currDef; - for (currDef = ddlRoot->ddlDef; currDef; currDef = currDef->next) - { - //if (currDef->version == version) - // return currDef; - printf("Version: %d\n", currDef->version); - } - } - - - MessageBoxA(0, fileName, std::to_string(version).data(), 0); - } - - void* DDL_LoadAssetWithVersion(const char* fileName, int version) - { - auto ddlRoot = (DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, fileName, 1, -1).data; - p__rint(fileName, version, ddlRoot); - - return xxx.invoke(fileName, version); - } - - enum StorageFileType - { - STORAGE_COMMON_SETTINGS = 0x0, - STORAGE_PROFILE_SHOUTCASTER = 0x1, - STORAGE_CP_STATS_ONLINE = 0x2, - STORAGE_CP_STATS_OFFLINE = 0x3, - STORAGE_CP_STATS_NIGHTMARE = 0x4, - STORAGE_CP_LOADOUTS = 0x5, - STORAGE_CP_LOADOUTS_OFFLINE = 0x6, - STORAGE_MP_STATS_ONLINE = 0x7, - STORAGE_MP_STATS_OFFLINE = 0x8, - STORAGE_MP_LOADOUTS = 0x9, - STORAGE_MP_LOADOUTS_OFFLINE = 0xA, - STORAGE_ZM_STATS_ONLINE = 0xB, - STORAGE_ZM_STATS_OFFLINE = 0xC, - STORAGE_ZM_LOADOUTS = 0xD, - STORAGE_ZM_LOADOUTS_OFFLINE = 0xE, - STORAGE_PAINTSHOP_DATA = 0xF, - STORAGE_GUNSMITH = 0x10, - STORAGE_PAINTJOBS = 0x11, - STORAGE_EMBLEMS = 0x12, - STORAGE_EXTERNAL_DATA = 0x13, - STORAGE_FILE_COUNT = 0x14, - STORAGE_FILE_FIRST = 0x0, - STORAGE_FILE_INVALID = 0xFFFFFFFF, - }; - - struct StorageFileInfo - { - const char* name; - const char* ddlPath; - const char** fileNames; - void* files; - int size; - int slots; - StorageFileType fileType; - /*StorageTargetType targetType; - game::eModes sessionMode; - CampaignMode campaignMode; - eNetworkModes networkMode; - eGameModes gameMode; - bool readOnLogin; - bool readOnly; - bool sendToServer; - bool useScratch; - bool optional;*/ - }; - - struct StorageFileMap - { - StorageFileInfo* info; - /*StorageFileCallbacks callbacks; - StorageFileDDL ddl; - UIModelIndex uiFileTypeModel;*/ - }; - - - bool should_transfer_stub(void* storage_file_info) - { - auto should_transfer = game::ShouldTransfer(storage_file_info); - - if(i >= 12 && i <= 15) - { - def = !def; - } - - return should_transfer; - } - - DDLDef* core_get_ddl(int a) - { - auto def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(a); - //MessageBoxA(0, (def->name + (" - core - " + std::to_string(a))).data(), std::to_string(def->version).data(), 0); - - if (a == 1) - { - def = ((DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, "gamedata/ddl/mp/mp_stats.ddl", true, -1).data)->ddlDef; - //for (int i = 0;; ++i) { - //def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(i); - // MessageBoxA(0, (def->name + (" - core - " + std::to_string(0))).data(), std::to_string(def->version).data(), 0); - //} - } - return def; - } - - DDLDef* Loadouts_get_ddl(uint64_t a) - { - auto def = reinterpret_cast(game::select(0x141EAF250, 0x140472AA0))(a); - //MessageBoxA(0, (def->name + (" - load - " + std::to_string(a))).data(), std::to_string(def->version).data(), 0); - - if (a == 3) - { - def = ((DDLRoot*)game::DB_FindXAssetHeader(game::ASSET_TYPE_DDL, "gamedata/ddl/loadouts/mp_loadouts.ddl", true, -1).data)->ddlDef; - //for (int i = 0;; ++i) { - //def = reinterpret_cast(game::select(0x141EA9730, 0x14046EC20))(i); - //MessageBoxA(0, (def->name + (" - load - " + std::to_string(0))).data(), std::to_string(def->version).data(), 0); - //} - } - - return def; - } - + struct component final : generic_component { void post_unpack() override { - //xxx.create(game::select(0x142522EE0, 0x140618AB0), DDL_LoadAssetWithVersion); - - - command::add("dlll", []() - { - game::DB_EnumXAssets(game::ASSET_TYPE_DDL, [](game::XAssetHeader h, void*) - { - auto* ddlr = ((DDLRoot*)h.data); - p__rint(ddlr->name, 0, ddlr); - }, nullptr, true); - }); - - command::add("lel", []() - { - for(int i = 0; i < 16;++i) - { - core_get_ddl(i); - } - }); - - if (game::is_server()) - { - /*utils::hook::call(0x140467F54_g, core_get_ddl); - utils::hook::call(0x14052D518_g, core_get_ddl); - utils::hook::call(0x14052E62E_g, core_get_ddl); - utils::hook::call(0x14054B5E1_g, core_get_ddl); - - utils::hook::call(game::select(0, 0x14052E66D), Loadouts_get_ddl); - utils::hook::call(game::select(0x142277447, 0x14054B607), Loadouts_get_ddl);*/ - } - else - { - - utils::hook::call(0x1422781E3_g, ShouldTransfer); - } - //utils::hook::jump(game::select(0x142254EF0, 0x140537730), get_assigned_team); network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data) From 74bb7bcf0b4ff8dd559fefe48d9408d7f083165e Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sat, 4 Feb 2023 16:04:22 +0100 Subject: [PATCH 04/29] Handle stats transfer for dedis Ugly workaround for #98 --- src/client/component/party.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 9b9e42e2..d9385321 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -15,6 +15,7 @@ namespace party { namespace { + std::atomic_bool is_connecting_to_dedi{ false }; game::netadr_t connect_host{{}, {}, game::NA_BAD, {}}; struct server_query @@ -159,6 +160,8 @@ namespace party return; } + is_connecting_to_dedi = info.get("dedicated") == "1"; + const auto gamename = info.get("gamename"); if (gamename != "T7"s) { @@ -226,11 +229,27 @@ namespace party network::send(host, "getInfo", challenge); } + int should_transfer_stub(uint8_t* storage_file_info) + { + auto should_transfer = game::ShouldTransfer(storage_file_info); + + const auto offset = storage_file_info - reinterpret_cast(0x14343CDF0_g); + const auto index = offset / 120; + + if (is_connecting_to_dedi && index >= 12 && index <= 15) + { + should_transfer = !should_transfer; + } + + return should_transfer; + } + struct component final : client_component { void post_unpack() override { utils::hook::jump(0x141EE6030_g, connect_stub); + utils::hook::call(0x1422781E3_g, should_transfer_stub); network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data) { From 89d055a559ee0c2451d7a045343fd2c7e3721d23 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 10:16:33 +0100 Subject: [PATCH 05/29] Skip the intro for dev builds #128 --- src/client/component/intro.cpp | 37 ++++++++++++++++++++++++++++++++++ src/client/game/symbols.hpp | 1 + 2 files changed, 38 insertions(+) create mode 100644 src/client/component/intro.cpp diff --git a/src/client/component/intro.cpp b/src/client/component/intro.cpp new file mode 100644 index 00000000..f207699d --- /dev/null +++ b/src/client/component/intro.cpp @@ -0,0 +1,37 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include + +namespace intro +{ + namespace + { + utils::hook::detour cinematic_start_playback_hook; + + void ccc(const char* name, const char* key, const unsigned int playback_flags, const float volume, + void* callback_info, const int id) + { + if (name == "BO3_Global_Logo_LogoSequence"s) + { + return; + } + + cinematic_start_playback_hook.invoke(name, key, playback_flags, volume, callback_info, id); + } + } + + class component final : public client_component + { + public: + void post_unpack() override + { + cinematic_start_playback_hook.create(game::Cinematic_StartPlayback, ccc); + } + }; +} + +#ifdef DEV_BUILD +REGISTER_COMPONENT(intro::component) +#endif diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 8b2f7cc9..7ce4b347 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -92,6 +92,7 @@ namespace game }; WEAK symbol Scr_GetNumParam{0x0, 0x140171320}; + WEAK symbol Cinematic_StartPlayback{0x1412BE3A0}; WEAK symbol Cinematic_StopPlayback{0x1412BEA70}; WEAK symbol ShouldTransfer{0x142276E10}; From 5a7be3732c62cf48a39ced05c8f7d3c419fc1d53 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 10:19:00 +0100 Subject: [PATCH 06/29] Small cleanup --- src/client/component/party.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index d9385321..436571f2 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -15,7 +15,7 @@ namespace party { namespace { - std::atomic_bool is_connecting_to_dedi{ false }; + std::atomic_bool is_connecting_to_dedi{false}; game::netadr_t connect_host{{}, {}, game::NA_BAD, {}}; struct server_query From 513f673c016e197741e3d8cf7acdb650696bf443 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 16:15:29 +0100 Subject: [PATCH 07/29] Add serverlist support --- src/client/component/getinfo.cpp | 2 +- src/client/component/network.cpp | 4 +- src/client/component/party.cpp | 59 +++-- src/client/component/party.hpp | 2 +- src/client/component/server_list.cpp | 148 +++++++++++++ src/client/component/server_list.hpp | 10 + src/client/game/structs.hpp | 2 + .../steam/interfaces/matchmaking_servers.cpp | 206 ++++++++++++++---- 8 files changed, 368 insertions(+), 65 deletions(-) create mode 100644 src/client/component/server_list.cpp create mode 100644 src/client/component/server_list.hpp diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index f53684a8..93de3f7d 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -61,7 +61,7 @@ namespace getinfo //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("protocol", utils::string::va("%i", PROTOCOL)); info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); //info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 8407265b..7ce154fe 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -153,12 +153,12 @@ namespace network return to; } - void send_data(const game::netadr_t& address, const void* data, const size_t size) + void send_data(const game::netadr_t& address, const void* data, const size_t length) { //game::NET_SendPacket(game::NS_CLIENT1, static_cast(size), data, &address); const auto to = convert_to_sockaddr(address); - sendto(*game::ip_socket, static_cast(data), static_cast(size), 0, + sendto(*game::ip_socket, static_cast(data), static_cast(length), 0, reinterpret_cast(&to), sizeof(to)); } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 436571f2..7328bc94 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -20,10 +20,11 @@ namespace party struct server_query { - game::netadr_t host; - std::string challenge; - query_callback callback; - std::chrono::high_resolution_clock::time_point query_time; + bool sent{false}; + game::netadr_t host{}; + std::string challenge{}; + query_callback callback{}; + std::chrono::high_resolution_clock::time_point query_time{}; }; utils::concurrency::container>& get_server_queries() @@ -153,7 +154,7 @@ namespace party } void handle_connect_query_response(const bool success, const game::netadr_t& target, - const utils::info_string& info) + const utils::info_string& info, uint32_t ping) { if (!success) { @@ -209,24 +210,28 @@ namespace party connect_host = target; query_server(target, handle_connect_query_response); } + + void send_server_query(server_query& query) + { + query.sent = true; + query.query_time = std::chrono::high_resolution_clock::now(); + query.challenge = utils::cryptography::random::get_challenge(); + + network::send(query.host, "getInfo", query.challenge); + } } void query_server(const game::netadr_t& host, query_callback callback) { - const auto challenge = utils::cryptography::random::get_challenge(); - server_query query{}; + query.sent = false; query.host = host; - query.query_time = std::chrono::high_resolution_clock::now(); query.callback = std::move(callback); - query.challenge = challenge; get_server_queries().access([&](std::vector& server_queries) { server_queries.emplace_back(std::move(query)); }); - - network::send(host, "getInfo", challenge); } int should_transfer_stub(uint8_t* storage_file_info) @@ -274,7 +279,10 @@ namespace party if (found_query) { - query.callback(true, query.host, info); + const auto ping = std::chrono::high_resolution_clock::now() - query.query_time; + const auto ping_ms = std::chrono::duration_cast(ping).count(); + + query.callback(true, query.host, info, static_cast(ping_ms)); } }); @@ -284,10 +292,23 @@ namespace party get_server_queries().access([&](std::vector& server_queries) { + size_t sent_queries = 0; + const auto now = std::chrono::high_resolution_clock::now(); for (auto i = server_queries.begin(); i != server_queries.end();) { - if ((now - i->query_time) < 10s) + if (!i->sent) + { + if (++sent_queries < 10) + { + send_server_query(*i); + } + + ++i; + continue; + } + + if ((now - i->query_time) < 2s) { ++i; continue; @@ -301,9 +322,17 @@ namespace party const utils::info_string empty{}; for (const auto& query : removed_queries) { - query.callback(false, query.host, empty); + query.callback(false, query.host, empty, 0); } - }, scheduler::async, 1s); + }, scheduler::async, 200ms); + } + + void pre_destroy() override + { + get_server_queries().access([](std::vector& s) + { + s = {}; + }); } }; } diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp index b26ed164..47bef009 100644 --- a/src/client/component/party.hpp +++ b/src/client/component/party.hpp @@ -3,7 +3,7 @@ namespace party { - using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info); + using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info, uint32_t ping); using query_callback = std::function; void query_server(const game::netadr_t& host, query_callback callback); diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp new file mode 100644 index 00000000..7bcff98e --- /dev/null +++ b/src/client/component/server_list.cpp @@ -0,0 +1,148 @@ +#include +#include "loader/component_loader.hpp" +#include "server_list.hpp" + +#include "game/game.hpp" + +#include +#include + +#include "network.hpp" +#include "scheduler.hpp" + +namespace server_list +{ + namespace + { + struct state + { + game::netadr_t address{}; + bool requesting{false}; + std::chrono::high_resolution_clock::time_point query_start{}; + callback callback{}; + }; + + utils::concurrency::container master_state; + + void handle_server_list_response(const game::netadr_t& target, + const network::data_view& data, state& s) + { + if (!s.requesting || s.address != target) + { + return; + } + + s.requesting = false; + const auto callback = std::move(s.callback); + + std::optional start{}; + + for (size_t i = 0; i + 6 < data.size(); ++i) + { + if (data[i + 6] == '\\') + { + start.emplace(i); + break; + } + } + + if (!start.has_value()) + { + callback(true, {}); + return; + } + + std::unordered_set result{}; + + for (auto i = start.value(); i + 6 < data.size(); i += 7) + { + if (data[i + 6] != '\\') + { + break; + } + + game::netadr_t address{}; + address.type = game::NA_RAWIP; + address.localNetID = game::NS_CLIENT1; + memcpy(&address.ipv4.a, data.data() + i + 0, 4); + memcpy(&address.port, data.data() + i + 4, 2); + address.port = ntohs(address.port); + + result.emplace(address); + } + + callback(true, result); + } + } + + bool get_master_server(game::netadr_t& address) + { + address = network::address_from_string("server.boiii.re:20810"); + return address.type != game::NA_BAD; + } + + void request_servers(callback callback) + { + master_state.access([&callback](state& s) + { + game::netadr_t addr{}; + if (!get_master_server(addr)) + { + return; + } + + s.requesting = true; + s.address = addr; + s.callback = std::move(callback); + s.query_start = std::chrono::high_resolution_clock::now(); + + network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL)); + }); + } + + struct component final : client_component + { + void post_unpack() override + { + network::on("getServersResponse", [](const game::netadr_t& target, const network::data_view& data) + { + master_state.access([&](state& s) + { + handle_server_list_response(target, data, s); + }); + }); + + scheduler::loop([] + { + master_state.access([](state& s) + { + if (!s.requesting) + { + return; + } + + const auto now = std::chrono::high_resolution_clock::now(); + if ((now - s.query_start) < 2s) + { + return; + } + + s.requesting = false; + s.callback(false, {}); + s.callback = {}; + }); + }, scheduler::async, 200ms); + } + + void pre_destroy() override + { + master_state.access([](state& s) + { + s.requesting = false; + s.callback = {}; + }); + } + }; +} + +REGISTER_COMPONENT(server_list::component) diff --git a/src/client/component/server_list.hpp b/src/client/component/server_list.hpp new file mode 100644 index 00000000..628795d9 --- /dev/null +++ b/src/client/component/server_list.hpp @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace server_list +{ + bool get_master_server(game::netadr_t& address); + + using callback = std::function&)>; + void request_servers(callback callback); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 35c0ed8a..6ab7ee4a 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1,5 +1,7 @@ #pragma once +#define PROTOCOL 1 + #ifdef __cplusplus namespace game { diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index cb2d612c..0fc83c1a 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -5,86 +5,178 @@ #include "component/party.hpp" #include "component/network.hpp" +#include "component/server_list.hpp" #include +#include namespace steam { namespace { - gameserveritem_t* get_server_item() + struct server { - static gameserveritem_t server{}; - server.m_NetAdr.m_usConnectionPort = 28960; - server.m_NetAdr.m_usQueryPort = 28960; + bool handled{false}; + game::netadr_t address{}; + gameserveritem_t server_item{}; + }; - uint32_t address{}; - inet_pton(AF_INET, "192.168.178.34", &address); - server.m_NetAdr.m_unIP = ntohl(address); + using servers = std::vector; - server.m_nPing = 10; - server.m_bHadSuccessfulResponse = true; - server.m_bDoNotRefresh = false; - strcpy_s(server.m_szGameDir, "usermaps"); - strcpy_s(server.m_szMap, "mp_nuketown_x"); - strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server"); - server.m_nAppID = 311210; - server.m_nPlayers = 0; - server.m_nMaxPlayers = 18; - server.m_nBotPlayers = 0; - server.m_bPassword = false; - server.m_bSecure = true; - server.m_ulTimeLastPlayed = 0; - server.m_nServerVersion = 1000; - strcpy_s(server.m_szServerName, "BO^3I^5I^6I ^7Server"); - strcpy_s(server.m_szGameTags, - R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\\playerCount\0)"); - server.m_steamID = steam_id(); + ::utils::concurrency::container queried_servers{}; + std::atomic current_response{}; - return &server; - } - - gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info) + gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info, + const uint32_t ping, const bool success) { gameserveritem_t server{}; server.m_NetAdr.m_usConnectionPort = address.port; server.m_NetAdr.m_usQueryPort = address.port; server.m_NetAdr.m_unIP = address.addr; - server.m_nPing = 10; - server.m_bHadSuccessfulResponse = true; + server.m_nPing = static_cast(ping); + server.m_bHadSuccessfulResponse = success; server.m_bDoNotRefresh = false; strcpy_s(server.m_szGameDir, ""); strcpy_s(server.m_szMap, info.get("mapname").data()); strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server"); server.m_nAppID = 311210; - server.m_nPlayers = 0; - server.m_nMaxPlayers = 18; - server.m_nBotPlayers = 0; + server.m_nPlayers = atoi(info.get("clients").data()); + server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data()); + server.m_nBotPlayers = atoi(info.get("bots").data()); server.m_bPassword = false; server.m_bSecure = true; server.m_ulTimeLastPlayed = 0; server.m_nServerVersion = 1000; - strcpy_s(server.m_szServerName, info.get("sv_hostname").data()); + strcpy_s(server.m_szServerName, info.get("hostname").data()); const auto playmode = info.get("playmode"); const auto mode = game::eModes(std::atoi(playmode.data())); const auto* tags = ::utils::string::va( - R"(\gametype\%s\dedicated\true\ranked\true\hardcore\false\zombies\%s\modName\\playerCount\0)", - info.get("gametype").data(), mode == game::MODE_ZOMBIES ? "true" : "false"); + R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\0)", + info.get("gametype").data(), + info.get("dedicated") == "1" ? "true" : "false", + mode == game::MODE_ZOMBIES ? "true" : "false"); strcpy_s(server.m_szGameTags, tags); server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); return server; } + + void handle_server_respone(const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) + { + bool all_handled = false; + std::optional index{}; + queried_servers.access([&](servers& srvs) + { + size_t i = 0; + for (; i < srvs.size(); ++i) + { + if (srvs[i].address == host) + { + break; + } + } + + if (i >= srvs.size()) + { + return; + } + + index = static_cast(i); + + auto& srv = srvs[i]; + srv.handled = true; + srv.server_item = create_server_item(host, info, ping, success); + + + for (const auto& entry : srvs) + { + if (!entry.handled) + { + return; + } + } + + all_handled = true; + }); + + const auto res = current_response.load(); + if (!index || !res) + { + return; + } + + if (success) + { + res->ServerResponded(reinterpret_cast(1), *index); + } + else + { + res->ServerFailedToRespond(reinterpret_cast(1), *index); + } + + if (all_handled) + { + res->RefreshComplete(reinterpret_cast(1), eServerResponded); + } + } + + void ping_server(const game::netadr_t& server) + { + party::query_server(server, handle_server_respone); + } } void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, matchmaking_server_list_response* pRequestServersResponse) { - pRequestServersResponse->ServerResponded(reinterpret_cast(1), 0); - pRequestServersResponse->RefreshComplete(reinterpret_cast(1), eServerResponded); + current_response = pRequestServersResponse; + + server_list::request_servers([](const bool success, const std::unordered_set& s) + { + const auto res = current_response.load(); + if (!res) + { + return; + } + + if (!success) + { + res->RefreshComplete(reinterpret_cast(1), eServerFailedToRespond); + return; + } + + if (s.empty()) + { + res->RefreshComplete(reinterpret_cast(1), eNoServersListedOnMasterServer); + return; + } + + queried_servers.access([&s](servers& srvs) + { + srvs = {}; + srvs.reserve(s.size()); + + for (auto& address : s) + { + server new_server{}; + new_server.address = address; + new_server.server_item = create_server_item(address, {}, 0, false); + + srvs.push_back(new_server); + } + }); + + for (auto& srv : s) + { + ping_server(srv); + } + + }); + return reinterpret_cast(1); } @@ -120,11 +212,27 @@ namespace steam void matchmaking_servers::ReleaseRequest(void* hServerListRequest) { + current_response = nullptr; } gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) { - return get_server_item(); + if (reinterpret_cast(1) != hRequest) + { + return nullptr; + } + + static thread_local gameserveritem_t server_item{}; + return queried_servers.access([iServer](const servers& s) -> gameserveritem_t* + { + if (iServer < 0 || static_cast(iServer) >= s.size()) + { + return nullptr; + } + + server_item = s[iServer].server_item; + return &server_item; + }); } void matchmaking_servers::CancelQuery(void* hRequest) @@ -142,7 +250,15 @@ namespace steam int matchmaking_servers::GetServerCount(void* hRequest) { - return (reinterpret_cast(1) == hRequest) ? 1 : 0; + if (reinterpret_cast(1) != hRequest) + { + return 0; + } + + return queried_servers.access([](const servers& s) + { + return static_cast(s.size()); + }); } void matchmaking_servers::RefreshServer(void* hRequest, int iServer) @@ -155,15 +271,13 @@ namespace steam auto response = pRequestServersResponse; const auto addr = network::address_from_ip(htonl(unIP), usPort); - OutputDebugStringA(::utils::string::va("Sending: %u", (uint32_t)usPort)); - party::query_server( - addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info) + addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info, + const uint32_t ping) { - OutputDebugStringA(::utils::string::va("Responded: %s", success ? "true" : "false")); if (success) { - auto server_item = create_server_item(host, info); + auto server_item = create_server_item(host, info, ping, success); response->ServerResponded(server_item); } else From e8372463cd654d7722c52f1f62011d6b7a454330 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 19:08:47 +0100 Subject: [PATCH 08/29] Remove bad stats hook --- src/client/component/dedicated.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index ed3ed615..e02bade7 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -20,8 +20,8 @@ namespace dedicated void post_unpack() override { // Ignore "bad stats" - utils::hook::set(0x14052D523_g, 0xEB); - utils::hook::nop(0x14052D4E4_g, 2); + //utils::hook::set(0x14052D523_g, 0xEB); + //utils::hook::nop(0x14052D4E4_g, 2); // Fix tell command for IW4M utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub); From a15d6df35b3105440db643a633d8e5cf64b23f60 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 19:39:44 +0100 Subject: [PATCH 09/29] Regularly send server heatbeats --- src/client/component/dedicated.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index e02bade7..2839858a 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -2,6 +2,10 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" +#include "command.hpp" +#include "network.hpp" +#include "scheduler.hpp" +#include "server_list.hpp" #include @@ -13,6 +17,15 @@ namespace dedicated { game::SV_SendServerCommand(cl_0, type, "%c \"GAME_SERVER\x15: %s\"", 79, text); } + + void send_heartbeat() + { + game::netadr_t target{}; + if (server_list::get_master_server(target)) + { + network::send(target, "heartbeat", "T7"); + } + } } struct component final : server_component @@ -25,6 +38,10 @@ namespace dedicated // Fix tell command for IW4M utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub); + + scheduler::once(send_heartbeat, scheduler::pipeline::server); + scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min); + command::add("heartbeat", send_heartbeat); } }; } From a2f52bce23bb2f4674e9096410bc4352648edec7 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 19:53:48 +0100 Subject: [PATCH 10/29] Run dedis without steam --- src/client/component/steam_proxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/steam_proxy.cpp b/src/client/component/steam_proxy.cpp index c196feff..44f78805 100644 --- a/src/client/component/steam_proxy.cpp +++ b/src/client/component/steam_proxy.cpp @@ -205,7 +205,7 @@ namespace steam_proxy } } - struct component final : generic_component + struct component final : client_component { void post_load() override { From c040651b2d881778fcdc0ed203273fd0758c1123 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 19:59:17 +0100 Subject: [PATCH 11/29] Fix ip address order --- src/client/steam/interfaces/matchmaking_servers.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 0fc83c1a..6acd282c 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -32,7 +32,7 @@ namespace steam gameserveritem_t server{}; server.m_NetAdr.m_usConnectionPort = address.port; server.m_NetAdr.m_usQueryPort = address.port; - server.m_NetAdr.m_unIP = address.addr; + server.m_NetAdr.m_unIP = ntohl(address.addr); server.m_nPing = static_cast(ping); server.m_bHadSuccessfulResponse = success; server.m_bDoNotRefresh = false; @@ -174,7 +174,6 @@ namespace steam { ping_server(srv); } - }); return reinterpret_cast(1); From f94b77509431286848f09576b22d8f427f3d9b42 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 5 Feb 2023 20:06:17 +0100 Subject: [PATCH 12/29] Revert "Fix ip address order" This reverts commit 12da0044be1f717b7e2b15a3cfb07842f4a1da1e. --- src/client/steam/interfaces/matchmaking_servers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 6acd282c..0fc83c1a 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -32,7 +32,7 @@ namespace steam gameserveritem_t server{}; server.m_NetAdr.m_usConnectionPort = address.port; server.m_NetAdr.m_usQueryPort = address.port; - server.m_NetAdr.m_unIP = ntohl(address.addr); + server.m_NetAdr.m_unIP = address.addr; server.m_nPing = static_cast(ping); server.m_bHadSuccessfulResponse = success; server.m_bDoNotRefresh = false; @@ -174,6 +174,7 @@ namespace steam { ping_server(srv); } + }); return reinterpret_cast(1); From 74100dc869341be08ce9f6cbe0386edc69c1969c Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 6 Feb 2023 18:39:55 +0100 Subject: [PATCH 13/29] Fix some serverlist issues --- .../steam/interfaces/matchmaking_servers.cpp | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 0fc83c1a..c941d446 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -21,6 +21,8 @@ namespace steam gameserveritem_t server_item{}; }; + auto* const internet_request = reinterpret_cast(1); + using servers = std::vector; ::utils::concurrency::container queried_servers{}; @@ -32,7 +34,7 @@ namespace steam gameserveritem_t server{}; server.m_NetAdr.m_usConnectionPort = address.port; server.m_NetAdr.m_usQueryPort = address.port; - server.m_NetAdr.m_unIP = address.addr; + server.m_NetAdr.m_unIP = ntohl(address.addr); server.m_nPing = static_cast(ping); server.m_bHadSuccessfulResponse = success; server.m_bDoNotRefresh = false; @@ -111,16 +113,16 @@ namespace steam if (success) { - res->ServerResponded(reinterpret_cast(1), *index); + res->ServerResponded(internet_request, *index); } else { - res->ServerFailedToRespond(reinterpret_cast(1), *index); + res->ServerFailedToRespond(internet_request, *index); } if (all_handled) { - res->RefreshComplete(reinterpret_cast(1), eServerResponded); + res->RefreshComplete(internet_request, eServerResponded); } } @@ -145,13 +147,13 @@ namespace steam if (!success) { - res->RefreshComplete(reinterpret_cast(1), eServerFailedToRespond); + res->RefreshComplete(internet_request, eServerFailedToRespond); return; } if (s.empty()) { - res->RefreshComplete(reinterpret_cast(1), eNoServersListedOnMasterServer); + res->RefreshComplete(internet_request, eNoServersListedOnMasterServer); return; } @@ -177,7 +179,7 @@ namespace steam }); - return reinterpret_cast(1); + return internet_request; } void* matchmaking_servers::RequestLANServerList(unsigned int iApp, @@ -212,12 +214,15 @@ namespace steam void matchmaking_servers::ReleaseRequest(void* hServerListRequest) { - current_response = nullptr; + if (internet_request == hServerListRequest) + { + current_response = nullptr; + } } gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) { - if (reinterpret_cast(1) != hRequest) + if (internet_request != hRequest) { return nullptr; } @@ -250,7 +255,7 @@ namespace steam int matchmaking_servers::GetServerCount(void* hRequest) { - if (reinterpret_cast(1) != hRequest) + if (internet_request != hRequest) { return 0; } @@ -261,11 +266,31 @@ namespace steam }); } - void matchmaking_servers::RefreshServer(void* hRequest, int iServer) + void matchmaking_servers::RefreshServer(void* hRequest, const int iServer) { + if (internet_request != hRequest) + { + return; + } + + std::optional address{}; + queried_servers.access([&](const servers& s) + { + if (iServer < 0 || static_cast(iServer) >= s.size()) + { + return; + } + + address = s[iServer].address; + }); + + if (address) + { + ping_server(*address); + } } - void* matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort, + void* matchmaking_servers::PingServer(const unsigned int unIP, const unsigned short usPort, matchmaking_ping_response* pRequestServersResponse) { auto response = pRequestServersResponse; From ec11bffbaab8466ceebf86667a26066beb4646f4 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 6 Feb 2023 18:45:21 +0100 Subject: [PATCH 14/29] Remove empty line --- src/client/steam/interfaces/matchmaking_servers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index c941d446..3fc0e429 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -176,7 +176,6 @@ namespace steam { ping_server(srv); } - }); return internet_request; From 34291fb4d3c5ebd215deb81b2e0c44f776174b2c Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 7 Feb 2023 18:56:16 +0100 Subject: [PATCH 15/29] Correct max player count --- src/client/component/getinfo.cpp | 23 +++++++++++++------ src/client/game/symbols.hpp | 1 + .../steam/interfaces/matchmaking_servers.cpp | 4 ++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index 93de3f7d..b8a92a73 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -12,8 +12,6 @@ #include -#include "command.hpp" - namespace getinfo { namespace @@ -29,6 +27,16 @@ namespace getinfo return game::Dvar_GetString(dvar); } + int get_dvar_int(const char* dvar_name) + { + const auto dvar = game::Dvar_FindVar(dvar_name); + if (!dvar) + { + return {}; + } + + return game::Dvar_GetInt(dvar); + } int Com_SessionMode_GetGameMode() { @@ -52,15 +60,16 @@ namespace getinfo 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("hostname", get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname")); info.set("gametype", get_dvar_string("g_gametype")); //info.set("sv_motd", get_dvar_string("sv_motd")); + info.set("description", game::is_server() ? get_dvar_string("live_steam_server_description") : ""); 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("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1"); + info.set("clients", utils::string::va("%i", 0)); + info.set("bots", utils::string::va("%i", /*get_bot_count()*/0)); + info.set("sv_maxclients", utils::string::va("%i", get_dvar_int("com_maxclients"))); info.set("protocol", utils::string::va("%i", PROTOCOL)); info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 7ce4b347..2ee50eff 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -79,6 +79,7 @@ namespace game WEAK symbol Dvar_FindMalleableVar{0x1422BD6A0}; WEAK symbol Dvar_GetDebugName{0x1422BDCB0}; WEAK symbol Dvar_GetString{0x1422BFFF0, 0x140575E30}; + WEAK symbol Dvar_GetInt{0x1422BF2C0, 0x140575C20}; WEAK symbol Dvar_SetFromStringByName{ 0x1422C7F60 }; diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 3fc0e429..6c7a50bd 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -40,12 +40,12 @@ namespace steam server.m_bDoNotRefresh = false; strcpy_s(server.m_szGameDir, ""); strcpy_s(server.m_szMap, info.get("mapname").data()); - strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server"); + strcpy_s(server.m_szGameDescription, info.get("description").data()); server.m_nAppID = 311210; server.m_nPlayers = atoi(info.get("clients").data()); server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data()); server.m_nBotPlayers = atoi(info.get("bots").data()); - server.m_bPassword = false; + server.m_bPassword = info.get("isPrivate") == "1"; server.m_bSecure = true; server.m_ulTimeLastPlayed = 0; server.m_nServerVersion = 1000; From 736c4735c0b962eda4065b705f35dd530a6a504a Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 7 Feb 2023 19:21:25 +0100 Subject: [PATCH 16/29] Fix client counts --- src/client/component/getinfo.cpp | 27 +++++++++++++++++-- .../steam/interfaces/matchmaking_servers.cpp | 4 +-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index b8a92a73..c1b20bd6 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -38,6 +38,29 @@ namespace getinfo return game::Dvar_GetInt(dvar); } + int get_max_client_count() + { + return get_dvar_int("com_maxclients"); + } + + int get_client_count() + { + int count =1; + const auto client_states = *reinterpret_cast(game::select(0x1576FB318, 0x14A178E98)); + const auto object_length = game::is_server() ? 0xE5110 : 0xE5170; + + for(int i = 0; i < get_max_client_count(); ++i) + { + const auto client_state = *reinterpret_cast(client_states + (i * object_length)); + if(client_state > 0) + { + ++count; + } + } + + return count; + } + int Com_SessionMode_GetGameMode() { return *reinterpret_cast(game::select(0x1568EF7F4, 0x14948DB04)) << 14 >> 28; @@ -67,9 +90,9 @@ namespace getinfo 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", 0)); + info.set("clients", utils::string::va("%i", get_client_count())); info.set("bots", utils::string::va("%i", /*get_bot_count()*/0)); - info.set("sv_maxclients", utils::string::va("%i", get_dvar_int("com_maxclients"))); + info.set("sv_maxclients", utils::string::va("%i", get_max_client_count())); info.set("protocol", utils::string::va("%i", PROTOCOL)); info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index 6c7a50bd..e9029ef8 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -55,10 +55,10 @@ namespace steam const auto mode = game::eModes(std::atoi(playmode.data())); const auto* tags = ::utils::string::va( - R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\0)", + R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\%d)", info.get("gametype").data(), info.get("dedicated") == "1" ? "true" : "false", - mode == game::MODE_ZOMBIES ? "true" : "false"); + mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers); strcpy_s(server.m_szGameTags, tags); server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); From 3ec1361ed96c9c9f256629d82dcde52e852adf50 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Feb 2023 19:27:03 +0100 Subject: [PATCH 17/29] Remove debug count --- src/client/component/getinfo.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index c1b20bd6..1bfbc829 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -45,14 +45,14 @@ namespace getinfo int get_client_count() { - int count =1; + int count = 0; const auto client_states = *reinterpret_cast(game::select(0x1576FB318, 0x14A178E98)); const auto object_length = game::is_server() ? 0xE5110 : 0xE5170; - for(int i = 0; i < get_max_client_count(); ++i) + for (int i = 0; i < get_max_client_count(); ++i) { const auto client_state = *reinterpret_cast(client_states + (i * object_length)); - if(client_state > 0) + if (client_state > 0) { ++count; } @@ -71,7 +71,7 @@ namespace getinfo { return (rand() % 2) + 1; } - + struct component final : generic_component { void post_unpack() override From cf61232020dc2a8408bc960e033b76578e8735e9 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Wed, 8 Feb 2023 18:27:30 +0100 Subject: [PATCH 18/29] Fix dedicated server heartbeats --- src/client/component/dedicated.cpp | 22 +++++++++++++-------- src/client/component/dedicated.hpp | 6 ++++++ src/client/steam/interfaces/game_server.cpp | 2 ++ 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/client/component/dedicated.hpp diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 2839858a..92da00c6 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -1,4 +1,5 @@ #include +#include "dedicated.hpp" #include "loader/component_loader.hpp" #include "game/game.hpp" @@ -13,18 +14,24 @@ namespace dedicated { namespace { - void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt, [[maybe_unused]] int c, char* text) + void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt, + [[maybe_unused]] int c, char* text) { game::SV_SendServerCommand(cl_0, type, "%c \"GAME_SERVER\x15: %s\"", 79, text); } + } - void send_heartbeat() + void send_heartbeat() + { + if (!game::is_server()) { - game::netadr_t target{}; - if (server_list::get_master_server(target)) - { - network::send(target, "heartbeat", "T7"); - } + return; + } + + game::netadr_t target{}; + if (server_list::get_master_server(target)) + { + network::send(target, "heartbeat", "T7"); } } @@ -39,7 +46,6 @@ namespace dedicated // Fix tell command for IW4M utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub); - scheduler::once(send_heartbeat, scheduler::pipeline::server); scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min); command::add("heartbeat", send_heartbeat); } diff --git a/src/client/component/dedicated.hpp b/src/client/component/dedicated.hpp new file mode 100644 index 00000000..140587ba --- /dev/null +++ b/src/client/component/dedicated.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace dedicated +{ + void send_heartbeat(); +} diff --git a/src/client/steam/interfaces/game_server.cpp b/src/client/steam/interfaces/game_server.cpp index 11e7a79d..8127c377 100644 --- a/src/client/steam/interfaces/game_server.cpp +++ b/src/client/steam/interfaces/game_server.cpp @@ -1,5 +1,6 @@ #include #include "../steam.hpp" +#include "../../component/dedicated.hpp" namespace steam { @@ -182,6 +183,7 @@ namespace steam void game_server::EnableHeartbeats(bool bActive) { + dedicated::send_heartbeat(); } void game_server::SetHeartbeatInterval(int iHeartbeatInterval) From ad77c9b387cfb60be59f19ff54b2f10d5f10d6d6 Mon Sep 17 00:00:00 2001 From: Edo Date: Thu, 9 Feb 2023 00:54:45 +0000 Subject: [PATCH 19/29] maint: update build.yml --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d69c73f..aeaf7473 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - Release steps: - name: Check out files - uses: actions/checkout@v3 + uses: actions/checkout@v3.3.0 with: submodules: true fetch-depth: 0 @@ -33,7 +33,7 @@ jobs: lfs: false - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.1.3 + uses: microsoft/setup-msbuild@v1.3.1 - name: Generate project files run: tools/premake5 vs2022 @@ -45,7 +45,7 @@ jobs: run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=x64 build/boiii.sln - name: Upload ${{matrix.configuration}} bundle - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v3.1.2 with: name: ${{matrix.configuration}} Bundle path: | @@ -54,7 +54,7 @@ jobs: - name: Upload ${{matrix.configuration}} binary if: matrix.configuration == 'Release' - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v3.1.2 with: name: ${{matrix.configuration}} Binary path: | @@ -62,7 +62,7 @@ jobs: - name: Upload version if: matrix.configuration == 'Release' - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v3.1.2 with: name: Version path: | From b1693514a02d3c633bcb28db0784aeeb018e72c4 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Thu, 9 Feb 2023 18:27:07 +0100 Subject: [PATCH 20/29] Remove leftovers --- src/client/steam/steam.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client/steam/steam.cpp b/src/client/steam/steam.cpp index 1d4f500c..135aa348 100644 --- a/src/client/steam/steam.cpp +++ b/src/client/steam/steam.cpp @@ -104,8 +104,6 @@ namespace steam results_.clear(); } - //extern "C" { - bool SteamAPI_RestartAppIfNecessary() { return false; @@ -282,6 +280,4 @@ namespace steam static user_stats user_stats; return &user_stats; } - - //} } From df1afddf8840891c4ca9a6f61803579860b18f0d Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Fri, 10 Feb 2023 15:00:53 +0100 Subject: [PATCH 21/29] Build dev builds by default atm We're in a dev/testing phase, so let's do dev-builds --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aeaf7473..17fe4e32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: uses: microsoft/setup-msbuild@v1.3.1 - name: Generate project files - run: tools/premake5 vs2022 + run: tools/premake5 vs2022 --dev-build - name: Set up problem matching uses: ammaraskar/msvc-problem-matcher@master From d8e862819bf52a3f5995622d213b4702572c413b Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 10:10:54 +0100 Subject: [PATCH 22/29] I'm a fucking retard. This fixes #145 --- src/client/component/party.cpp | 3 ++- src/client/game/demonware/byte_buffer.cpp | 3 ++- .../steam/interfaces/matchmaking_servers.cpp | 10 +++++----- src/common/utils/string.cpp | 19 +++++++++++++++++++ src/common/utils/string.hpp | 8 ++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 7328bc94..8549e67e 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -7,6 +7,7 @@ #include "scheduler.hpp" #include +#include #include #include #include @@ -127,7 +128,7 @@ namespace party host.info.netAdr = addr; host.info.xuid = xuid; - strcpy_s(host.info.name, hostname.data()); + utils::string::copy(host.info.name, hostname.data()); host.lobbyType = game::LOBBY_TYPE_PRIVATE; host.lobbyParams.networkMode = game::LOBBY_NETWORKMODE_LIVE; diff --git a/src/client/game/demonware/byte_buffer.cpp b/src/client/game/demonware/byte_buffer.cpp index af5bee9a..906b521f 100644 --- a/src/client/game/demonware/byte_buffer.cpp +++ b/src/client/game/demonware/byte_buffer.cpp @@ -1,5 +1,6 @@ #include #include "byte_buffer.hpp" +#include namespace demonware { @@ -90,7 +91,7 @@ namespace demonware { if (!this->read_data_type(16)) return false; - strcpy_s(output, length, const_cast(this->buffer_.data()) + this->current_byte_); + utils::string::copy(output, static_cast(length), const_cast(this->buffer_.data()) + this->current_byte_); this->current_byte_ += strlen(output) + 1; return true; diff --git a/src/client/steam/interfaces/matchmaking_servers.cpp b/src/client/steam/interfaces/matchmaking_servers.cpp index e9029ef8..8f772b65 100644 --- a/src/client/steam/interfaces/matchmaking_servers.cpp +++ b/src/client/steam/interfaces/matchmaking_servers.cpp @@ -38,9 +38,9 @@ namespace steam server.m_nPing = static_cast(ping); server.m_bHadSuccessfulResponse = success; server.m_bDoNotRefresh = false; - strcpy_s(server.m_szGameDir, ""); - strcpy_s(server.m_szMap, info.get("mapname").data()); - strcpy_s(server.m_szGameDescription, info.get("description").data()); + ::utils::string::copy(server.m_szGameDir, ""); + ::utils::string::copy(server.m_szMap, info.get("mapname").data()); + ::utils::string::copy(server.m_szGameDescription, info.get("description").data()); server.m_nAppID = 311210; server.m_nPlayers = atoi(info.get("clients").data()); server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data()); @@ -49,7 +49,7 @@ namespace steam server.m_bSecure = true; server.m_ulTimeLastPlayed = 0; server.m_nServerVersion = 1000; - strcpy_s(server.m_szServerName, info.get("hostname").data()); + ::utils::string::copy(server.m_szServerName, info.get("hostname").data()); const auto playmode = info.get("playmode"); const auto mode = game::eModes(std::atoi(playmode.data())); @@ -60,7 +60,7 @@ namespace steam info.get("dedicated") == "1" ? "true" : "false", mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers); - strcpy_s(server.m_szGameTags, tags); + ::utils::string::copy(server.m_szGameTags, tags); server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16); return server; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 3313eca9..cf030dd4 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -174,4 +174,23 @@ namespace utils::string return str; } + + void copy(char* dest, const size_t max_size, const char* src) + { + for (size_t i = 0;; ++i) + { + if (i + 1 == max_size) + { + dest[i] = 0; + break; + } + + dest[i] = src[i]; + + if (!src[i]) + { + break; + } + } + } } diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index edc5cc1b..6a2cdc76 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -94,4 +94,12 @@ namespace utils::string std::wstring convert(const std::string& str); std::string replace(std::string str, const std::string& from, const std::string& to); + + void copy(char* dest, size_t max_size, const char* src); + + template + void copy(char (&dest)[Size], const char* src) + { + copy(dest, Size, src); + } } From 54b15224ddc92b6dec0616038e213d301f6e57a6 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 10:15:00 +0100 Subject: [PATCH 23/29] Small fix --- src/common/utils/string.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index cf030dd4..d440c99e 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -177,6 +177,11 @@ namespace utils::string void copy(char* dest, const size_t max_size, const char* src) { + if (!max_size) + { + return; + } + for (size_t i = 0;; ++i) { if (i + 1 == max_size) From 21b0e419bd3ae9779221d3b2f6e5b7b4e6701988 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 10:51:15 +0100 Subject: [PATCH 24/29] Automatically trigger map rotation This maybe fixes #129 --- src/client/component/dedicated.cpp | 15 +++++++++++++ src/client/component/getinfo.cpp | 36 +++++++----------------------- src/client/component/party.cpp | 2 +- src/client/game/utils.cpp | 29 ++++++++++++++++++++++++ src/client/game/utils.hpp | 7 ++++++ 5 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 src/client/game/utils.cpp create mode 100644 src/client/game/utils.hpp diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 92da00c6..8fae056d 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -3,6 +3,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" +#include "game/utils.hpp" #include "command.hpp" #include "network.hpp" #include "scheduler.hpp" @@ -35,6 +36,17 @@ namespace dedicated } } + void trigger_map_rotation() + { + scheduler::once([] + { + if (!game::get_dvar_string("sv_maprotation").empty()) + { + game::Cbuf_AddText(0, "map_rotate\n"); + } + }, scheduler::pipeline::main, 1s); + } + struct component final : server_component { void post_unpack() override @@ -48,6 +60,9 @@ namespace dedicated scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min); command::add("heartbeat", send_heartbeat); + + // Hook GScr_ExitLevel + utils::hook::jump(0x1402D1AA0_g, trigger_map_rotation); } }; } diff --git a/src/client/component/getinfo.cpp b/src/client/component/getinfo.cpp index 1bfbc829..72a7db2c 100644 --- a/src/client/component/getinfo.cpp +++ b/src/client/component/getinfo.cpp @@ -12,35 +12,15 @@ #include +#include "game/utils.hpp" + 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); - } - - int get_dvar_int(const char* dvar_name) - { - const auto dvar = game::Dvar_FindVar(dvar_name); - if (!dvar) - { - return {}; - } - - return game::Dvar_GetInt(dvar); - } - int get_max_client_count() { - return get_dvar_int("com_maxclients"); + return game::get_dvar_int("com_maxclients"); } int get_client_count() @@ -83,13 +63,13 @@ namespace getinfo utils::info_string info{}; info.set("challenge", std::string(data.begin(), data.end())); info.set("gamename", "T7"); - info.set("hostname", get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname")); - info.set("gametype", get_dvar_string("g_gametype")); + info.set("hostname", game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname")); + info.set("gametype", game::get_dvar_string("g_gametype")); //info.set("sv_motd", get_dvar_string("sv_motd")); - info.set("description", game::is_server() ? get_dvar_string("live_steam_server_description") : ""); + info.set("description", game::is_server() ? game::get_dvar_string("live_steam_server_description") : ""); 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("mapname", game::get_dvar_string("mapname")); + info.set("isPrivate", game::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()*/0)); info.set("sv_maxclients", utils::string::va("%i", get_max_client_count())); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 8549e67e..84300691 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -190,7 +190,7 @@ namespace party //const auto hostname = info.get("sv_hostname"); const auto playmode = info.get("playmode"); - const auto mode = game::eModes(std::atoi(playmode.data())); + const auto mode = static_cast(std::atoi(playmode.data())); //const auto xuid = strtoull(info.get("xuid").data(), nullptr, 16); scheduler::once([=] diff --git a/src/client/game/utils.cpp b/src/client/game/utils.cpp new file mode 100644 index 00000000..92ff426a --- /dev/null +++ b/src/client/game/utils.cpp @@ -0,0 +1,29 @@ +#include + +#include "game.hpp" +#include "utils.hpp" + +namespace game +{ + std::string get_dvar_string(const char* dvar_name) + { + const auto dvar = Dvar_FindVar(dvar_name); + if (!dvar) + { + return {}; + } + + return Dvar_GetString(dvar); + } + + int get_dvar_int(const char* dvar_name) + { + const auto dvar = Dvar_FindVar(dvar_name); + if (!dvar) + { + return {}; + } + + return Dvar_GetInt(dvar); + } +} diff --git a/src/client/game/utils.hpp b/src/client/game/utils.hpp new file mode 100644 index 00000000..5bd1a9a4 --- /dev/null +++ b/src/client/game/utils.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace game +{ + std::string get_dvar_string(const char* dvar_name); + int get_dvar_int(const char* dvar_name); +} From 2826053c666795efda16e3f68e9db512cafbbba9 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 11:33:33 +0100 Subject: [PATCH 25/29] Delay sending the heartbeat packet --- src/client/component/dedicated.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 8fae056d..fd719503 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -20,6 +20,15 @@ namespace dedicated { game::SV_SendServerCommand(cl_0, type, "%c \"GAME_SERVER\x15: %s\"", 79, text); } + + void send_heartbeat_packet() + { + game::netadr_t target{}; + if (server_list::get_master_server(target)) + { + network::send(target, "heartbeat", "T7"); + } + } } void send_heartbeat() @@ -29,11 +38,7 @@ namespace dedicated return; } - game::netadr_t target{}; - if (server_list::get_master_server(target)) - { - network::send(target, "heartbeat", "T7"); - } + scheduler::once(send_heartbeat_packet, scheduler::pipeline::main, 3s); } void trigger_map_rotation() From 97bb7ae49ba08c3c097f7098ba23e9500ecb6054 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 14:11:24 +0100 Subject: [PATCH 26/29] Send heartbeat after maprotation --- src/client/component/dedicated.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index fd719503..6a5cde7f 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -38,7 +38,7 @@ namespace dedicated return; } - scheduler::once(send_heartbeat_packet, scheduler::pipeline::main, 3s); + scheduler::once(send_heartbeat_packet, scheduler::pipeline::main, 5s); } void trigger_map_rotation() @@ -48,6 +48,7 @@ namespace dedicated if (!game::get_dvar_string("sv_maprotation").empty()) { game::Cbuf_AddText(0, "map_rotate\n"); + send_heartbeat(); } }, scheduler::pipeline::main, 1s); } @@ -63,7 +64,7 @@ namespace dedicated // Fix tell command for IW4M utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub); - scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min); + scheduler::loop(send_heartbeat, scheduler::pipeline::main, 5min); command::add("heartbeat", send_heartbeat); // Hook GScr_ExitLevel From ce4ab4b7cc685320bd9d2a017390b2f12b8cff86 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Sun, 12 Feb 2023 18:21:06 +0100 Subject: [PATCH 27/29] Use curl for http requests Fixes #150 --- .gitmodules | 3 + deps/curl | 1 + deps/premake/curl.lua | 73 ++++++++++++++++++++ src/common/utils/http.cpp | 138 ++++++++++++++++++++++++++++++-------- src/common/utils/http.hpp | 6 +- 5 files changed, 190 insertions(+), 31 deletions(-) create mode 160000 deps/curl create mode 100644 deps/premake/curl.lua diff --git a/.gitmodules b/.gitmodules index 9e7892ca..5c593a25 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "deps/stb"] path = deps/stb url = https://github.com/nothings/stb.git +[submodule "deps/curl"] + path = deps/curl + url = https://github.com/curl/curl.git diff --git a/deps/curl b/deps/curl new file mode 160000 index 00000000..7ce140ba --- /dev/null +++ b/deps/curl @@ -0,0 +1 @@ +Subproject commit 7ce140ba97de1bf3e27299a72b0cc229c9e1364e diff --git a/deps/premake/curl.lua b/deps/premake/curl.lua new file mode 100644 index 00000000..8db164e1 --- /dev/null +++ b/deps/premake/curl.lua @@ -0,0 +1,73 @@ +curl = { + source = path.join(dependencies.basePath, "curl"), +} + +function curl.import() + links { "curl" } + + filter "toolset:msc*" + links { "Crypt32.lib" } + filter {} + + curl.includes() +end + +function curl.includes() +filter "toolset:msc*" + includedirs { + path.join(curl.source, "include"), + } + + defines { + "CURL_STRICTER", + "CURL_STATICLIB", + "CURL_DISABLE_LDAP", + } +filter {} +end + +function curl.project() + if not os.istarget("windows") then + return + end + + project "curl" + language "C" + + curl.includes() + + includedirs { + path.join(curl.source, "lib"), + } + + files { + path.join(curl.source, "lib/**.c"), + path.join(curl.source, "lib/**.h"), + } + + defines { + "BUILDING_LIBCURL", + } + + filter "toolset:msc*" + + defines { + "USE_SCHANNEL", + "USE_WINDOWS_SSPI", + "USE_THREADS_WIN32", + } + + filter "toolset:not msc*" + + defines { + "USE_GNUTLS", + "USE_THREADS_POSIX", + } + + filter {} + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, curl) \ No newline at end of file diff --git a/src/common/utils/http.cpp b/src/common/utils/http.cpp index 3cb59991..a208ac67 100644 --- a/src/common/utils/http.cpp +++ b/src/common/utils/http.cpp @@ -1,48 +1,128 @@ #include "http.hpp" -#include "nt.hpp" -#include +#include +#include "finally.hpp" + +#pragma comment(lib, "ws2_32.lib") namespace utils::http { - std::optional get_data(const std::string& url) + namespace { - CComPtr stream; + struct progress_helper + { + const std::function* callback{}; + std::exception_ptr exception{}; + }; - if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr))) + int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, + const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/) + { + auto* helper = static_cast(clientp); + + try + { + if (*helper->callback) + { + (*helper->callback)(dlnow); + } + } + catch (...) + { + helper->exception = std::current_exception(); + return -1; + } + + return 0; + } + + size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp) + { + auto* buffer = static_cast(userp); + + const auto total_size = size * nmemb; + buffer->append(static_cast(contents), total_size); + return total_size; + } + } + + std::optional get_data(const std::string& url, const headers& headers, + const std::function& callback, const uint32_t retries) + { + curl_slist* header_list = nullptr; + auto* curl = curl_easy_init(); + if (!curl) { return {}; } - char buffer[0x1000]; - std::string result; - - HRESULT status{}; - - do - { - DWORD bytes_read = 0; - status = stream->Read(buffer, sizeof(buffer), &bytes_read); - - if (bytes_read > 0) + auto _ = utils::finally([&]() { - result.append(buffer, bytes_read); + curl_slist_free_all(header_list); + curl_easy_cleanup(curl); + }); + + for (const auto& header : headers) + { + auto data = header.first + ": " + header.second; + header_list = curl_slist_append(header_list, data.data()); + } + + std::string buffer{}; + progress_helper helper{}; + helper.callback = &callback; + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "xlabs-updater/1.0"); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + + for (auto i = 0u; i < retries + 1; ++i) + { + // Due to CURLOPT_FAILONERROR, CURLE_OK will not be met when the server returns 400 or 500 + if (curl_easy_perform(curl) == CURLE_OK) + { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code >= 200) + { + return { std::move(buffer) }; + } + + throw std::runtime_error( + "Bad status code " + std::to_string(http_code) + " met while trying to download file " + url); + } + + if (helper.exception) + { + std::rethrow_exception(helper.exception); + } + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code > 0) + { + break; } } - while (SUCCEEDED(status) && status != S_FALSE); - if (FAILED(status)) - { - return {}; - } - - return {result}; + return {}; } - std::future> get_data_async(const std::string& url) + std::future> get_data_async(const std::string& url, const headers& headers) { - return std::async(std::launch::async, [url]() - { - return get_data(url); - }); + return std::async(std::launch::async, [url, headers]() + { + return get_data(url, headers); + }); } } diff --git a/src/common/utils/http.hpp b/src/common/utils/http.hpp index 65628a9f..2fb650b3 100644 --- a/src/common/utils/http.hpp +++ b/src/common/utils/http.hpp @@ -6,6 +6,8 @@ namespace utils::http { - std::optional get_data(const std::string& url); - std::future> get_data_async(const std::string& url); + using headers = std::unordered_map; + + std::optional get_data(const std::string& url, const headers& headers = {}, const std::function& callback = {}, uint32_t retries = 2); + std::future> get_data_async(const std::string& url, const headers& headers = {}); } From 257a0c33caa5efbce831f6b9ffdf4134cb42d099 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 12 Feb 2023 18:54:03 +0100 Subject: [PATCH 28/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45b65b4c..0f66f0c6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3. Very experimental - [ ] Disable Anti-Debugging Mechanisms (probably never gonna happen cause who needs that if you have printf debugging) - [x] Process wrapper - [x] P2P multiplayer -- [ ] Dedicated Servers +- [x] Dedicated Servers ## Disclaimer From 39e442c95316c97c8eae7e2beae3fe373de53177 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Sun, 12 Feb 2023 19:29:54 +0100 Subject: [PATCH 29/29] Restrict curl --- deps/premake/curl.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/deps/premake/curl.lua b/deps/premake/curl.lua index 8db164e1..d4ef2684 100644 --- a/deps/premake/curl.lua +++ b/deps/premake/curl.lua @@ -21,7 +21,20 @@ filter "toolset:msc*" defines { "CURL_STRICTER", "CURL_STATICLIB", + "CURL_DISABLE_DICT", + "CURL_DISABLE_FILE", "CURL_DISABLE_LDAP", + "CURL_DISABLE_LDAPS", + "CURL_DISABLE_FTP", + "CURL_DISABLE_GOPHER", + "CURL_DISABLE_IMAP", + "CURL_DISABLE_MQTT", + "CURL_DISABLE_POP3", + "CURL_DISABLE_RTSP", + "CURL_DISABLE_SMTP", + "CURL_DISABLE_SMB", + "CURL_DISABLE_TELNET", + "CURL_DISABLE_TFTP", } filter {} end @@ -70,4 +83,4 @@ function curl.project() kind "StaticLib" end -table.insert(dependencies, curl) \ No newline at end of file +table.insert(dependencies, curl)