iw7-mod/src/client/component/profile_infos.cpp
2024-02-28 16:50:29 +02:00

438 lines
11 KiB
C++

#include <std_include.hpp>
#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 <utils/concurrency.hpp>
#include <utils/io.hpp>
#include <utils/hook.hpp>
#include "game/utils/fragment_handler.hpp"
namespace profile_infos
{
namespace
{
using profile_map = std::unordered_map<std::uint64_t, profile_info>;
utils::concurrency::container<profile_map, std::recursive_mutex> profile_mapping{};
std::optional<profile_info> 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<std::uint64_t> get_connected_client_xuids()
{
if (!game::Com_IsAnyLocalServerRunning()) // is_host()
{
return {};
}
std::unordered_set<std::uint64_t> 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<profile_info> get_profile_info()
{
return load_profile_info();
}
std::optional<profile_info> 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<std::optional<profile_info>>([user_id](profile_map& profiles)
{
std::optional<profile_info> 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<std::uint64_t>(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<const char*>(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<void>(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<uint64_t>();
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<std::uint32_t>();
const auto xuid = buffer.read<std::uint64_t>();
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)