From 199206ee5ae6919cdd101f2f5e5aa76a1f676ce3 Mon Sep 17 00:00:00 2001 From: diamante0018 Date: Sat, 11 Jan 2025 10:20:05 +0100 Subject: [PATCH] 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')) {