From aef16d1e0c25e5040695f7110f95bced6c7f8bda Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:57:06 +0200 Subject: [PATCH 01/12] Allow custom gamesettings to be loaded from disk --- src/client/component/gamesettings.cpp | 135 ++++++++++++++++++++++++++ src/client/game/symbols.hpp | 6 ++ 2 files changed, 141 insertions(+) create mode 100644 src/client/component/gamesettings.cpp diff --git a/src/client/component/gamesettings.cpp b/src/client/component/gamesettings.cpp new file mode 100644 index 00000000..9d672b76 --- /dev/null +++ b/src/client/component/gamesettings.cpp @@ -0,0 +1,135 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include "scheduler.hpp" + +#include +#include +#include + +namespace gamesettings +{ + namespace + { + // + std::unordered_map gamesettings_files; + + const std::string get_gamesettings_name(const std::vector& sub_strings) + { + if (sub_strings.size() > 2) + { + return sub_strings[sub_strings.size() - 2] + '/' + sub_strings[sub_strings.size() - 1]; + } + + return ""; + } + + const std::string get_gamesettings_path(const std::string& name) + { + const auto itr = gamesettings_files.find(name); + return (itr == gamesettings_files.end()) ? "" : itr->second; + } + + void search_gamesettings_folder(const std::string& gamesettings_dir) + { + if (!utils::io::directory_exists(gamesettings_dir)) + { + return; + } + + const auto files = utils::io::list_files(gamesettings_dir, true); + + for (const auto& path : files) + { + if (!std::filesystem::is_directory(path)) + { + auto sub_strings = utils::string::split(path.generic_string(), '/'); + gamesettings_files.insert_or_assign(get_gamesettings_name(sub_strings), path.generic_string()); + } + } + } + + bool has_gamesettings_file_on_disk(const char* path) + { + if (!path) + { + return false; + } + + auto sub_strings = utils::string::split(path, '/'); + auto gamesettings_name = get_gamesettings_name(sub_strings); + + return get_gamesettings_path(gamesettings_name) != ""; + } + + void cmd_exec_stub(utils::hook::assembler& a) + { + const auto exec_from_fastfile = a.newLabel(); + const auto exec_from_disk = a.newLabel(); + + a.pushad64(); + + a.mov(rcx, r10); + a.call_aligned(has_gamesettings_file_on_disk); + a.cmp(rax, 1); +; + a.popad64(); + + a.jnz(exec_from_fastfile); + + a.bind(exec_from_disk); + a.jmp(game::select(0x1420ED087, 0x1404F855E)); + + a.bind(exec_from_fastfile); + a.lea(rdx, ptr(rsp, (game::is_server() ? 0x30 : 0x40))); + a.jmp(game::select(0x1420ED007, 0x1404F853F)); + } + + int read_file_stub(const char* qpath, void** buffer) + { + auto sub_strings = utils::string::split(qpath, '/'); + auto game_settings_name = get_gamesettings_name(sub_strings); + + std::string gamesettings_data; + utils::io::read_file(get_gamesettings_path(game_settings_name), &gamesettings_data); + + if (!gamesettings_data.empty()) + { + ++(*game::fs_loadStack); + + auto len = static_cast(gamesettings_data.length()); + auto buf = game::FS_AllocMem(len + 1); + + *buffer = buf; + gamesettings_data.copy(reinterpret_cast(*buffer), len); + buf[len] = '\0'; + + return len; + } + + return utils::hook::invoke(game::select(0x1422A48D0, 0x140564F70), qpath, buffer); + } + + void search_gamesettings_files_on_disk() + { + const utils::nt::library host{}; + + search_gamesettings_folder((host.get_folder() / "boiii/gamesettings").string()); + search_gamesettings_folder((game::get_appdata_path() / "data/gamesettings").string()); + } + } + + struct component final : generic_component + { + void post_unpack() override + { + search_gamesettings_files_on_disk(); + + utils::hook::call(game::select(0x1420ED0A1, 0x1404F857D), read_file_stub); + utils::hook::jump(game::select(0x1420ED002, 0x1404F853A), utils::hook::assemble(cmd_exec_stub)); + } + }; +}; + +REGISTER_COMPONENT(gamesettings::component) diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index f754090f..422a74c3 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -35,6 +35,7 @@ namespace game WEAK symbol Com_SwitchMode{ 0x14214A4D0 }; + WEAK symbol Com_LoadRawTextFile{0x1420F61B0}; WEAK symbol Cbuf_AddText{0x1420EC010, 0x1404F75B0}; WEAK symbol Cbuf_ExecuteBuffer{ @@ -180,6 +181,9 @@ namespace game WEAK symbol SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0}; WEAK symbol SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670}; + // FS + WEAK symbol FS_AllocMem{0x1422AC9F0, 0x14056C340}; + // Utils WEAK symbol I_CleanStr{0x1422E9050, 0x140580E80}; @@ -198,6 +202,8 @@ namespace game WEAK symbol s_dvarPool{0x157AC6220, 0x14A3CB620}; WEAK symbol g_dvarCount{0x157AC61CC, 0x14A3CB5FC}; + WEAK symbol fs_loadStack{0x157A65310, 0x14A39C650}; + // Client and dedi struct size differs :( WEAK symbol svs_clients_cl{0x1576F9318, 0}; WEAK symbol svs_clients{0x0, 0x14A178E98}; From 825fc9f6f081926ef279bc3401ea42811e468eae Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:59:08 +0200 Subject: [PATCH 02/12] Stop executing default steam config files Since we're setting up our config files differently for dedicated servers this is no longer necessary. --- src/client/component/dedicated_patches.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/component/dedicated_patches.cpp b/src/client/component/dedicated_patches.cpp index 6e7f249e..80a0245c 100644 --- a/src/client/component/dedicated_patches.cpp +++ b/src/client/component/dedicated_patches.cpp @@ -104,6 +104,9 @@ namespace dedicated_patches utils::hook::jump(0x14052F0F5_g, 0x14052F139_g); utils::hook::call(0x1402853D7_g, sv_get_player_xuid_stub); // PlayerCmd_GetXuid + + // Stop executing default_dedicated.cfg & language_settings.cfg + utils::hook::set(0x1405063C0_g, 0xC3); } }; } From 6c1cb108d0e80bb476cc8cd78e37f6c5c76e6256 Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:59:58 +0200 Subject: [PATCH 03/12] Fix typo in gamesettings_escort.cfg --- data/gamesettings/mp/gamesettings_escort.cfg | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 data/gamesettings/mp/gamesettings_escort.cfg diff --git a/data/gamesettings/mp/gamesettings_escort.cfg b/data/gamesettings/mp/gamesettings_escort.cfg new file mode 100644 index 00000000..5b713e17 --- /dev/null +++ b/data/gamesettings/mp/gamesettings_escort.cfg @@ -0,0 +1,26 @@ +gametype_setting timelimit 5 +gametype_setting scorelimit 0 +gametype_setting roundscorelimit 1 +gametype_setting roundwinlimit 2 +gametype_setting roundlimit 2 +gametype_setting preroundperiod 10 +gametype_setting teamCount 2 + +gametype_setting shutdownDamage 3 +gametype_setting bootTime 5 +gametype_setting rebootTime 15 +gametype_setting rebootPlayers 0 +gametype_setting movePlayers 1 +gametype_setting robotSpeed 1 +gametype_setting robotShield 0 + + +gametype_setting scoreHeroPowerGainFactor 0.788 //Score earned towards Hero Weapons and Abilities are multiplied by this factor +gametype_setting scoreHeroPowerTimeFactor 0.788 + +gametype_setting spawntraptriggertime 5 + +gametype_setting disableVehicleSpawners 1 + +gametype_setting gameAdvertisementRuleTimeLeft 3.5 +gametype_setting gameAdvertisementRuleRound 3 From bd7355bf094e328d5c4d7db73f0eb171008ca115 Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Sun, 9 Apr 2023 19:22:52 +0200 Subject: [PATCH 04/12] Give priority too boiii folder in root directory --- src/client/component/gamesettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/gamesettings.cpp b/src/client/component/gamesettings.cpp index 9d672b76..713a2a0b 100644 --- a/src/client/component/gamesettings.cpp +++ b/src/client/component/gamesettings.cpp @@ -115,8 +115,8 @@ namespace gamesettings { const utils::nt::library host{}; - search_gamesettings_folder((host.get_folder() / "boiii/gamesettings").string()); search_gamesettings_folder((game::get_appdata_path() / "data/gamesettings").string()); + search_gamesettings_folder((host.get_folder() / "boiii/gamesettings").string()); } } From 229beb1bb708e1d9104c22983b1b15019f4c57e9 Mon Sep 17 00:00:00 2001 From: BrentVL-1952840 <70229620+Brentdevent@users.noreply.github.com> Date: Sun, 9 Apr 2023 19:48:17 +0200 Subject: [PATCH 05/12] Remove unused include --- src/client/component/gamesettings.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/component/gamesettings.cpp b/src/client/component/gamesettings.cpp index 713a2a0b..c511ef5c 100644 --- a/src/client/component/gamesettings.cpp +++ b/src/client/component/gamesettings.cpp @@ -2,8 +2,6 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" -#include "scheduler.hpp" - #include #include #include From 9326405be31cbb24f611137bf9268b74387026a3 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Mon, 10 Apr 2023 09:02:07 +0200 Subject: [PATCH 06/12] Add profile info logging --- src/client/component/profile_infos.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/client/component/profile_infos.cpp b/src/client/component/profile_infos.cpp index 597be96a..16bd8dbc 100644 --- a/src/client/component/profile_infos.cpp +++ b/src/client/component/profile_infos.cpp @@ -100,6 +100,10 @@ namespace profile_infos } else { +#ifdef DEV_BUILD + printf("Erasing profile info: %llX\n", i->first); +#endif + i = profiles.erase(i); } } @@ -126,6 +130,10 @@ namespace profile_infos return; } +#ifdef DEV_BUILD + printf("Adding profile info: %llX\n", user_id); +#endif + profile_mapping.access([&](profile_map& profiles) { profiles[user_id] = info; @@ -202,7 +210,17 @@ namespace profile_infos if (profile_entry != profiles.end()) { result = profile_entry->second; + +#ifdef DEV_BUILD + printf("Requesting profile info: %llX - good\n", user_id); +#endif } +#ifdef DEV_BUILD + else + { + printf("Requesting profile info: %llX - bad\n", user_id); + } +#endif return result; }); From 008790b1ba223e9eddb85196a587d91f0624b59f Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 10 Apr 2023 10:10:22 +0200 Subject: [PATCH 07/12] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c75df1e0..821ee1a3 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ # BOIII ☄️ -Reverse engineering and analysis of Call of Duty: Black Ops 3. +An attempt at reverse engineering and analyzing of Call of Duty: Black Ops 3. -## Roadmap +## Technical Features - [x] Steam API Emulation - [x] Steam Integrity Bypass @@ -24,6 +24,8 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3. - [x] P2P multiplayer - [x] Dedicated Servers +Check out the closed issues for more gameplay related features and fixes that have been added! + ## Writeups & Articles - Reverse engineering integrity checks in Black Ops 3 From 27c197e95e75f4d9d37acf6d7a835904a9555907 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Mon, 10 Apr 2023 10:13:14 +0200 Subject: [PATCH 08/12] cleanup game settings --- .../{gamesettings.cpp => game_settings.cpp} | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) rename src/client/component/{gamesettings.cpp => game_settings.cpp} (60%) diff --git a/src/client/component/gamesettings.cpp b/src/client/component/game_settings.cpp similarity index 60% rename from src/client/component/gamesettings.cpp rename to src/client/component/game_settings.cpp index c511ef5c..85680f6e 100644 --- a/src/client/component/gamesettings.cpp +++ b/src/client/component/game_settings.cpp @@ -11,54 +11,54 @@ namespace gamesettings namespace { // - std::unordered_map gamesettings_files; + std::unordered_map game_settings_files; - const std::string get_gamesettings_name(const std::vector& sub_strings) + std::string get_game_settings_name(const std::vector& sub_strings) { if (sub_strings.size() > 2) { return sub_strings[sub_strings.size() - 2] + '/' + sub_strings[sub_strings.size() - 1]; } - return ""; + return std::string(); } - const std::string get_gamesettings_path(const std::string& name) + std::string get_game_settings_path(const std::string& name) { - const auto itr = gamesettings_files.find(name); - return (itr == gamesettings_files.end()) ? "" : itr->second; + const auto itr = game_settings_files.find(name); + return (itr == game_settings_files.end()) ? std::string() : itr->second; } - void search_gamesettings_folder(const std::string& gamesettings_dir) + void search_game_settings_folder(const std::string& game_settings_dir) { - if (!utils::io::directory_exists(gamesettings_dir)) + if (!utils::io::directory_exists(game_settings_dir)) { return; } - const auto files = utils::io::list_files(gamesettings_dir, true); + const auto files = utils::io::list_files(game_settings_dir, true); for (const auto& path : files) { if (!std::filesystem::is_directory(path)) { auto sub_strings = utils::string::split(path.generic_string(), '/'); - gamesettings_files.insert_or_assign(get_gamesettings_name(sub_strings), path.generic_string()); + game_settings_files.insert_or_assign(get_game_settings_name(sub_strings), path.generic_string()); } } } - bool has_gamesettings_file_on_disk(const char* path) + bool has_game_settings_file_on_disk(const char* path) { if (!path) { return false; } - auto sub_strings = utils::string::split(path, '/'); - auto gamesettings_name = get_gamesettings_name(sub_strings); + const auto sub_strings = utils::string::split(path, '/'); + const auto game_settings_name = get_game_settings_name(sub_strings); - return get_gamesettings_path(gamesettings_name) != ""; + return !get_game_settings_path(game_settings_name).empty(); } void cmd_exec_stub(utils::hook::assembler& a) @@ -69,7 +69,7 @@ namespace gamesettings a.pushad64(); a.mov(rcx, r10); - a.call_aligned(has_gamesettings_file_on_disk); + a.call_aligned(has_game_settings_file_on_disk); a.cmp(rax, 1); ; a.popad64(); @@ -86,11 +86,11 @@ namespace gamesettings int read_file_stub(const char* qpath, void** buffer) { - auto sub_strings = utils::string::split(qpath, '/'); - auto game_settings_name = get_gamesettings_name(sub_strings); + const auto sub_strings = utils::string::split(qpath, '/'); + const auto game_settings_name = get_game_settings_name(sub_strings); std::string gamesettings_data; - utils::io::read_file(get_gamesettings_path(game_settings_name), &gamesettings_data); + utils::io::read_file(get_game_settings_path(game_settings_name), &gamesettings_data); if (!gamesettings_data.empty()) { @@ -113,8 +113,8 @@ namespace gamesettings { const utils::nt::library host{}; - search_gamesettings_folder((game::get_appdata_path() / "data/gamesettings").string()); - search_gamesettings_folder((host.get_folder() / "boiii/gamesettings").string()); + search_game_settings_folder((game::get_appdata_path() / "data/gamesettings").string()); + search_game_settings_folder((host.get_folder() / "boiii/gamesettings").string()); } } From 481e892351abc73b98d9a34570184e6c2e6bf4a5 Mon Sep 17 00:00:00 2001 From: Edo Date: Mon, 10 Apr 2023 10:20:48 +0200 Subject: [PATCH 09/12] use brace'd initializer --- src/client/component/game_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/game_settings.cpp b/src/client/component/game_settings.cpp index 85680f6e..0572f0b8 100644 --- a/src/client/component/game_settings.cpp +++ b/src/client/component/game_settings.cpp @@ -20,7 +20,7 @@ namespace gamesettings return sub_strings[sub_strings.size() - 2] + '/' + sub_strings[sub_strings.size() - 1]; } - return std::string(); + return {}; } std::string get_game_settings_path(const std::string& name) From a44675aa68d7df7ab3180bdb8644021f01da7dc2 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 10 Apr 2023 11:59:05 +0200 Subject: [PATCH 10/12] Print errors in connect stub --- src/client/component/auth.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index e870dfc5..959d6e57 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -109,20 +109,29 @@ namespace auth int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, int len) { - std::string buffer{}; - - const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0; - if (is_connect_sequence) + try { - buffer.append("connect"); - buffer.push_back(' '); - buffer.append(serialize_connect_data(data, len)); + std::string buffer{}; - data = buffer.data(); - len = static_cast(buffer.size()); + const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0; + if (is_connect_sequence) + { + buffer.append("connect"); + buffer.push_back(' '); + buffer.append(serialize_connect_data(data, len)); + + data = buffer.data(); + len = static_cast(buffer.size()); + } + + return reinterpret_cast(0x142173600_g)(sock, adr, data, len); + } + catch (std::exception& e) + { + printf("Error: %s\n", e.what()); } - return reinterpret_cast(0x142173600_g)(sock, adr, data, len); + return 0; } void handle_connect_packet(const game::netadr_t& target, const network::data_view& data) From 282d5e97a37ac732b560c0582b665954bdb527e5 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 10 Apr 2023 15:05:11 +0200 Subject: [PATCH 11/12] Struct fixes --- src/client/game/structs.hpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 54dde0c0..ca9b7904 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -689,6 +689,7 @@ namespace game byte color[4]; const dvar_t* indirect[3]; } value; + uint64_t encryptedValue; }; @@ -699,26 +700,31 @@ namespace game int stringCount; const char** strings; } enumeration; + struct { int min; int max; } integer; + struct { int64_t min; int64_t max; } integer64; + struct { uint64_t min; uint64_t max; } unsignedInt64; + struct { float min; float max; } value; + struct { vec_t min; @@ -1028,7 +1034,7 @@ namespace game JoinResult joinResult; }; - +#ifdef __cplusplus namespace hks { struct lua_State; @@ -1051,7 +1057,7 @@ namespace game typedef size_t hksSize; typedef void* (*lua_Alloc)(void*, void*, size_t, size_t); - typedef hksInt32(*lua_CFunction)(lua_State*); + typedef hksInt32 (*lua_CFunction)(lua_State*); struct GenericChunkHeader { @@ -1108,11 +1114,14 @@ namespace game TNUMBER = 0x3, TSTRING = 0x4, TTABLE = 0x5, - TFUNCTION = 0x6, // idk + TFUNCTION = 0x6, + // idk TUSERDATA = 0x7, TTHREAD = 0x8, - TIFUNCTION = 0x9, // Lua function - TCFUNCTION = 0xA, // C function + TIFUNCTION = 0x9, + // Lua function + TCFUNCTION = 0xA, + // C function TUI64 = 0xB, TSTRUCT = 0xC, NUM_TYPE_OBJECTS = 0xE, @@ -1294,7 +1303,7 @@ namespace game int _m_isHksGlobalMemoTestingMode; HksCompilerSettings_BytecodeSharingFormat m_bytecodeSharingFormat; HksCompilerSettings_IntLiteralOptions m_enableIntLiterals; - int(*m_debugMap)(const char*, int); + int (*m_debugMap)(const char*, int); }; enum HksBytecodeSharingMode : __int64 @@ -1504,7 +1513,7 @@ namespace game void* m_profiler; RuntimeProfileData m_runProfilerData; HksCompilerSettings m_compilerSettings; - int(*m_panicFunction)(lua_State*); + int (*m_panicFunction)(lua_State*); void* m_luaplusObjectList; int m_heapAssertionFrequency; int m_heapAssertionCount; @@ -1533,6 +1542,7 @@ namespace game HksError m_error; }; } +#endif typedef uint32_t ScrVarCanonicalName_t; @@ -1556,19 +1566,23 @@ namespace game char __pad4[0x29DAC]; }; +#ifdef __cplusplus static_assert(sizeof(client_s) == 0xE5110); static_assert(offsetof(game::client_s, address) == 0x2C); static_assert(offsetof(game::client_s, xuid) == 0x55C8); static_assert(offsetof(game::client_s, guid) == 0xBB354); static_assert(offsetof(game::client_s, bIsTestClient) == 0xBB360); +#endif struct client_s_cl : client_s { char __pad1_0[0x60]; }; +#ifdef __cplusplus static_assert(sizeof(client_s_cl) == 0xE5170); +#endif enum scriptInstance_t { @@ -1598,7 +1612,9 @@ namespace game unsigned char __pad1[0x2A0]; }; +#ifdef __cplusplus static_assert(sizeof(gentity_s) == 0x4F8); +#endif enum workshop_type { @@ -1623,7 +1639,9 @@ namespace game workshop_type type; }; +#ifdef __cplusplus static_assert(sizeof(workshop_data) == 0x4C8); +#endif struct DDLMember { @@ -1695,7 +1713,7 @@ namespace game }; struct DDLContext; - typedef void(* DDLWriteCB)(DDLContext*, void*); + typedef void (* DDLWriteCB)(DDLContext*, void*); struct DDLContext { From 601be011e18098dcda1306eeacb02fe4d3eee9ba Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 10 Apr 2023 15:05:23 +0200 Subject: [PATCH 12/12] Prepare sendpacket fixes --- src/client/component/network.cpp | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 9a722405..c517e862 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -256,6 +256,38 @@ namespace network return a.port == b.port && a.addr == b.addr; } + int net_sendpacket_stub(const game::netsrc_t sock, const int length, const char* data, const game::netadr_t* to) + { + printf("Sending packet of size: %X\n", length); + + if (to->type != game::NA_RAWIP) + { + printf("NET_SendPacket: bad address type\n"); + return 0; + } + + const auto s = *game::ip_socket; + if (!s || sock > game::NS_MAXCLIENTS) + { + return 0; + } + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_port = htons(to->port); + address.sin_addr.s_addr = htonl(((to->ipv4.c | ((to->ipv4.b | (to->ipv4.a << 8)) << 8)) << 8) | to->ipv4.d); + + const auto size = static_cast(length); + + std::vector buffer{}; + buffer.resize(size + 1); + buffer[0] = static_cast((static_cast(sock) & 0xF) | ((to->localNetID & 0xF) << 4)); + memcpy(buffer.data() + 1, data, size); + + return sendto(s, buffer.data(), static_cast(buffer.size()), 0, reinterpret_cast(&address), + sizeof(address)); + } + struct component final : generic_component { void post_unpack() override @@ -268,6 +300,9 @@ namespace network // skip checksum verification utils::hook::set(game::select(0x14233249E, 0x140596F2E), 0); // don't add checksum to packet + // Recreate NET_SendPacket to increase max packet size + utils::hook::jump(game::select(0x1423323B0, 0x140596E40), net_sendpacket_stub); + utils::hook::set(game::select(0x14134C6E0, 0x14018E574), 5); // set initial connection state to challenging