Merge pull request #456 from momo5502/feature/auth-protocol

Distribute profile infos to connected clients
This commit is contained in:
Maurice Heumann 2023-04-07 14:47:23 +02:00 committed by GitHub
commit 48b5ad815f
20 changed files with 636 additions and 87 deletions

View File

@ -2,13 +2,18 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "auth.hpp" #include "auth.hpp"
#include "command.hpp"
#include "network.hpp"
#include "profile_infos.hpp"
#include <game/game.hpp> #include <game/game.hpp>
#include <game/utils.hpp>
#include <utils/nt.hpp> #include <utils/nt.hpp>
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/smbios.hpp> #include <utils/smbios.hpp>
#include <utils/byte_buffer.hpp>
#include <utils/info_string.hpp> #include <utils/info_string.hpp>
#include <utils/cryptography.hpp> #include <utils/cryptography.hpp>
@ -92,16 +97,59 @@ namespace auth
return !is_first; return !is_first;
} }
int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, const int len) std::string serialize_connect_data(const char* data, const int length)
{ {
/*const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0; 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();
}
int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, int len)
{
std::string buffer{};
const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0;
if (is_connect_sequence) if (is_connect_sequence)
{ {
MessageBoxA(0, "CONNECT", 0, 0); buffer.append("connect");
}*/ buffer.push_back(' ');
buffer.append(serialize_connect_data(data, len));
data = buffer.data();
len = static_cast<int>(buffer.size());
}
return reinterpret_cast<decltype(&send_connect_data_stub)>(0x142173600_g)(sock, adr, data, len); return reinterpret_cast<decltype(&send_connect_data_stub)>(0x142173600_g)(sock, adr, data, len);
} }
void handle_connect_packet(const game::netadr_t& target, const network::data_view& data)
{
if (!game::is_server_running())
{
return;
}
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 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);
}
} }
uint64_t get_guid() uint64_t get_guid()
@ -123,6 +171,11 @@ namespace auth
{ {
void post_unpack() override void post_unpack() override
{ {
// Skip connect handler
//utils::hook::set<uint8_t>(game::select(0x142253EFA, 0x14053714A), 0xEB);
//network::on("connect", handle_connect_packet);
(void)&handle_connect_packet;
// Patch steam id bit check // Patch steam id bit check
std::vector<std::pair<size_t, size_t>> patches{}; std::vector<std::pair<size_t, size_t>> patches{};
const auto p = [&patches](const size_t a, const size_t b) const auto p = [&patches](const size_t a, const size_t b)
@ -168,7 +221,8 @@ namespace auth
p(0x141EB5992_g, 0x141EB59D5_g); p(0x141EB5992_g, 0x141EB59D5_g);
p(0x141EB74D2_g, 0x141EB7515_g); // ? p(0x141EB74D2_g, 0x141EB7515_g); // ?
utils::hook::call(0x14134BF7D_g, send_connect_data_stub); //utils::hook::call(0x14134BF7D_g, send_connect_data_stub);
(void)&send_connect_data_stub;
} }
for (const auto& patch : patches) for (const auto& patch : patches)

View File

@ -122,7 +122,7 @@ namespace chat
// Overwrite say command // Overwrite say command
utils::hook::jump(0x14052A6C0_g, +[] utils::hook::jump(0x14052A6C0_g, +[]
{ {
if (!game::get_dvar_bool("sv_running")) if (!game::is_server_running())
{ {
printf("Server is not running\n"); printf("Server is not running\n");
return; return;
@ -138,7 +138,7 @@ namespace chat
// Overwrite tell command // Overwrite tell command
utils::hook::jump(0x14052A7E0_g, +[] utils::hook::jump(0x14052A7E0_g, +[]
{ {
if (!game::get_dvar_bool("sv_running")) if (!game::is_server_running())
{ {
printf("Server is not running\n"); printf("Server is not running\n");
return; return;

View File

@ -7,6 +7,7 @@
#include <utils/memory.hpp> #include <utils/memory.hpp>
#include <game/game.hpp> #include <game/game.hpp>
#include <steam/steam.hpp>
namespace command namespace command
{ {
@ -66,15 +67,6 @@ namespace command
} }
} }
struct component final : generic_component
{
void post_unpack() override
{
// Disable whitelist
utils::hook::jump(game::select(0x1420EE860, 0x1404F9CD0), update_whitelist_stub);
}
};
params::params() params::params()
: nesting_(get_cmd_args()->nesting) : nesting_(get_cmd_args()->nesting)
{ {
@ -227,6 +219,15 @@ namespace command
game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command, game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command,
allocator.allocate<game::cmd_function_s>()); allocator.allocate<game::cmd_function_s>());
} }
struct component final : generic_component
{
void post_unpack() override
{
// Disable whitelist
utils::hook::jump(game::select(0x1420EE860, 0x1404F9CD0), update_whitelist_stub);
}
};
} }
REGISTER_COMPONENT(command::component) REGISTER_COMPONENT(command::component)

View File

@ -27,11 +27,11 @@ namespace dedicated_info
const auto mapname = game::get_dvar_string("mapname"); const auto mapname = game::get_dvar_string("mapname");
const std::string window_text = utils::string::va("%s on %s [%d/%d] (%d)", const std::string window_text = utils::string::va("%s on %s [%zu/%zu] (%zu)",
clean_server_name, clean_server_name,
mapname.data(), mapname.data(),
getinfo::get_client_count(), getinfo::get_client_count(),
getinfo::get_max_client_count(), game::get_max_client_count(),
getinfo::get_bot_count()); getinfo::get_bot_count());
console::set_title(window_text); console::set_title(window_text);

View File

@ -36,22 +36,22 @@ namespace dedicated_patches
{ {
const std::vector<uintptr_t> is_mod_loaded_addresses = const std::vector<uintptr_t> is_mod_loaded_addresses =
{ {
{ 0x14019CFC4_g }, {0x14019CFC4_g},
{ 0x14024D4A0_g }, {0x14024D4A0_g},
{ 0x14024D669_g }, {0x14024D669_g},
{ 0x14024D939_g }, {0x14024D939_g},
{ 0x14024DC64_g }, {0x14024DC64_g},
{ 0x14024E13A_g }, {0x14024E13A_g},
{ 0x14024E5A3_g }, {0x14024E5A3_g},
{ 0x14024FFB9_g }, {0x14024FFB9_g},
{ 0x140251E9E_g }, {0x140251E9E_g},
{ 0x140253680_g }, {0x140253680_g},
{ 0x140257BF6_g }, {0x140257BF6_g},
{ 0x1402D296D_g }, {0x1402D296D_g},
{ 0x1402D58E9_g }, {0x1402D58E9_g},
{ 0x140468374_g }, {0x140468374_g},
{ 0x14046B796_g }, {0x14046B796_g},
{ 0x14048003D_g }, {0x14048003D_g},
}; };
for (const auto& address : is_mod_loaded_addresses) for (const auto& address : is_mod_loaded_addresses)
@ -68,15 +68,20 @@ namespace dedicated_patches
spawn_server_hook.invoke(controllerIndex, server, preload, savegame); spawn_server_hook.invoke(controllerIndex, server, preload, savegame);
} }
uint64_t sv_get_player_xuid_stub(int client_num) uint64_t sv_get_player_xuid_stub(const int client_num)
{ {
return static_cast<uint64_t>((*game::svs_clients)[client_num].xuid); const auto* clients = *game::svs_clients;
if (!clients)
{
return 0;
}
return clients[client_num].xuid;
} }
} }
struct component final : server_component struct component final : server_component
{ {
static_assert(offsetof(game::client_s, xuid) == 0xBB354);
void post_unpack() override void post_unpack() override
{ {

View File

@ -21,21 +21,18 @@ namespace getinfo
return game::get_dvar_int("com_maxclients"); return game::get_dvar_int("com_maxclients");
} }
int get_client_count() template <typename T>
int get_client_count(T* client_states)
{ {
int count = 0;
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576F9318, 0x14A178E98));
if (!client_states) if (!client_states)
{ {
return 0; return 0;
} }
const auto object_length = game::is_server() ? 0xE5110 : 0xE5170; int count = 0;
for (int i = 0; i < get_max_client_count(); ++i) for (int i = 0; i < get_max_client_count(); ++i)
{ {
const auto client_state = *reinterpret_cast<int*>(client_states + (i * object_length)); if (client_states[i].client_state > 0)
if (client_state > 0)
{ {
++count; ++count;
} }
@ -44,23 +41,28 @@ namespace getinfo
return count; return count;
} }
int get_bot_count() size_t get_client_count()
{ {
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576F9318, 0x14A178E98)); size_t count = 0;
if (!client_states) game::foreach_connected_client([&count](const game::client_s&)
{ {
return 0; ++count;
} });
int count = 0; return count;
}
for (int i = 0; i < get_max_client_count(); ++i) size_t get_bot_count()
{
size_t count = 0;
game::foreach_connected_client([&count](const game::client_s&, const size_t index)
{ {
if (game::SV_IsTestClient(i)) if (game::SV_IsTestClient(static_cast<int>(index)))
{ {
++count; ++count;
} }
} });
return count; return count;
} }
@ -103,13 +105,13 @@ namespace getinfo
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits)); info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", game::get_dvar_string("mapname")); info.set("mapname", game::get_dvar_string("mapname"));
info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1"); info.set("isPrivate", game::get_dvar_string("g_password").empty() ? "0" : "1");
info.set("clients", utils::string::va("%i", get_client_count())); info.set("clients", utils::string::va("%zu", get_client_count()));
info.set("bots", utils::string::va("%i", get_bot_count())); info.set("bots", utils::string::va("%zu", get_bot_count()));
info.set("sv_maxclients", utils::string::va("%i", get_max_client_count())); info.set("sv_maxclients", utils::string::va("%zu", get_max_client_count()));
info.set("protocol", utils::string::va("%i", PROTOCOL)); info.set("protocol", utils::string::va("%i", PROTOCOL));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode()));
info.set("sv_running", utils::string::va("%i", game::get_dvar_bool("sv_running"))); info.set("sv_running", utils::string::va("%i", game::is_server_running()));
info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0)); info.set("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0));
info.set("shortversion", SHORTVERSION); info.set("shortversion", SHORTVERSION);

View File

@ -2,8 +2,7 @@
namespace getinfo namespace getinfo
{ {
int get_max_client_count(); size_t get_client_count();
int get_client_count(); size_t get_bot_count();
int get_bot_count();
bool is_host(); bool is_host();
} }

View File

@ -35,7 +35,18 @@ namespace network
const std::basic_string_view data(message->data + offset, message->cursize - offset); const std::basic_string_view data(message->data + offset, message->cursize - offset);
handler->second(*address, data); try
{
handler->second(*address, data);
}
catch (const std::exception& e)
{
printf("Error: %s\n", e.what());
}
catch (...)
{
}
return 0; return 0;
} }

View File

@ -6,6 +6,7 @@
#include "network.hpp" #include "network.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "workshop.hpp" #include "workshop.hpp"
#include "profile_infos.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -36,7 +37,7 @@ namespace party
} }
void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode, void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode,
const std::string& pub_id) const std::string& pub_id)
{ {
workshop::load_usermap_mod_if_needed(pub_id); workshop::load_usermap_mod_if_needed(pub_id);
@ -55,7 +56,8 @@ namespace party
} }
void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname, void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname,
const std::string& gametype, const std::string& pub_id, const bool was_retried = false) const std::string& gametype, const std::string& pub_id,
const bool was_retried = false)
{ {
if (game::Com_SessionMode_IsMode(mode)) if (game::Com_SessionMode_IsMode(mode))
{ {
@ -144,6 +146,13 @@ namespace party
is_connecting_to_dedi = info.get("dedicated") == "1"; is_connecting_to_dedi = info.get("dedicated") == "1";
if (atoi(info.get("protocol").data()) != PROTOCOL)
{
const auto str = "Invalid protocol.";
printf("%s\n", str);
return;
}
const auto gamename = info.get("gamename"); const auto gamename = info.get("gamename");
if (gamename != "T7"s) if (gamename != "T7"s)
{ {
@ -203,6 +212,7 @@ namespace party
connect_host = target; connect_host = target;
} }
profile_infos::clear_profile_infos();
query_server(connect_host, handle_connect_query_response); query_server(connect_host, handle_connect_query_response);
} }
@ -306,6 +316,11 @@ namespace party
return *reinterpret_cast<game::netadr_t*>(address); return *reinterpret_cast<game::netadr_t*>(address);
} }
bool is_host(const game::netadr_t& addr)
{
return get_connected_server() == addr || connect_host == addr;
}
struct component final : client_component struct component final : client_component
{ {
void post_unpack() override void post_unpack() override

View File

@ -9,4 +9,6 @@ namespace party
void query_server(const game::netadr_t& host, query_callback callback); void query_server(const game::netadr_t& host, query_callback callback);
game::netadr_t get_connected_server(); game::netadr_t get_connected_server();
bool is_host(const game::netadr_t& addr);
} }

View File

@ -3,6 +3,8 @@
#include "profile_infos.hpp" #include "profile_infos.hpp"
#include "network.hpp" #include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
#include <utils/nt.hpp> #include <utils/nt.hpp>
#include <utils/properties.hpp> #include <utils/properties.hpp>
@ -11,12 +13,14 @@
#include "../steam/steam.hpp" #include "../steam/steam.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
#include "game/utils.hpp"
namespace profile_infos namespace profile_infos
{ {
namespace namespace
{ {
using profile_map = std::unordered_map<uint64_t, profile_info>; using profile_map = std::unordered_map<uint64_t, profile_info>;
utils::concurrency::container<profile_map> profile_mapping; utils::concurrency::container<profile_map> profile_mapping{};
std::optional<profile_info> load_profile_info() std::optional<profile_info> load_profile_info()
{ {
@ -29,7 +33,7 @@ namespace profile_infos
profile_info info{}; profile_info info{};
constexpr auto version_size = sizeof(info.version); constexpr auto version_size = sizeof(info.version);
if(data.size() < sizeof(version_size)) if (data.size() < sizeof(version_size))
{ {
return {}; return {};
} }
@ -37,15 +41,171 @@ namespace profile_infos
memcpy(&info.version, data.data(), version_size); memcpy(&info.version, data.data(), version_size);
info.ddl.assign(data.begin() + version_size, data.end()); info.ddl.assign(data.begin() + version_size, data.end());
return { std::move(info) }; return {std::move(info)};
}
void send_profile_info(const game::netadr_t& address, const std::string& buffer)
{
network::send(address, "profileInfo", buffer);
}
void distribute_profile_info(const uint64_t user_id, const profile_info& info)
{
if (user_id == steam::SteamUser()->GetSteamID().bits)
{
return;
}
utils::byte_buffer buffer{};
buffer.write(user_id);
info.serialize(buffer);
const std::string data = buffer.move_buffer();
game::foreach_connected_client([&](const game::client_s& client)
{
send_profile_info(client.address, data);
});
}
void schedule_pcache_update()
{
static std::atomic_bool update_triggered{false};
if (game::is_server() || update_triggered.exchange(true))
{
return;
}
scheduler::once([]
{
game::PCache_DeleteEntries(game::CONTROLLER_INDEX_FIRST);
update_triggered = false;
}, scheduler::main, 5s);
}
std::unordered_set<uint64_t> get_connected_client_xuids()
{
std::unordered_set<uint64_t> connected_clients{};
connected_clients.reserve(game::get_max_client_count());
game::foreach_connected_client([&](const game::client_s& client)
{
connected_clients.emplace(client.xuid);
});
return connected_clients;
}
void clean_cached_profile_infos()
{
if (!game::is_server_running())
{
return;
}
const auto xuids = get_connected_client_xuids();
profile_mapping.access([&](profile_map& profiles)
{
for (auto i = profiles.begin(); i != profiles.end();)
{
if (xuids.contains(i->first))
{
++i;
}
else
{
i = profiles.erase(i);
}
}
});
} }
} }
std::optional<profile_info> get_profile_info(uint64_t user_id) profile_info::profile_info(utils::byte_buffer& buffer)
{
this->version = buffer.read<int32_t>();
this->ddl = buffer.read_string();
}
void profile_info::serialize(utils::byte_buffer& buffer) const
{
buffer.write(this->version);
buffer.write_string(this->ddl);
}
void add_profile_info(const uint64_t user_id, const profile_info& info)
{ {
if (user_id == steam::SteamUser()->GetSteamID().bits) if (user_id == steam::SteamUser()->GetSteamID().bits)
{ {
return load_profile_info(); return;
}
profile_mapping.access([&](profile_map& profiles)
{
profiles[user_id] = info;
});
schedule_pcache_update();
}
void distribute_profile_info_to_user(const game::netadr_t& addr, const uint64_t user_id, const profile_info& info)
{
utils::byte_buffer buffer{};
buffer.write(user_id);
info.serialize(buffer);
send_profile_info(addr, buffer.get_buffer());
}
void distribute_profile_infos_to_user(const game::netadr_t& addr)
{
profile_mapping.access([&](const profile_map& profiles)
{
for (const auto& entry : profiles)
{
distribute_profile_info_to_user(addr, entry.first, entry.second);
}
});
if (!game::is_server())
{
const auto info = get_profile_info();
if (info)
{
distribute_profile_info_to_user(addr, steam::SteamUser()->GetSteamID().bits, *info);
}
}
}
void add_and_distribute_profile_info(const game::netadr_t& addr, const uint64_t user_id, const profile_info& info)
{
distribute_profile_infos_to_user(addr);
add_profile_info(user_id, info);
distribute_profile_info(user_id, info);
}
void clear_profile_infos()
{
profile_mapping.access([&](profile_map& profiles)
{
profiles = {};
});
}
std::optional<profile_info> get_profile_info()
{
return load_profile_info();
}
std::optional<profile_info> get_profile_info(const uint64_t user_id)
{
printf("Requesting profile info: %llX\n", user_id);
if (user_id == steam::SteamUser()->GetSteamID().bits)
{
return get_profile_info();
} }
return profile_mapping.access<std::optional<profile_info>>([user_id](const profile_map& profiles) return profile_mapping.access<std::optional<profile_info>>([user_id](const profile_map& profiles)
@ -75,15 +235,26 @@ namespace profile_infos
struct component final : generic_component struct component final : generic_component
{ {
void post_load() override
{
}
void post_unpack() override void post_unpack() override
{ {
/*network::on("profileInfo", [](const game::netadr_t& server, const network::data_view& data) scheduler::loop(clean_cached_profile_infos, scheduler::main, 5s);
if (game::is_client())
{ {
});*/ network::on("profileInfo", [](const game::netadr_t& server, const network::data_view& data)
{
if (!party::is_host(server))
{
return;
}
utils::byte_buffer buffer(data);
const auto user_id = buffer.read<uint64_t>();
const profile_info info(buffer);
add_profile_info(user_id, info);
});
}
} }
}; };
} }

View File

@ -1,13 +1,26 @@
#pragma once #pragma once
#include <game/game.hpp>
#include <utils/byte_buffer.hpp>
namespace profile_infos namespace profile_infos
{ {
struct profile_info struct profile_info
{ {
int32_t version; int32_t version{3};
std::string ddl; std::string ddl{};
profile_info() = default;
profile_info(utils::byte_buffer& buffer);
void serialize(utils::byte_buffer& buffer) const;
}; };
void add_profile_info(uint64_t user_id, const profile_info& info);
void add_and_distribute_profile_info(const game::netadr_t& addr, uint64_t user_id, const profile_info& info);
void clear_profile_infos();
std::optional<profile_info> get_profile_info();
std::optional<profile_info> get_profile_info(uint64_t user_id); std::optional<profile_info> get_profile_info(uint64_t user_id);
void update_profile_info(const profile_info& info); void update_profile_info(const profile_info& info);
} }

View File

@ -1544,14 +1544,31 @@ namespace game
struct client_s struct client_s
{ {
char __pad0[0xBB354]; int client_state;
int xuid; char __pad0[0x28];
char __pad1[0x8]; netadr_t address;
char __pad1[0x5588];
uint64_t xuid;
char __pad2[0xB5D84];
int guid;
char __pad3[0x8];
bool bIsTestClient; bool bIsTestClient;
char __pad2[0x29DAC]; char __pad4[0x29DAC];
}; };
static_assert(sizeof(client_s) == 0xE5110); static_assert(sizeof(client_s) <= 0xE5110);
static_assert(offsetof(game::client_s, address) == 0x2C);
static_assert(offsetof(game::client_s, xuid) == 0x55C8);
static_assert(offsetof(game::client_s, guid) == 0xBB354);
static_assert(offsetof(game::client_s, bIsTestClient) == 0xBB360);
struct client_s_cl : client_s
{
char __pad1_0[0x60];
};
static_assert(sizeof(client_s_cl) == 0xE5170);
enum scriptInstance_t enum scriptInstance_t
{ {

View File

@ -144,7 +144,7 @@ namespace game
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0}; WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0};
// Scr // Scr
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x1412E9870, 0x14016F160}; WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x14016F160};
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320}; WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320};
WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490}; WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490};
WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{ WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{
@ -162,6 +162,9 @@ namespace game
0x141CD98D0 0x141CD98D0
}; };
// PCache
WEAK symbol<void(ControllerIndex_t controllerIndex)> PCache_DeleteEntries{0x141E8D710};
// SV // SV
WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460}; WEAK symbol<bool()> SV_Loaded{0x142252250, 0x140535460};
WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0}; WEAK symbol<void*()> SV_AddTestClient{0x142248F40, 0x14052E3E0};
@ -195,6 +198,8 @@ namespace game
WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620}; WEAK symbol<char> s_dvarPool{0x157AC6220, 0x14A3CB620};
WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC}; WEAK symbol<int> g_dvarCount{0x157AC61CC, 0x14A3CB5FC};
// Client and dedi struct size differs :(
WEAK symbol<client_s_cl*> svs_clients_cl{0x1576F9318, 0};
WEAK symbol<client_s*> svs_clients{0x0, 0x14A178E98}; WEAK symbol<client_s*> svs_clients{0x0, 0x14A178E98};
// Dvar variables // Dvar variables

View File

@ -45,7 +45,8 @@ namespace game
return dvar->current.value.enabled; return dvar->current.value.enabled;
} }
const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, const bool value, const int flags, const char* description, const eModes mode) const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, const bool value, const int flags,
const char* description, const eModes mode)
{ {
const auto hash = Dvar_GenerateHash(dvar_name); const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_SessionModeRegisterBool(hash, dvar_name, value, flags, description); auto* registered_dvar = Dvar_SessionModeRegisterBool(hash, dvar_name, value, flags, description);
@ -83,7 +84,8 @@ namespace game
return registered_dvar; return registered_dvar;
} }
const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags, const char* description) const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags,
const char* description)
{ {
const auto hash = Dvar_GenerateHash(dvar_name); const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_RegisterFloat(hash, dvar_name, value, min, max, flags, description); auto* registered_dvar = Dvar_RegisterFloat(hash, dvar_name, value, min, max, flags, description);
@ -96,7 +98,8 @@ namespace game
return registered_dvar; return registered_dvar;
} }
const dvar_t* register_dvar_string(const char* dvar_name, const char* value, const int flags, const char* description) const dvar_t* register_dvar_string(const char* dvar_name, const char* value, const int flags,
const char* description)
{ {
const auto hash = Dvar_GenerateHash(dvar_name); const auto hash = Dvar_GenerateHash(dvar_name);
auto* registered_dvar = Dvar_RegisterString(hash, dvar_name, value, flags, description); auto* registered_dvar = Dvar_RegisterString(hash, dvar_name, value, flags, description);
@ -148,4 +151,67 @@ namespace game
dvar_to_change->flags = flags; dvar_to_change->flags = flags;
} }
bool is_server_running()
{
return get_dvar_bool("sv_running");
}
size_t get_max_client_count()
{
return static_cast<size_t>(get_dvar_int("com_maxclients"));
}
template <typename T>
static void foreach_client(T* client_states, const std::function<void(client_s&, size_t index)>& callback)
{
if (!client_states || !callback)
{
return;
}
for (size_t i = 0; i < get_max_client_count(); ++i)
{
callback(client_states[i], i);
}
}
void foreach_client(const std::function<void(client_s&, size_t index)>& callback)
{
if (is_server())
{
foreach_client(*svs_clients, callback);
}
else
{
foreach_client(*svs_clients_cl, callback);
}
}
void foreach_client(const std::function<void(client_s&)>& callback)
{
foreach_client([&](client_s& client, size_t)
{
callback(client);
});
}
void foreach_connected_client(const std::function<void(client_s&, size_t index)>& callback)
{
foreach_client([&](client_s& client, const size_t index)
{
if (client.client_state > 0)
{
callback(client, index);
}
});
}
void foreach_connected_client(const std::function<void(client_s&)>& callback)
{
foreach_connected_client([&](client_s& client, size_t)
{
callback(client);
});
}
} }

View File

@ -12,6 +12,16 @@ namespace game
const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags, const char* description); const dvar_t* register_dvar_float(const char* dvar_name, float value, float min, float max, const int flags, const char* description);
const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, bool value, int flags, const char* description, eModes mode = MODE_COUNT); const dvar_t* register_sessionmode_dvar_bool(const char* dvar_name, bool value, int flags, const char* description, eModes mode = MODE_COUNT);
const dvar_t* register_dvar_string(const char* dvar_name, const char* value, int flags, const char* description); const dvar_t* register_dvar_string(const char* dvar_name, const char* value, int flags, const char* description);
void dvar_add_flags(const char* dvar, dvarFlags_e flags); void dvar_add_flags(const char* dvar, dvarFlags_e flags);
void dvar_set_flags(const char* dvar_name, dvarFlags_e flags); void dvar_set_flags(const char* dvar_name, dvarFlags_e flags);
bool is_server_running();
size_t get_max_client_count();
void foreach_client(const std::function<void(client_s&, size_t index)>& callback);
void foreach_client(const std::function<void(client_s&)>& callback);
void foreach_connected_client(const std::function<void(client_s&, size_t index)>& callback);
void foreach_connected_client(const std::function<void(client_s&)>& callback);
} }

View File

@ -0,0 +1,53 @@
#include "byte_buffer.hpp"
#include <cstring>
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<const char*>(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::vector<uint8_t> byte_buffer::read_data(const size_t length)
{
std::vector<uint8_t> result{};
result.resize(length);
this->read(result.data(), result.size());
return result;
}
}

View File

@ -0,0 +1,119 @@
#pragma once
#include <string>
#include <vector>
#include <stdexcept>
namespace utils
{
class byte_buffer
{
public:
byte_buffer();
byte_buffer(std::string buffer);
template <typename T>
byte_buffer(const std::basic_string_view<T>& buffer)
: byte_buffer(std::string(reinterpret_cast<const char*>(buffer.data()), buffer.size() * sizeof(T)))
{
}
void write(const void* buffer, size_t length);
void write_string(const char* str, const size_t length)
{
this->write<uint32_t>(static_cast<uint32_t>(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 <typename T>
void write(const T& object)
{
this->write(&object, sizeof(object));
}
template <typename T>
void write(const std::vector<T>& vec)
{
this->write(vec.data(), vec.size() * sizeof(T));
}
template <typename T>
void write_vector(const std::vector<T>& vec)
{
this->write(static_cast<uint32_t>(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 <typename T>
T read()
{
T object{};
this->read(&object, sizeof(object));
return object;
}
template <typename T>
std::vector<T> read_vector()
{
std::vector<T> result{};
const auto size = this->read<uint32_t>();
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<uint32_t>();
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;
}
std::vector<uint8_t> read_data(size_t length);
private:
bool writing_{false};
size_t offset_{0};
std::string buffer_{};
};
}

View File

@ -8,6 +8,11 @@ namespace utils
this->parse(buffer); this->parse(buffer);
} }
info_string::info_string(const char* buffer)
: info_string(std::string{buffer})
{
}
info_string::info_string(const std::string_view& buffer) info_string::info_string(const std::string_view& buffer)
: info_string(std::string{buffer}) : info_string(std::string{buffer})
{ {

View File

@ -9,8 +9,9 @@ namespace utils
{ {
public: public:
info_string() = default; info_string() = default;
info_string(const std::string& buffer); explicit info_string(const std::string& buffer);
info_string(const std::string_view& buffer); explicit info_string(const char* buffer);
explicit info_string(const std::string_view& buffer);
info_string(const std::basic_string_view<uint8_t>& buffer); info_string(const std::basic_string_view<uint8_t>& buffer);
void set(const std::string& key, const std::string& value); void set(const std::string& key, const std::string& value);