438 lines
11 KiB
C++
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)
|