t7x/src/client/component/auth.cpp
2023-04-25 11:01:07 +02:00

414 lines
9.9 KiB
C++

#include <std_include.hpp>
#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 <game/game.hpp>
#include <game/utils.hpp>
#include <utils/nt.hpp>
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/smbios.hpp>
#include <utils/byte_buffer.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
#include <game/fragment_handler.hpp>
namespace auth
{
namespace
{
const game::dvar_t* password;
std::array<uint64_t, 18> 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<uint8_t*>(input.data());
data_in.cbData = static_cast<DWORD>(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<char*>(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<size_t>(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<int>(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<uint32_t>(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<uint32_t>(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<uint32_t>();
const auto xuid = buffer.read<uint64_t>();
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<uint8_t>(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<std::pair<size_t, size_t>> 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<uint8_t>(0x14134B970_g, 0xC3);
}
for (const auto& patch : patches)
{
utils::hook::jump(patch.first, patch.second);
}
}
};
}
REGISTER_COMPONENT(auth::component)