diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 7cf7205c..496676ad 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -4,9 +4,11 @@ #include "network.hpp" #include "game/game.hpp" +#include "game/utils/fragment_handler.hpp" #include "console/console.hpp" #include "dvars.hpp" +#include "scheduler.hpp" #include #include @@ -30,16 +32,27 @@ namespace network auto& callbacks = get_callbacks(); const auto handler = callbacks.find(cmd_string); const auto offset = cmd_string.size() + 5; - if (message->cursize < offset || handler == callbacks.end()) + if (message->cursize < 0 || static_cast(message->cursize) < offset || handler == callbacks.end()) { return false; } - const std::string_view data(message->data + offset, message->cursize - offset); + const std::basic_string_view data(message->data + offset, message->cursize - offset); - //console::debug("[Network] Handling command %s\n", cmd_string.data()); + console::debug("[network] handling command \"%s\"\n", cmd_string.data()); + + try + { + handler->second(*address, data); + } + catch (const std::exception& e) + { + printf("Error: %s\n", e.what()); + } + //catch (...) + //{ + //} - handler->second(*address, data); return true; } @@ -232,15 +245,14 @@ namespace network void send_data(const game::netadr_s& address, const std::string& data) { auto size = static_cast(data.size()); + if (size > 1280) + { + console::error("Packet was too long. Truncated!\n"); + size = 1280; + } + if (address.type == game::NA_LOOPBACK) { - // TODO: Fix this for loopback - if (size > 1280) - { - console::error("Packet was too long. Truncated!\n"); - size = 1280; - } - game::NET_SendLoopPacket(game::NS_CLIENT1, size, data.data(), &address); } else @@ -284,6 +296,8 @@ namespace network public: void post_unpack() override { + scheduler::loop(game::fragment_handler::clean, scheduler::async, 5s); + // redirect dw packet sends to our stub utils::hook::jump(0xD942C0_b, dw_send_to_stub); diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index f9e3e0ab..fc067fcc 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -10,6 +10,7 @@ #include "command.hpp" #include "console/console.hpp" #include "network.hpp" +#include "profile_infos.hpp" #include "scheduler.hpp" #include @@ -21,12 +22,7 @@ namespace party { namespace { - struct - { - game::netadr_s host{}; - std::string challenge{}; - bool hostDefined{ false }; - } connect_state; + connection_state server_connection_state{}; bool preloaded_map = false; @@ -165,6 +161,8 @@ namespace party void sv_start_map_for_party_stub(const char* map, const char* game_type, int client_count, int agent_count, bool hardcore, bool map_is_preloaded, bool migrate) { + profile_infos::xuid::clear_xuids(); + preloaded_map = map_is_preloaded; sv_start_map_for_party_hook.invoke(map, game_type, client_count, agent_count, hardcore, map_is_preloaded, migrate); } @@ -196,7 +194,7 @@ namespace party a.popad64(); a.jmp(0xC563E2_b); - }; + } } void start_map(const std::string& mapname, bool dev) @@ -313,26 +311,30 @@ namespace party void connect(const game::netadr_s& target) { - //command::execute("lui_open_popup popup_acceptinginvite", false); + command::execute("luiOpenPopup AcceptingInvite", false); - connect_state.host = target; - connect_state.challenge = utils::cryptography::random::get_challenge(); - connect_state.hostDefined = true; + profile_infos::xuid::clear_xuids(); + profile_infos::clear_profile_infos(); - network::send(target, "getInfo", connect_state.challenge); + server_connection_state.host = target; + server_connection_state.challenge = utils::cryptography::random::get_challenge(); + server_connection_state.hostDefined = true; + + network::send(target, "getInfo", server_connection_state.challenge); } void info_response_error(const std::string& error) { console::error("%s\n", error.data()); - //if (game::Menu_IsMenuOpenAndVisible(0, "popup_acceptinginvite")) - //{ - // command::execute("lui_close popup_acceptinginvite", false); - //} - + command::execute("luiLeaveMenu AcceptingInvite", false); game::Com_SetLocalizedErrorMessage(error.data(), "MENU_NOTICE"); } + connection_state get_server_connection_state() + { + return server_connection_state; + } + class component final : public component_interface { public: @@ -483,45 +485,45 @@ namespace party network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data) { - const utils::info_string info{ data }; + const utils::info_string info = data; //server_list::handle_info_response(target, info); - if (connect_state.host != target) + if (server_connection_state.host != target) { return; } - if (info.get("challenge") != connect_state.challenge) + if (info.get("challenge") != server_connection_state.challenge) { - info_response_error("Invalid challenge."); + info_response_error("Connection failed: Invalid challenge."); return; } const auto gamename = info.get("gamename"); if (gamename != "IW7"s) { - info_response_error("Invalid gamename."); + info_response_error("Connection failed: Invalid gamename."); return; } const auto playmode = info.get("playmode"); if (game::GameModeType(std::atoi(playmode.data())) != game::Com_GameMode_GetActiveGameMode()) { - info_response_error("Invalid playmode."); + info_response_error("Connection failed: Invalid playmode."); return; } const auto sv_running = info.get("sv_running"); if (!std::atoi(sv_running.data())) { - info_response_error("Server not running."); + info_response_error("Connection failed: Server not running."); return; } const auto mapname = info.get("mapname"); if (mapname.empty()) { - info_response_error("Invalid map."); + info_response_error("Connection failed: Invalid map."); return; } @@ -536,12 +538,21 @@ namespace party const auto sv_maxclients = std::atoi(sv_maxclients_str.data()); if (!sv_maxclients) { - info_response_error("Invalid sv_maxclients."); + info_response_error("Connection failed: Invalid sv_maxclients."); return; } - //party::sv_motd = info.get("sv_motd"); - //party::sv_maxclients = std::stoi(info.get("sv_maxclients")); + server_connection_state.motd = info.get("sv_motd"); + server_connection_state.max_clients = std::stoi(sv_maxclients_str); + + const auto profile_info = profile_infos::get_profile_info(); + if (!profile_info.has_value()) + { + console::error("profile_info has no value to send, possible undefined behavior ahead\n"); + } + + const auto profile_info_value = profile_info.value_or(profile_infos::profile_info{}); + profile_infos::send_profile_info(target, steam::SteamUser()->GetSteamID().bits, profile_info_value); connect_to_party(target, mapname, gametype, sv_maxclients); }); diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp index 9f44f52a..80c839fe 100644 --- a/src/client/component/party.hpp +++ b/src/client/component/party.hpp @@ -3,17 +3,21 @@ namespace party { - void info_response_error(const std::string& error); + struct connection_state + { + game::netadr_s host; + std::string challenge; + bool hostDefined; + std::string motd; + int max_clients; + }; - void reset_connect_state(); + void info_response_error(const std::string& error); void connect(const game::netadr_s& target); void start_map(const std::string& mapname, bool dev = false); - void clear_sv_motd(); - game::netadr_s get_state_host(); - std::string get_state_challenge(); - int server_client_count(); + connection_state get_server_connection_state(); int get_client_num_by_name(const std::string& name); diff --git a/src/client/component/profile_infos.cpp b/src/client/component/profile_infos.cpp new file mode 100644 index 00000000..52a6932a --- /dev/null +++ b/src/client/component/profile_infos.cpp @@ -0,0 +1,426 @@ +#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("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("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_BotIsBot(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_BotIsBot(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_BotIsBot(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); + + 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(game::SV_MainMP_GetServerLobby(), 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); + 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) diff --git a/src/client/component/profile_infos.hpp b/src/client/component/profile_infos.hpp new file mode 100644 index 00000000..3d60ba49 --- /dev/null +++ b/src/client/component/profile_infos.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace profile_infos +{ + namespace xuid + { + using client_xuid_array = std::array; + std::uint64_t get_client_xuid(const std::uint32_t& client_index); + void clear_xuids(); + client_xuid_array get_xuids(); + } + + struct profile_info + { + std::string m_memberplayer_card{}; + + profile_info() = default; + profile_info(utils::byte_buffer& buffer); + void serialize(utils::byte_buffer& buffer) const; + }; + + void clear_profile_infos(); + + void send_profile_info(const game::netadr_s& address, const std::uint64_t user_id, const profile_info& info); + + std::optional get_profile_info(); + std::optional get_profile_info(const uint64_t user_id); + + void update_profile_info(const profile_info& info); +} diff --git a/src/client/game/demonware/data_types.hpp b/src/client/game/demonware/data_types.hpp index e87b8faa..45eaa11d 100644 --- a/src/client/game/demonware/data_types.hpp +++ b/src/client/game/demonware/data_types.hpp @@ -1,5 +1,4 @@ #pragma once -#include "dw_include.hpp" namespace demonware { @@ -243,4 +242,122 @@ namespace demonware buffer->read_blob(&this->data); } }; + + class bdContextUserStorageFileInfo final : public bdTaskResult + { + public: + std::uint32_t create_time; + std::uint32_t modifed_time; + bool priv; + std::uint64_t owner_id; + std::string account_type; + std::string filename; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint32(this->create_time); + buffer->write_uint32(this->modifed_time); + buffer->write_bool(this->priv); + buffer->write_uint64(this->owner_id); + buffer->write_string(this->account_type); + buffer->write_string(this->filename); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint32(&this->create_time); + buffer->read_uint32(&this->modifed_time); + buffer->read_bool(&this->priv); + buffer->read_uint64(&this->owner_id); + buffer->read_string(&this->account_type); + buffer->read_string(&this->filename); + } + }; + + class bdPublicProfileInfo final : public bdTaskResult + { + public: + std::uint64_t m_entityID; + std::string m_memberplayer_card; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint64(this->m_entityID); + buffer->write_blob(this->m_memberplayer_card); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint64(&this->m_entityID); + buffer->read_blob(&this->m_memberplayer_card); + } + }; + + class bdSessionID final : public bdTaskResult + { + public: + uint64_t session_id; + + void serialize(byte_buffer* buffer) override + { + buffer->write_blob(LPSTR(&this->session_id), sizeof this->session_id); + } + + void deserialize(byte_buffer* buffer) override + { + int size{}; + char* data{}; + buffer->read_blob(&data, &size); + + if (data && uint32_t(size) >= sizeof(this->session_id)) + { + this->session_id = *reinterpret_cast(data); + } + } + }; + + class bdMatchMakingInfo final : bdTaskResult + { + bdSessionID m_sessionID; + std::string m_hostAddr; + uint32_t m_hostAddrSize; + uint32_t m_gameType; + uint32_t m_maxPlayers; + uint32_t m_numPlayers; + + void serialize(byte_buffer* buffer) override + { + buffer->write_blob(this->m_hostAddr); + this->m_sessionID.serialize(buffer); + buffer->write_uint32(this->m_gameType); + buffer->write_uint32(this->m_maxPlayers); + buffer->write_uint32(this->m_numPlayers); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_blob(&this->m_hostAddr); + buffer->read_uint32(&this->m_gameType); + buffer->read_uint32(&this->m_maxPlayers); + } + }; + + class bdPerformanceValue final : public bdTaskResult + { + public: + uint64_t user_id; + int64_t performance; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint64(this->user_id); + buffer->write_int64(this->performance); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint64(&this->user_id); + buffer->read_int64(&this->performance); + } + }; } diff --git a/src/client/game/demonware/service.hpp b/src/client/game/demonware/service.hpp index c4393d50..157911f3 100644 --- a/src/client/game/demonware/service.hpp +++ b/src/client/game/demonware/service.hpp @@ -1,5 +1,6 @@ #pragma once #include + #include "servers/service_server.hpp" namespace demonware diff --git a/src/client/game/demonware/services/bdMatchMaking.cpp b/src/client/game/demonware/services/bdMatchMaking.cpp index 0b460ef2..331e25a1 100644 --- a/src/client/game/demonware/services/bdMatchMaking.cpp +++ b/src/client/game/demonware/services/bdMatchMaking.cpp @@ -1,6 +1,8 @@ #include #include "../dw_include.hpp" +#include "steam/steam.hpp" + namespace demonware { bdMatchMaking::bdMatchMaking() : service(138, "bdMatchMaking") @@ -24,8 +26,11 @@ namespace demonware void bdMatchMaking::createSession(service_server* server, byte_buffer* /*buffer*/) const { - // TODO: + auto id = std::make_unique(); + id->session_id = steam::SteamUser()->GetSteamID().bits; + auto reply = server->create_reply(this->task_id()); + reply.add(id); reply.send(); } @@ -36,9 +41,14 @@ namespace demonware reply.send(); } - void bdMatchMaking::deleteSession(service_server* server, byte_buffer* /*buffer*/) const + void bdMatchMaking::deleteSession(service_server* server, byte_buffer* buffer) const { - // TODO: + bdSessionID id; + id.deserialize(buffer); + + byte_buffer out_data; + id.serialize(&out_data); + auto reply = server->create_reply(this->task_id()); reply.send(); } @@ -59,8 +69,12 @@ namespace demonware void bdMatchMaking::getPerformanceValues(service_server* server, byte_buffer* /*buffer*/) const { - // TODO: + auto result = std::make_unique(); + result->user_id = steam::SteamUser()->GetSteamID().bits; + result->performance = 10; + auto reply = server->create_reply(this->task_id()); + reply.add(result); reply.send(); } diff --git a/src/client/game/demonware/services/bdProfiles.cpp b/src/client/game/demonware/services/bdProfiles.cpp index 48963743..d6ca98b4 100644 --- a/src/client/game/demonware/services/bdProfiles.cpp +++ b/src/client/game/demonware/services/bdProfiles.cpp @@ -1,6 +1,8 @@ #include #include "../dw_include.hpp" +#include "../../../component/profile_infos.hpp" + namespace demonware { bdProfiles::bdProfiles() : service(8, "bdProfiles") @@ -15,16 +17,40 @@ namespace demonware this->register_task(8, &bdProfiles::setPublicInfoByUserID); } - void bdProfiles::getPublicInfos(service_server* server, byte_buffer* /*buffer*/) const + void bdProfiles::getPublicInfos(service_server* server, byte_buffer* buffer) const { - // TODO: - auto reply = server->create_reply(this->task_id()); + std::vector> profile_infos{}; + + std::uint64_t entity_id; + while (buffer->read_uint64(&entity_id)) + { + auto profile = profile_infos::get_profile_info(entity_id); + if (profile) + { + profile_infos.emplace_back(entity_id, std::move(*profile)); + } + } + + auto reply = server->create_reply(this->task_id(), profile_infos.empty() ? game::BD_NO_PROFILE_INFO_EXISTS : game::BD_NO_ERROR); + + for (auto& info : profile_infos) + { + auto result = std::make_unique(); + result->m_entityID = info.first; + result->m_memberplayer_card = std::move(info.second.m_memberplayer_card); + + reply.add(result); + } + reply.send(); } - void bdProfiles::setPublicInfo(service_server* server, byte_buffer* /*buffer*/) const + void bdProfiles::setPublicInfo(service_server* server, byte_buffer* buffer) const { - // TODO: + profile_infos::profile_info info{}; + buffer->read_blob(&info.m_memberplayer_card); + profile_infos::update_profile_info(info); + auto reply = server->create_reply(this->task_id()); reply.send(); } diff --git a/src/client/game/demonware/services/bdStorage.cpp b/src/client/game/demonware/services/bdStorage.cpp index 85381404..5d5bb9d4 100644 --- a/src/client/game/demonware/services/bdStorage.cpp +++ b/src/client/game/demonware/services/bdStorage.cpp @@ -14,6 +14,7 @@ namespace demonware this->register_task(20, &bdStorage::listAllPublisherFiles); this->register_task(21, &bdStorage::getPublisherFile); this->register_task(24, &bdStorage::uploadAndValidateFiles); + this->register_task(18, &bdStorage::uploadFiles); this->register_task(16, &bdStorage::getFiles); this->register_task(12, &bdStorage::getFile); @@ -173,6 +174,51 @@ namespace demonware info->filename = filename; info->data = data; +#ifdef DW_DEBUG + printf("[DW]: [bdStorage]: set user file: %s\n", filename.data()); +#endif + + reply.add(info); + } + + reply.send(); + } + + void bdStorage::uploadFiles(service_server* server, byte_buffer* buffer) const + { + std::string game, platform; + std::uint64_t owner; + std::uint32_t numfiles; + + buffer->read_string(&game); + buffer->read_uint64(&owner); + buffer->read_string(&platform); + buffer->read_uint32(&numfiles); + + auto reply = server->create_reply(this->task_id()); + + for (uint32_t i = 0; i < numfiles; i++) + { + std::string filename, data; + std::uint32_t version; + bool priv; + + buffer->read_string(&filename); + buffer->read_blob(&data); + buffer->read_uint32(&version); + buffer->read_bool(&priv); + + const auto path = get_user_file_path(filename); + utils::io::write_file(path, data); + + auto info = std::make_unique(); + info->modifed_time = static_cast(time(nullptr)); + info->create_time = info->modifed_time; + info->priv = priv; + info->owner_id = owner; + info->account_type = platform; + info->filename = filename; + #ifdef DW_DEBUG printf("[DW]: [bdStorage]: set user file: %s\n", filename.data()); #endif @@ -186,26 +232,26 @@ namespace demonware void bdStorage::getFiles(service_server* server, byte_buffer* buffer) const { std::string platform; - uint32_t numunk; - uint32_t numfiles; + uint32_t num_users; + uint32_t num_files; uint64_t user_id = 0; std::string game; buffer->read_string(&platform); - buffer->read_uint32(&numunk); + buffer->read_uint32(&num_users); - for (uint32_t i = 0; i < numunk; i++) + for (uint32_t i = 0; i < num_users; i++) { buffer->read_uint64(&user_id); buffer->read_string(&game); } - buffer->read_uint32(&numfiles); + buffer->read_uint32(&num_files); auto reply = server->create_reply(this->task_id()); uint32_t count = 0; - for (uint32_t i = 0; i < numfiles; i++) + for (uint32_t i = 0; i < num_files; i++) { std::string filename, data; buffer->read_string(&filename); diff --git a/src/client/game/demonware/services/bdStorage.hpp b/src/client/game/demonware/services/bdStorage.hpp index f4d8887f..297737b5 100644 --- a/src/client/game/demonware/services/bdStorage.hpp +++ b/src/client/game/demonware/services/bdStorage.hpp @@ -7,6 +7,8 @@ namespace demonware public: bdStorage(); + static std::string get_user_file_path(const std::string& name); + private: using callback = std::function; using resource_variant = std::variant; @@ -19,9 +21,8 @@ namespace demonware void listAllPublisherFiles(service_server* server, byte_buffer* buffer); void getPublisherFile(service_server* server, byte_buffer* buffer); void uploadAndValidateFiles(service_server* server, byte_buffer* buffer) const; + void uploadFiles(service_server* server, byte_buffer* buffer) const; void getFiles(service_server* server, byte_buffer* buffer) const; void getFile(service_server* server, byte_buffer* buffer) const; - - static std::string get_user_file_path(const std::string& name); }; } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 098873b3..8029b039 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1,19 +1,16 @@ #pragma once #include "types/database.hpp" -#include "types/ddl.hpp" #include "types/demonware.hpp" -#include "types/party.hpp" -#include "types/pmem.hpp" -#include "types/sv.hpp" namespace game { using namespace database; - using namespace ddl; using namespace demonware; - using namespace party; - using namespace pmem; - using namespace sv; + + struct XUID + { + std::uint64_t m_id; + }; enum GameModeType : std::uint32_t { @@ -27,6 +24,7 @@ namespace game { FEATURE_GRAVITY = 33, FEATURE_TIMESCALE = 69, + FEATURE_RANDOM_PLAYERCARD_WHEN_MISSING = 163, }; enum Sys_Folder @@ -392,7 +390,11 @@ namespace game struct netadr_s { netadrtype_t type; - unsigned char ip[4]; + union + { + unsigned char ip[4]; + uint32_t addr; + }; unsigned __int16 port; netsrc_t localNetID; unsigned int addrHandleIndex; @@ -500,8 +502,13 @@ namespace game struct entityState_t { __int16 number; // 0 + char __pad0[150]; + int surfType; + int clientNum; }; // sizeof = ? + assert_offsetof(entityState_t, clientNum, 156); + struct gclient_s { char __pad0[19376]; @@ -547,14 +554,16 @@ namespace game struct client_t { clientHeader_t header; // 0 - char __pad0[120]; + char __pad0[120]; // 16 gentity_s* gentity; // 136 char __pad1[20]; char userinfo[1024]; char name[32]; // 1188 - char __pad2[648396]; + char __pad2[648396]; // 1220 netadr_s remoteAddress; // 649616 - char __pad3[65780]; + char __pad5[2460]; // 649636 + char playerGuid[21]; // 652096 + char __pad6[63299]; // 652117 }; static_assert(sizeof(client_t) == 715416); static_assert(offsetof(client_t, header.state) == 8); @@ -562,6 +571,7 @@ namespace game static_assert(offsetof(client_t, userinfo) == 164); static_assert(offsetof(client_t, name) == 1188); static_assert(offsetof(client_t, remoteAddress) == 649616); + static_assert(offsetof(client_t, playerGuid) == 652096); } using namespace entity; @@ -788,4 +798,254 @@ namespace game unsigned __int16 childVariableBucket[65536]; ChildVariableValue childVariableValue[384000]; }; + + enum PLAYERCARD_CACHE_TASK_STAGE + { + PLAYERCARD_CACHE_TASK_STAGE_WAITING = 0x0, + PLAYERCARD_CACHE_TASK_STAGE_WORKING = 0x1, + PLAYERCARD_CACHE_TASK_STAGE_ALL_DONE = 0x2, + }; + + struct CachedPlayerProfile + { + bool has_data; + XUID userID; + char profile[2201]; + int time; + }; + + namespace ddl + { + enum DDLType + { + DDL_INVALID_TYPE = 0xFFFFFFFF, + DDL_BYTE_TYPE = 0x0, + DDL_SHORT_TYPE = 0x1, + DDL_UINT_TYPE = 0x2, + DDL_INT_TYPE = 0x3, + DDL_UINT64_TYPE = 0x4, + DDL_FLOAT_TYPE = 0x5, + DDL_FIXEDPOINT_TYPE = 0x6, + DDL_STRING_TYPE = 0x7, + DDL_STRUCT_TYPE = 0x8, + DDL_ENUM_TYPE = 0x9, + }; + + union DDLValue + { + int intValue; + unsigned int uintValue; + unsigned __int64 uint64Value; + float floatValue; + float fixedPointValue; + const char* stringPtr; + }; + + struct DDLMember + { + const char* name; + int index; + int bitSize; + int limitSize; + int offset; + int type; + int externalIndex; + unsigned int rangeLimit; + bool isArray; + int arraySize; + int enumIndex; + }; + + struct DDLState + { + bool isValid; + int offset; + int arrayIndex; + DDLMember* member; + //const DDLDef* ddlDef; + }; + + struct DDLContext + { + + }; + } + using namespace ddl; + + namespace session + { + struct SessionStaticData + { + const char* sessionName; + bool registerUsersWithVoice; + }; + + struct ClientInfo + { + bool registered; + bool voiceRegistered; + unsigned __int64 xuid; + int natType; + netadr_s addr; + int usrVoiceConnectivityBits; + int nextConnectivityTestTime[1]; + bool muted; + bool privateSlot; + }; + + struct RegisteredUser + { + bool active; + unsigned __int64 xuid; + bool privateSlot; + }; + + struct SessionDynamicData + { + bool sessionHandle; + char __pad0[47]; + bool keysGenerated; + bool sessionStartCalled; + unsigned __int64 sessionNonce; + int privateSlots; + int publicSlots; + int flags; + bool qosListenEnabled; + ClientInfo users[18]; + int localVoiceConnectivityBits; + int sessionCreateController; + int sessionDeleteTime; + bool allowJoining; + RegisteredUser internalRegisteredUsers[18]; + }; + + struct SessionData + { + SessionStaticData staticData; + SessionDynamicData dyn; + }; + + assert_sizeof(SessionData, 1552); + assert_offsetof(SessionData, dyn.users, 96); + assert_offsetof(SessionData, dyn.internalRegisteredUsers, 1120); + } + using namespace session; + + namespace party + { + enum PartyPreloadMapStage : std::uint32_t + { + PRELOAD_MAP_IDLE = 0x0, + PRELOAD_MAP_INITIATED = 0x1, + PRELOAD_MAP_STARTED = 0x2, + PRELOAD_MAP_COUNT = 0x3, + }; + + struct PartyData + { + SessionData* session; + char __pad0[11436]; + PartyPreloadMapStage preloadingMapStage; + char __pad1[101]; + bool m_gameStartSkipCountdown; + char __pad2[110]; + int lobbyFlags; + bool gameStartRequested; + }; + static_assert(offsetof(PartyData, preloadingMapStage) == 11444); + static_assert(offsetof(PartyData, m_gameStartSkipCountdown) == 11549); + static_assert(offsetof(PartyData, lobbyFlags) == 11660); + static_assert(offsetof(PartyData, gameStartRequested) == 11664); + } + using namespace party; + + namespace sv + { + struct SvServerInitSettings + { + char mapName[64]; + char gameType[64]; + char serverHostName[64]; + bool hardcoreMode; + unsigned int maxClientCount; + unsigned int maxAgentCount; + bool isMapPreloaded; + bool isSaveGame; + bool isRestart; + bool isFrontEnd; + char __pad0[2]; + bool serverThreadStartup; + }; //static_assert(sizeof(SvServerInitSettings) == 212); + static_assert(offsetof(SvServerInitSettings, maxClientCount) == 196); + static_assert(offsetof(SvServerInitSettings, isMapPreloaded) == 204); + static_assert(offsetof(SvServerInitSettings, isFrontEnd) == 207); + static_assert(offsetof(SvServerInitSettings, serverThreadStartup) == 210); + } + using namespace sv; + + namespace pmem + { + enum PMem_Stack : __int32 + { + PMEM_STACK_GAME = 0x0, + PMEM_STACK_RENDER_TARGETS = 0x1, + PMEM_STACK_MEM_VIRTUAL = 0x2, + PMEM_STACK_MEMCARD_LARGE_BUFFER = 0x3, + PMEM_STACK_SOUND = 0x4, + PMEM_STACK_STASHED_MEMORY = 0x5, + PMEM_STACK_CINEMATIC = 0x6, + PMEM_STACK_COUNT = 0x7, + }; + + enum PMem_Source + { + PMEM_SOURCE_EXTERNAL = 0x0, + PMEM_SOURCE_SCRIPT = 0x1, + }; + + enum PMem_Direction : __int32 + { + PHYS_ALLOC_LOW = 0x0, + PHYS_ALLOC_HIGH = 0x1, + PHYS_ALLOC_COUNT = 0x2, + }; + + enum Mem_PageID + { + }; + + struct Mem_PageRange + { + Mem_PageID firstPageID; + Mem_PageID lastPageID; + }; + + struct PhysicalMemoryAllocation + { + const char* name; + char* prev_buffer; + char* next_buffer; + unsigned __int64 pos; + Mem_PageRange pageRange; + }; + + struct PhysicalMemoryPrim + { + const char* name; + unsigned int allocListCount; + char __pad0[4]; + unsigned __int8* buf; + unsigned __int64 unk_pos; + int unk1; + char __pad2[4]; + unsigned __int64 pos; + PhysicalMemoryAllocation allocList[32]; + }; + + struct PhysicalMemory + { + PhysicalMemoryPrim prim[2]; + }; + } + using namespace pmem; } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 06d19095..ea92846a 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -149,6 +149,7 @@ namespace game WEAK symbol LUI_CoD_CLoseAll{ 0x6135C0 }; WEAK symbol Live_SyncOnlineDataFlags{ 0xDC5CE0 }; + WEAK symbol Live_GetXuid{ 0xD32A20 }; WEAK symbol Lobby_GetPartyData{ 0x9C3E20 }; @@ -163,6 +164,8 @@ namespace game WEAK symbol Party_GetActiveParty{ 0x9CC010 }; + WEAK symbol PlayercardCache_AddToDownload{ 0xDB72E0 }; + WEAK symbol R_RegisterFont{ 0xDFC670 }; WEAK symbol R_TextWidth{ 0xDFC770 }; WEAK symbol R_GetFontHeight{ 0x12727B0 }; @@ -178,6 +181,11 @@ namespace game #define R_AddCmdDrawTextWithCursor(TXT, MC, F, UNK, X, Y, XS, YS, R, C, S, CP, CC) \ IW7_AddBaseDrawTextCmd(TXT, MC, F, game::R_GetFontHeight(F), X, Y, XS, YS, R, C, CP, CC, game::R_DrawSomething(S), 0, 0, 0, 0) + WEAK symbol Session_GetXuid{ 0xC72AB0 }; + WEAK symbol Session_IsHost{ 0xD9B470 }; + + WEAK symbol StringToXUID{ 0xCE6C40 }; + WEAK symbol Sys_Cwd{ 0xCFE5A0 }; WEAK symbol Sys_Milliseconds{ 0xD58110 }; @@ -229,8 +237,9 @@ namespace game WEAK symbol SV_CmdsSP_MapRestart_f{ 0xC12B30 }; WEAK symbol SV_CmdsSP_FastRestart_f{ 0xC12AF0 }; WEAK symbol SV_ClientMP_GetClientPing{ 0xC507D0 }; - WEAK symbol SV_GameMP_GetGuid{ 0XC12410 }; + WEAK symbol SV_GameMP_GetGuid{ 0xC12410 }; WEAK symbol SV_MainMP_KillLocalServer{ 0xC58DF0 }; + WEAK symbol SV_MainMP_GetServerLobby{ 0xC58DA0 }; WEAK symbol SV_GameSendServerCommand{ 0xC54780 }; WEAK symbol SV_DropClient{ 0xC4FBA0 }; WEAK symbol SV_Loaded{ 0xC114C0 }; @@ -258,6 +267,9 @@ namespace game WEAK symbol cmd_functions{ 0x5D65CC8 }; WEAK symbol command_whitelist{ 0x14D1B70 }; + WEAK symbol g_DWPlayercardCacheDownloadTaskStage{ 0x80AE414 }; + WEAK symbol cached_playercards{ 0x80AE420 }; + WEAK symbol gfxDrawMethod{ 0x83E86A8 }; WEAK symbol keyCatchers{ 0x2246C34 }; @@ -273,6 +285,8 @@ namespace game WEAK symbol svs_numclients{ 0x6B229E0 }; WEAK symbol svs_clients{ 0x6B22950 }; + WEAK symbol g_serverSession{ 0x6B4E080 }; + WEAK symbol clientUIActives{ 0x2246C30 }; WEAK symbol cl_con_data{ 0x1FE58B8 }; diff --git a/src/client/game/types/database.hpp b/src/client/game/types/database.hpp index a420bad7..84352bd5 100644 --- a/src/client/game/types/database.hpp +++ b/src/client/game/types/database.hpp @@ -1,9 +1,6 @@ #pragma once #include -#define assert_sizeof(__ASSET__, __SIZE__) static_assert(sizeof(__ASSET__) == __SIZE__) -#define assert_offsetof(__ASSET__, __VARIABLE__, __OFFSET__) static_assert(offsetof(__ASSET__, __VARIABLE__) == __OFFSET__) - namespace game::database { typedef float vec_t; diff --git a/src/client/game/types/ddl.hpp b/src/client/game/types/ddl.hpp deleted file mode 100644 index c0817645..00000000 --- a/src/client/game/types/ddl.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -namespace game::ddl -{ - enum DDLType - { - DDL_INVALID_TYPE = 0xFFFFFFFF, - DDL_BYTE_TYPE = 0x0, - DDL_SHORT_TYPE = 0x1, - DDL_UINT_TYPE = 0x2, - DDL_INT_TYPE = 0x3, - DDL_UINT64_TYPE = 0x4, - DDL_FLOAT_TYPE = 0x5, - DDL_FIXEDPOINT_TYPE = 0x6, - DDL_STRING_TYPE = 0x7, - DDL_STRUCT_TYPE = 0x8, - DDL_ENUM_TYPE = 0x9, - }; - - union DDLValue - { - int intValue; - unsigned int uintValue; - unsigned __int64 uint64Value; - float floatValue; - float fixedPointValue; - const char* stringPtr; - }; - - struct DDLMember - { - const char* name; - int index; - int bitSize; - int limitSize; - int offset; - int type; - int externalIndex; - unsigned int rangeLimit; - bool isArray; - int arraySize; - int enumIndex; - }; - - struct DDLState - { - bool isValid; - int offset; - int arrayIndex; - DDLMember* member; - //const DDLDef* ddlDef; - }; - - struct DDLContext - { - - }; -} \ No newline at end of file diff --git a/src/client/game/types/party.hpp b/src/client/game/types/party.hpp deleted file mode 100644 index 2c195fbf..00000000 --- a/src/client/game/types/party.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -namespace game::party -{ - enum PartyPreloadMapStage : std::uint32_t - { - PRELOAD_MAP_IDLE = 0x0, - PRELOAD_MAP_INITIATED = 0x1, - PRELOAD_MAP_STARTED = 0x2, - PRELOAD_MAP_COUNT = 0x3, - }; - - struct PartyData - { - char __pad0[11444]; - PartyPreloadMapStage preloadingMapStage; - char __pad1[101]; - bool m_gameStartSkipCountdown; - char __pad2[110]; - int lobbyFlags; - bool gameStartRequested; - }; - static_assert(offsetof(PartyData, preloadingMapStage) == 11444); - static_assert(offsetof(PartyData, m_gameStartSkipCountdown) == 11549); - static_assert(offsetof(PartyData, lobbyFlags) == 11660); - static_assert(offsetof(PartyData, gameStartRequested) == 11664); -} \ No newline at end of file diff --git a/src/client/game/types/pmem.hpp b/src/client/game/types/pmem.hpp deleted file mode 100644 index 5d227361..00000000 --- a/src/client/game/types/pmem.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -namespace game::pmem -{ - enum PMem_Stack : __int32 - { - PMEM_STACK_GAME = 0x0, - PMEM_STACK_RENDER_TARGETS = 0x1, - PMEM_STACK_MEM_VIRTUAL = 0x2, - PMEM_STACK_MEMCARD_LARGE_BUFFER = 0x3, - PMEM_STACK_SOUND = 0x4, - PMEM_STACK_STASHED_MEMORY = 0x5, - PMEM_STACK_CINEMATIC = 0x6, - PMEM_STACK_COUNT = 0x7, - }; - - enum PMem_Source - { - PMEM_SOURCE_EXTERNAL = 0x0, - PMEM_SOURCE_SCRIPT = 0x1, - }; - - enum PMem_Direction : __int32 - { - PHYS_ALLOC_LOW = 0x0, - PHYS_ALLOC_HIGH = 0x1, - PHYS_ALLOC_COUNT = 0x2, - }; - - enum Mem_PageID - { - }; - - struct Mem_PageRange - { - Mem_PageID firstPageID; - Mem_PageID lastPageID; - }; - - struct PhysicalMemoryAllocation - { - const char* name; - char* prev_buffer; - char* next_buffer; - unsigned __int64 pos; - Mem_PageRange pageRange; - }; - - struct PhysicalMemoryPrim - { - const char* name; - unsigned int allocListCount; - char __pad0[4]; - unsigned __int8* buf; - unsigned __int64 unk_pos; - int unk1; - char __pad2[4]; - unsigned __int64 pos; - PhysicalMemoryAllocation allocList[32]; - }; - - struct PhysicalMemory - { - PhysicalMemoryPrim prim[2]; - }; -} \ No newline at end of file diff --git a/src/client/game/types/sv.hpp b/src/client/game/types/sv.hpp deleted file mode 100644 index f92ee228..00000000 --- a/src/client/game/types/sv.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -namespace game::sv -{ - struct SvServerInitSettings - { - char mapName[64]; - char gameType[64]; - char serverHostName[64]; - bool hardcoreMode; - unsigned int maxClientCount; - unsigned int maxAgentCount; - bool isMapPreloaded; - bool isSaveGame; - bool isRestart; - bool isFrontEnd; - char __pad0[2]; - bool serverThreadStartup; - }; //static_assert(sizeof(SvServerInitSettings) == 212); - static_assert(offsetof(SvServerInitSettings, maxClientCount) == 196); - static_assert(offsetof(SvServerInitSettings, isMapPreloaded) == 204); - static_assert(offsetof(SvServerInitSettings, isFrontEnd) == 207); - static_assert(offsetof(SvServerInitSettings, serverThreadStartup) == 210); -} \ No newline at end of file diff --git a/src/client/game/utils/fragment_handler.cpp b/src/client/game/utils/fragment_handler.cpp new file mode 100644 index 00000000..4121fe4c --- /dev/null +++ b/src/client/game/utils/fragment_handler.cpp @@ -0,0 +1,162 @@ +#include +#include "fragment_handler.hpp" + +namespace game::fragment_handler +{ + namespace + { + constexpr size_t MAX_FRAGMENTS = 100; + + using fragments = std::unordered_map; + + struct fragmented_packet + { + size_t fragment_count{0}; + fragments fragments{}; + std::chrono::high_resolution_clock::time_point insertion_time = std::chrono::high_resolution_clock::now(); + }; + + using id_fragment_map = std::unordered_map; + using address_fragment_map = std::unordered_map; + + utils::concurrency::container global_map{}; + + std::vector construct_fragments(const void* data, const size_t length) + { + std::vector fragments{}; + + constexpr size_t max_fragment_size = 0x400; + + for (size_t i = 0; i < length; i += max_fragment_size) + { + const auto current_fragment_size = std::min(length - i, max_fragment_size); + + std::string fragment(static_cast(data) + i, current_fragment_size); + fragments.push_back(std::move(fragment)); + } + + return fragments; + } + } + + bool handle(const netadr_s& target, utils::byte_buffer& buffer, std::string& final_packet) + { + const auto fragment_id = buffer.read(); + const size_t fragment_count = buffer.read(); + const size_t fragment_index = buffer.read(); + + auto fragment_data = buffer.get_remaining_data(); + + // Check for valid fragment_count and fragment_index + if (fragment_count == 0 || fragment_count > MAX_FRAGMENTS || fragment_index >= fragment_count) + { + return false; + } + + return global_map.access([&](address_fragment_map& map) + { + auto& user_map = map[target]; + + // Check if the user_map is full + if (!user_map.contains(fragment_id) && user_map.size() > MAX_FRAGMENTS) + { + return false; + } + + auto& packet_queue = user_map[fragment_id]; + + // Set fragment_count if not set + if (packet_queue.fragment_count == 0) + { + packet_queue.fragment_count = fragment_count; + } + + // Check if fragment_count matches + if (packet_queue.fragment_count != fragment_count) + { + return false; + } + + // Ensure fragment_index is within bounds + if (packet_queue.fragments.size() + 1 < fragment_count) + { + packet_queue.fragments[fragment_index] = std::move(fragment_data); + return false; + } + + final_packet.clear(); + + // Reconstruct final_packet + for (size_t i = 0; i < fragment_count; ++i) + { + if (i == fragment_index) + { + final_packet.append(fragment_data.data(), fragment_data.size()); + } + else + { + final_packet.append(packet_queue.fragments[i].data(), packet_queue.fragments[i].size()); + } + } + + return true; + }); + } + + void clean() + { + global_map.access([](address_fragment_map& map) + { + for (auto i = map.begin(); i != map.end();) + { + auto& user_map = i->second; + + for (auto j = user_map.begin(); j != user_map.end();) + { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - j->second.insertion_time; + + if (diff > 5s) + { + j = user_map.erase(j); + } + else + { + ++j; + } + } + + if (user_map.empty()) + { + i = map.erase(i); + } + else + { + ++i; + } + } + }); + } + + void fragment_handler::fragment_data(const void* data, const size_t size, + const std::function& callback) + { + static std::atomic_uint64_t current_id{0}; + const auto id = current_id++; + + const auto fragments = construct_fragments(data, size); + + for (size_t i = 0; i < fragments.size(); ++i) + { + utils::byte_buffer buffer{}; + buffer.write(id); + buffer.write(static_cast(fragments.size())); + buffer.write(static_cast(i)); + + auto& fragment = fragments.at(i); + buffer.write(fragment.data(), fragment.size()); + + callback(buffer); + } + } +} diff --git a/src/client/game/utils/fragment_handler.hpp b/src/client/game/utils/fragment_handler.hpp new file mode 100644 index 00000000..1468e3dc --- /dev/null +++ b/src/client/game/utils/fragment_handler.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include "../../component/network.hpp" + +namespace game::fragment_handler +{ + bool handle(const netadr_s& target, utils::byte_buffer& buffer, + std::string& final_packet); + + void clean(); + + void fragment_data(const void* data, const size_t size, + const std::function& callback); +} diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 863a376d..51da9aa8 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -102,4 +102,7 @@ using namespace std::literals; +#define assert_sizeof(__ASSET__, __SIZE__) static_assert(sizeof(__ASSET__) == __SIZE__) +#define assert_offsetof(__ASSET__, __VARIABLE__, __OFFSET__) static_assert(offsetof(__ASSET__, __VARIABLE__) == __OFFSET__) + #define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) diff --git a/src/common/utils/byte_buffer.cpp b/src/common/utils/byte_buffer.cpp new file mode 100644 index 00000000..0303d07f --- /dev/null +++ b/src/common/utils/byte_buffer.cpp @@ -0,0 +1,53 @@ +#include "byte_buffer.hpp" + +#include + +namespace utils +{ + byte_buffer::byte_buffer() + : writing_(true) + { + } + + byte_buffer::byte_buffer(std::string buffer) + : writing_(false) + , buffer_(std::move(buffer)) + { + } + + void byte_buffer::write(const void* buffer, const size_t length) + { + if (!this->writing_) + { + throw std::runtime_error("Writing to readable byte buffer"); + } + + this->buffer_.append(static_cast(buffer), length); + } + + void byte_buffer::read(void* data, const size_t length) + { + if (this->writing_) + { + throw std::runtime_error("Reading from writable byte buffer"); + } + + if (this->offset_ + length > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + memcpy(data, this->buffer_.data() + this->offset_, length); + this->offset_ += length; + } + + std::string byte_buffer::read_data(const size_t length) + { + std::string result{}; + result.resize(length); + + this->read(result.data(), result.size()); + + return result; + } +} diff --git a/src/common/utils/byte_buffer.hpp b/src/common/utils/byte_buffer.hpp new file mode 100644 index 00000000..f9ee1924 --- /dev/null +++ b/src/common/utils/byte_buffer.hpp @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include + +namespace utils +{ + class byte_buffer + { + public: + byte_buffer(); + byte_buffer(std::string buffer); + + template + byte_buffer(const std::basic_string_view& buffer) + : byte_buffer(std::string(reinterpret_cast(buffer.data()), buffer.size() * sizeof(T))) + { + } + + void write(const void* buffer, size_t length); + + void write(const char* text) + { + this->write(text, strlen(text)); + } + + void write_string(const char* str, const size_t length) + { + this->write(static_cast(length)); + this->write(str, length); + } + + void write_string(const std::string& str) + { + this->write_string(str.data(), str.size()); + } + + void write_string(const char* str) + { + this->write_string(str, strlen(str)); + } + + template + void write(const T& object) + { + this->write(&object, sizeof(object)); + } + + template<> + void write(const byte_buffer& object) + { + const auto& buffer = object.get_buffer(); + this->write(buffer.data(), buffer.size()); + } + + template + void write(const std::vector& vec) + { + this->write(vec.data(), vec.size() * sizeof(T)); + } + + template + void write_vector(const std::vector& vec) + { + this->write(static_cast(vec.size())); + this->write(vec); + } + + const std::string& get_buffer() const + { + return this->buffer_; + } + + std::string move_buffer() + { + return std::move(this->buffer_); + } + + void read(void* data, size_t length); + + template + T read() + { + T object{}; + this->read(&object, sizeof(object)); + return object; + } + + template + std::vector read_vector() + { + std::vector result{}; + const auto size = this->read(); + const auto totalSize = size * sizeof(T); + + if (this->offset_ + totalSize > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + result.resize(size); + this->read(result.data(), totalSize); + + return result; + } + + std::string read_string() + { + std::string result{}; + const auto size = this->read(); + + if (this->offset_ + size > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + result.resize(size); + this->read(result.data(), size); + + return result; + } + + size_t get_remaining_size() const + { + return this->buffer_.size() - offset_; + } + + std::string get_remaining_data() + { + return this->read_data(this->get_remaining_size()); + } + + std::string read_data(size_t length); + + private: + bool writing_{false}; + size_t offset_{0}; + std::string buffer_{}; + }; +} diff --git a/src/common/utils/concurrency.hpp b/src/common/utils/concurrency.hpp index 05c5d3ad..0bc7795f 100644 --- a/src/common/utils/concurrency.hpp +++ b/src/common/utils/concurrency.hpp @@ -39,6 +39,11 @@ namespace utils::concurrency T& get_raw() { return object_; } const T& get_raw() const { return object_; } + std::unique_lock accquire_lock() + { + return std::unique_lock{mutex_}; + } + private: mutable MutexType mutex_{}; T object_{};