Basic working dedicated server support

This commit is contained in:
Maurice Heumann 2023-01-02 13:57:00 +01:00
parent 59586e18e6
commit 55f12e3313
13 changed files with 206 additions and 96 deletions

View File

@ -79,24 +79,24 @@ namespace auth
static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy());
return key;
}
}
bool is_second_instance()
{
static const auto is_first = []
bool is_second_instance()
{
static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex");
return mutex && GetLastError() != ERROR_ALREADY_EXISTS;
}();
static const auto is_first = []
{
static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex");
return mutex && GetLastError() != ERROR_ALREADY_EXISTS;
}();
return !is_first;
return !is_first;
}
}
uint64_t get_guid()
{
static const auto guid = []() -> uint64_t
{
if (is_second_instance())
if (game::is_server() || is_second_instance())
{
return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000);
}
@ -112,10 +112,55 @@ namespace auth
void post_unpack() override
{
// Patch steam id bit check
utils::hook::jump(0x141E19D7D_g, 0x141E19DCB_g);
utils::hook::jump(0x141EB2D06_g, 0x141EB2D46_g);
utils::hook::jump(0x141EB2E3D_g, 0x141EB2E82_g);
utils::hook::jump(0x141EB3CC5_g, 0x141EB3D06_g);
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(0x141E19D7D_g, 0x141E19DCB_g);
p(0x141EB2D06_g, 0x141EB2D46_g);
p(0x141EB2E3D_g, 0x141EB2E82_g);
p(0x141EB3CC5_g, 0x141EB3D06_g);
p(0x141E19B60_g, 0x141E19BB6_g);
//
p(0x141EB0F78_g, 0x141EB0FB9_g);
p(0x141EB1038_g, 0x141EB1079_g);
p(0x141EB25B5_g, 0x141EB2603_g);
p(0x141EB26DD_g, 0x141EB2733_g);
p(0x141EB280D_g, 0x141EB2857_g);
p(0x141EB2B7A_g, 0x141EB2B8A_g);
p(0x141EB2B91_g, 0x141EB2BC3_g);
p(0x141EB31C7_g, 0x141EB31D7_g);
p(0x141EB31DE_g, 0x141EB320F_g);
p(0x141EB5407_g, 0x141EB544F_g); // ?
p(0x141EB5A22_g, 0x141EB5A65_g);
p(0x141EB7562_g, 0x141EB75A5_g); // ?
}
for (const auto& patch : patches)
{
utils::hook::jump(patch.first, patch.second);
}
}
};
}

View File

@ -1,6 +1,8 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace dedicated
@ -13,6 +15,9 @@ namespace dedicated
{
void post_unpack() override
{
// Ignore "bad stats"
utils::hook::set<uint8_t>(0x14052D523_g, 0xEB);
utils::hook::nop(0x14052D4E4_g, 2);
}
};
}

View File

@ -476,17 +476,20 @@ namespace demonware
{
server_thread = utils::thread::create_named_thread("Demonware", server_main);
utils::hook::set<uint8_t>(game::select(0x14293E829, 0x1407D5879), 0x0); // CURLOPT_SSL_VERIFYPEER
utils::hook::set<uint8_t>(game::select(0x15F3CCFED, 0x1407D5865), 0xAF); // CURLOPT_SSL_VERIFYHOST
utils::hook::copy_string(game::select(0x1430B96E0, 0x140EE4C68), "http://prod.umbrella.demonware.net");
if (game::is_server())
{
return;
}
utils::hook::set<uint8_t>(0x14293E829_g, 0x0); // CURLOPT_SSL_VERIFYPEER
utils::hook::set<uint8_t>(0x15F3CCFED_g, 0xAF); // CURLOPT_SSL_VERIFYHOST
utils::hook::set<uint8_t>(0x1430B9810_g, 0x0); // HTTPS -> HTTP
utils::hook::copy_string(0x1430B96E0_g, "http://prod.umbrella.demonware.net");
utils::hook::copy_string(0x1430B9BE0_g, "http://prod.uno.demonware.net/v1.0");
utils::hook::set<uint8_t>(0x1430B9810_g, 0x0); // HTTPS -> HTTP
utils::hook::copy_string(0x1430B93C8_g, "http://%s:%d/auth/");
utils::hook::set<uint32_t>(0x141EC4B50_g, 0xC3D08948); // Skip publisher file signature stuff

View File

@ -0,0 +1,60 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "steam/steam.hpp"
#include "network.hpp"
#include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <version.hpp>
namespace getinfo
{
namespace
{
std::string get_dvar_string(const char* dvar_name)
{
const auto dvar = game::Dvar_FindVar(dvar_name);
if (!dvar)
{
return {};
}
return game::Dvar_GetString(dvar);
}
}
struct component final : generic_component
{
void post_unpack() override
{
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
{
utils::info_string info{};
info.set("challenge", std::string(data.begin(), data.end()));
info.set("gamename", "T7");
info.set("hostname", get_dvar_string("sv_hostname"));
info.set("gametype", get_dvar_string("g_gametype"));
//info.set("sv_motd", get_dvar_string("sv_motd"));
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", get_dvar_string("mapname"));
//info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1");
//info.set("clients", utils::string::va("%i", get_client_count()));
//info.set("bots", utils::string::va("%i", get_bot_count()));
//info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));
info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0));
info.set("shortversion", SHORTVERSION);
network::send(target, "infoResponse", info.build(), '\n');
});
}
};
}
REGISTER_COMPONENT(getinfo::component)

View File

@ -39,11 +39,13 @@ namespace network
void handle_command_stub(utils::hook::assembler& a)
{
const auto sv = game::is_server();
a.pushad64();
a.mov(r8, r12); // msg
a.mov(rdx, rcx); // command
a.mov(rcx, r15); // address
a.mov(rdx, rcx); // command
a.mov(r8, sv ? r15 : r12); // msg
a.mov(rcx, sv ? r14 : r15); // address
a.call_aligned(handle_command);
@ -51,7 +53,7 @@ namespace network
a.popad64();
a.jmp(0x14134D14B_g);
a.ret();
}
bool socket_set_blocking(const SOCKET s, const bool blocking)
@ -210,24 +212,21 @@ namespace network
return 2;
}
struct component final : client_component
struct component final : generic_component
{
void post_unpack() override
{
//utils::hook::jump(0x14143CAB0_g, ret2); // patch dwGetConnectionStatus
//utils::hook::jump(0x14233307E_g, 0x1423330C7_g);
utils::hook::nop(game::select(0x142332E76, 0x140596DF6), 4); // don't increment data pointer to optionally skip socket byte
utils::hook::call(game::select(0x142332E43, 0x140596DC3), read_socket_byte_stub); // optionally read socket byte
utils::hook::call(game::select(0x142332E81, 0x140596E01), verify_checksum_stub); // skip checksum verification
utils::hook::set<uint8_t>(game::select(0x14233305E, 0x140596F2E), 0); // don't add checksum to packet
utils::hook::nop(0x142332E76_g, 4); // don't increment data pointer to optionally skip socket byte
utils::hook::call(0x142332E43_g, read_socket_byte_stub); // optionally read socket byte
utils::hook::call(0x142332E81_g, verify_checksum_stub); // skip checksum verification
utils::hook::set<uint8_t>(0x14233305E_g, 0); // don't add checksum to packet
utils::hook::set<uint32_t>(0x14134C6E0_g, 5); // set initial connection state to challenging
utils::hook::set<uint32_t>(game::select(0x14134C6E0, 0x14018E574), 5); // set initial connection state to challenging
// intercept command handling
utils::hook::jump(0x14134D146_g, utils::hook::assemble(handle_command_stub));
utils::hook::call(game::select(0x14134D146, 0x14018EED0), utils::hook::assemble(handle_command_stub));
utils::hook::set<uint8_t>(0x14224E90D_g, 0xEB); // don't kick clients without dw handle
utils::hook::set<uint8_t>(game::select(0x14224E90D, 0x1405315F9), 0xEB); // don't kick clients without dw handle
// TODO: Fix that
scheduler::once(create_ip_socket, scheduler::main);

View File

@ -1,20 +1,16 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "steam/steam.hpp"
#include "party.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
#include <utils/concurrency.hpp>
#include <version.hpp>
namespace party
{
namespace
@ -186,17 +182,6 @@ namespace party
connect_host = target;
query_server(target, handle_connect_query_response);
}
std::string get_dvar_string(const char* dvar_name)
{
const auto dvar = game::Dvar_FindVar(dvar_name);
if (!dvar)
{
return {};
}
return game::Dvar_GetString(dvar);
}
}
void query_server(const game::netadr_t& host, query_callback callback)
@ -221,32 +206,8 @@ namespace party
{
void post_unpack() override
{
utils::hook::jump(0x141E19B60_g, 0x141E19BB6_g); // patch steam id validity check
utils::hook::jump(0x141EE6030_g, connect_stub);
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
{
utils::info_string info{};
info.set("challenge", std::string(data.begin(), data.end()));
info.set("gamename", "T7");
info.set("hostname", get_dvar_string("sv_hostname"));
info.set("gametype", get_dvar_string("g_gametype"));
//info.set("sv_motd", get_dvar_string("sv_motd"));
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", get_dvar_string("mapname"));
//info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1");
//info.set("clients", utils::string::va("%i", get_client_count()));
//info.set("bots", utils::string::va("%i", get_bot_count()));
//info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));
//info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
info.set("shortversion", SHORTVERSION);
network::send(target, "infoResponse", info.build(), '\n');
});
network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data)
{
bool found_query = false;

View File

@ -75,8 +75,8 @@ namespace scheduler
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()),
std::move_iterator(new_tasks.end()));
new_tasks = {};
});
});
@ -167,10 +167,13 @@ namespace scheduler
{
if (!game::is_server())
{
r_end_frame_hook.create(0x142273560_g, r_end_frame_stub); // some func called before R_EndFrame, maybe SND_EndFrame?
g_run_frame_hook.create(0x14065C360_g, server_frame_stub); // GlassSv_Update
main_frame_hook.create(0x1420F9860_g, main_frame_stub); // Com_Frame_Try_Block_Function
r_end_frame_hook.create(0x142273560_g, r_end_frame_stub);
// some func called before R_EndFrame, maybe SND_EndFrame?
}
//g_run_frame_hook.create(0x14065C360_g, server_frame_stub); // GlassSv_Update
main_frame_hook.create(game::select(0x1420F9860, 0x1405020E0), main_frame_stub);
// Com_Frame_Try_Block_Function
}
void pre_destroy() override

View File

@ -9,7 +9,7 @@ namespace game
{
const utils::nt::library& get_host_library()
{
static auto host_library = []
static const auto host_library = []
{
utils::nt::library host{};
if (!host || host == utils::nt::library::get_by_address(get_base))
@ -26,13 +26,13 @@ namespace game
size_t get_base()
{
static auto base = reinterpret_cast<size_t>(get_host_library().get_ptr());
static const auto base = reinterpret_cast<size_t>(get_host_library().get_ptr());
return base;
}
bool is_server()
{
static auto server = get_host_library().get_optional_header()->CheckSum == 0x14C28B4;
static const auto server = get_host_library().get_optional_header()->CheckSum == 0x14C28B4;
return server;
}
}

View File

@ -1,7 +1,5 @@
#pragma once
#include "structs.hpp"
namespace game
{
size_t get_base();
@ -9,12 +7,16 @@ namespace game
inline size_t relocate(const size_t val)
{
if (!val) return 0;
const auto base = get_base();
return base + (val - 0x140000000);
}
inline size_t derelocate(const size_t val)
{
if (!val) return 0;
const auto base = get_base();
return (val - base) + 0x140000000;
}
@ -24,6 +26,16 @@ namespace game
return derelocate(reinterpret_cast<size_t>(val));
}
inline size_t select(const size_t client_val, const size_t server_val)
{
return relocate(is_server() ? server_val : client_val);
}
inline size_t select(const void* client_val, const void* server_val)
{
return select(reinterpret_cast<size_t>(client_val), reinterpret_cast<size_t>(server_val));
}
template <typename T>
class symbol
{
@ -33,9 +45,15 @@ namespace game
{
}
symbol(const size_t address, const size_t server_address)
: address_(address)
, server_address_(server_address)
{
}
T* get() const
{
return reinterpret_cast<T*>(relocate(this->address_));
return reinterpret_cast<T*>(select(this->address_, this->server_address_));
}
operator T*() const
@ -49,7 +67,8 @@ namespace game
}
private:
size_t address_;
size_t address_{};
size_t server_address_{};
};
}
@ -58,4 +77,5 @@ inline size_t operator"" _g(const size_t val)
return game::relocate(val);
}
#include "structs.hpp"
#include "symbols.hpp"

View File

@ -828,7 +828,7 @@ namespace game
JOIN_RESPONSE_COUNT = 0x1E,
};
typedef void (*joinCompleteCallback)(const int, JoinResult);
typedef void (*joinCompleteCallback)(int, JoinResult);
struct AgreementStatus
{

View File

@ -6,6 +6,6 @@ namespace game
{
eModes Com_SessionMode_GetMode()
{
return eModes(*reinterpret_cast<uint32_t*>(0x1568EF7F4_g) << 28 >> 28);
return eModes(*reinterpret_cast<uint32_t*>(game::select(0x1568EF7F4, 0x14948DB04)) << 28 >> 28);
}
}

View File

@ -40,12 +40,11 @@ namespace game
WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C410};
// MSG
WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155EB0};
WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155EB0, 0x14050D1B0};
// NET
WEAK symbol<bool(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendPacket{0x142332F70};
WEAK symbol<bool(char const*, netadr_t*)> NET_StringToAdr{0x1421731E0};
WEAK symbol<bool(netadr_t*, char const*)> NetAdr_InitFromString{0x142332F70};
WEAK symbol<bool(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendPacket{0x142332F70, 0x140596E40};
WEAK symbol<bool(char const*, netadr_t*)> NET_StringToAdr{0x1421731E0, 0x140515110};
// Sys
WEAK symbol<int()> Sys_Milliseconds{0x142333430};
@ -53,11 +52,11 @@ namespace game
WEAK symbol<TLSData*()> Sys_GetTLS{0x142184210};
// Dvar
WEAK symbol<const dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BD730};
WEAK symbol<const dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BD730, 0x140575540};
WEAK symbol<unsigned int(const char* str)> Dvar_GenerateHash{0x14133DBF0};
WEAK symbol<dvar_t*(unsigned int hash)> Dvar_FindMalleableVar{0x1422BD6A0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetDebugName{0x1422BDCB0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BFFF0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BFFF0, 0x140575E30};
WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{
0x1422C7F60
};
@ -76,7 +75,7 @@ namespace game
WEAK symbol<cmd_function_s> cmd_functions{0x15689FF58};
WEAK symbol<CmdArgs> sv_cmd_args{0x15689CE30};
WEAK symbol<SOCKET> ip_socket{0x157E77818};
WEAK symbol<SOCKET> ip_socket{0x157E77818, 0x14A640988};
WEAK symbol<Join> s_join{0x15574C640};

View File

@ -7,9 +7,12 @@
#include <utils/hook.hpp>
#include <utils/nt.hpp>
#include <utils/io.hpp>
#include <utils/flags.hpp>
#include <steam/steam.hpp>
#include "game/game.hpp"
namespace
{
volatile bool g_call_tls_callbacks = false;
@ -55,6 +58,12 @@ namespace
return steam::SteamAPI_RestartAppIfNecessary();
}
BOOL set_process_dpi_aware_stub()
{
component_loader::post_unpack();
return SetProcessDPIAware();
}
void patch_imports()
{
patch_steam_import("SteamAPI_RegisterCallback", steam::SteamAPI_RegisterCallback);
@ -74,7 +83,9 @@ namespace
//patch_steam_import("SteamAPI_Shutdown", steam::SteamAPI_Shutdown);
g_original_import = patch_steam_import("SteamAPI_RestartAppIfNecessary", restart_app_if_necessary_stub);
utils::hook::set(utils::nt::library{}.get_iat_entry("kernel32.dll", "ExitProcess"), exit_hook);
const utils::nt::library game{};
utils::hook::set(game.get_iat_entry("kernel32.dll", "ExitProcess"), exit_hook);
utils::hook::set(game.get_iat_entry("user32.dll", "SetProcessDPIAware"), set_process_dpi_aware_stub);
}
void remove_crash_file()
@ -163,7 +174,6 @@ namespace
{
if (handle_process_runner())
{
TerminateProcess(GetCurrentProcess(), 0);
return 0;
}
@ -186,7 +196,7 @@ namespace
const auto client_binary = "BlackOps3.exe"s;
const auto server_binary = "BlackOps3_UnrankedDedicatedServer.exe"s;
const auto has_server = utils::io::file_exists(server_binary);
const auto has_server = !utils::io::file_exists(client_binary) || utils::flags::has_flag("dedicated");
if (!component_loader::activate(has_server))
{
@ -199,6 +209,11 @@ namespace
throw std::runtime_error("Unable to load binary into memory");
}
if (has_server != game::is_server())
{
throw std::runtime_error("Bad binary loaded into memory");
}
patch_imports();
if (!component_loader::post_load())