From 45907301eec9233946222ace7969f31cb5d998c6 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Wed, 18 Dec 2024 12:39:55 +0100 Subject: [PATCH 01/11] Christmas Day Update --- deps/GSL | 2 +- deps/WinToast | 2 +- deps/asmjit | 2 +- deps/gsc-tool | 2 +- deps/libtomcrypt | 2 +- deps/libtommath | 2 +- deps/minhook | 2 +- deps/rapidjson | 2 +- deps/zlib | 2 +- src/client/component/asset_restrict.cpp | 35 ++++ src/client/component/auth.cpp | 25 ++- src/client/component/command.cpp | 37 +++-- src/client/component/dedicated_info.cpp | 12 +- src/client/component/dvar_cheats.cpp | 2 +- src/client/component/fastfiles.cpp | 24 +-- src/client/component/filesystem.cpp | 5 + src/client/component/game_log.cpp | 9 +- src/client/component/gsc/script_error.cpp | 32 ++-- src/client/component/gsc/script_extension.cpp | 128 +++++++++++--- src/client/component/gsc/script_extension.hpp | 27 +++ src/client/component/gsc/script_loading.cpp | 135 +++++++++++---- src/client/component/mods.cpp | 157 ++++++++++++++++++ src/client/component/mods.hpp | 7 + src/client/component/notifies.cpp | 22 +-- src/client/component/party.cpp | 85 ++++++++-- src/client/component/scripting.cpp | 17 +- src/client/component/scripting.hpp | 2 +- src/client/game/dvars.cpp | 4 +- src/client/game/dvars.hpp | 2 + src/client/game/game.cpp | 7 + src/client/game/game.hpp | 2 + src/client/game/structs.hpp | 43 ++++- src/client/game/symbols.hpp | 37 +++-- src/client/std_include.hpp | 2 + src/common/utils/properties.cpp | 25 +++ src/common/utils/properties.hpp | 7 + 36 files changed, 724 insertions(+), 184 deletions(-) create mode 100644 src/client/component/asset_restrict.cpp create mode 100644 src/client/component/mods.cpp create mode 100644 src/client/component/mods.hpp create mode 100644 src/common/utils/properties.cpp create mode 100644 src/common/utils/properties.hpp diff --git a/deps/GSL b/deps/GSL index f1a494c..2724630 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit f1a494cfd2ce55fe88b5134eab985f5852667b8d +Subproject commit 272463043ef3e442f6c80a530d3dd38ee6781381 diff --git a/deps/WinToast b/deps/WinToast index 821c481..a78ce46 160000 --- a/deps/WinToast +++ b/deps/WinToast @@ -1 +1 @@ -Subproject commit 821c4818ade1aa4da56ac753285c159ce26fd597 +Subproject commit a78ce469b456c06103b3b30d4bd37e7bb80da30c diff --git a/deps/asmjit b/deps/asmjit index bfa0bf6..cfc9f81 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit bfa0bf690c2e90cc0844f2f012efa41b916bde7e +Subproject commit cfc9f813cc6ccda63cad872edb32b38e0662bedb diff --git a/deps/gsc-tool b/deps/gsc-tool index b8e30e6..9ff3b87 160000 --- a/deps/gsc-tool +++ b/deps/gsc-tool @@ -1 +1 @@ -Subproject commit b8e30e6334aa33ea731caf8d0700a9e6c7794c09 +Subproject commit 9ff3b871ef59b14eb8b32d258bf18347e14a3e18 diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 7e863d2..c900951 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 7e863d21429f94ed6a720e24499a12a3f852bb31 +Subproject commit c900951dab1bb94bab803fc57688dac18e3b71f9 diff --git a/deps/libtommath b/deps/libtommath index 8314bde..5809141 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 8314bde5e5c8e5d9331460130a9d1066e324f091 +Subproject commit 5809141a3a6ec1bf3443c927c02b955e19224016 diff --git a/deps/minhook b/deps/minhook index f5485b8..c1a7c38 160000 --- a/deps/minhook +++ b/deps/minhook @@ -1 +1 @@ -Subproject commit f5485b8454544c2f034c78f8f127c1d03dea3636 +Subproject commit c1a7c3843bd1a5fe3eb779b64c0d823bca3dc339 diff --git a/deps/rapidjson b/deps/rapidjson index 6089180..d621dc9 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 6089180ecb704cb2b136777798fa1be303618975 +Subproject commit d621dc9e9c77f81e5c8a35b8dcc16dcd63351321 diff --git a/deps/zlib b/deps/zlib index 9f0f2d4..ef24c4c 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 9f0f2d4f9f1f28be7e16d8bf3b4e9d4ada70aa9f +Subproject commit ef24c4c7502169f016dcd2a26923dbaf3216748c diff --git a/src/client/component/asset_restrict.cpp b/src/client/component/asset_restrict.cpp new file mode 100644 index 0000000..5637ddf --- /dev/null +++ b/src/client/component/asset_restrict.cpp @@ -0,0 +1,35 @@ +#include +#include +#include "game/game.hpp" + +#include + +namespace asset_restrict +{ + namespace + { + void reallocate_asset_pool(game::XAssetType type, const int size) + { + const auto asset_size = game::DB_GetXAssetTypeSize(type); + const auto new_size = size * game::g_poolSize[type]; + + const auto new_allocation_size = static_cast(new_size * asset_size); + + const game::XAssetHeader pool_entry = { .data = utils::memory::allocate(new_allocation_size) }; + game::DB_XAssetPool[type] = pool_entry.data; + game::g_poolSize[type] = new_size; + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + reallocate_asset_pool(game::ASSET_TYPE_XANIMPARTS, 2); + reallocate_asset_pool(game::ASSET_TYPE_ATTACHMENT, 2); + } + }; +} + +REGISTER_COMPONENT(asset_restrict::component) diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index 74115b2..3840601 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -8,11 +8,13 @@ #include "console.hpp" #include "network.hpp" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include namespace auth { @@ -31,13 +33,26 @@ namespace auth std::string get_hw_profile_guid() { + auto hw_profile_path = (utils::properties::get_appdata_path() / "iw6-guid.dat").generic_string(); + if (utils::io::file_exists(hw_profile_path)) + { + std::string hw_profile_info; + if (utils::io::read_file(hw_profile_path, &hw_profile_info) && !hw_profile_info.empty()) + { + return hw_profile_info; + } + } + HW_PROFILE_INFO info; if (!GetCurrentHwProfileA(&info)) { return {}; } - return std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) }; + auto hw_profile_info = std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) }; + utils::io::write_file(hw_profile_path, hw_profile_info); + + return hw_profile_info; } std::string get_protected_data() diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 32127b6..c7dc997 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -232,7 +232,10 @@ namespace command void execute(std::string command, const bool sync) { - command += "\n"; + if (!command.ends_with('\n')) + { + command += "\n"; + } if (sync) { @@ -553,12 +556,12 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].flags ^= game::FL_GODMODE; - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"godmode %s\"", game::mp::g_entities[client_num].flags & game::FL_GODMODE ? "^2on" @@ -569,12 +572,12 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET; - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"notarget %s\"", game::mp::g_entities[client_num].flags & game::FL_NOTARGET ? "^2on" @@ -585,12 +588,12 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].client->flags ^= 1; - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"noclip %s\"", game::mp::g_entities[client_num].client->flags & 1 ? "^2on" @@ -601,12 +604,12 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].client->flags ^= 2; - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"ufo %s\"", game::mp::g_entities[client_num].client->flags & 2 ? "^2on" @@ -617,13 +620,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 4) { - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify the correct number of coordinates\""); return; } @@ -637,13 +640,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 4) { - game::SV_GameSendServerCommand(client_num, 1, + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify the correct number of coordinates\""); return; } @@ -657,13 +660,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 2) { - game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); return; } @@ -680,13 +683,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 2) { - game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\""); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); return; } diff --git a/src/client/component/dedicated_info.cpp b/src/client/component/dedicated_info.cpp index 9f0de69..5dacf01 100644 --- a/src/client/component/dedicated_info.cpp +++ b/src/client/component/dedicated_info.cpp @@ -25,28 +25,28 @@ namespace dedicated_info scheduler::loop([]() { - auto* sv_running = game::Dvar_FindVar("sv_running"); + const auto* sv_running = game::Dvar_FindVar("sv_running"); if (!sv_running || !sv_running->current.enabled) { console::set_title("iw6-mod Dedicated Server"); return; } - auto* const sv_hostname = game::Dvar_FindVar("sv_hostname"); - auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients"); - auto* const mapname = game::Dvar_FindVar("mapname"); + const auto* sv_hostname = game::Dvar_FindVar("sv_hostname"); + const auto* sv_maxclients = game::Dvar_FindVar("sv_maxclients"); + const auto* mapname = game::Dvar_FindVar("mapname"); auto client_count = 0; auto bot_count = 0; - for (auto i = 0; i < sv_maxclients->current.integer; i++) + for (auto i = 0; i < sv_maxclients->current.integer; ++i) { auto* client = &game::mp::svs_clients[i]; auto* self = &game::mp::g_entities[i]; if (client->header.state > game::CS_FREE && self && self->client) { - client_count++; + ++client_count; if (game::SV_BotIsBot(i)) { ++bot_count; diff --git a/src/client/component/dvar_cheats.cpp b/src/client/component/dvar_cheats.cpp index 19450f2..04c4654 100644 --- a/src/client/component/dvar_cheats.cpp +++ b/src/client/component/dvar_cheats.cpp @@ -136,7 +136,7 @@ namespace dvar_cheats const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack const auto* command = utils::string::va("q %s \"%s\"", dvar, value); - game::SV_GameSendServerCommand(entity_num, 1, command); + game::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command); } const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a) diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 16b93a0..f5bb265 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -1,12 +1,12 @@ #include #include "loader/component_loader.hpp" - #include "game/game.hpp" #include "game/dvars.hpp" -#include "fastfiles.hpp" #include "command.hpp" #include "console.hpp" +#include "fastfiles.hpp" + #include #include @@ -22,7 +22,7 @@ namespace fastfiles void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map) { console::info("Loading fastfile %s\n", zone_name); - return db_try_load_x_file_internal_hook.invoke(zone_name, zone_flags, is_base_map); + db_try_load_x_file_internal_hook.invoke(zone_name, zone_flags, is_base_map); } void dump_gsc_script(const std::string& name, game::XAssetHeader header) @@ -114,21 +114,9 @@ namespace fastfiles utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub); utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub); - command::add("loadzone", [](const command::params& params) - { - if (params.size() < 2) - { - console::info("usage: loadzone \n"); - return; - } - - game::XZoneInfo info; - info.name = params.get(1); - info.allocFlags = 1; - info.freeFlags = 0; - - game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_SYNC); - }); + // Allow loading of unsigned fastfiles + utils::hook::set(0x1402FBF23, 0xEB); // DB_LoadXFile + utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor command::add("materiallist", [](const command::params& params) { diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index 9bcac55..dee28a3 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -86,6 +86,11 @@ namespace filesystem register_path("iw6"); register_path(get_binary_directory() + "\\data"); + if (get_binary_directory() != std::filesystem::current_path()) + { + register_path(std::filesystem::current_path() / "data"); + } + // game's search paths register_path("devraw"); register_path("devraw_shared"); diff --git a/src/client/component/game_log.cpp b/src/client/component/game_log.cpp index 5c46d91..e8262b0 100644 --- a/src/client/component/game_log.cpp +++ b/src/client/component/game_log.cpp @@ -102,9 +102,14 @@ namespace game_log g_log_printf("InitGame\n"); }); - scripting::on_shutdown([](int free_scripts) + scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void { - console::info("==== ShutdownGame (%d) ====\n", free_scripts); + if (post_shutdown) + { + return; + } + + console::info("==== ShutdownGame (%d) ====\n", clear_scripts); g_log_printf("ShutdownGame:\n"); g_log_printf("------------------------------------------------------------\n"); diff --git a/src/client/component/gsc/script_error.cpp b/src/client/component/gsc/script_error.cpp index 9bd853c..62027a7 100644 --- a/src/client/component/gsc/script_error.cpp +++ b/src/client/component/gsc/script_error.cpp @@ -113,7 +113,6 @@ namespace gsc return res; } - } unsigned int scr_get_object(unsigned int index) @@ -284,28 +283,23 @@ namespace gsc public: void post_unpack() override { - if (game::environment::is_sp()) - { - return; - } + scr_emit_function_hook.create(SELECT_VALUE(0x1403D3350, 0x14042E150), &scr_emit_function_stub); - scr_emit_function_hook.create(0x14042E150, &scr_emit_function_stub); - - utils::hook::call(0x14042E0E4, compile_error_stub); // LinkFile - utils::hook::call(0x14042E138, compile_error_stub); // LinkFile - utils::hook::call(0x14042E22B, find_variable_stub); // Scr_EmitFunction + utils::hook::call(SELECT_VALUE(0x1403D32E4, 0x14042E0E4), compile_error_stub); // LinkFile + utils::hook::call(SELECT_VALUE(0x1403D3338, 0x14042E138), compile_error_stub); // LinkFile + utils::hook::call(SELECT_VALUE(0x1403D342A, 0x14042E22B), find_variable_stub); // Scr_EmitFunction // Restore basic error messages to scr functions - utils::hook::jump(0x140438ED0, scr_get_object); - utils::hook::jump(0x140438AD0, scr_get_const_string); - utils::hook::jump(0x1404388B0, scr_get_const_istring); - utils::hook::jump(0x1404393D0, scr_get_vector); - utils::hook::jump(0x140438E10, scr_get_int); - utils::hook::jump(0x140438D60, scr_get_float); + utils::hook::jump(game::Scr_GetObject, scr_get_object); + utils::hook::jump(game::Scr_GetConstString, scr_get_const_string); + utils::hook::jump(game::Scr_GetConstIString, scr_get_const_istring); + utils::hook::jump(game::Scr_GetVector, scr_get_vector); + utils::hook::jump(game::Scr_GetInt, scr_get_int); + utils::hook::jump(game::Scr_GetFloat, scr_get_float); - utils::hook::jump(0x1404390B0, scr_get_pointer_type); - utils::hook::jump(0x140439280, scr_get_type); - utils::hook::jump(0x1404392F0, scr_get_type_name); + utils::hook::jump(SELECT_VALUE(0x1403DE150, 0x1404390B0), scr_get_pointer_type); + utils::hook::jump(SELECT_VALUE(0x1403DE320, 0x140439280), scr_get_type); + utils::hook::jump(SELECT_VALUE(0x1403DE390, 0x1404392F0), scr_get_type_name); } void pre_destroy() override diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp index 753a861..bab3816 100644 --- a/src/client/component/gsc/script_extension.cpp +++ b/src/client/component/gsc/script_extension.cpp @@ -37,9 +37,60 @@ namespace gsc bool force_error_print = false; std::optional gsc_error_msg; + std::vector devmap_entries{}; + + std::optional get_devmap_entry(const std::uint8_t* codepos) + { + const auto itr = std::ranges::find_if(devmap_entries, [codepos](const devmap_entry& entry) -> bool + { + return codepos >= entry.bytecode && codepos < entry.bytecode + entry.size; + }); + + if (itr != devmap_entries.end()) + { + return *itr; + } + + return {}; + } + + std::optional> get_line_and_col_for_codepos(const std::uint8_t* codepos) + { + const auto entry = get_devmap_entry(codepos); + + if (!entry.has_value()) + { + return {}; + } + + std::optional> best_line_info{}; + std::uint32_t best_codepos = 0; + + assert(codepos >= entry->bytecode); + const std::uint32_t codepos_offset = static_cast(codepos - entry->bytecode); + + for (const auto& instruction : entry->devmap) + { + if (instruction.codepos > codepos_offset) + { + continue; + } + + if (best_line_info.has_value() && codepos_offset - instruction.codepos > codepos_offset - best_codepos) + { + continue; + } + + best_line_info = { { instruction.line, instruction.col } }; + best_codepos = instruction.codepos; + } + + return best_line_info; + } + unsigned int scr_get_function_stub(const char** p_name, int* type) { - const auto result = utils::hook::invoke(0x1403CD9F0, p_name, type); + const auto result = game::Scr_GetFunction(p_name, type); for (const auto& [name, func] : functions) { @@ -120,27 +171,36 @@ namespace gsc const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos; const auto function = find_function(frame->fs.pos); + const char* location; if (function.has_value()) { - console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data()); + location = utils::string::va("function \"%s\" in file \"%s\"", function.value().first.data(), function.value().second.data()); } else { - console::warn("\tat unknown location %p\n", pos); + location = utils::string::va("unknown location %p", pos); } + + const auto line_info = get_line_and_col_for_codepos(reinterpret_cast(pos)); + if (line_info.has_value()) + { + location = utils::string::va("%s line \"%d\" column \"%d\"", location, line_info->first, line_info->second); + } + + console::warn("\tat %s\n", location); } } - void vm_error_stub(int mark_pos) + void vm_error_stub(const unsigned __int64 mark_pos) { if (!dvars::com_developer_script->current.enabled && !force_error_print) { - utils::hook::invoke(0x1404E4D00, mark_pos); + game::LargeLocalResetToMark(mark_pos); return; } console::warn("******* script runtime error ********\n"); - const auto opcode_id = *reinterpret_cast(0x144D57840); + const auto opcode_id = *reinterpret_cast(SELECT_VALUE(0x1455BE740, 0x144D57840)); const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string(); @@ -166,7 +226,7 @@ namespace gsc print_callstack(); console::warn("************************************\n"); - utils::hook::invoke(0x1404E4D00, mark_pos); + game::LargeLocalResetToMark(mark_pos); } void inc_in_param() @@ -228,6 +288,11 @@ namespace gsc scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0))); } + void scr_cmd_is_dedicated_server() + { + game::Scr_AddInt(game::environment::is_dedi()); + } + const char* get_code_pos(const int index) { if (static_cast(index) >= game::scr_VmPub->outparamcount) @@ -248,6 +313,22 @@ namespace gsc } } + void add_devmap_entry(std::uint8_t* codepos, std::size_t size, const std::string& name, xsk::gsc::buffer devmap_buf) + { + std::vector devmap{}; + const auto* devmap_ptr = reinterpret_cast(devmap_buf.data); + + devmap.resize(devmap_ptr->num_instructions); + std::memcpy(devmap.data(), devmap_ptr->instructions, sizeof(dev_map_instruction) * devmap_ptr->num_instructions); + + devmap_entries.emplace_back(codepos, size, name, std::move(devmap)); + } + + void clear_devmap() + { + devmap_entries.clear(); + } + void add_function(const std::string& name, game::BuiltinFunction function) { ++function_id_start; @@ -271,27 +352,18 @@ namespace gsc utils::hook::set(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print); utils::hook::set(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln); - utils::hook::set(SELECT_VALUE(0x1403D353C, 0x14042E33C), 0x1000); // Scr_RegisterFunction + utils::hook::set(SELECT_VALUE(0x1403D353B + 1, 0x14042E33B + 1), 0x1000); // Scr_RegisterFunction utils::hook::set(SELECT_VALUE(0x1403D3542 + 4, 0x14042E342 + 4), RVA(&func_table)); // Scr_RegisterFunction - utils::hook::set(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0 + // utils::hook::set(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0 utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts - if (game::environment::is_sp()) - { - return; - } + utils::hook::nop(SELECT_VALUE(0x1403E0BDD + 5, 0x14043BBBE + 5), 2); + utils::hook::call(SELECT_VALUE(0x1403E0BDD, 0x14043BBBE), vm_call_builtin_function); - utils::hook::nop(0x14043BBBE + 5, 2); - utils::hook::call(0x14043BBBE, vm_call_builtin_function); + utils::hook::call(SELECT_VALUE(0x1403D391F, 0x14042E76F), scr_get_function_stub); - utils::hook::call(0x14043CEB1, vm_error_stub); - - utils::hook::call(0x14042E76F, scr_get_function_stub); - - utils::hook::set(0x1409E6E38, assert_ex_cmd); - utils::hook::set(0x1409E6E50, assert_msg_cmd); - utils::hook::set(0x1409E6E20, assert_cmd); + utils::hook::call(SELECT_VALUE(0x1403E1ED0, 0x14043CEB1), vm_error_stub); add_function("replacefunc", [] { @@ -309,10 +381,16 @@ namespace gsc command::execute(cmd); }); - add_function("isdedicated", [] + if (game::environment::is_sp()) { - game::Scr_AddInt(game::environment::is_dedi()); - }); + return; + } + + utils::hook::set(0x1409E6E38, assert_ex_cmd); + utils::hook::set(0x1409E6E50, assert_msg_cmd); + utils::hook::set(0x1409E6E20, assert_cmd); + + utils::hook::set(0x1409E94D0, scr_cmd_is_dedicated_server); } }; } diff --git a/src/client/component/gsc/script_extension.hpp b/src/client/component/gsc/script_extension.hpp index c8cc2f4..0205896 100644 --- a/src/client/component/gsc/script_extension.hpp +++ b/src/client/component/gsc/script_extension.hpp @@ -1,9 +1,36 @@ #pragma once +#include namespace gsc { extern void* func_table[0x1000]; +#pragma pack(push, 1) + struct dev_map_instruction + { + std::uint32_t codepos; + std::uint16_t line; + std::uint16_t col; + }; + + struct dev_map + { + std::uint32_t num_instructions; + dev_map_instruction instructions[1]; + }; +#pragma pack(pop) + + struct devmap_entry + { + const std::uint8_t* bytecode; + std::size_t size; + std::string script_name; + std::vector devmap; + }; + + void add_devmap_entry(std::uint8_t*, std::size_t, const std::string&, xsk::gsc::buffer); + void clear_devmap(); + void add_function(const std::string& name, game::BuiltinFunction function); void scr_error(const char* error); diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp index 7532bee..fa5a9ec 100644 --- a/src/client/component/gsc/script_loading.cpp +++ b/src/client/component/gsc/script_loading.cpp @@ -3,15 +3,16 @@ #include "game/game.hpp" #include "game/dvars.hpp" -#include -#include -#include #include +#include +#include +#include -#include "component/filesystem.hpp" #include "component/console.hpp" +#include "component/filesystem.hpp" #include "component/scripting.hpp" +#include "script_extension.hpp" #include "script_loading.hpp" namespace gsc @@ -32,6 +33,7 @@ namespace gsc init_handles.clear(); loaded_scripts.clear(); script_allocator.clear(); + clear_devmap(); } bool read_raw_script_file(const std::string& name, std::string* data) @@ -114,6 +116,12 @@ namespace gsc loaded_scripts[real_name] = script_file_ptr; + const auto devmap = std::get<2>(output_script); + if (devmap.size > 0 && (gsc_ctx->build() & xsk::gsc::build::dev_maps) != xsk::gsc::build::prod) + { + add_devmap_entry(script_file_ptr->bytecode, byte_code_size, real_name, devmap); + } + return script_file_ptr; } catch (const std::exception& ex) @@ -180,13 +188,9 @@ namespace gsc } } - void load_scripts(const std::filesystem::path& root_dir) + void load_scripts_from_folder(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir) { - const std::filesystem::path script_dir = root_dir / "scripts"; - if (!utils::io::directory_exists(script_dir.generic_string())) - { - return; - } + console::info("Scanning directory '%s' for custom GSC scripts...\n", script_dir.generic_string().data()); const auto scripts = utils::io::list_files(script_dir.generic_string()); for (const auto& script : scripts) @@ -204,6 +208,44 @@ namespace gsc } } + void load_scripts(const std::filesystem::path& root_dir) + { + const auto load = [&root_dir](const std::filesystem::path& folder) -> void + { + const std::filesystem::path script_dir = root_dir / folder; + if (utils::io::directory_exists(script_dir.generic_string())) + { + load_scripts_from_folder(root_dir, script_dir); + } + }; + + const std::filesystem::path base_dir = "scripts"; + + load(base_dir); + + const auto* map_name = game::Dvar_FindVar("mapname"); + + if (game::environment::is_sp()) + { + const std::filesystem::path game_folder = "sp"; + + load(base_dir / game_folder); + + load(base_dir / game_folder / map_name->current.string); + } + else + { + const std::filesystem::path game_folder = "mp"; + + load(base_dir / game_folder); + + load(base_dir / game_folder / map_name->current.string); + + const auto* game_type = game::Dvar_FindVar("g_gametype"); + load(base_dir / game_folder / game_type->current.string); + } + } + int db_is_x_asset_default(game::XAssetType type, const char* name) { if (loaded_scripts.contains(name)) @@ -218,12 +260,22 @@ namespace gsc { utils::hook::invoke(0x1403CCB10); - clear(); + for (const auto& path : filesystem::get_search_paths()) + { + load_scripts(path); + } + } + + int g_scr_load_script_and_label_stub(game::ScriptFunctions* functions) + { + const auto result = utils::hook::invoke(0x140349790, functions); for (const auto& path : filesystem::get_search_paths()) { load_scripts(path); } + + return result; } void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size) @@ -250,7 +302,26 @@ namespace gsc utils::hook::invoke(0x1403D2CA0); } - void scr_load_level_stub() + void scr_load_level_singleplayer_stub() + { + for (auto& function_handle : main_handles) + { + console::info("Executing '%s::main'\n", function_handle.first.data()); + const auto thread = game::Scr_ExecThread(static_cast(function_handle.second), 0); + game::RemoveRefToObject(thread); + } + + utils::hook::invoke(0x1403401B0); + + for (auto& function_handle : init_handles) + { + console::info("Executing '%s::init'\n", function_handle.first.data()); + const auto thread = game::Scr_ExecThread(static_cast(function_handle.second), 0); + game::RemoveRefToObject(thread); + } + } + + void scr_load_level_multiplayer_stub() { utils::hook::invoke(0x1403CDC60); @@ -345,31 +416,35 @@ namespace gsc utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts + // ProcessScript + utils::hook::call(SELECT_VALUE(0x1403DC887, 0x1404378D7), find_script); + utils::hook::call(SELECT_VALUE(0x1403DC897, 0x1404378E7), db_is_x_asset_default); + dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments"); - if (game::environment::is_sp()) + scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void { - return; - } - - // ProcessScript - utils::hook::call(0x1404378D7, find_script); - utils::hook::call(0x1404378E7, db_is_x_asset_default); - - // GScr_LoadScripts - utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub); - - // Exec script handles - utils::hook::call(0x14039F64E, g_load_structs_stub); - utils::hook::call(0x14039F653, scr_load_level_stub); - - scripting::on_shutdown([](int free_scripts) - { - if (free_scripts) + if (clear_scripts && post_shutdown) { clear(); } }); + + if (game::environment::is_sp()) + { + utils::hook::call(0x14034996F, g_scr_load_script_and_label_stub); + + utils::hook::call(0x140316591, scr_load_level_singleplayer_stub); + } + else + { + // GScr_LoadScripts + utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub); + + // Exec script handles + utils::hook::call(0x14039F64E, g_load_structs_stub); + utils::hook::call(0x14039F653, scr_load_level_multiplayer_stub); + } } }; } diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp new file mode 100644 index 0000000..fd588b4 --- /dev/null +++ b/src/client/component/mods.cpp @@ -0,0 +1,157 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "mods.hpp" + +#include + +namespace mods +{ + namespace + { + utils::hook::detour sys_create_file_hook; + + void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, unsigned int size, char* filename) + { + char user_map[MAX_PATH]{}; + + switch (source) + { + case game::FFD_DEFAULT: + (void)sprintf_s(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name); + break; + case game::FFD_MOD_DIR: + assert(mods::is_using_mods()); + + (void)sprintf_s(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name); + break; + case game::FFD_USER_MAP: + strncpy_s(user_map, zone_name, _TRUNCATE); + + (void)sprintf_s(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name); + break; + default: + assert(false && "inconceivable"); + break; + } + } + + game::Sys_File sys_create_file_stub(const char* dir, const char* filename) + { + auto result = sys_create_file_hook.invoke(dir, filename); + + if (result.handle != INVALID_HANDLE_VALUE) + { + return result; + } + + if (!is_using_mods()) + { + return result; + } + + // .ff extension was added previously + if (!std::strcmp(filename, "mod.ff") && mods::db_mod_file_exists()) + { + char file_path[MAX_PATH]{}; + db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path); + result.handle = game::Sys_OpenFileReliable(file_path); + } + + return result; + } + + void db_load_x_assets_stub(game::XZoneInfo* zone_info, unsigned int zone_count, game::DBSyncMode sync_mode) + { + std::vector zones(zone_info, zone_info + zone_count); + + if (db_mod_file_exists()) + { + zones.emplace_back("mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0); + } + + game::DB_LoadXAssets(zones.data(), static_cast(zones.size()), sync_mode); + } + + const auto skip_extra_zones_stub = utils::hook::assemble([](utils::hook::assembler& a) + { + const auto skip = a.newLabel(); + const auto original = a.newLabel(); + + a.pushad64(); + a.test(ebp, game::DB_ZONE_CUSTOM); // allocFlags + a.jnz(skip); + + a.bind(original); + a.popad64(); + a.mov(rdx, 0x140835F28); + a.mov(rcx, rsi); + a.call_aligned(strcmp); + a.jmp(0x1403217C0); + + a.bind(skip); + a.popad64(); + a.mov(r15d, 0x80); + a.not_(r15d); + a.and_(ebp, r15d); + a.jmp(0x1403217F6); + }); + } + + bool is_using_mods() + { + return (*dvars::fs_gameDirVar) && *(*dvars::fs_gameDirVar)->current.string; + } + + bool db_mod_file_exists() + { + if (!*(*dvars::fs_gameDirVar)->current.string) + { + return false; + } + + char filename[MAX_PATH]{}; + db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(filename), filename); + + if (auto zone_file = game::Sys_OpenFileReliable(filename); zone_file != INVALID_HANDLE_VALUE) + { + ::CloseHandle(zone_file); + return true; + } + + return false; + } + + class component final : public component_interface + { + public: + static_assert(sizeof(game::Sys_File) == 8); + + void post_unpack() override + { + dvars::fs_gameDirVar = reinterpret_cast(SELECT_VALUE(0x145856D38, 0x147876000)); + + // Remove DVAR_INIT from fs_game + utils::hook::set(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO)); + + if (game::environment::is_sp()) + { + return; + } + + // Don't load eng_ + patch_ with loadzone + utils::hook::nop(0x1403217B1, 15); + utils::hook::jump(0x1403217B1, skip_extra_zones_stub, true); + + // Add custom zone paths + sys_create_file_hook.create(game::Sys_CreateFile, sys_create_file_stub); + + // Load mod.ff + utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from + } + }; +} + +REGISTER_COMPONENT(mods::component) diff --git a/src/client/component/mods.hpp b/src/client/component/mods.hpp new file mode 100644 index 0000000..66ac5a1 --- /dev/null +++ b/src/client/component/mods.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace mods +{ + bool is_using_mods(); + bool db_mod_file_exists(); +} diff --git a/src/client/component/notifies.cpp b/src/client/component/notifies.cpp index 72f35ff..7fc89e4 100644 --- a/src/client/component/notifies.cpp +++ b/src/client/component/notifies.cpp @@ -126,7 +126,7 @@ namespace notifies a.lea(eax, dword_ptr(r15, -0x17)); a.mov(dword_ptr(rbp, 0x60), r15d); - a.jmp(0x14043A593); + a.jmp(SELECT_VALUE(0x1403DF5B3, 0x14043A593)); a.bind(replace); @@ -174,22 +174,22 @@ namespace notifies public: void post_unpack() override { + utils::hook::jump(SELECT_VALUE(0x1403DF5A4, 0x14043A584), utils::hook::assemble(vm_execute_stub), true); + + scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void + { + if (clear_scripts && post_shutdown) + { + vm_execute_hooks.clear(); + } + }); + if (game::environment::is_sp()) { return; } utils::hook::call(0x1404724DD, client_command_stub); - - utils::hook::jump(0x14043A584, utils::hook::assemble(vm_execute_stub), true); - - scripting::on_shutdown([](const bool free_scripts) - { - if (free_scripts) - { - vm_execute_hooks.clear(); - } - }); } }; } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index bc073e6..f6ec909 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -81,6 +81,26 @@ namespace party a.mov(ecx, 2); a.jmp(0x1402C617D); }); + + utils::info_string get_info() + { + utils::info_string info; + + info.set("gamename", "IW6"); + info.set("hostname", dvars::get_string("sv_hostname")); + info.set("gametype", dvars::get_string("g_gametype")); + info.set("sv_motd", dvars::get_string("sv_motd")); + info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); + info.set("mapname", dvars::get_string("mapname")); + info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1"); + info.set("clients", std::to_string(get_client_count())); + info.set("bots", std::to_string(get_bot_count())); + info.set("sv_maxclients", std::to_string(*game::mp::svs_clientCount)); + info.set("protocol", std::to_string(PROTOCOL)); + info.set("shortversion", SHORTVERSION); + + return info; + } } void switch_gamemode_if_necessary(const std::string& gametype) @@ -360,7 +380,7 @@ namespace party const auto message = params.join(2); const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; - game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); console::info("%s -> %i: %s\n", name, client_num, message.data()); }); @@ -374,7 +394,7 @@ namespace party const auto client_num = atoi(params.get(1)); const auto message = params.join(2); - game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s\"", 84, message.data())); + game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); console::info("%i: %s\n", client_num, message.data()); }); @@ -388,7 +408,7 @@ namespace party const auto message = params.join(1); const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; - game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); + game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); console::info("%s: %s\n", name, message.data()); }); @@ -401,30 +421,61 @@ namespace party const auto message = params.join(1); - game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data())); + game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); console::info("%s\n", message.data()); }); network::on("getInfo", [](const game::netadr_s& target, const std::string& data) { - utils::info_string info; + utils::info_string info = get_info(); info.set("challenge", data); - info.set("gamename", "IW6"); - info.set("hostname", dvars::get_string("sv_hostname")); - info.set("gametype", dvars::get_string("g_gametype")); - info.set("sv_motd", dvars::get_string("sv_motd")); - info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); - info.set("mapname", dvars::get_string("mapname")); - info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1"); - info.set("clients", std::to_string(get_client_count())); - info.set("bots", std::to_string(get_bot_count())); - info.set("sv_maxclients", std::to_string(*game::mp::svs_clientCount)); - info.set("protocol", std::to_string(PROTOCOL)); - info.set("shortversion", SHORTVERSION); network::send(target, "infoResponse", info.build(), '\n'); }); + network::on("getStatus", [](const game::netadr_s& target, const std::string& data) + { + std::string player_list; + + utils::info_string info = get_info(); + info.set("challenge", data); + + const auto* sv_running = game::Dvar_FindVar("sv_running"); + if (!sv_running || !sv_running->current.enabled) + { + return; + } + + for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; ++i) + { + auto* client = &game::mp::svs_clients[i]; + auto* self = &game::mp::g_entities[i]; + + if (client->header.state < game::CS_ACTIVE) + { + continue; + } + + if (!self || !self->client) + { + continue; + } + + if (game::SV_BotIsBot(i)) + { + continue; + } + + const auto score = game::G_GetClientScore(i); + const auto ping = game::mp::svs_clients[i].ping; + const std::string name = game::mp::svs_clients[i].name; + + player_list.append(std::format("{} {} \"{}\"\n", score, ping, name)); + } + + network::send(target, "statusResponse", info.build() + "\n"s + player_list + "\n"s, '\n'); + }); + if (game::environment::is_dedi()) { return; diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index bf535a4..8b1b48c 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -34,7 +34,7 @@ namespace scripting std::unordered_map canonical_string_table; - std::vector> shutdown_callbacks; + std::vector> shutdown_callbacks; std::vector> init_callbacks; void scr_load_level_stub() @@ -47,9 +47,9 @@ namespace scripting } } - void g_shutdown_game_stub(const int free_scripts) + void g_shutdown_game_stub(const int clear_scripts) { - if (free_scripts) + if (clear_scripts) { script_function_table_sort.clear(); script_function_table.clear(); @@ -59,10 +59,15 @@ namespace scripting for (const auto& callback : shutdown_callbacks) { - callback(free_scripts); + callback(clear_scripts ,false); } - return g_shutdown_game_hook.invoke(free_scripts); + g_shutdown_game_hook.invoke(clear_scripts); + + for (const auto& callback : shutdown_callbacks) + { + callback(clear_scripts, true); + } } void process_script_stub(const char* filename) @@ -165,7 +170,7 @@ namespace scripting return find_token(id); } - void on_shutdown(const std::function& callback) + void on_shutdown(const std::function& callback) { shutdown_callbacks.push_back(callback); } diff --git a/src/client/component/scripting.hpp b/src/client/component/scripting.hpp index c841903..8f615bd 100644 --- a/src/client/component/scripting.hpp +++ b/src/client/component/scripting.hpp @@ -8,7 +8,7 @@ namespace scripting extern std::string current_file; - void on_shutdown(const std::function& callback); + void on_shutdown(const std::function& callback); void on_init(const std::function& callback); std::optional get_canonical_string(unsigned int id); diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index 63b4eb5..19a64c1 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -46,7 +46,9 @@ namespace dvars game::dvar_t* cg_legacyCrashHandling = nullptr; game::dvar_t* com_developer_script = nullptr; - game::dvar_t** com_developer; + game::dvar_t** com_developer = nullptr; + + game::dvar_t** fs_gameDirVar = nullptr; std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain) { diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index c273a6b..f25e287 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -47,6 +47,8 @@ namespace dvars extern game::dvar_t* com_developer_script; extern game::dvar_t** com_developer; + extern game::dvar_t** fs_gameDirVar; + std::string dvar_get_vector_domain(int components, const game::dvar_limits& domain); std::string dvar_get_domain(game::dvar_type type, const game::dvar_limits& domain); } diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index ed33f04..16229ad 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -25,6 +25,13 @@ namespace game return sv_cmd_args->argv[sv_cmd_args->nesting][index]; } + HANDLE Sys_OpenFileReliable(const char* filename) + { + return ::CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr); + } + namespace environment { launcher::mode mode = launcher::mode::none; diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index d0c6188..33dcb60 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -63,6 +63,8 @@ namespace game [[nodiscard]] int SV_Cmd_Argc(); [[nodiscard]] const char* SV_Cmd_Argv(int index); + [[nodiscard]] HANDLE Sys_OpenFileReliable(const char* filename); + [[nodiscard]] bool is_headless(); void show_error(const std::string& text, const std::string& title = "Error"); diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 87e6847..475fedb 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -47,6 +47,12 @@ namespace game PMF_UNK3 = 0x4000, }; + enum svscmd_type + { + SV_CMD_CAN_IGNORE, + SV_CMD_RELIABLE, + }; + enum XAssetType { ASSET_TYPE_PHYSPRESET = 0x0, @@ -127,6 +133,19 @@ namespace game DB_LOAD_SYNC_SKIP_ALWAYS_LOADED = 0x5, }; + enum + { + DB_ZONE_COMMON = 0x1, + DB_ZONE_UI = 0x2, + DB_ZONE_GAME = 0x4, + DB_ZONE_LOAD = 0x8, + DB_ZONE_DEV = 0x10, + DB_ZONE_BASEMAP = 0x20, + DB_ZONE_TRANSIENT_POOL = 0x40, + DB_ZONE_TRANSIENT_MASK = 0x40, + DB_ZONE_CUSTOM = 0x80, + }; + enum { THREAD_CONTEXT_MAIN = 0x0, @@ -1106,6 +1125,7 @@ namespace game DVAR_FLAG_LATCHED = 0x2, DVAR_FLAG_CHEAT = 0x4, DVAR_FLAG_REPLICATED = 0x8, + DVAR_FLAG_SERVERINFO = 0x400, DVAR_FLAG_WRITE = 0x800, DVAR_FLAG_READ = 0x2000, }; @@ -1553,6 +1573,13 @@ namespace game VAR_TOTAL_COUNT = 0x1B, }; + struct ScriptFunctions + { + int maxSize; + int count; + int* address; + }; + struct VariableStackBuffer { const char* pos; @@ -1952,8 +1979,8 @@ namespace game SurfaceFxTable *surfaceFx;*/ RawFile* rawfile; ScriptFile* scriptfile; - /*StringTable *stringTable; - LeaderboardDef *leaderboardDef; + StringTable* stringTable; + /*LeaderboardDef *leaderboardDef; StructuredDataDefSet *structuredDataDefSet; TracerDef *tracerDef; VehicleDef *vehDef; @@ -2015,6 +2042,18 @@ namespace game const void* weapCompleteDef; }; + enum FF_DIR + { + FFD_DEFAULT = 0x0, + FFD_MOD_DIR = 0x1, + FFD_USER_MAP = 0x2, + }; + + struct Sys_File + { + HANDLE handle; + }; + namespace sp { // very shit structures for the moment cuz i cba mapping the whole thing out right now... diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 69b3007..90f3897 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -8,12 +8,12 @@ namespace game * Functions **************************************************************/ - WEAK symbol AddRefToObject{0, 0x1404326D0}; + WEAK symbol AddRefToObject{0x1403D7A10, 0x1404326D0}; WEAK symbol AddRefToValue{0x1403D7740, 0x1404326E0}; WEAK symbol AllocThread{0, 0x1404329B0}; WEAK symbol AllocVariable{0x1403D7A70, 0x140432A10}; WEAK symbol RemoveRefToValue{0x1403D90F0, 0x1404340C0}; - WEAK symbol RemoveRefToObject{0, 0x140433FB0}; + WEAK symbol RemoveRefToObject{0x1403D8FE0, 0x140433FB0}; WEAK symbol RemoveVariableValue{0x1403D91C0, 0x140434190}; WEAK symbol AimAssist_AddToTargetList{0, 0x140139D80}; @@ -54,11 +54,11 @@ namespace game WEAK symbol DB_FindXAssetHeader{0x140272300, 0x14031F3A0}; WEAK symbol DB_XAssetExists{0x140276200, 0x1403245E0}; WEAK symbol DB_IsXAssetDefault{0x140273480 , 0x1403204D0}; - WEAK symbol DB_GetRawFileLen{0x0140272E80, 0x14031FF80}; + WEAK symbol DB_GetRawFileLen{0x140272E80, 0x14031FF80}; WEAK symbol DB_GetRawBuffer{0x140272D50, 0x14031FE50}; WEAK symbol DB_IsLocalized{0x140273210, 0x140320360}; - WEAK symbol PMem_AllocFromSource_NoDebug{0x0140430B80, 0x001404F46C0}; + WEAK symbol PMem_AllocFromSource_NoDebug{0x140430B80, 0x1404F46C0}; WEAK symbol PMem_Free{0x140430EC0 , 0x1404F4A30}; WEAK symbol Hunk_AllocateTempMemoryHighInternal{0x140423C70, 0x1404E4E20}; @@ -103,6 +103,7 @@ namespace game WEAK symbol G_FindConfigstringIndex{0x0, 0x140161F90}; WEAK symbol G_RunFrame{0x0, 0x1403A05E0}; + WEAK symbol G_GetClientScore{0x0, 0x14039EF60}; WEAK symbol HudElem_Alloc{0x0, 0x1403997E0}; @@ -132,8 +133,9 @@ namespace game WEAK symbolStructuredDataDef_GetAsset{0, 0x1404E6560}; WEAK symbolStringTable_GetAsset{0, 0x1404E6170}; - WEAK symbol StringTable_GetColumnValueForRow{0, 0x1404E61A0}; - WEAK symbol StringTable_LookupRowNumForValue{0, 0x1404E6260}; + WEAK symbol StringTable_GetColumnValueForRow{0, 0x1404E61A0}; + WEAK symbol StringTable_LookupRowNumForValue{0, 0x1404E6260}; + WEAK symbol StringTable_HashString{0x1404259A0, 0x1404E6320}; WEAK symbol LUI_OpenMenu{0x1403FD460, 0x1404B3610}; // Made up name, replaced by ScopedCriticalSection on Black Ops 3 @@ -163,13 +165,17 @@ namespace game WEAK symbol FindVariable{0x1403D84F0, 0x1404334A0}; WEAK symbol FindEntityId{0, 0x1404333A0}; WEAK symbol GetVariableName{0x1403D8E90, 0x140433E60}; - WEAK symbol GetEntityFieldValue{0x1403DC810, 0x140437860}; + WEAK symbol GetEntityFieldValue{0x1403DC810, 0x140437860}; WEAK symbol Scr_AllocVector{0x1403D9AF0, 0x140434A10}; - WEAK symbol Scr_GetString{0, 0x140439160}; + WEAK symbol Scr_GetString{0x1403DE200, 0x140439160}; + WEAK symbol Scr_GetConstString{0x1403DDAC0, 0x140438AD0}; + WEAK symbol Scr_GetConstIString{0x1403DD8A0, 0x1404388B0}; WEAK symbol Scr_AddInt{0x0, 0x140437E70}; WEAK symbol Scr_AddString{0x0, 0x1404381D0}; - WEAK symbol Scr_GetInt{0x0, 0x140438E10}; - WEAK symbol Scr_GetFloat{0, 0x140438D60}; + WEAK symbol Scr_GetInt{0x1403DDEB0, 0x140438E10}; + WEAK symbol Scr_GetFloat{0x1403DDD50, 0x140438D60}; + WEAK symbol Scr_GetVector{0x1403DE470, 0x1404393D0}; + WEAK symbol Scr_GetObject{0x1403DDF70, 0x140438ED0}; WEAK symbol Scr_GetNumParam{0x1403DDF60, 0x140438EC0}; WEAK symbol Scr_ClearOutParams{0x1403DD500, 0x140438600}; WEAK symbol Scr_GetEntityIdRef{0x1403DBDC0, 0x140436E10}; @@ -178,15 +184,16 @@ namespace game WEAK symbol Scr_NotifyId{0x1403DE730, 0x140439700}; WEAK symbol Scr_NotifyLevel{0x0, 0x1404397D0}; WEAK symbol Scr_GetEntityId{0x0, 0x140436D60}; - WEAK symbol Scr_CastString{0x0, 0x140434AC0}; + WEAK symbol Scr_CastString{0x1403D9BA0, 0x140434AC0}; WEAK symbol Scr_ExecThread{0x1403DD600, 0x1404386E0}; WEAK symbol Scr_LoadScript{0x1403D3C50, 0x14042EAA0}; WEAK symbol Scr_GetFunctionHandle{0x1403D3AD0 , 0x14042E920}; WEAK symbol Scr_RegisterFunction{0x1403D3530, 0x14042E330}; - WEAK symbol Scr_ErrorInternal{0x0, 0x140438660}; + WEAK symbol Scr_GetFunction{0x14034A950, 0x1403CD9F0}; + WEAK symbol Scr_ErrorInternal{0x1403DD580 , 0x140438660}; - WEAK symbol GetObjectType{0x0, 0x140433CF0}; + WEAK symbol GetObjectType{0x1403D8D30, 0x140433CF0}; WEAK symbol VM_Execute{0, 0x14043A280}; @@ -204,7 +211,6 @@ namespace game WEAK symbol SV_MatchEnd{0x0, 0x14047A090}; WEAK symbol SV_DirectConnect{0, 0x140471390}; - WEAK symbol SV_GameSendServerCommand{0x140490F40, 0x1404758C0}; WEAK symbol SV_Loaded{0x140491820, 0x1404770C0}; WEAK symbol SV_StartMap{0, 0x140470170}; WEAK symbol SV_StartMapForParty{0, 0x1404702F0}; @@ -220,6 +226,7 @@ namespace game WEAK symbol SV_GetGuid{0, 0x140475990}; WEAK symbol SV_KickClientNum{0, 0x14046F730}; WEAK symbol SV_SetConfigstring{0, 0x140477450}; + WEAK symbol SV_GameSendServerCommand{0x140490F40, 0x1404758C0}; WEAK symbol Sys_Error{0x14043AC20, 0x1404FF510}; WEAK symbol Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920}; @@ -246,6 +253,8 @@ namespace game WEAK symbol Jump_ClearState{0x0, 0x140213120}; + WEAK symbol LargeLocalResetToMark{0x140423B50, 0x1404E4D00}; + WEAK symbol longjmp{0x14062E030, 0x140738060}; WEAK symbol _setjmp{0x14062F030, 0x140739060}; diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 3f7c182..11b7eb2 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -5,7 +5,9 @@ #define BINARY_PAYLOAD_SIZE 0x0B500000 // Decide whether to load the game as lib or to inject it +#if 1 #define INJECT_HOST_AS_LIB +#endif #pragma warning(push) #pragma warning(disable: 4100) diff --git a/src/common/utils/properties.cpp b/src/common/utils/properties.cpp new file mode 100644 index 0000000..8c85bfd --- /dev/null +++ b/src/common/utils/properties.cpp @@ -0,0 +1,25 @@ +#include "properties.hpp" + +#include + +#include + +namespace utils::properties +{ + std::filesystem::path get_appdata_path() + { + PWSTR path; + if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) + { + throw std::runtime_error("Failed to read APPDATA path!"); + } + + auto _ = gsl::finally([&path] + { + CoTaskMemFree(path); + }); + + static auto appdata = std::filesystem::path(path) / "alterware"; + return appdata; + } +} diff --git a/src/common/utils/properties.hpp b/src/common/utils/properties.hpp new file mode 100644 index 0000000..92aff82 --- /dev/null +++ b/src/common/utils/properties.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace utils::properties +{ + std::filesystem::path get_appdata_path(); +} From 199206ee5ae6919cdd101f2f5e5aa76a1f676ce3 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Sat, 11 Jan 2025 10:20:05 +0100 Subject: [PATCH 02/11] maint: January Update Co-authored-by: Anomaly --- deps/gsc-tool | 2 +- src/client/component/auth.cpp | 88 +++++++++++++++---- src/client/component/colors.cpp | 45 ++++++++-- src/client/component/dedicated_info.cpp | 3 +- src/client/component/map_rotation.cpp | 2 +- src/client/component/rcon.cpp | 30 ++++--- src/client/component/security.cpp | 33 +++++++ src/client/game/demonware/data_types.hpp | 6 +- .../game/demonware/services/bdGroup.cpp | 24 ++++- .../game/demonware/services/bdGroup.hpp | 2 +- .../game/demonware/services/bdStorage.cpp | 40 ++++----- src/client/game/structs.hpp | 27 ++++++ src/client/game/symbols.hpp | 2 + src/common/utils/string.cpp | 4 +- 14 files changed, 236 insertions(+), 72 deletions(-) diff --git a/deps/gsc-tool b/deps/gsc-tool index 9ff3b87..e4cb6a7 160000 --- a/deps/gsc-tool +++ b/deps/gsc-tool @@ -1 +1 @@ -Subproject commit 9ff3b871ef59b14eb8b32d258bf18347e14a3e18 +Subproject commit e4cb6a7819726d9ea33cd6a52f0dcd9382258b47 diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index 3840601..ef9ad26 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -36,11 +36,8 @@ namespace auth auto hw_profile_path = (utils::properties::get_appdata_path() / "iw6-guid.dat").generic_string(); if (utils::io::file_exists(hw_profile_path)) { - std::string hw_profile_info; - if (utils::io::read_file(hw_profile_path, &hw_profile_info) && !hw_profile_info.empty()) - { - return hw_profile_info; - } + // Migration + utils::io::remove_file(hw_profile_path); } HW_PROFILE_INFO info; @@ -50,8 +47,6 @@ namespace auth } auto hw_profile_info = std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) }; - utils::io::write_file(hw_profile_path, hw_profile_info); - return hw_profile_info; } @@ -67,7 +62,7 @@ namespace auth return {}; } - const auto size = std::min(data_out.cbData, 52ul); + const auto size = std::min(data_out.cbData, 52); std::string result{ reinterpret_cast(data_out.pbData), size }; LocalFree(data_out.pbData); @@ -91,9 +86,72 @@ namespace auth return entropy; } - utils::cryptography::ecc::key& get_key() + bool load_key(utils::cryptography::ecc::key& key) { - static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); + std::string data{}; + + auto key_path = (utils::properties::get_appdata_path() / "iw6-private.key").generic_string(); + if (!utils::io::read_file(key_path, &data)) + { + return false; + } + + key.deserialize(data); + if (!key.is_valid()) + { + console::error("Loaded key is invalid!\n"); + return false; + } + + return true; + } + + utils::cryptography::ecc::key generate_key() + { + auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); + if (!key.is_valid()) + { + throw std::runtime_error("Failed to generate cryptographic key!"); + } + + auto key_path = (utils::properties::get_appdata_path() / "iw6-private.key").generic_string(); + if (!utils::io::write_file(key_path, key.serialize())) + { + console::error("Failed to write cryptographic key!\n"); + } + + console::info("Generated cryptographic key: %llX\n", key.get_hash()); + return key; + } + + utils::cryptography::ecc::key load_or_generate_key() + { + utils::cryptography::ecc::key key{}; + if (load_key(key)) + { + console::info("Loaded cryptographic key: %llX\n", key.get_hash()); + return key; + } + + return generate_key(); + } + + utils::cryptography::ecc::key get_key_internal() + { + auto key = load_or_generate_key(); + + auto key_path = (utils::properties::get_appdata_path() / "iw6-public.key").generic_string(); + if (!utils::io::write_file(key_path, key.get_public_key())) + { + console::error("Failed to write public key!\n"); + } + + return key; + } + + const utils::cryptography::ecc::key& get_key() + { + static auto key = get_key_internal(); return key; } @@ -101,7 +159,7 @@ namespace auth { std::string connect_string(format, len); game::SV_Cmd_TokenizeString(connect_string.data()); - const auto _ = gsl::finally([]() + const auto _0 = gsl::finally([]() { game::SV_Cmd_EndTokenizedString(); }); @@ -124,7 +182,7 @@ namespace auth proto::network::connect_info info; info.set_publickey(get_key().get_public_key()); - info.set_signature(sign_message(get_key(), challenge)); + info.set_signature(utils::cryptography::ecc::sign_message(get_key(), challenge)); info.set_infostring(connect_string); network::send(*adr, "connect", info.SerializeAsString()); @@ -165,14 +223,14 @@ namespace auth utils::cryptography::ecc::key key; key.set(info.publickey()); - const auto xuid = strtoull(steam_id.data(), nullptr, 16); + const auto xuid = std::strtoull(steam_id.data(), nullptr, 16); if (xuid != key.get_hash()) { network::send(*from, "error", "XUID doesn't match the certificate!", '\n'); return; } - if (!key.is_valid() || !verify_message(key, challenge, info.signature())) + if (!key.is_valid() || !utils::cryptography::ecc::verify_message(key, challenge, info.signature())) { network::send(*from, "error", "Challenge signature was invalid!", '\n'); return; @@ -232,7 +290,7 @@ namespace auth utils::hook::call(0x1402C4F8E, send_connect_data_stub); } - command::add("guid", [] + command::add("guid", []() -> void { console::info("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits); }); diff --git a/src/client/component/colors.cpp b/src/client/component/colors.cpp index 7912ce5..9391656 100644 --- a/src/client/component/colors.cpp +++ b/src/client/component/colors.cpp @@ -76,7 +76,16 @@ namespace colors void com_clean_name_stub(const char* in, char* out, const int out_size) { - strncpy_s(out, out_size, in, _TRUNCATE); + // Check that the name is at least 3 char without colors + char name[16]{}; + + game::I_strncpyz(out, in, std::min(out_size, sizeof(name))); + + utils::string::strip(out, name, std::strlen(out) + 1); + if (std::strlen(name) < 3) + { + game::I_strncpyz(out, "UnnamedPlayer", std::min(out_size, sizeof(name))); + } } char* i_clean_str_stub(char* string) @@ -86,18 +95,31 @@ namespace colors return string; } - size_t get_client_name_stub(const int local_client_num, const int index, char* buf, const int size, - const size_t unk, const size_t unk2) + int cl_get_client_name_and_clan_tag_stub(const int local_client_num, const int index, char* name_buf, const int name_size, char* clan_tag_buf, int clan_tag_size) { - // CL_GetClientName (CL_GetClientNameAndClantag?) - const auto result = reinterpret_cast(0x1402CF790)( - local_client_num, index, buf, size, unk, unk2); + // CL_GetClientNameAndClanTag -> CL_GetClientNameAndClanTagColorize + const auto result = utils::hook::invoke(0x1402CF790, local_client_num, index, name_buf, name_size, clan_tag_buf, clan_tag_size); - utils::string::strip(buf, buf, size); + utils::string::strip(name_buf, name_buf, name_size); return result; } + int com_sprintf_stub(char* dest, int size, const char* fmt, const char* name) + { + const auto len = sprintf_s(dest, size, fmt, name); + if (len < 0) + { + game::I_strncpyz(dest, "UnnamedAgent", size); + return len; + } + + // This should inherit the name of the owner (a player) which already passed the length check in Com_CleanName + utils::string::strip(dest, dest, len + 1); + + return len; + } + void rb_lookup_color_stub(const char index, DWORD* color) { *color = RGB(255, 255, 255); @@ -138,10 +160,15 @@ namespace colors if (!game::environment::is_sp()) { // allows colored name in-game - utils::hook::jump(0x1404F5FC0, com_clean_name_stub); + utils::hook::call(0x1403881CF, com_clean_name_stub); + utils::hook::call(0x140388224, com_clean_name_stub); // don't apply colors to overhead names - utils::hook::call(0x14025CE79, get_client_name_stub); + utils::hook::call(0x14025CE79, cl_get_client_name_and_clan_tag_stub); + + // don't apply colors to overhead names of agents (like dogs or juggernauts) + // hook Com_sprintf in CL_GetAgentName + utils::hook::call(0x1402CF760, com_sprintf_stub); // patch I_CleanStr utils::hook::jump(0x1404F63C0, i_clean_str_stub); diff --git a/src/client/component/dedicated_info.cpp b/src/client/component/dedicated_info.cpp index 5dacf01..39986e6 100644 --- a/src/client/component/dedicated_info.cpp +++ b/src/client/component/dedicated_info.cpp @@ -56,8 +56,7 @@ namespace dedicated_info std::string cleaned_hostname = sv_hostname->current.string; - utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(), - cleaned_hostname.size() + 1); + utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(), cleaned_hostname.size() + 1); console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(), mapname->current.string, client_count, diff --git a/src/client/component/map_rotation.cpp b/src/client/component/map_rotation.cpp index 5fd4b4a..8df2d99 100644 --- a/src/client/component/map_rotation.cpp +++ b/src/client/component/map_rotation.cpp @@ -99,7 +99,7 @@ namespace map_rotation console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name); } #ifdef _DEBUG - console::info("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size()); + console::info("dedicated_rotation size after parsing is '%llu'\n", dedicated_rotation.get_entries_size()); #endif } diff --git a/src/client/component/rcon.cpp b/src/client/component/rcon.cpp index 52dff19..7fd3cd8 100644 --- a/src/client/component/rcon.cpp +++ b/src/client/component/rcon.cpp @@ -53,23 +53,25 @@ namespace rcon const auto client = &game::mp::svs_clients[i]; auto self = &game::mp::g_entities[i]; + if (client->header.state == game::CS_FREE || !self || !self->client) + { + continue; + } + char clean_name[32]{}; strncpy_s(clean_name, self->client->sess.cs.name, sizeof(clean_name)); game::I_CleanStr(clean_name); - - if (client->header.state > game::CS_FREE && self && self->client) - { - buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n", - i, - self->client->sess.scores.score, - game::SV_BotIsBot(i) ? "Yes" : "No", - (client->header.state == game::CS_RECONNECTING) ? "CNCT" : (client->header.state == game::CS_ZOMBIE) ? "ZMBI" : utils::string::va("%4i", client->ping), - game::SV_GetGuid(i), - clean_name, - network::net_adr_to_string(client->header.netchan.remoteAddress), - client->header.netchan.remoteAddress.port) - ); - } + + buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n", + i, + self->client->sess.scores.score, + game::SV_BotIsBot(i) ? "Yes" : "No", + (client->header.state == game::CS_RECONNECTING) ? "CNCT" : (client->header.state == game::CS_ZOMBIE) ? "ZMBI" : utils::string::va("%4i", client->ping), + game::SV_GetGuid(i), + clean_name, + network::net_adr_to_string(client->header.netchan.remoteAddress), + client->header.netchan.remoteAddress.port) + ); } return buffer; diff --git a/src/client/component/security.cpp b/src/client/component/security.cpp index 327af7e..03e0cac 100644 --- a/src/client/component/security.cpp +++ b/src/client/component/security.cpp @@ -10,6 +10,8 @@ namespace security { namespace { + utils::hook::detour ui_replace_directive_hook; + void set_cached_playerdata_stub(const int localclient, const int index1, const int index2) { if (index1 >= 0 && index1 < 18 && index2 >= 0 && index2 < 42) @@ -30,6 +32,35 @@ namespace security utils::hook::invoke(0x140472500, client, msg); } + + void ui_replace_directive_stub(const int local_client_num, const char* src_string, char* dst_string, const int dst_buffer_size) + { + assert(src_string); + if (!src_string) + { + return; + } + + assert(dst_string); + if (!dst_string) + { + return; + } + + assert(dst_buffer_size > 0); + if (dst_buffer_size <= 0) + { + return; + } + + constexpr std::size_t MAX_HUDELEM_TEXT_LEN = 0x100; + if (std::strlen(src_string) > MAX_HUDELEM_TEXT_LEN) + { + return; + } + + ui_replace_directive_hook.invoke(local_client_num, src_string, dst_string, dst_buffer_size); + } } class component final : public component_interface @@ -44,6 +75,8 @@ namespace security // It is possible to make the server hang if left unchecked utils::hook::call(0x14047A29A, sv_execute_client_message_stub); + + ui_replace_directive_hook.create(0x1404D8A00, ui_replace_directive_stub); } }; } diff --git a/src/client/game/demonware/data_types.hpp b/src/client/game/demonware/data_types.hpp index e61bd1b..7d3ba1b 100644 --- a/src/client/game/demonware/data_types.hpp +++ b/src/client/game/demonware/data_types.hpp @@ -30,7 +30,7 @@ namespace demonware uint64_t file_id; uint32_t create_time; uint32_t modified_time; - bool priv; + bool visibility; uint64_t owner_id; std::string filename; uint32_t file_size; @@ -41,7 +41,7 @@ namespace demonware buffer->write_uint64(this->file_id); buffer->write_uint32(this->create_time); buffer->write_uint32(this->modified_time); - buffer->write_bool(this->priv); + buffer->write_bool(this->visibility); buffer->write_uint64(this->owner_id); buffer->write_string(this->filename); } @@ -52,7 +52,7 @@ namespace demonware buffer->read_uint64(&this->file_id); buffer->read_uint32(&this->create_time); buffer->read_uint32(&this->modified_time); - buffer->read_bool(&this->priv); + buffer->read_bool(&this->visibility); buffer->read_uint64(&this->owner_id); buffer->read_string(&this->filename); } diff --git a/src/client/game/demonware/services/bdGroup.cpp b/src/client/game/demonware/services/bdGroup.cpp index fd75336..e665c7d 100644 --- a/src/client/game/demonware/services/bdGroup.cpp +++ b/src/client/game/demonware/services/bdGroup.cpp @@ -10,19 +10,35 @@ namespace demonware this->register_service(4, &bdGroup::get_groups); } - void bdGroup::set_groups(i_server* server, byte_buffer* /*buffer*/) const + void bdGroup::set_groups(i_server* server, byte_buffer* buffer) { - //uint32_t groupCount; - // TODO: Implement array reading + uint32_t entries_count{}; + buffer->read_array_header(game::BD_BB_UNSIGNED_INTEGER32_TYPE, &entries_count); auto reply = server->create_reply(this->get_sub_type()); + + buffer->set_use_data_types(false); + + for (uint32_t i = 0; i < entries_count; ++i) + { + uint32_t group_id{}; + buffer->read_uint32(&group_id); + + if (group_id < ARRAYSIZE(this->groups)) + { + this->groups[group_id] = 999; + } + } + + buffer->set_use_data_types(true); + reply->send(); } void bdGroup::get_groups(i_server* server, byte_buffer* buffer) { uint32_t group_count; - buffer->read_array_header(8, &group_count); + buffer->read_array_header(game::BD_BB_UNSIGNED_INTEGER32_TYPE, &group_count); auto reply = server->create_reply(this->get_sub_type()); diff --git a/src/client/game/demonware/services/bdGroup.hpp b/src/client/game/demonware/services/bdGroup.hpp index 7b85f1c..90f2f70 100644 --- a/src/client/game/demonware/services/bdGroup.hpp +++ b/src/client/game/demonware/services/bdGroup.hpp @@ -9,7 +9,7 @@ namespace demonware bdGroup(); private: - void set_groups(i_server* server, byte_buffer* buffer) const; + void set_groups(i_server* server, byte_buffer* buffer); void get_groups(i_server* server, byte_buffer* buffer); uint32_t groups[512]{}; diff --git a/src/client/game/demonware/services/bdStorage.cpp b/src/client/game/demonware/services/bdStorage.cpp index 36297c4..d628a29 100644 --- a/src/client/game/demonware/services/bdStorage.cpp +++ b/src/client/game/demonware/services/bdStorage.cpp @@ -119,11 +119,11 @@ namespace demonware void bdStorage::set_legacy_user_file(i_server* server, byte_buffer* buffer) const { - bool priv; + bool visibility; std::string filename, data; buffer->read_string(&filename); - buffer->read_bool(&priv); + buffer->read_bool(&visibility); buffer->read_blob(&data); const auto id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); @@ -139,11 +139,11 @@ namespace demonware info->file_id = id; info->filename = filename; - info->create_time = uint32_t(time(nullptr)); + info->create_time = static_cast(std::time(nullptr)); info->modified_time = info->create_time; - info->file_size = uint32_t(data.size()); + info->file_size = static_cast(data.size()); info->owner_id = 0; - info->priv = priv; + info->visibility = visibility; auto reply = server->create_reply(this->get_sub_type()); reply->add(info); @@ -170,11 +170,11 @@ namespace demonware info->file_id = id; info->filename = "<>"; - info->create_time = uint32_t(time(nullptr)); + info->create_time = static_cast(std::time(nullptr)); info->modified_time = info->create_time; - info->file_size = uint32_t(data.size()); + info->file_size = static_cast(data.size()); info->owner_id = 0; - info->priv = false; + info->visibility = false; auto reply = server->create_reply(this->get_sub_type()); reply->add(info); @@ -207,12 +207,12 @@ namespace demonware void bdStorage::list_legacy_user_files(i_server* server, byte_buffer* buffer) const { - uint64_t unk; + uint64_t owner; uint32_t date; uint16_t num_results, offset; std::string filename, data; - buffer->read_uint64(&unk); + buffer->read_uint64(&owner); buffer->read_uint32(&date); buffer->read_uint16(&num_results); buffer->read_uint16(&offset); @@ -229,9 +229,9 @@ namespace demonware info->filename = filename; info->create_time = 0; info->modified_time = info->create_time; - info->file_size = uint32_t(data.size()); - info->owner_id = 0; - info->priv = false; + info->file_size = static_cast(data.size()); + info->owner_id = owner; + info->visibility = false; reply->add(info); } @@ -260,9 +260,9 @@ namespace demonware info->filename = filename; info->create_time = 0; info->modified_time = info->create_time; - info->file_size = uint32_t(data.size()); + info->file_size = static_cast(data.size()); info->owner_id = 0; - info->priv = false; + info->visibility = false; reply->add(info); } @@ -312,13 +312,13 @@ namespace demonware void bdStorage::set_user_file(i_server* server, byte_buffer* buffer) const { - bool priv; + bool visibility; uint64_t owner; std::string game, filename, data; buffer->read_string(&game); buffer->read_string(&filename); - buffer->read_bool(&priv); + buffer->read_bool(&visibility); buffer->read_blob(&data); buffer->read_uint64(&owner); @@ -329,11 +329,11 @@ namespace demonware info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); info->filename = filename; - info->create_time = uint32_t(time(nullptr)); + info->create_time = static_cast(std::time(nullptr)); info->modified_time = info->create_time; - info->file_size = uint32_t(data.size()); + info->file_size = static_cast(data.size()); info->owner_id = owner; - info->priv = priv; + info->visibility = visibility; auto reply = server->create_reply(this->get_sub_type()); reply->add(info); diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 475fedb..5f80604 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -641,6 +641,33 @@ namespace game BD_MAX_ERROR_CODE = 0x27E2, }; + enum bdBitBufferDataType + { + BD_BB_NO_TYPE = 0x0, + BD_BB_BOOL_TYPE = 0x1, + BD_BB_SIGNED_CHAR8_TYPE = 0x2, + BD_BB_UNSIGNED_CHAR8_TYPE = 0x3, + BD_BB_WCHAR16_TYPE = 0x4, + BD_BB_SIGNED_INTEGER16_TYPE = 0x5, + BD_BB_UNSIGNED_INTEGER16_TYPE = 0x6, + BD_BB_SIGNED_INTEGER32_TYPE = 0x7, + BD_BB_UNSIGNED_INTEGER32_TYPE = 0x8, + BD_BB_SIGNED_INTEGER64_TYPE = 0x9, + BD_BB_UNSIGNED_INTEGER64_TYPE = 0xA, + BD_BB_RANGED_SIGNED_INTEGER32_TYPE = 0xB, + BD_BB_RANGED_UNSIGNED_INTEGER32_TYPE = 0xC, + BD_BB_FLOAT32_TYPE = 0xD, + BD_BB_FLOAT64_TYPE = 0xE, + BD_BB_RANGED_FLOAT32_TYPE = 0xF, + BD_BB_SIGNED_CHAR8_STRING_TYPE = 0x10, + BD_BB_UNSIGNED_CHAR8_STRING_TYPE = 0x11, + BD_BB_MBSTRING_TYPE = 0x12, + BD_BB_BLOB_TYPE = 0x13, + BD_BB_NAN_TYPE = 0x14, + BD_BB_FULL_TYPE = 0x15, + BD_BB_MAX_TYPE = 0x20, + }; + enum bdNATType : uint8_t { BD_NAT_UNKNOWN = 0x0, diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 90f3897..a96c362 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -108,6 +108,7 @@ namespace game WEAK symbol HudElem_Alloc{0x0, 0x1403997E0}; WEAK symbol I_CleanStr{0x140432460, 0x1404F63C0}; + WEAK symbol I_strncpyz{0x140432810, 0x1404F67A0}; WEAK symbol @@ -243,6 +244,7 @@ namespace game WEAK symbol UI_LocalizeMapname{0, 0x1404B96D0}; WEAK symbol UI_LocalizeGametype{0, 0x1404B90F0}; + WEAK symbol UI_ReplaceDirective{0x0, 0x1404D8A00}; WEAK symbol dwGetLogOnStatus{0, 0x140589490}; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 6236584..649c590 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -115,13 +115,13 @@ namespace utils::string void strip(const char* in, char* out, size_t max) { - if (!in || !out) return; + if (!in || !out || !max) return; max--; auto current = 0u; while (*in != 0 && current < max) { - const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48); + const auto color_index = (static_cast(*(in + 1) - 48)) >= 0xC ? 7 : (*(in + 1) - 48); if (*in == '^' && (color_index != 7 || *(in + 1) == '7')) { From 3d77a21f3da483a57d0a26d5f719af171a0091aa Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Fri, 24 Jan 2025 13:59:57 +0100 Subject: [PATCH 03/11] assets(weapons): cache weapons from code (Code quaK/H1-Mod probably took from Pluto xD) Co-authored-by: H1-Mod Team Co-authored-by: HighTechRedNeck --- src/client/component/assets/weapons.cpp | 61 ++++++++ src/client/component/command.cpp | 76 ++++------ src/client/component/dvar_cheats.cpp | 4 +- src/client/component/party.cpp | 9 +- src/client/component/patches.cpp | 5 + src/client/game/engine/sv_game.cpp | 181 ++++++++++++++++++++++++ src/client/game/engine/sv_game.hpp | 6 + src/client/game/structs.hpp | 42 +++++- src/client/game/symbols.hpp | 7 +- src/client/std_include.hpp | 1 + 10 files changed, 331 insertions(+), 61 deletions(-) create mode 100644 src/client/component/assets/weapons.cpp create mode 100644 src/client/game/engine/sv_game.cpp create mode 100644 src/client/game/engine/sv_game.hpp diff --git a/src/client/component/assets/weapons.cpp b/src/client/component/assets/weapons.cpp new file mode 100644 index 0000000..5e414c1 --- /dev/null +++ b/src/client/component/assets/weapons.cpp @@ -0,0 +1,61 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include "component/console.hpp" + +#include + +namespace weapons +{ + namespace + { + void g_setup_level_weapon_def_stub() + { + game::G_SetupLevelWeaponDef(); + + // The count on most maps is well below 200 + std::array weapons{}; + const auto count = game::DB_GetAllXAssetOfType_FastFile(game::ASSET_TYPE_WEAPON, (void**)weapons.data(), static_cast(weapons.max_size())); + + std::sort(weapons.begin(), weapons.begin() + count, [](game::WeaponCompleteDef* weapon1, game::WeaponCompleteDef* weapon2) + { + assert(weapon1->szInternalName); + assert(weapon2->szInternalName); + + return std::strcmp(weapon1->szInternalName, weapon2->szInternalName) < 0; + }); + +#ifdef _DEBUG + console::info("Found %i weapons to precache\n", count); +#endif + + for (auto i = 0; i < count; ++i) + { +#ifdef _DEBUG + console::info("Precaching weapon \"%s\"\n", weapons[i]->szInternalName); +#endif + (void)game::G_GetWeaponForName(weapons[i]->szInternalName); + } + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_sp()) return; + + // Kill Scr_PrecacheItem (We are going to do this from code) + utils::hook::nop(0x1403BDB10, 4); + utils::hook::set(0x1403BDB10, 0xC3); + + // Load weapons from the DB + utils::hook::call(0x14039F382, g_setup_level_weapon_def_stub); + utils::hook::call(0x1403AE31A, g_setup_level_weapon_def_stub); + } + }; +} + +REGISTER_COMPONENT(weapons::component) diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index c7dc997..bfc3218 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -2,6 +2,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" +#include "game/engine/sv_game.hpp" #include "command.hpp" #include "console.hpp" @@ -459,10 +460,7 @@ namespace command } game::sp::g_entities[0].flags ^= game::FL_GODMODE; - game::CG_GameMessage(0, utils::string::va("godmode %s", - game::sp::g_entities[0].flags & game::FL_GODMODE - ? "^2on" - : "^1off")); + game::CG_GameMessage(0, utils::string::va("godmode %s", game::sp::g_entities[0].flags & game::FL_GODMODE ? "^2on" : "^1off")); }); add("notarget", [] @@ -473,10 +471,7 @@ namespace command } game::sp::g_entities[0].flags ^= game::FL_NOTARGET; - game::CG_GameMessage(0, utils::string::va("notarget %s", - game::sp::g_entities[0].flags & game::FL_NOTARGET - ? "^2on" - : "^1off")); + game::CG_GameMessage(0, utils::string::va("notarget %s", game::sp::g_entities[0].flags & game::FL_NOTARGET ? "^2on" : "^1off")); }); add("noclip", [] @@ -487,10 +482,7 @@ namespace command } game::sp::g_entities[0].client->flags ^= 1; - game::CG_GameMessage(0, utils::string::va("noclip %s", - game::sp::g_entities[0].client->flags & 1 - ? "^2on" - : "^1off")); + game::CG_GameMessage(0, utils::string::va("noclip %s", game::sp::g_entities[0].client->flags & 1 ? "^2on" : "^1off")); }); add("ufo", [] @@ -501,10 +493,7 @@ namespace command } game::sp::g_entities[0].client->flags ^= 2; - game::CG_GameMessage(0, utils::string::va("ufo %s", - game::sp::g_entities[0].client->flags & 2 - ? "^2on" - : "^1off")); + game::CG_GameMessage(0, utils::string::va("ufo %s", game::sp::g_entities[0].client->flags & 2 ? "^2on" : "^1off")); }); add("give", [](const params& params) @@ -556,78 +545,65 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].flags ^= game::FL_GODMODE; - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - utils::string::va("f \"godmode %s\"", - game::mp::g_entities[client_num].flags & game::FL_GODMODE - ? "^2on" - : "^1off")); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"godmode %s\"", game::mp::g_entities[client_num].flags & game::FL_GODMODE ? "^2on" : "^1off")); }); add_sv("notarget", [](const int client_num, const params_sv&) { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET; - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - utils::string::va("f \"notarget %s\"", - game::mp::g_entities[client_num].flags & game::FL_NOTARGET - ? "^2on" - : "^1off")); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"notarget %s\"", game::mp::g_entities[client_num].flags & game::FL_NOTARGET ? "^2on" : "^1off")); }); add_sv("noclip", [](const int client_num, const params_sv&) { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].client->flags ^= 1; - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - utils::string::va("f \"noclip %s\"", - game::mp::g_entities[client_num].client->flags & 1 - ? "^2on" - : "^1off")); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"noclip %s\"", game::mp::g_entities[client_num].client->flags & 1 ? "^2on" : "^1off")); }); add_sv("ufo", [](const int client_num, const params_sv&) { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } game::mp::g_entities[client_num].client->flags ^= 2; - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - utils::string::va("f \"ufo %s\"", - game::mp::g_entities[client_num].client->flags & 2 - ? "^2on" - : "^1off")); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + utils::string::va("f \"ufo %s\"", game::mp::g_entities[client_num].client->flags & 2 ? "^2on" : "^1off")); }); add_sv("setviewpos", [](const int client_num, const params_sv& params) { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 4) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - "f \"You did not specify the correct number of coordinates\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"You did not specify the correct number of coordinates\""); return; } @@ -640,14 +616,14 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 4) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, - "f \"You did not specify the correct number of coordinates\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, + "f \"You did not specify the correct number of coordinates\""); return; } @@ -660,13 +636,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 2) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); return; } @@ -683,13 +659,13 @@ namespace command { if (!dvars::sv_cheats->current.enabled) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); return; } if (params.size() < 2) { - game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); return; } diff --git a/src/client/component/dvar_cheats.cpp b/src/client/component/dvar_cheats.cpp index 04c4654..72ce8af 100644 --- a/src/client/component/dvar_cheats.cpp +++ b/src/client/component/dvar_cheats.cpp @@ -1,8 +1,8 @@ #include #include "loader/component_loader.hpp" - #include "game/game.hpp" #include "game/dvars.hpp" +#include "game/engine/sv_game.hpp" #include "console.hpp" @@ -136,7 +136,7 @@ namespace dvar_cheats const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack const auto* command = utils::string::va("q %s \"%s\"", dvar, value); - game::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command); + game::engine::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command); } const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a) diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index f6ec909..4b964f4 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -2,6 +2,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" +#include "game/engine/sv_game.hpp" #include "command.hpp" #include "console.hpp" @@ -380,7 +381,7 @@ namespace party const auto message = params.join(2); const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; - game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); console::info("%s -> %i: %s\n", name, client_num, message.data()); }); @@ -394,7 +395,7 @@ namespace party const auto client_num = atoi(params.get(1)); const auto message = params.join(2); - game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); + game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); console::info("%i: %s\n", client_num, message.data()); }); @@ -408,7 +409,7 @@ namespace party const auto message = params.join(1); const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; - game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); + game::engine::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); console::info("%s: %s\n", name, message.data()); }); @@ -421,7 +422,7 @@ namespace party const auto message = params.join(1); - game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); + game::engine::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); console::info("%s\n", message.data()); }); diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index 59afda4..f5c9bb4 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -2,6 +2,7 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" #include "game/dvars.hpp" +#include "game/engine/sv_game.hpp" #include "command.hpp" #include "console.hpp" @@ -290,6 +291,10 @@ namespace patches static void patch_mp() { + // Bypass Arxan function + utils::hook::nop(0x1404758C0, 16); + utils::hook::jump(0x1404758C0, game::engine::SV_GameSendServerCommand, true); + // Register dvars com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub); diff --git a/src/client/game/engine/sv_game.cpp b/src/client/game/engine/sv_game.cpp new file mode 100644 index 0000000..4f83e3b --- /dev/null +++ b/src/client/game/engine/sv_game.cpp @@ -0,0 +1,181 @@ +#include +#include + +#include "sv_game.hpp" + +#include + +#include + +namespace game::engine +{ + char* SV_ExpandNewlines(char* in) + { + static char string[1024]; + + unsigned int l = 0; + while (*in && l < sizeof(string) - 3) + { + if (*in == '\n') + { + string[l++] = '\\'; + string[l++] = 'n'; + } + else + { + if (*in != '\x14' && *in != '\x15') + { + string[l++] = *in; + } + } + + ++in; + } + + string[l] = '\0'; + return string; + } + + void SV_CullIgnorableServerCommands(mp::client_t* client) + { + int to = client->reliableSent + 1; + for (int from = to; from <= client->reliableSequence; ++from) + { + int from_index = from & 0x7F; + assert(client->netBuf.reliableCommandInfo[from_index].time >= 0); + if (client->netBuf.reliableCommandInfo[from_index].type) + { + int to_index = to & 0x7F; + if (to_index != from_index) + { + client->netBuf.reliableCommandInfo[to_index] = client->netBuf.reliableCommandInfo[from_index]; + } + + ++to; + } + } + + client->reliableSequence = to - 1; + } + + void SV_DelayDropClient(mp::client_t* drop, const char* reason) + { + assert(drop); + assert(reason); + assert(drop->header.state != CS_FREE); + if (drop->header.state == CS_ZOMBIE) + { +#ifdef _DEBUG + console::info("(drop->dropReason) = %s", drop->dropReason); +#endif + } + else if (!drop->dropReason) + { + drop->dropReason = reason; + } + } + + void SV_AddServerCommand(mp::client_t* client, svscmd_type type, const char* cmd) + { + static_assert(offsetof(mp::client_t, netBuf.reliableCommandInfo[0].cmd) == 0xC44); + + if (client->testClient == TC_BOT) + { + return; + } + + if (client->reliableSequence - client->reliableAcknowledge < 64 && client->header.state == CS_ACTIVE || (SV_CullIgnorableServerCommands(client), type)) + { + int len = static_cast(std::strlen(cmd)) + 1; + int to = SV_CanReplaceServerCommand(client, reinterpret_cast(cmd), len); + if (to < 0) + { + ++client->reliableSequence; + } + else + { + int from = to + 1; + while (from <= client->reliableSequence) + { + client->netBuf.reliableCommandInfo[to & 0x7F] = client->netBuf.reliableCommandInfo[from & 0x7F]; + ++from; + ++to; + } + } + + if (client->reliableSequence - client->reliableAcknowledge == 129) + { +#ifdef _DEBUG + console::info("===== pending server commands =====\n"); + int i = 0; + for (i = client->reliableAcknowledge + 1; i <= client->reliableSequence; ++i) + { + console::info("cmd %5d: %8d: %s\n", i, client->netBuf.reliableCommandInfo[i & 0x7F].time, client->netBuf.reliableCommandInfo[i & 0x7F].cmd); + } + console::info("cmd %5d: %8d: %s\n", i, *game::mp::serverTime, cmd); +#endif + NET_OutOfBandPrint(NS_SERVER, &client->header.netchan.remoteAddress, "disconnect"); + SV_DelayDropClient(client, "EXE_SERVERCOMMANDOVERFLOW"); + type = SV_CMD_RELIABLE; + cmd = utils::string::va("%c \"EXE_SERVERCOMMANDOVERFLOW\"", 'r'); + } + + int index = client->reliableSequence & 0x7F; + MSG_WriteReliableCommandToBuffer(cmd, client->netBuf.reliableCommandInfo[index].cmd, sizeof(client->netBuf.reliableCommandInfo[index].cmd)); + client->netBuf.reliableCommandInfo[index].time = *game::mp::serverTime; + client->netBuf.reliableCommandInfo[index].type = type; + } + } + + void SV_SendServerCommand(mp::client_t* cl, svscmd_type type, const char* fmt, ...) + { + mp::client_t* client; + int j, len; + va_list va; + + const auto server_command_buf_large = std::make_unique(0x20000); + + va_start(va, fmt); + len = vsnprintf_s(server_command_buf_large.get(), 0x20000, _TRUNCATE, fmt, va); + va_end(va); + + assert(len >= 0); + + if (cl) + { + SV_AddServerCommand(cl, type, server_command_buf_large.get()); + return; + } + + if (environment::is_dedi() && !std::strncmp(server_command_buf_large.get(), "print", 5)) + { + console::info("broadcast: %s\n", SV_ExpandNewlines(server_command_buf_large.get())); + } + + const auto* sv_maxclients = Dvar_FindVar("sv_maxclients"); + for (j = 0, client = mp::svs_clients; j < sv_maxclients->current.integer; j++, client++) + { + if (client->header.state < CS_CLIENTLOADING) + { + continue; + } + + SV_AddServerCommand(client, type, server_command_buf_large.get()); + } + } + + void SV_GameSendServerCommand(int clientNum, svscmd_type type, const char* text) + { + [[maybe_unused]] const auto* sv_maxclients = Dvar_FindVar("sv_maxclients"); + + if (clientNum == -1) + { + SV_SendServerCommand(nullptr, type, "%s", text); + return; + } + + assert(sv_maxclients->current.integer >= 1 && sv_maxclients->current.integer <= 18); + assert(static_cast(clientNum) < sv_maxclients->current.unsignedInt); + SV_SendServerCommand(&mp::svs_clients[clientNum], type, "%s", text); + } +} diff --git a/src/client/game/engine/sv_game.hpp b/src/client/game/engine/sv_game.hpp new file mode 100644 index 0000000..f1b7b70 --- /dev/null +++ b/src/client/game/engine/sv_game.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace game::engine +{ + void SV_GameSendServerCommand(int clientNum, svscmd_type type, const char* text); +} diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 5f80604..10220df 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1963,6 +1963,12 @@ namespace game const char* buffer; }; + struct WeaponCompleteDef + { + const char* szInternalName; + WeaponDef* weapDef; + }; // Incomplete + union XAssetHeader { void* data; @@ -1998,9 +2004,9 @@ namespace game menuDef_t *menu; AnimationClass *animClass; LocalizeEntry *localize; - WeaponAttachment *attachment; - WeaponCompleteDef *weapon; - SndDriverGlobals *sndDriverGlobals; + WeaponAttachment *attachment;*/ + WeaponCompleteDef* weapon; + /*SndDriverGlobals *sndDriverGlobals; FxEffectDef *fx; FxImpactTable *impactFx; SurfaceFxTable *surfaceFx;*/ @@ -2434,6 +2440,24 @@ namespace game int isInKillcam; }; + struct svscmd_info_t + { + int time; + int type; + char cmd[1024]; + }; + + static_assert(sizeof(svscmd_info_t) == 0x408); + + struct client_net_buffers_t + { + svscmd_info_t reliableCommandInfo[128]; + char netchanOutgoingBuffer[131072]; + char netchanIncomingBuffer[2048]; + }; + + static_assert(sizeof(client_net_buffers_t) == 0x40C00); + struct client_t { clientHeader_t header; @@ -2443,7 +2467,17 @@ namespace game int reliableAcknowledge; int reliableSent; int messageAcknowledge; - char _0xC30[0x41238]; + int largeCommandSequence; + int gamestateMessageNum; + int challenge; + client_net_buffers_t netBuf; + int cumulThinkTime; + int beginCmdIndex; + int currCmdIndex; + usercmd_s lastUsercmd; + usercmd_s cmds[8]; + int lastClientCommand; + char lastClientCommandString[1024]; gentity_s* gentity; char name[16]; int lastPacketTime; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index a96c362..fdcb525 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -19,6 +19,7 @@ namespace game WEAK symbol AimAssist_AddToTargetList{0, 0x140139D80}; WEAK symbol BG_GetWeaponNameComplete{0, 0x140239370}; + WEAK symbol BG_ClearWeaponDef{0x0, 0x140238D20}; WEAK symbol Com_Frame_Try_Block_Function{0x1403BC980, 0x1404131A0}; WEAK symbol Com_Parse{0x1404313E0, 0x1404F50E0}; @@ -57,6 +58,7 @@ namespace game WEAK symbol DB_GetRawFileLen{0x140272E80, 0x14031FF80}; WEAK symbol DB_GetRawBuffer{0x140272D50, 0x14031FE50}; WEAK symbol DB_IsLocalized{0x140273210, 0x140320360}; + WEAK symbol DB_GetAllXAssetOfType_FastFile{0x0, 0x14031FC00}; WEAK symbol PMem_AllocFromSource_NoDebug{0x140430B80, 0x1404F46C0}; WEAK symbol PMem_Free{0x140430EC0 , 0x1404F4A30}; @@ -96,6 +98,7 @@ namespace game WEAK symbol G_FindItem{0x140462490, 0x14021B7E0}; WEAK symbol G_GivePlayerWeapon{0x140359E20, 0x1403DA5E0}; WEAK symbol G_GetWeaponForName{0x140359890, 0x1403DA060}; + WEAK symbol G_SetupLevelWeaponDef{0x0, 0x1403DA910}; WEAK symbol G_Glass_Update{0x14030E680, 0x140397450}; WEAK symbol G_InitializeAmmo{0x140311F00, 0x14039AEA0}; WEAK symbol G_SelectWeapon{0x14035A200, 0x1403DA880}; @@ -227,7 +230,7 @@ namespace game WEAK symbol SV_GetGuid{0, 0x140475990}; WEAK symbol SV_KickClientNum{0, 0x14046F730}; WEAK symbol SV_SetConfigstring{0, 0x140477450}; - WEAK symbol SV_GameSendServerCommand{0x140490F40, 0x1404758C0}; + WEAK symbol SV_CanReplaceServerCommand{0x0, 0x140478F00}; WEAK symbol Sys_Error{0x14043AC20, 0x1404FF510}; WEAK symbol Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920}; @@ -255,6 +258,8 @@ namespace game WEAK symbol Jump_ClearState{0x0, 0x140213120}; + WEAK symbol MSG_WriteReliableCommandToBuffer{0x0, 0x1404232B0}; + WEAK symbol LargeLocalResetToMark{0x140423B50, 0x1404E4D00}; WEAK symbol longjmp{0x14062E030, 0x140738060}; diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 11b7eb2..de4b4d0 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include From bc0372a2491907d1aae6dc7e26f0511ac670406d Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Mon, 27 Jan 2025 22:28:55 +0100 Subject: [PATCH 04/11] feat(mods): add LUI menu --- src/client/component/dvar_cheats.cpp | 2 +- src/client/component/filesystem.cpp | 18 ++++--- src/client/component/mods.cpp | 77 +++++++++++++++++++++++++++ src/client/component/ui_scripting.cpp | 21 ++++++++ src/client/game/structs.hpp | 18 +++++-- src/client/game/symbols.hpp | 6 ++- 6 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/client/component/dvar_cheats.cpp b/src/client/component/dvar_cheats.cpp index 72ce8af..a06000b 100644 --- a/src/client/component/dvar_cheats.cpp +++ b/src/client/component/dvar_cheats.cpp @@ -11,7 +11,7 @@ namespace dvar_cheats { - void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value) + void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::DvarValue* value) { if (dvar && dvar->name == "sv_cheats"s) { diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index dee28a3..6abf5e5 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -83,20 +83,22 @@ namespace filesystem void startup() { - register_path("iw6"); + const auto base = std::filesystem::current_path(); + + register_path(base / "iw6"); register_path(get_binary_directory() + "\\data"); - if (get_binary_directory() != std::filesystem::current_path()) + if (get_binary_directory() != base) { - register_path(std::filesystem::current_path() / "data"); + register_path(base / "data"); } // game's search paths - register_path("devraw"); - register_path("devraw_shared"); - register_path("raw_shared"); - register_path("raw"); - register_path("main"); + register_path(base / "devraw"); + register_path(base / "devraw_shared"); + register_path(base / "raw_shared"); + register_path(base / "raw"); + register_path(base / "main"); } void check_for_startup() diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index fd588b4..e6612a3 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -3,9 +3,12 @@ #include "game/game.hpp" #include "game/dvars.hpp" +#include "command.hpp" +#include "console.hpp" #include "mods.hpp" #include +#include namespace mods { @@ -98,6 +101,40 @@ namespace mods a.and_(ebp, r15d); a.jmp(0x1403217F6); }); + + bool fs_game_dir_domain_func(game::dvar_t* dvar, game::DvarValue new_value) + { + if (*new_value.string == '\0') + { + return true; + } + + if (game::I_strnicmp(new_value.string, "mods", 4) != 0) + { + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } + + if (5 < std::strlen(new_value.string) && (new_value.string[4] == '\\' || new_value.string[4] == '/')) + { + const auto* s1 = std::strstr(new_value.string, ".."); + const auto* s2 = std::strstr(new_value.string, "::"); + if (s1 == nullptr && s2 == nullptr) + { + return true; + } + + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } + + // Invalid path specified + game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0); + console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name); + return false; + } } bool is_using_mods() @@ -136,6 +173,8 @@ namespace mods // Remove DVAR_INIT from fs_game utils::hook::set(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO)); + utils::hook::inject(SELECT_VALUE(0x14041C097 + 3, 0x1404DDA57 + 3), &fs_game_dir_domain_func); + if (game::environment::is_sp()) { return; @@ -150,6 +189,44 @@ namespace mods // Load mod.ff utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from + + command::add("loadmod", [](const command::params& params) -> void + { + if (params.size() != 2) + { + console::info("USAGE: %s \"mods/\"", params.get(0)); + return; + } + + std::string mod_name = utils::string::to_lower(params.get(1)); + + if (!mod_name.empty() && !mod_name.starts_with("mods/")) + { + mod_name = "mods/" + mod_name; + } + + // change fs_game if needed + if (mod_name != (*dvars::fs_gameDirVar)->current.string) + { + game::Dvar_SetString((*dvars::fs_gameDirVar), mod_name.c_str()); + command::execute("vid_restart\n"); + } + }); + + command::add("unloadmod", [](const command::params& params) -> void + { + if (*dvars::fs_gameDirVar == nullptr || *(*dvars::fs_gameDirVar)->current.string == '\0') + { + return; + } + + game::Dvar_SetString(*dvars::fs_gameDirVar, ""); + command::execute("vid_restart\n"); + }); + + // TODO: without a way to monitor all the ways fs_game can be changed there is no way to detect when we + // should unregister the path from the internal filesystem we use + // HINT: It could be done in fs_game_dir_domain_func, but I haven't tested if that's the best place to monitor for changes and register/unregister the mods folder } }; } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 5a1f7cc..ac1e879 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -195,6 +195,27 @@ namespace ui_scripting setup_functions(); lua["print"] = function(reinterpret_cast(0x14017B120)); // hks::base_print + + lua["directoryexists"] = [](const std::string& string) + { + return utils::io::directory_exists(string); + }; + + lua["listfiles"] = [](const std::string& string) + { + return utils::io::list_files(string); + }; + + lua["directoryisempty"] = [](const std::string& string) + { + return utils::io::directory_is_empty(string); + }; + + lua["fileexists"] = [](const std::string& string) + { + return utils::io::file_exists(string); + }; + lua["table"]["unpack"] = lua["unpack"]; lua["luiglobals"] = lua; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 10220df..7eba9b1 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -10,6 +10,14 @@ namespace game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; + enum ControllerIndex_t + { + INVALID_CONTROLLER_PORT = -1, + CONTROLLER_INDEX_0 = 0x0, + CONTROLLER_INDEX_FIRST = 0x0, + CONTROLLER_INDEX_COUNT = 0x1, + }; + enum { FL_GODMODE = 0x1, @@ -1171,7 +1179,7 @@ namespace game rgb = 9 // Color without alpha }; - union dvar_value + union DvarValue { bool enabled; int integer; @@ -1214,9 +1222,9 @@ namespace game unsigned int flags; //08 dvar_type type; //0C bool modified; //0D - dvar_value current; //10 - dvar_value latched; - dvar_value reset; + DvarValue current; //10 + DvarValue latched; + DvarValue reset; dvar_limits domain; }; @@ -1967,7 +1975,7 @@ namespace game { const char* szInternalName; WeaponDef* weapDef; - }; // Incomplete + }; union XAssetHeader { diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index fdcb525..b4abdc0 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -20,6 +20,7 @@ namespace game WEAK symbol BG_GetWeaponNameComplete{0, 0x140239370}; WEAK symbol BG_ClearWeaponDef{0x0, 0x140238D20}; + WEAK symbol BG_BotsConnectType{0x0, 0x140217080}; WEAK symbol Com_Frame_Try_Block_Function{0x1403BC980, 0x1404131A0}; WEAK symbol Com_Parse{0x1404313E0, 0x1404F50E0}; @@ -87,7 +88,7 @@ namespace game WEAK symbol Dvar_SetString{0x14042D6E0, 0x1404F08E0}; WEAK symbol Dvar_SetFromStringByNameFromSource{0x14042D000, 0x1404F00B0}; WEAK symbol Dvar_Sort{0x14042DEF0, 0x1404F1210}; - WEAK symbol Dvar_ValueToString{0x14042E710, 0x1404F1A30}; + WEAK symbol Dvar_ValueToString{0x14042E710, 0x1404F1A30}; WEAK symbol FS_ReadFile{0x14041D0B0, 0x1404DE900}; WEAK symbol FS_FreeFile{0x14041D0A0, 0x1404DE8F0}; @@ -120,6 +121,7 @@ namespace game WEAK symbol Key_KeynumToString{0x14023D9A0, 0x1402C40E0}; WEAK symbol Live_SyncOnlineDataFlags{0, 0x1405ABF70}; + WEAK symbol LiveStorage_StatsWriteNotNeeded{0x1403BA420, 0x140409120}; WEAK symbol LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730}; WEAK symbol LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820}; @@ -262,6 +264,8 @@ namespace game WEAK symbol LargeLocalResetToMark{0x140423B50, 0x1404E4D00}; + WEAK symbol I_strnicmp{0x140432840, 0x1404F67D0}; + WEAK symbol longjmp{0x14062E030, 0x140738060}; WEAK symbol _setjmp{0x14062F030, 0x140739060}; From 833e25700cf1d2bbc652be1aeb0fe5e5b0eadc37 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Tue, 28 Jan 2025 08:57:47 +0100 Subject: [PATCH 05/11] feat(gsc): make bots join a server automatically --- src/client/component/gsc/script_extension.cpp | 14 ++++++++++++++ src/client/game/dvars.cpp | 1 + src/client/game/dvars.hpp | 1 + 3 files changed, 16 insertions(+) diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp index bab3816..df0d59f 100644 --- a/src/client/component/gsc/script_extension.cpp +++ b/src/client/component/gsc/script_extension.cpp @@ -293,6 +293,17 @@ namespace gsc game::Scr_AddInt(game::environment::is_dedi()); } + void scr_bot_auto_connect_enabled() + { + if (game::environment::is_dedi() && dvars::sv_botsAutoJoin->current.enabled) + { + game::Scr_AddInt(1); // 2 seems to be unused (incomplete GSC/game mode) + return; + } + + game::Scr_AddInt(1); + } + const char* get_code_pos(const int index) { if (static_cast(index) >= game::scr_VmPub->outparamcount) @@ -391,6 +402,9 @@ namespace gsc utils::hook::set(0x1409E6E20, assert_cmd); utils::hook::set(0x1409E94D0, scr_cmd_is_dedicated_server); + + dvars::sv_botsAutoJoin = game::Dvar_RegisterBool("sv_botsAutoJoin", false, game::DVAR_FLAG_NONE, ""); + utils::hook::set(0x1409E92F0, scr_bot_auto_connect_enabled); } }; } diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index 19a64c1..e5a1e23 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -16,6 +16,7 @@ namespace dvars game::dvar_t* con_inputCmdMatchColor = nullptr; game::dvar_t* sv_cheats = nullptr; + game::dvar_t* sv_botsAutoJoin = nullptr; game::dvar_t* g_playerEjection = nullptr; game::dvar_t* g_playerCollision = nullptr; diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index f25e287..b805a63 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -15,6 +15,7 @@ namespace dvars extern game::dvar_t* con_inputCmdMatchColor; extern game::dvar_t* sv_cheats; + extern game::dvar_t* sv_botsAutoJoin; extern game::dvar_t* g_playerCollision; extern game::dvar_t* g_playerEjection; From ea8d2e483ffbfeae1dabd290b302d0cb90e1e0d7 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Tue, 28 Jan 2025 10:38:57 +0100 Subject: [PATCH 06/11] fix(gameplay): fix gun game --- src/client/component/patches.cpp | 4 ++++ src/client/game/structs.hpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index f5c9bb4..25f445e 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -331,6 +331,10 @@ namespace patches dvars::override::register_int("igs_s1", 1, 0, 1, 0); dvars::override::register_int("igs_crossgame", 1, 0, 1, 0); + // Required by UI scripts. Missing when joining a dedi and causes crashes + game::Dvar_RegisterInt("scr_gun_winlimit", 1, 0, 10, game::DVAR_FLAG_REPLICATED, "Win limit for Gun Game"); + game::Dvar_RegisterInt("scr_gun_scorelimit", 18, 1, 1000, game::DVAR_FLAG_REPLICATED, "Score limit for Gun Game"); + // Patch game chat on resolutions higher than 1080p to use the right font utils::hook::call(0x14025C825, get_chat_font_handle); utils::hook::call(0x1402BC42F, get_chat_font_handle); diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 7eba9b1..2d2f9b3 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1160,6 +1160,8 @@ namespace game DVAR_FLAG_LATCHED = 0x2, DVAR_FLAG_CHEAT = 0x4, DVAR_FLAG_REPLICATED = 0x8, + DVAR_FLAG_INTERNAL = 0x80, + DVAR_FLAG_EXTERNAL = 0x100, DVAR_FLAG_SERVERINFO = 0x400, DVAR_FLAG_WRITE = 0x800, DVAR_FLAG_READ = 0x2000, From 03b44731a89aae42c8a86dbe718532579deb9758 Mon Sep 17 00:00:00 2001 From: 6arelyFuture Date: Wed, 29 Jan 2025 08:21:56 +0000 Subject: [PATCH 07/11] fix(gsc): I got one big silly goose in me --- src/client/component/gsc/script_extension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp index df0d59f..6825a3e 100644 --- a/src/client/component/gsc/script_extension.cpp +++ b/src/client/component/gsc/script_extension.cpp @@ -301,7 +301,7 @@ namespace gsc return; } - game::Scr_AddInt(1); + game::Scr_AddInt(game::BG_BotsConnectType()); } const char* get_code_pos(const int index) From 6f2c293970c1ed5fbd3fdf49e46cac5eb63aa290 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Wed, 29 Jan 2025 13:25:42 +0100 Subject: [PATCH 08/11] chore: update deps --- deps/gsc-tool | 2 +- deps/libtomcrypt | 2 +- src/client/component/gsc/script_loading.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/gsc-tool b/deps/gsc-tool index e4cb6a7..616595f 160000 --- a/deps/gsc-tool +++ b/deps/gsc-tool @@ -1 +1 @@ -Subproject commit e4cb6a7819726d9ea33cd6a52f0dcd9382258b47 +Subproject commit 616595f8e0dd62d6d57ed7a4ef7635a4c65e5dde diff --git a/deps/libtomcrypt b/deps/libtomcrypt index c900951..427e055 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit c900951dab1bb94bab803fc57688dac18e3b71f9 +Subproject commit 427e0551c0e27c1ef9a3aa969310a895163631ba diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp index fa5a9ec..f7cb7cb 100644 --- a/src/client/component/gsc/script_loading.cpp +++ b/src/client/component/gsc/script_loading.cpp @@ -403,7 +403,7 @@ namespace gsc public: loading() { - gsc_ctx = std::make_unique(); + gsc_ctx = std::make_unique(xsk::gsc::instance::server); } void post_unpack() override From b60692f0d70e5f632ef9fe5a022fa22b8f0d773f Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Thu, 30 Jan 2025 13:14:21 +0100 Subject: [PATCH 09/11] feat: more arxan patches --- src/client/component/dedicated.cpp | 22 -------------- src/client/component/fastfiles.cpp | 46 +++++++++++++++++++++++++++--- src/client/component/patches.cpp | 2 ++ src/client/game/dvars.cpp | 1 + src/client/game/dvars.hpp | 1 + src/client/game/engine/sv_game.cpp | 25 ++++++++++++++++ src/client/game/engine/sv_game.hpp | 2 ++ src/client/game/structs.hpp | 21 ++++++++++++++ src/client/game/symbols.hpp | 7 +++++ 9 files changed, 101 insertions(+), 26 deletions(-) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 03b951b..40cc3c1 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -198,25 +198,6 @@ namespace dedicated return hwnd; } - void sys_error_stub(const char* msg, ...) - { - char buffer[2048]{}; - - va_list ap; - va_start(ap, msg); - - vsnprintf_s(buffer, _TRUNCATE, msg, ap); - - va_end(ap); - - scheduler::once([]() - { - command::execute("map_rotate"); - }, scheduler::pipeline::main, 3s); - - game::Com_Error(game::ERR_DROP, "%s", buffer); - } - void add_commands() { command::add("map", [](const command::params& params) @@ -364,9 +345,6 @@ namespace dedicated utils::hook::nop(0x1404F8BE1, 2); // ^ utils::hook::set(0x140328660, 0xC3); // Disable image pak file loading - // Stop crashing from sys_errors - utils::hook::jump(0x1404FF510, sys_error_stub); - // Reduce min required memory utils::hook::set(0x1404FA6BD, 0x80000000); utils::hook::set(0x1404FA76F, 0x80000000); diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index f5bb265..5a28a84 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -7,10 +7,10 @@ #include "console.hpp" #include "fastfiles.hpp" - #include -#include #include +#include +#include namespace fastfiles { @@ -32,6 +32,12 @@ namespace fastfiles return; } + const auto out_name = std::format("gsc_dump/{}.gscbin", name); + if (utils::io::file_exists(out_name)) + { + return; + } + std::string buffer; buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1); buffer.append(reinterpret_cast(&header.scriptfile->compressedLen), 4); @@ -40,12 +46,38 @@ namespace fastfiles buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen); buffer.append(reinterpret_cast(header.scriptfile->bytecode), header.scriptfile->bytecodeLen); - const auto out_name = std::format("gsc_dump/{}.gscbin", name); utils::io::write_file(out_name, buffer); - console::info("Dumped %s\n", out_name.data()); + console::info("Dumped %s\n", out_name.c_str()); } + void dump_csv_table(const std::string& name, game::XAssetHeader header) + { + if (!dvars::g_dump_string_tables->current.enabled) + { + return; + } + + const auto out_name = std::format("csv_dump/{}.csv", name); + if (utils::io::file_exists(out_name)) + { + return; + } + + std::string buffer; + + for (auto row = 0; row < header.stringTable->rowCount; row++) + { + for (auto column = 0; column < header.stringTable->columnCount; column++) + { + const auto* string = header.stringTable->values[(row * header.stringTable->columnCount) + column].string; + buffer.append(utils::string::va("%s%s", string ? string : "", (column == header.stringTable->columnCount - 1) ? "\n" : ",")); + } + } + + utils::io::write_file(out_name, buffer); + console::info("Dumped %s\n", out_name.c_str()); + } game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default) { @@ -58,6 +90,11 @@ namespace fastfiles dump_gsc_script(name, result); } + if (type == game::ASSET_TYPE_STRINGTABLE) + { + dump_csv_table(name, result); + } + if (diff > 100) { console::print( @@ -110,6 +147,7 @@ namespace fastfiles db_find_x_asset_header_hook.create(game::DB_FindXAssetHeader, db_find_x_asset_header_stub); dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts to binary format"); + dvars::g_dump_string_tables = game::Dvar_RegisterBool("g_dumpStringTables", false, game::DVAR_FLAG_NONE, "Dump CSV files"); utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub); utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub); diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index 25f445e..fa4735a 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -295,6 +295,8 @@ namespace patches utils::hook::nop(0x1404758C0, 16); utils::hook::jump(0x1404758C0, game::engine::SV_GameSendServerCommand, true); + utils::hook::call(0x140477399, game::engine::SV_SendServerCommand); + // Register dvars com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub); diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index e5a1e23..a10b151 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -25,6 +25,7 @@ namespace dvars game::dvar_t* g_rocketPushbackScale = nullptr; game::dvar_t* g_enableElevators = nullptr; game::dvar_t* g_dump_scripts = nullptr; + game::dvar_t* g_dump_string_tables = nullptr; game::dvar_t* g_log = nullptr; game::dvar_t* bg_surfacePenetration = nullptr; diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index b805a63..0dedd0f 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -24,6 +24,7 @@ namespace dvars extern game::dvar_t* g_rocketPushbackScale; extern game::dvar_t* g_enableElevators; extern game::dvar_t* g_dump_scripts; + extern game::dvar_t* g_dump_string_tables; extern game::dvar_t* g_log; extern game::dvar_t* bg_surfacePenetration; diff --git a/src/client/game/engine/sv_game.cpp b/src/client/game/engine/sv_game.cpp index 4f83e3b..950d401 100644 --- a/src/client/game/engine/sv_game.cpp +++ b/src/client/game/engine/sv_game.cpp @@ -178,4 +178,29 @@ namespace game::engine assert(static_cast(clientNum) < sv_maxclients->current.unsignedInt); SV_SendServerCommand(&mp::svs_clients[clientNum], type, "%s", text); } + + void SV_ReconnectClients(int savepersist) + { + const auto* sv_maxclients = Dvar_FindVar("sv_maxclients"); + for (int i = 0; i < sv_maxclients->current.integer; ++i) + { + mp::client_t* client = &mp::svs_clients[i]; + if (client->header.state < CS_CONNECTED) + { + continue; + } + + SV_AddServerCommand(client, SV_CMD_RELIABLE, utils::string::va("%c", savepersist != 0 ? 107 : 118)); + const char* denied = ClientConnect(i, client->scriptId); + if (denied) + { + SV_DropClient(client, denied, true); + console::info("SV_MapRestart_f: dropped client %i - denied!\n", i); + } + else if (client->header.state == CS_ACTIVE) + { + SV_ClientEnterWorld(client, &client->lastUsercmd); + } + } + } } diff --git a/src/client/game/engine/sv_game.hpp b/src/client/game/engine/sv_game.hpp index f1b7b70..c1c5a46 100644 --- a/src/client/game/engine/sv_game.hpp +++ b/src/client/game/engine/sv_game.hpp @@ -2,5 +2,7 @@ namespace game::engine { + void SV_SendServerCommand(mp::client_t* cl, svscmd_type type, const char* fmt, ...); void SV_GameSendServerCommand(int clientNum, svscmd_type type, const char* text); + void SV_ReconnectClients(int savepersist); } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 2d2f9b3..84a21e5 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -990,6 +990,27 @@ namespace game LOOKUP_ERROR_COUNT = 0x5, }; + struct ComStreamSyncModel + { + unsigned __int16 modelIndex; + unsigned __int8 alternateIndex; + }; + + static_assert(sizeof(ComStreamSyncModel) == 4); + + struct ComStreamedSyncModelList + { + unsigned int modelCount; + ComStreamSyncModel models[18]; + }; + + static_assert(sizeof(ComStreamedSyncModelList) == 0x4C); + + struct ComStreamedSyncSwapBuffer + { + ComStreamedSyncModelList modelLists[2]; + }; + struct StructuredDataEnumEntry { unsigned int name; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index b4abdc0..93e694b 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -30,6 +30,7 @@ namespace game WEAK symbol Com_SetSlowMotion{0, 0x1404158C0}; WEAK symbol Com_TokenizeString{0x1403B4150, 0x1403F7CC0}; WEAK symbol Com_EndTokenizeString{0x1403B37C0, 0x1403F7330}; + WEAK symbol Com_StreamSync_UpdateLaunchData{0x0, 0x140411B50}; WEAK symbol Conbuf_AppendText{0x14043DDE0, 0x1405028C0}; @@ -220,6 +221,10 @@ namespace game WEAK symbol SV_Loaded{0x140491820, 0x1404770C0}; WEAK symbol SV_StartMap{0, 0x140470170}; WEAK symbol SV_StartMapForParty{0, 0x1404702F0}; + WEAK symbol SV_StreamSync_ClientConnect{0x0, 0x140488080}; + WEAK symbol ClientConnect{0x0, 0x140387630}; + WEAK symbol SV_ClientEnterWorld{0x0, 0x1404710F0}; + WEAK symbol SV_DropClient{0x0, 0x140472110}; WEAK symbol SV_AddBot{0, 0x140470920}; WEAK symbol SV_BotIsBot{0, 0x140461340}; @@ -344,6 +349,8 @@ namespace game WEAK symbol serverTime{0, 0x14647B280}; WEAK symbol g_zones_0{0, 0x143A46498}; + + WEAK symbol s_launchDataAvailable{0x0, 0x1445CE354}; } namespace hks From 27d598557444bcf7b0e035bb9c2d050084c3f149 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Mon, 3 Feb 2025 20:02:50 +0100 Subject: [PATCH 10/11] fix noob team --- src/client/component/fastfiles.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index 5a28a84..e00c50c 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -152,10 +152,6 @@ namespace fastfiles utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub); utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub); - // Allow loading of unsigned fastfiles - utils::hook::set(0x1402FBF23, 0xEB); // DB_LoadXFile - utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor - command::add("materiallist", [](const command::params& params) { game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*) @@ -170,6 +166,10 @@ namespace fastfiles if (!game::environment::is_sp()) { reallocate_asset_pool(game::ASSET_TYPE_WEAPON, 320); + + // Allow loading of unsigned fastfiles + utils::hook::set(0x1402FBF23, 0xEB); // DB_LoadXFile + utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor } } }; From a299874dd2f40b75e73e26f384737b9bcb537a9f Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Thu, 6 Feb 2025 12:34:59 +0100 Subject: [PATCH 11/11] fix: remove _s poison --- src/client/component/binding.cpp | 8 ++--- src/client/component/colors.cpp | 2 +- src/client/component/console.cpp | 11 ++++--- src/client/component/demonware.cpp | 2 +- src/client/component/game_console.cpp | 20 ++++++------- src/client/component/game_log.cpp | 23 +++++--------- src/client/component/logger.cpp | 41 ++----------------------- src/client/component/mods.cpp | 10 +++---- src/client/component/party.cpp | 19 +++++++----- src/client/component/patches.cpp | 7 ++++- src/client/component/rcon.cpp | 2 +- src/client/component/security.cpp | 43 +++++++++++++++++++++++++++ src/client/game/engine/sv_game.cpp | 2 +- src/client/game/symbols.hpp | 5 ++-- src/client/main.cpp | 4 +-- src/common/utils/string.hpp | 2 +- 16 files changed, 101 insertions(+), 100 deletions(-) diff --git a/src/client/component/binding.cpp b/src/client/component/binding.cpp index 44b47c0..5d5638a 100644 --- a/src/client/component/binding.cpp +++ b/src/client/component/binding.cpp @@ -25,9 +25,7 @@ namespace binding if (value && value < 100) { - const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used), - "bind %s \"%s\"\n", key_button, game::command_whitelist[value]); - + const auto len = game::Com_sprintf(&buffer[bytes_used], (buffer_size_align - bytes_used), "bind %s \"%s\"\n", key_button, game::command_whitelist[value]); if (len < 0) { return bytes_used; @@ -40,9 +38,7 @@ namespace binding value -= 100; if (static_cast(value) < custom_binds.size() && !custom_binds[value].empty()) { - const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used), - "bind %s \"%s\"\n", key_button, custom_binds[value].data()); - + const auto len = game::Com_sprintf(&buffer[bytes_used], (buffer_size_align - bytes_used), "bind %s \"%s\"\n", key_button, custom_binds[value].data()); if (len < 0) { return bytes_used; diff --git a/src/client/component/colors.cpp b/src/client/component/colors.cpp index 9391656..6ea61b2 100644 --- a/src/client/component/colors.cpp +++ b/src/client/component/colors.cpp @@ -107,7 +107,7 @@ namespace colors int com_sprintf_stub(char* dest, int size, const char* fmt, const char* name) { - const auto len = sprintf_s(dest, size, fmt, name); + const auto len = game::Com_sprintf(dest, size, fmt, name); if (len < 0) { game::I_strncpyz(dest, "UnnamedAgent", size); diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp index 3e37281..2cc9c22 100644 --- a/src/client/component/console.cpp +++ b/src/client/component/console.cpp @@ -84,15 +84,14 @@ namespace console void print_stub(const char* fmt, ...) { + char buffer[4096]{}; + va_list ap; va_start(ap, fmt); - - char buffer[4096]{}; - const auto res = vsnprintf_s(buffer, _TRUNCATE, fmt, ap); - (void)res; - print_message(buffer); - + [[maybe_unused]] const auto len = vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); + + print_message(buffer); } void append_text(const char* text) diff --git a/src/client/component/demonware.cpp b/src/client/component/demonware.cpp index fca1521..81bf2c3 100644 --- a/src/client/component/demonware.cpp +++ b/src/client/component/demonware.cpp @@ -222,7 +222,7 @@ namespace demonware va_list ap; va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); + vsnprintf(buffer, sizeof(buffer), msg, ap); printf("%s: %s\n", function, buffer); va_end(ap); diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 5b8fe91..9a5bc32 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -63,7 +63,7 @@ namespace game_console void clear() { - strncpy_s(con.buffer, "", sizeof(con.buffer)); + game::I_strncpyz(con.buffer, "", sizeof(con.buffer)); con.cursor = 0; fixed_input = ""; @@ -249,7 +249,7 @@ namespace game_console dvars::con_inputDvarInactiveValueColor->current.vector, offset); } - strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); + game::I_strncpyz(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); con.globals.may_auto_complete = true; } else if (matches.size() > 1) @@ -274,7 +274,7 @@ namespace game_console } } - strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); + game::I_strncpyz(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); con.globals.may_auto_complete = true; } } @@ -364,11 +364,11 @@ namespace game_console void print_internal(const char* fmt, ...) { - char va_buffer[0x200] = { 0 }; + char va_buffer[1024]{}; va_list ap; va_start(ap, fmt); - vsprintf_s(va_buffer, fmt, ap); + vsnprintf(va_buffer, sizeof(va_buffer), fmt, ap); va_end(ap); const auto formatted = std::string(va_buffer); @@ -424,7 +424,7 @@ namespace game_console con.buffer[1] = '\0'; } - strncat_s(con.buffer, con.globals.auto_complete_choice, 64); + game::I_strncat(con.buffer, sizeof(con.buffer), con.globals.auto_complete_choice); con.cursor = static_cast(std::string(con.buffer).length()); if (con.cursor != 254) @@ -549,7 +549,7 @@ namespace game_console if (history_index != -1) { - strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); + game::I_strncpyz(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); con.cursor = static_cast(strlen(con.buffer)); } } @@ -564,7 +564,7 @@ namespace game_console if (history_index != -1) { - strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); + game::I_strncpyz(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); con.cursor = static_cast(strlen(con.buffer)); } } @@ -721,7 +721,7 @@ namespace game_console con.output_visible = false; con.display_line_offset = 0; con.line_count = 0; - strncpy_s(con.buffer, "", sizeof(con.buffer)); + game::I_strncpyz(con.buffer, "", sizeof(con.buffer)); con.globals.x = 0.0f; con.globals.y = 0.0f; @@ -729,7 +729,7 @@ namespace game_console con.globals.font_height = 0.0f; con.globals.may_auto_complete = false; con.globals.info_line_count = 0; - strncpy_s(con.globals.auto_complete_choice, "", sizeof(con.globals.auto_complete_choice)); + game::I_strncpyz(con.globals.auto_complete_choice, "", sizeof(con.globals.auto_complete_choice)); // add clear command command::add("clear", [&]() diff --git a/src/client/component/game_log.cpp b/src/client/component/game_log.cpp index e8262b0..0697c1c 100644 --- a/src/client/component/game_log.cpp +++ b/src/client/component/game_log.cpp @@ -3,12 +3,10 @@ #include "game/game.hpp" #include "game/dvars.hpp" -#include "scheduler.hpp" -#include "scripting.hpp" #include "console.hpp" #include "game_log.hpp" - -#include "gsc/script_extension.hpp" +#include "scheduler.hpp" +#include "scripting.hpp" #include #include @@ -23,7 +21,7 @@ namespace game_log char buf[1024]{}; std::size_t out_chars = 0; - for (auto i = 0u; i < game::Scr_GetNumParam(); ++i) + for (std::uint32_t i = 0; i < game::Scr_GetNumParam(); ++i) { const auto* value = game::Scr_GetString(i); const auto len = std::strlen(value); @@ -34,7 +32,7 @@ namespace game_log break; } - strncat_s(buf, value, _TRUNCATE); + game::I_strncat(buf, sizeof(buf), value); } g_log_printf("%s", buf); @@ -49,22 +47,15 @@ namespace game_log return; } - char buffer[0x400]{}; + char buffer[1024]{}; va_list ap; va_start(ap, fmt); - - vsprintf_s(buffer, fmt, ap); - + vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); const auto time = *game::level_time / 1000; - utils::io::write_file(log, utils::string::va("%3i:%i%i %s", - time / 60, - time % 60 / 10, - time % 60 % 10, - buffer - ), true); + utils::io::write_file(log, utils::string::va("%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, buffer), true); } class component final : public component_interface diff --git a/src/client/component/logger.cpp b/src/client/component/logger.cpp index 1449658..b36a374 100644 --- a/src/client/component/logger.cpp +++ b/src/client/component/logger.cpp @@ -10,43 +10,15 @@ namespace logger { namespace { - utils::hook::detour com_error_hook; - const game::dvar_t* logger_dev = nullptr; - void print_com_error(int, const char* msg, ...) - { - char buffer[2048]{}; - va_list ap; - - va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); - va_end(ap); - - console::error("%s", buffer); - } - - void com_error_stub(const int error, const char* msg, ...) - { - char buffer[2048]{}; - va_list ap; - - va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); - va_end(ap); - - console::error("Error: %s\n", buffer); - - com_error_hook.invoke(error, "%s", buffer); - } - void print_warning(const char* msg, ...) { char buffer[2048]{}; va_list ap; va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); + vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); console::warn("%s", buffer); @@ -58,7 +30,7 @@ namespace logger va_list ap; va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); + vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); console::info("%s", buffer); @@ -75,7 +47,7 @@ namespace logger va_list ap; va_start(ap, msg); - vsnprintf_s(buffer, _TRUNCATE, msg, ap); + vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); console::info("%s", buffer); @@ -141,13 +113,6 @@ namespace logger sub_1401DAA40(); } - if (!game::environment::is_sp()) - { - utils::hook::call(0x140501AE3, print_com_error); - } - - com_error_hook.create(game::Com_Error, com_error_stub); - // Make havok script's print function actually print utils::hook::jump(SELECT_VALUE(0x1406283A4, 0x140732184), print); diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index e6612a3..d992e82 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -16,24 +16,24 @@ namespace mods { utils::hook::detour sys_create_file_hook; - void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, unsigned int size, char* filename) + void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, int size, char* filename) { char user_map[MAX_PATH]{}; switch (source) { case game::FFD_DEFAULT: - (void)sprintf_s(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name); + (void)game::Com_sprintf(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name); break; case game::FFD_MOD_DIR: assert(mods::is_using_mods()); - (void)sprintf_s(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name); + (void)game::Com_sprintf(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name); break; case game::FFD_USER_MAP: - strncpy_s(user_map, zone_name, _TRUNCATE); + game::I_strncpyz(user_map, zone_name, sizeof(user_map)); - (void)sprintf_s(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name); + (void)game::Com_sprintf(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name); break; default: assert(false && "inconceivable"); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 4b964f4..723f5d5 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -181,18 +181,21 @@ namespace party { for (auto i = 0; !name.empty() && i < *game::mp::svs_clientCount; ++i) { - if (game::mp::g_entities[i].client) + if (!game::mp::g_entities[i].client) { - char client_name[16] = {0}; - strncpy_s(client_name, game::mp::g_entities[i].client->sess.cs.name, sizeof(client_name)); - game::I_CleanStr(client_name); + continue; + } - if (client_name == name) - { - return i; - } + char client_name[16]{}; + game::I_strncpyz(client_name, game::mp::g_entities[i].client->sess.cs.name, sizeof(client_name)); + game::I_CleanStr(client_name); + + if (client_name == name) + { + return i; } } + return -1; } diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index fa4735a..640fe03 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -73,8 +73,10 @@ namespace patches if (exec_params.size() == 2) { std::string file_name = exec_params.get(1); - if (file_name.find(".cfg") == std::string::npos) + if (!file_name.ends_with(".cfg")) + { file_name.append(".cfg"); + } const auto file = filesystem::file(file_name); if (file.exists()) @@ -368,6 +370,9 @@ namespace patches utils::hook::nop(0x1403A1A0F, 1); // ^^ utils::hook::nop(0x1403A072F, 5); // LiveStorage_RecordMovementInMatchdata + + // Disable Com_Error in NET_SendPacket + utils::hook::nop(0x140501AE3, 5); } static void patch_sp() diff --git a/src/client/component/rcon.cpp b/src/client/component/rcon.cpp index 7fd3cd8..6f740ce 100644 --- a/src/client/component/rcon.cpp +++ b/src/client/component/rcon.cpp @@ -59,7 +59,7 @@ namespace rcon } char clean_name[32]{}; - strncpy_s(clean_name, self->client->sess.cs.name, sizeof(clean_name)); + game::I_strncpyz(clean_name, self->client->sess.cs.name, sizeof(clean_name)); game::I_CleanStr(clean_name); buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n", diff --git a/src/client/component/security.cpp b/src/client/component/security.cpp index 03e0cac..581107e 100644 --- a/src/client/component/security.cpp +++ b/src/client/component/security.cpp @@ -61,6 +61,42 @@ namespace security ui_replace_directive_hook.invoke(local_client_num, src_string, dst_string, dst_buffer_size); } + + int hud_elem_set_enum_string_stub(char* string, const char* format, ...) + { + va_list ap; + va_start(ap, format); + const auto len = vsnprintf(string, 0x800, format, ap); + va_end(ap); + + string[0x800 - 1] = '\0'; + + return len; + } + + int sv_add_bot_stub(char* string, const char* format, ...) + { + va_list ap; + va_start(ap, format); + const auto len = vsnprintf(string, 0x400, format, ap); + va_end(ap); + + string[0x400 - 1] = '\0'; + + return len; + } + + int sv_add_test_client_stub(char* string, const char* format, ...) + { + va_list ap; + va_start(ap, format); + const auto len = vsnprintf(string, 0x400, format, ap); + va_end(ap); + + string[0x400 - 1] = '\0'; + + return len; + } } class component final : public component_interface @@ -68,11 +104,18 @@ namespace security public: void post_unpack() override { + // sprinf + utils::hook::call(SELECT_VALUE(0x140310D0F, 0x140399B0F), hud_elem_set_enum_string_stub); + if (game::environment::is_sp()) return; // Patch vulnerability in PlayerCards_SetCachedPlayerData utils::hook::call(0x140287C5C, set_cached_playerdata_stub); + // sprinf + utils::hook::call(0x140470A88, sv_add_bot_stub); + utils::hook::call(0x140470F68, sv_add_test_client_stub); + // It is possible to make the server hang if left unchecked utils::hook::call(0x14047A29A, sv_execute_client_message_stub); diff --git a/src/client/game/engine/sv_game.cpp b/src/client/game/engine/sv_game.cpp index 950d401..dca8050 100644 --- a/src/client/game/engine/sv_game.cpp +++ b/src/client/game/engine/sv_game.cpp @@ -136,7 +136,7 @@ namespace game::engine const auto server_command_buf_large = std::make_unique(0x20000); va_start(va, fmt); - len = vsnprintf_s(server_command_buf_large.get(), 0x20000, _TRUNCATE, fmt, va); + len = vsnprintf(server_command_buf_large.get(), 0x20000, fmt, va); va_end(va); assert(len >= 0); diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 93e694b..b90bd78 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -31,6 +31,7 @@ namespace game WEAK symbol Com_TokenizeString{0x1403B4150, 0x1403F7CC0}; WEAK symbol Com_EndTokenizeString{0x1403B37C0, 0x1403F7330}; WEAK symbol Com_StreamSync_UpdateLaunchData{0x0, 0x140411B50}; + WEAK symbol Com_sprintf{0x140432310, 0x1404F6260}; WEAK symbol Conbuf_AppendText{0x14043DDE0, 0x1405028C0}; @@ -114,10 +115,10 @@ namespace game WEAK symbol I_CleanStr{0x140432460, 0x1404F63C0}; WEAK symbol I_strncpyz{0x140432810, 0x1404F67A0}; + WEAK symbol I_strncat{0x140432740, 0x1404F66D0}; WEAK symbol - Image_Setup{0x140517910, 0x1405E4380}; + uint32_t imageFlags, DXGI_FORMAT imageFormat, const char* name, const void* initData)> Image_Setup{0x140517910, 0x1405E4380}; WEAK symbol Key_KeynumToString{0x14023D9A0, 0x1402C40E0}; diff --git a/src/client/main.cpp b/src/client/main.cpp index e50f1cc..2412db9 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -64,9 +64,7 @@ LONG WINAPI exception_handler(PEXCEPTION_POINTERS exception_info) &exception_information, nullptr, nullptr)) { char buf[4096]{}; - sprintf_s(buf, "An exception 0x%08X occurred at location 0x%p\n", - exception_info->ExceptionRecord->ExceptionCode, - exception_info->ExceptionRecord->ExceptionAddress); + sprintf_s(buf, "An exception 0x%08X occurred at location 0x%p\n", exception_info->ExceptionRecord->ExceptionCode, exception_info->ExceptionRecord->ExceptionAddress); game::show_error(buf); } diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 6a50c3e..c579118 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -29,7 +29,7 @@ namespace utils::string while (true) { - const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + const int res = vsnprintf(entry->buffer, entry->size, format, ap); if (res > 0) break; // Success if (res == 0) return nullptr; // Error