#include #include "loader/component_loader.hpp" #include "../steam/steam.hpp" #include "dvars.hpp" #include "console/console.hpp" #include "network.hpp" #include "party.hpp" #include "profile_infos.hpp" #include #include #include #include "game/utils/fragment_handler.hpp" namespace profile_infos { namespace { using profile_map = std::unordered_map; utils::concurrency::container profile_mapping{}; std::optional load_profile_info() { std::string data{}; if (!utils::io::read_file("iw7-mod/players2/user/profile_info", &data)) { //console::error("[load_profile_info] failed to load profile_info for self!\n"); return {}; } profile_info info{}; info.m_memberplayer_card.assign(data); return {std::move(info)}; } std::unordered_set get_connected_client_xuids() { if (!game::Com_IsAnyLocalServerRunning()) // is_host() { return {}; } std::unordered_set xuids{}; const auto* svs_clients = *game::svs_clients; for (unsigned int i = 0; i < *game::svs_numclients; ++i) { if (svs_clients[i].header.state >= 1) { xuids.emplace(xuid::get_client_xuid(i)); } } return xuids; } void set_playercardcache_to_download(const std::uint64_t user_id) { game::XUID xuid{ user_id }; game::PlayercardCache_AddToDownload(0, xuid); *game::g_DWPlayercardCacheDownloadTaskStage = game::PLAYERCARD_CACHE_TASK_STAGE_WAITING; } void set_client_xuid_to_session(game::SessionData* session, const std::uint32_t client_index) { session->dyn.users[client_index].xuid = xuid::get_client_xuid(client_index); } } profile_info::profile_info(utils::byte_buffer& buffer) { this->m_memberplayer_card = buffer.read_string(); } void profile_info::serialize(utils::byte_buffer& buffer) const { buffer.write_string(this->m_memberplayer_card); } void clear_profile_infos() { profile_mapping.access([](profile_map& profiles) { profiles.clear(); }); } void remove_profile_info(const std::uint64_t user_id) { profile_mapping.access([user_id](profile_map& profiles) { for (auto i = profiles.begin(); i != profiles.end();) { if (i->first == user_id) { i = profiles.erase(i); } else { i++; } } }); } void remove_profile_info_by_client_index(const std::uint32_t client_index) { const auto user_id = xuid::get_client_xuid(client_index); if (!user_id) { return; } remove_profile_info(user_id); } void send_profile_info(const game::netadr_s& address, const std::string& data) { game::fragment_handler::fragment_data(data.data(), data.size(), [&address](const utils::byte_buffer& buffer) { network::send(address, "profileInfo", buffer.get_buffer()); }); } void send_profile_info(const game::netadr_s& address, const std::uint64_t user_id, const profile_info& info) { utils::byte_buffer buffer{}; buffer.write(user_id); info.serialize(buffer); const std::string data = buffer.move_buffer(); send_profile_info(address, data); } std::optional get_profile_info() { return load_profile_info(); } std::optional get_profile_info(const uint64_t user_id) { const auto my_xuid = steam::SteamUser()->GetSteamID().bits; if (user_id == my_xuid) { return get_profile_info(); } return profile_mapping.access>([user_id](profile_map& profiles) { std::optional result{}; const auto profile_entry = profiles.find(user_id); if (profile_entry != profiles.end()) { result = profile_entry->second; } #ifdef DEBUG else { console::error("[get_profile_info] requesting profile info for %llX (bad)\n", user_id); } #endif return result; }); } void update_profile_info(const profile_info& info) { std::string data{}; data.reserve(info.m_memberplayer_card.size()); data.append(info.m_memberplayer_card); utils::io::write_file("iw7-mod/players2/user/profile_info", data); } void send_all_profile_infos(const game::netadr_s& sender_addr) { profile_mapping.access([&](const profile_map& profiles) { for (const auto& entry : profiles) { send_profile_info(sender_addr, entry.first, entry.second); } }); } void send_profile_info_to_all_clients(const std::uint64_t user_id, const profile_info& info) { const auto* svs_clients = *game::svs_clients; for (unsigned int i = 0; i < *game::svs_numclients; ++i) { if (svs_clients[i].header.state >= 1 && !game::SV_ClientIsBot(i) && !game::Session_IsHost(game::SV_MainMP_GetServerLobby(), i)) { send_profile_info(svs_clients[i].remoteAddress, user_id, info); } } } void send_self_profile(const game::netadr_s& addr) { const auto* svs_clients = *game::svs_clients; for (unsigned int i = 0; i < *game::svs_numclients; ++i) { if (svs_clients[i].header.state >= 1 && !game::SV_ClientIsBot(i) && game::Session_IsHost(game::SV_MainMP_GetServerLobby(), i)) { assert(i == 0); auto self = load_profile_info(); if (self.has_value()) { send_profile_info(addr, xuid::get_client_xuid(i), self.value()); break; } } } } void add_profile_info(const game::netadr_s& sender_addr, const std::uint64_t user_id, const profile_info& info) { if (user_id == steam::SteamUser()->GetSteamID().bits) { return; } if (game::Com_IsAnyLocalServerRunning()) // is_host() { send_all_profile_infos(sender_addr); // send all stored profile infos to the new player if (!game::environment::is_dedi()) { send_self_profile(sender_addr); // send self profile info to the new player too } // send new player info to all clients send_profile_info_to_all_clients(user_id, info); } profile_mapping.access([&](profile_map& profiles) { profiles[user_id] = info; }); } namespace xuid { client_xuid_array client_xuids{}; void add_client_xuid(const std::uint32_t& client_index, const std::uint64_t& xuid) { if (client_xuids[client_index] && client_xuids[client_index] != xuid) { remove_profile_info(client_xuids[client_index]); // remove profile if it exists } client_xuids[client_index] = xuid; set_client_xuid_to_session(game::SV_MainMP_GetServerLobby(), client_index); } std::uint64_t get_client_xuid(const std::uint32_t& client_index) { if (client_xuids[client_index]) { // returns xuid for player. this must be on both the client & server // client recieves data for this via playerXuid packet return client_xuids[client_index]; } return static_cast(0); } void remove_client_xuid(const std::uint32_t& client_index) { client_xuids[client_index] = 0; } void clear_xuids() { for (auto& xuid : client_xuids) { xuid = 0; } } client_xuid_array get_xuids() { return client_xuids; } void send_xuid(const game::netadr_s& addr, const std::uint64_t xuid, const std::uint32_t client_index) { utils::byte_buffer buffer{}; buffer.write(client_index); buffer.write(xuid); const std::string data = buffer.move_buffer(); game::fragment_handler::fragment_data(data.data(), data.size(), [&](const utils::byte_buffer& buffer) { network::send(addr, "playerXuid", buffer.get_buffer()); }); } void send_xuid_to_all_clients(const std::uint64_t xuid, const std::uint32_t& client_index) { const auto* svs_clients = *game::svs_clients; for (unsigned int i = 0; i < *game::svs_numclients; ++i) { if (svs_clients[i].header.state >= 1 && !game::SV_ClientIsBot(i) && !game::Session_IsHost(game::SV_MainMP_GetServerLobby(), i)) { send_xuid(svs_clients[i].remoteAddress, xuid, client_index); } } } void send_all_xuids(const game::netadr_s& addr) { int i = 0; for (const auto xuid : xuid::get_xuids()) { if (xuid == 0) { continue; } send_xuid(addr, xuid, i++); } if (!game::environment::is_dedi()) { // send self xuid here too send_xuid(addr, steam::SteamUser()->GetSteamID().bits, 0); } } } namespace { utils::hook::detour client_connect_hook; const char* client_connect_stub(int client_num, unsigned __int16 script_pers_id) { auto result = client_connect_hook.invoke(client_num, script_pers_id); if (game::SV_ClientIsBot(client_num)) { return result; } const auto client = game::svs_clients[client_num]; std::uint64_t xuid{}; game::StringToXUID(client->playerGuid, &xuid); xuid::add_client_xuid(client_num, xuid); // add to self // don't send if client is self if (client_num == 0 && !game::environment::is_dedi() && game::Com_IsAnyLocalServerRunning()) { return result; } xuid::send_xuid_to_all_clients(xuid, client_num); // add to all connected xuid::send_all_xuids(client->remoteAddress); return result; } utils::hook::detour session_unregister_remote_player_hook; void session_unregister_remote_player_stub(game::SessionData* session, const int slot) { session_unregister_remote_player_hook.invoke(session, slot); set_client_xuid_to_session(session, slot); } } class component final : public component_interface { public: void post_unpack() override { client_connect_hook.create(0xAFFF10_b, client_connect_stub); session_unregister_remote_player_hook.create(0xC73970_b, session_unregister_remote_player_stub); dvars::override::register_int("playercard_cache_validity_life", 5000, 0, 3600000, 0x0); // 5sec network::on("profileInfo", [](const game::netadr_s& client_addr, const std::string_view& data) { utils::byte_buffer buffer(data); std::string final_packet{}; if (game::fragment_handler::handle(client_addr, buffer, final_packet)) { buffer = utils::byte_buffer(final_packet); const auto user_id = buffer.read(); const profile_info info(buffer); if (info.m_memberplayer_card.empty()) { // player has no card set return; } add_profile_info(client_addr, user_id, info); set_playercardcache_to_download(user_id); } }); network::on("playerXuid", [](const game::netadr_s& server_addr, const std::string_view& data) { utils::byte_buffer buffer(data); std::string final_packet{}; if (!game::fragment_handler::handle(server_addr, buffer, final_packet)) { return; } buffer = utils::byte_buffer(final_packet); const auto client_index = buffer.read(); const auto xuid = buffer.read(); if (!game::Com_IsAnyLocalServerRunning() && server_addr.addr != party::get_server_connection_state().host.addr) { console::debug("playerXuid call from an unknown address\n"); return; } xuid::add_client_xuid(client_index, xuid); }); } }; } REGISTER_COMPONENT(profile_infos::component)