#include #include "loader/component_loader.hpp" #include "auth.hpp" #include "party.hpp" #include "command.hpp" #include "network.hpp" #include "scheduler.hpp" #include "profile_infos.hpp" #include #include #include #include #include #include #include #include #include #include namespace auth { namespace { const game::dvar_t* password; std::array client_xuids{}; std::string get_hdd_serial() { DWORD serial{}; if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0)) { return {}; } return utils::string::va("%08X", serial); } std::string get_hw_profile_guid() { HW_PROFILE_INFO info; if (!GetCurrentHwProfileA(&info)) { return {}; } return std::string{info.szHwProfileGuid, sizeof(info.szHwProfileGuid)}; } std::string get_protected_data() { std::string input = "momo5502-boiii-auth"; DATA_BLOB data_in{}, data_out{}; data_in.pbData = reinterpret_cast(input.data()); data_in.cbData = static_cast(input.size()); if (CryptProtectData(&data_in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_LOCAL_MACHINE, &data_out) != TRUE) { return {}; } const auto size = std::min(data_out.cbData, 52ul); std::string result{reinterpret_cast(data_out.pbData), size}; LocalFree(data_out.pbData); return result; } std::string get_key_entropy() { std::string entropy{}; entropy.append(utils::smbios::get_uuid()); entropy.append(get_hw_profile_guid()); entropy.append(get_protected_data()); entropy.append(get_hdd_serial()); if (entropy.empty()) { entropy.resize(32); utils::cryptography::random::get_data(entropy.data(), entropy.size()); } return entropy; } utils::cryptography::ecc::key& get_key() { static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); return key; } bool is_second_instance() { static const auto is_first = [] { static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex"); return mutex && GetLastError() != ERROR_ALREADY_EXISTS; }(); return !is_first; } std::string serialize_connect_data(const char* data, const int length) { utils::byte_buffer buffer{}; profile_infos::get_profile_info().value_or(profile_infos::profile_info{}).serialize(buffer); buffer.write_string(data, static_cast(length)); return buffer.move_buffer(); } void send_fragmented_connect_packet(const game::netsrc_t sock, game::netadr_t* adr, const char* data, const int length) { const auto connect_data = serialize_connect_data(data, length); game::fragment_handler::fragment_data // (connect_data.data(), connect_data.size(), [&](const utils::byte_buffer& buffer) { utils::byte_buffer packet_buffer{}; packet_buffer.write("connect"); packet_buffer.write(" "); packet_buffer.write(buffer); const auto& fragment_packet = packet_buffer.get_buffer(); game::NET_OutOfBandData( sock, adr, fragment_packet.data(), static_cast(fragment_packet.size())); }); } int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, const int len) { try { const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0; if (!is_connect_sequence) { return game::NET_OutOfBandData(sock, adr, data, len); } send_fragmented_connect_packet(sock, adr, data, len); return true; } catch (std::exception& e) { printf("Error: %s\n", e.what()); } return 0; } void distribute_player_xuid(const game::netadr_t& target, const size_t player_index, const uint64_t xuid) { if (player_index >= 18) { return; } utils::byte_buffer buffer{}; buffer.write(static_cast(player_index)); buffer.write(xuid); game::foreach_connected_client([&](const game::client_s& client, const size_t index) { if (client.address.type != game::NA_BOT) { network::send(client.address, "playerXuid", buffer.get_buffer()); } if (index != player_index && target.type != game::NA_BOT) { utils::byte_buffer current_buffer{}; current_buffer.write(static_cast(index)); current_buffer.write(client.xuid); network::send(target, "playerXuid", current_buffer.get_buffer()); } }); } void handle_new_player(const game::netadr_t& target) { const command::params_sv params{}; if (params.size() < 2) { return; } const utils::info_string info_string(params[1]); const auto xuid = strtoull(info_string.get("xuid").data(), nullptr, 16); size_t player_index = 18; game::foreach_connected_client([&](game::client_s& client, const size_t index) { if (client.address == target) { client.xuid = xuid; player_index = index; } }); distribute_player_xuid(target, player_index, xuid); } void dispatch_connect_packet(const game::netadr_t& target, const std::string& data) { utils::byte_buffer buffer(data); const profile_infos::profile_info info(buffer); const auto connect_data = buffer.read_string(); const command::params_sv params(connect_data); if (params.size() < 2) { return; } const auto _ = profile_infos::acquire_profile_lock(); const utils::info_string info_string(params[1]); const auto xuid = strtoull(info_string.get("xuid").data(), nullptr, 16); profile_infos::add_and_distribute_profile_info(target, xuid, info); game::SV_DirectConnect(target); handle_new_player(target); } void handle_connect_packet_fragment(const game::netadr_t& target, const network::data_view& data) { if (!game::is_server_running()) { return; } utils::byte_buffer buffer(data); std::string final_packet{}; if (game::fragment_handler::handle(target, buffer, final_packet)) { scheduler::once([t = target, p = std::move(final_packet)] { dispatch_connect_packet(t, p); }, scheduler::server); } } void handle_player_xuid_packet(const game::netadr_t& target, const network::data_view& data) { if (game::is_server_running() || !party::is_host(target)) { return; } utils::byte_buffer buffer(data); const auto player_id = buffer.read(); const auto xuid = buffer.read(); if (player_id < client_xuids.size()) { client_xuids[player_id] = xuid; } } void direct_connect_bots_stub(const game::netadr_t address) { game::SV_DirectConnect(address); handle_new_player(address); } } uint64_t get_guid() { static const auto guid = []() -> uint64_t { if (game::is_server() || is_second_instance()) { return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000); } return get_key().get_hash(); }(); return guid; } uint64_t get_guid(const size_t client_num) { if (client_num >= 18) { return 0; } if (!game::is_server_running()) { return client_xuids[client_num]; } uint64_t xuid = 0; const auto callback = [&xuid](const game::client_s& client) { xuid = client.xuid; }; if (!game::access_connected_client(client_num, callback)) { return 0; } return xuid; } void clear_stored_guids() { for (auto& xuid : client_xuids) { xuid = 0; } } void info_set_value_for_key_stub(char* s, const char* key, const char* value) { game::Info_SetValueForKey.call_safe(s, key, value); game::Info_SetValueForKey.call_safe(s, "password", password->current.value.string); } struct component final : generic_component { void post_unpack() override { // Skip connect handler utils::hook::set(game::select(0x142253EFA, 0x14053714A), 0xEB); network::on("connect", handle_connect_packet_fragment); network::on("playerXuid", handle_player_xuid_packet); // Intercept SV_DirectConnect in SV_AddTestClient utils::hook::call(game::select(0x1422490DC, 0x14052E582), direct_connect_bots_stub); scheduler::once([] { password = game::register_dvar_string("password", "", game::DVAR_USERINFO, "password"); }, scheduler::pipeline::main); // Patch steam id bit check std::vector> patches{}; const auto p = [&patches](const size_t a, const size_t b) { patches.emplace_back(a, b); }; if (game::is_server()) { p(0x1404747C6_g, 0x140474806_g); p(0x140474A24_g, 0x140474A68_g); p(0x140474A85_g, 0x140474AC6_g); p(0x140457ED0_g, 0x140457F26_g); p(0x140473DD8_g, 0x140473E19_g); p(0x1404743D5_g, 0x140474423_g); p(0x1404744FD_g, 0x140474553_g); p(0x14047462D_g, 0x140474677_g); p(0x140475057_g, 0x14047509F_g); // ? p(0x140475672_g, 0x1404756B5_g); p(0x140477322_g, 0x140477365_g); // ? } else { p(0x141E19CED_g, 0x141E19D3B_g); p(0x141EB2C76_g, 0x141EB2CB6_g); p(0x141EB2DAD_g, 0x141EB2DF2_g); p(0x141EB3C35_g, 0x141EB3C76_g); p(0x141E19AD0_g, 0x141E19B26_g); // p(0x141EB0EE8_g, 0x141EB0F29_g); p(0x141EB0FA8_g, 0x141EB0FE9_g); p(0x141EB2525_g, 0x141EB2573_g); p(0x141EB264D_g, 0x141EB26A3_g); p(0x141EB277D_g, 0x141EB27C7_g); p(0x141EB2AEA_g, 0x141EB2AFA_g); p(0x141EB2B01_g, 0x141EB2B33_g); p(0x141EB3137_g, 0x141EB3147_g); p(0x141EB314E_g, 0x141EB317F_g); p(0x141EB5377_g, 0x141EB53BF_g); // ? p(0x141EB5992_g, 0x141EB59D5_g); p(0x141EB74D2_g, 0x141EB7515_g); // ? utils::hook::call(0x14134BF7D_g, send_connect_data_stub); utils::hook::call(0x14134BEFE_g, info_set_value_for_key_stub); // Fix crash utils::hook::set(0x14134B970_g, 0xC3); } for (const auto& patch : patches) { utils::hook::jump(patch.first, patch.second); } } }; } REGISTER_COMPONENT(auth::component)