This commit is contained in:
2024-01-24 10:45:25 +01:00
commit bcdbe48523
267 changed files with 39510 additions and 0 deletions

View File

@ -0,0 +1,165 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
namespace arxan
{
namespace
{
utils::hook::detour nt_close_hook;
utils::hook::detour nt_query_information_process_hook;
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
const PVOID info,
const ULONG info_length, const PULONG ret_length)
{
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.
get_original());
const auto status = orig(handle, info_class, info, info_length, ret_length);
if (NT_SUCCESS(status))
{
if (info_class == ProcessBasicInformation)
{
static DWORD explorer_pid = 0;
if (!explorer_pid)
{
auto* const shell_window = GetShellWindow();
GetWindowThreadProcessId(shell_window, &explorer_pid);
}
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
}
else if (info_class == 30) // ProcessDebugObjectHandle
{
*static_cast<HANDLE*>(info) = nullptr;
return 0xC0000353;
}
else if (info_class == 7) // ProcessDebugPort
{
*static_cast<HANDLE*>(info) = nullptr;
}
else if (info_class == 31)
{
*static_cast<ULONG*>(info) = 1;
}
}
return status;
}
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
{
char info[16];
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0)
{
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
return orig(handle);
}
return STATUS_INVALID_HANDLE;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
{
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void hide_being_debugged()
{
auto* const peb = PPEB(__readgsqword(0x60));
peb->BeingDebugged = false;
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
}
void remove_hardware_breakpoints()
{
CONTEXT context;
ZeroMemory(&context, sizeof(context));
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
auto* const thread = GetCurrentThread();
GetThreadContext(thread, &context);
context.Dr0 = 0;
context.Dr1 = 0;
context.Dr2 = 0;
context.Dr3 = 0;
context.Dr6 = 0;
context.Dr7 = 0;
SetThreadContext(thread, &context);
}
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
{
if (!game::environment::is_sp()
&& game::dwGetLogOnStatus(0) == game::DW_LIVE_CONNECTED
&& context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
{
return TRUE;
}
return SetThreadContext(thread, context);
}
}
class component final : public component_interface
{
public:
void* load_import(const std::string& library, const std::string& function) override
{
if (function == "SetThreadContext")
{
return set_thread_context_stub;
}
return nullptr;
}
void post_load() override
{
hide_being_debugged();
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
const utils::nt::library ntdll("ntdll.dll");
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
nt_query_information_process_hook.create(ntdll.get_proc<void*>("NtQueryInformationProcess"),
nt_query_information_process_stub);
AddVectoredExceptionHandler(1, exception_filter);
}
void post_unpack() override
{
if (game::environment::is_sp()) return;
utils::hook::jump(0x1404FE1E0, 0x1404FE2D0); // idk
utils::hook::jump(0x140558C20, 0x140558CB0); // dwNetPump
utils::hook::jump(0x140591850, 0x1405918E0); // dwLobbyPump
utils::hook::jump(0x140589480, 0x140589490); // dwGetLogonStatus
// Fix arxan crashes
// Are these opaque predicates?
utils::hook::nop(0x14AE2B384, 6); // 0000000140035EA7
utils::hook::nop(0x14A31E98E, 4); // 000000014B1A892E
utils::hook::nop(0x14A920E10, 4); // 000000014AEF4F39
utils::hook::nop(0x14A1A2425, 4); // 000000014A0B52A8
utils::hook::nop(0x14AE07CEA, 4); // 000000014A143BFF
scheduler::on_game_initialized(remove_hardware_breakpoints, scheduler::pipeline::main);
}
};
}
REGISTER_COMPONENT(arxan::component)

View File

@ -0,0 +1,228 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "steam/steam.hpp"
#include "auth.hpp"
#include "command.hpp"
#include "console.hpp"
#include "network.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/smbios.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
namespace auth
{
namespace
{
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 = "AlterWare-IW6-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;
}
int send_connect_data_stub(game::netsrc_t sock, game::netadr_s* adr, const char* format, const int len)
{
std::string connect_string(format, len);
game::SV_Cmd_TokenizeString(connect_string.data());
const auto _ = gsl::finally([]()
{
game::SV_Cmd_EndTokenizedString();
});
const command::params_sv params;
if (params.size() < 3)
{
return false;
}
const utils::info_string info_string{std::string{params[2]}};
const auto challenge = info_string.get("challenge");
connect_string.clear();
connect_string.append(params[0]);
connect_string.append(" ");
connect_string.append(params[1]);
connect_string.append(" ");
connect_string.append("\"" + info_string.build() + "\"");
proto::network::connect_info info;
info.set_publickey(get_key().get_public_key());
info.set_signature(sign_message(get_key(), challenge));
info.set_infostring(connect_string);
network::send(*adr, "connect", info.SerializeAsString());
return true;
}
void direct_connect(game::netadr_s* from, game::msg_t* msg)
{
const auto offset = sizeof("connect") + 4;
proto::network::connect_info info;
if (msg->cursize < offset || !info.ParseFromArray(msg->data + offset, msg->cursize - offset))
{
network::send(*from, "error", "Invalid connect data!", '\n');
return;
}
game::SV_Cmd_EndTokenizedString();
game::SV_Cmd_TokenizeString(info.infostring().data());
const command::params_sv params;
if (params.size() < 3)
{
network::send(*from, "error", "Invalid connect string!", '\n');
return;
}
const utils::info_string info_string{std::string{params[2]}};
const auto steam_id = info_string.get("xuid");
const auto challenge = info_string.get("challenge");
if (steam_id.empty() || challenge.empty())
{
network::send(*from, "error", "Invalid connect data!", '\n');
return;
}
utils::cryptography::ecc::key key;
key.set(info.publickey());
const auto xuid = strtoull(steam_id.data(), nullptr, 16);
if (xuid != key.get_hash())
{
network::send(*from, "error", "XUID doesn't match the certificate!", '\n');
return;
}
if (!key.is_valid() || !verify_message(key, challenge, info.signature()))
{
network::send(*from, "error", "Challenge signature was invalid!", '\n');
return;
}
game::SV_DirectConnect(from);
}
void* get_direct_connect_stub()
{
return utils::hook::assemble([](utils::hook::assembler& a)
{
a.lea(rcx, qword_ptr(rsp, 0x20));
a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
a.pushad64();
a.mov(rdx, rsi);
a.call_aligned(direct_connect);
a.popad64();
a.jmp(0x140479757);
});
}
}
uint64_t get_guid()
{
if (game::environment::is_dedi())
{
return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000);
}
return get_key().get_hash();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Patch steam id bit check
if (game::environment::is_sp())
{
utils::hook::jump(0x1404BF3F0, 0x1404BF446);
utils::hook::jump(0x1404C02AF, 0x1404C02F0);
utils::hook::jump(0x1404C07F4, 0x1404C0842);
}
else
{
utils::hook::jump(0x140585410, 0x140585466);
utils::hook::jump(0x140142252, 0x140142293);
utils::hook::jump(0x140142334, 0x140142389);
utils::hook::jump(0x1405864EF, 0x140586530);
utils::hook::jump(0x140586A80, 0x140586AC6);
utils::hook::jump(0x140479636, get_direct_connect_stub(), true);
utils::hook::call(0x1402C4F8E, send_connect_data_stub);
}
command::add("guid", []
{
console::info("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits);
});
}
};
}
REGISTER_COMPONENT(auth::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace auth
{
uint64_t get_guid();
}

View File

@ -0,0 +1,133 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace binding
{
namespace
{
std::vector<std::string> custom_binds = {};
utils::hook::detour cl_execute_key_hook;
int key_write_bindings_to_buffer_stub(int /*localClientNum*/, char* buffer, const int buffer_size)
{
auto bytes_used = 0;
const auto buffer_size_align = static_cast<std::int32_t>(buffer_size) - 4;
for (auto key_index = 0; key_index < 256; ++key_index)
{
const auto* const key_button = game::Key_KeynumToString(key_index, 0, 1);
auto value = game::playerKeys->keys[key_index].binding;
if (value && value < 100)
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
else if (value >= 100)
{
value -= 100;
if (static_cast<size_t>(value) < custom_binds.size() && !custom_binds[value].empty())
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, custom_binds[value].data());
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
}
}
buffer[bytes_used] = 0;
return bytes_used;
}
int get_binding_for_custom_command(const char* command)
{
auto index = 0;
for (auto& bind : custom_binds)
{
if (bind == command)
{
return index;
}
index++;
}
custom_binds.emplace_back(command);
index = static_cast<unsigned int>(custom_binds.size()) - 1;
return index;
}
int key_get_binding_for_cmd_stub(const char* command)
{
// original binds
for (auto i = 0; i <= 100; i++)
{
if (game::command_whitelist[i] && !strcmp(command, game::command_whitelist[i]))
{
return i;
}
}
// custom binds
return 100 + get_binding_for_custom_command(command);
}
void cl_execute_key_stub(const int local_client_num, int key, const int down, const unsigned int time)
{
if (key >= 100)
{
key -= 100;
if (static_cast<size_t>(key) < custom_binds.size() && !custom_binds[key].empty())
{
game::Cbuf_AddText(local_client_num, utils::string::va("%s\n", custom_binds[key].data()));
}
return;
}
cl_execute_key_hook.invoke<void>(local_client_num, key, down, time);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
// write all bindings to config file
utils::hook::call(SELECT_VALUE(0x14023DF2B, 0x1402C465B), key_write_bindings_to_buffer_stub);
// links a custom command to an index
utils::hook::jump(SELECT_VALUE(0x14023D6E0, 0x1402C3E50), key_get_binding_for_cmd_stub);
// execute custom binds
cl_execute_key_hook.create(SELECT_VALUE(0x140239970, 0x1402BF0E0), &cl_execute_key_stub);
}
};
}
REGISTER_COMPONENT(binding::component)

View File

@ -0,0 +1,204 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
#include "server_list.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace bots
{
namespace
{
constexpr std::size_t MAX_NAME_LENGTH = 16;
bool can_add()
{
return party::get_client_count() < *game::mp::svs_clientCount;
}
void bot_team_join(const int entity_num)
{
// schedule the select team call
scheduler::once([entity_num]()
{
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value),
false);
// scheduler the select class call
scheduler::once([entity_num]()
{
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
utils::string::va("lui 5 %i %i", (rand() % 5) + 10,
*game::mp::sv_serverId_value), false);
}, scheduler::pipeline::server, 1s);
}, scheduler::pipeline::server, 1s);
}
void bot_team(const int entity_num)
{
if (game::SV_BotIsBot(game::mp::g_entities[entity_num].s.clientNum))
{
if (game::mp::g_entities[entity_num].client->sess.cs.team == game::mp::team_t::TEAM_SPECTATOR)
{
bot_team_join(entity_num);
}
scheduler::once([entity_num]()
{
bot_team(entity_num);
}, scheduler::pipeline::server, 3s);
}
}
void spawn_bot(const int entity_num)
{
scheduler::once([entity_num]()
{
game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]);
bot_team(entity_num);
}, scheduler::pipeline::server, 1s);
}
void add_bot()
{
if (!can_add())
{
return;
}
auto* bot_name = game::SV_BotGetRandomName();
auto* bot_ent = game::SV_AddBot(bot_name, 26, 62, 0);
if (bot_ent)
{
spawn_bot(bot_ent->s.number);
}
}
utils::hook::detour get_bot_name_hook;
volatile bool bot_names_received = false;
std::vector<std::string> bot_names;
const char* get_random_bot_name()
{
if (bot_names.empty())
{
return get_bot_name_hook.invoke<const char*>();
}
const auto index = std::rand() % bot_names.size();
const auto& name = bot_names.at(index);
return utils::string::va("%.*s", static_cast<int>(name.size()), name.data());
}
bool should_update_bot_names()
{
return !filesystem::exists("bots.txt");
}
void update_bot_names()
{
bot_names_received = false;
game::netadr_s master{};
if (server_list::get_master_server(master))
{
console::info("Getting bots...\n");
network::send(master, "getbots");
}
}
void parse_bot_names_from_file()
{
std::string data;
filesystem::read_file("bots.txt", &data);
if (data.empty())
{
return;
}
auto name_list = utils::string::split(data, '\n');
for (auto& entry : name_list)
{
// Take into account CR line endings
entry = utils::string::replace(entry, "\r", "");
if (entry.empty())
{
continue;
}
entry = entry.substr(0, MAX_NAME_LENGTH - 1);
bot_names.emplace_back(entry);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name);
command::add("spawnBot", [](const command::params& params)
{
if (!game::SV_Loaded()) return;
auto num_bots = 1;
if (params.size() == 2)
{
num_bots = atoi(params.get(1));
}
num_bots = std::min(num_bots, *game::mp::svs_clientCount);
console::info("Spawning %i %s\n", num_bots, (num_bots == 1 ? "bot" : "bots"));
for (auto i = 0; i < num_bots; i++)
{
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
}
});
if (should_update_bot_names())
{
scheduler::on_game_initialized([]()
{
update_bot_names();
scheduler::loop(update_bot_names, scheduler::main, 1h);
}, scheduler::main);
}
else
{
parse_bot_names_from_file();
}
network::on("getbotsResponse", [](const game::netadr_s& target, const std::string& data)
{
game::netadr_s master{};
if (server_list::get_master_server(master) && !bot_names_received && target == master)
{
bot_names = utils::string::split(data, '\n');
bot_names_received = true;
}
});
}
};
}
REGISTER_COMPONENT(bots::component)

View File

@ -0,0 +1,66 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "localized_strings.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "version.hpp"
namespace branding
{
namespace
{
utils::hook::detour ui_get_formatted_build_number_hook;
void dvar_set_string_stub(game::dvar_t* dvar, const char* string)
{
game::Dvar_SetString(dvar, utils::string::va("iw6-mod %s (game %s)", VERSION, string));
}
const char* ui_get_formatted_build_number_stub()
{
const auto* const build_num = ui_get_formatted_build_number_hook.invoke<const char*>();
return utils::string::va("%s (%s)", VERSION, build_num);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "iw6-mod: " VERSION " by AlterWare.\n");
utils::hook::call(SELECT_VALUE(0x1403BDABA, 0x140414424), dvar_set_string_stub);
ui_get_formatted_build_number_hook.create(
SELECT_VALUE(0x140415FD0, 0x1404D7C00), ui_get_formatted_build_number_stub);
scheduler::loop([]()
{
const auto x = 3;
const auto y = 0;
const auto scale = 0.5f;
float color[4] = {0.666f, 0.666f, 0.666f, 0.666f};
const auto* text = "iw6-mod: " VERSION;
auto* font = game::R_RegisterFont("fonts/normalfont");
if (!font) return;
game::R_AddCmdDrawText(text, std::numeric_limits<int>::max(), font, static_cast<float>(x),
y + static_cast<float>(font->pixelHeight) * scale,
scale, scale, 0.0f, color, 0);
}, scheduler::pipeline::renderer);
}
};
} // namespace branding
REGISTER_COMPONENT(branding::component)

View File

@ -0,0 +1,44 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace bullet
{
namespace
{
utils::hook::detour bg_get_surface_penetration_depth_hook;
float bg_get_surface_penetration_depth_stub(game::Weapon weapon, bool is_alternate, int surfaceType)
{
if (dvars::bg_surfacePenetration->current.value > 0.0f)
{
return dvars::bg_surfacePenetration->current.value;
}
return bg_get_surface_penetration_depth_hook.invoke<float>(weapon, is_alternate, surfaceType);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
dvars::bg_surfacePenetration = game::Dvar_RegisterFloat("bg_surfacePenetration", 0.0f,
0.0f, std::numeric_limits<float>::max(), game::DVAR_FLAG_SAVED,
"Set to a value greater than 0 to override the surface penetration depth");
bg_get_surface_penetration_depth_hook.create(0x140238FD0, &bg_get_surface_penetration_depth_stub);
}
};
}
REGISTER_COMPONENT(bullet::component)

View File

@ -0,0 +1,173 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace colors
{
namespace
{
std::vector<DWORD> color_table;
DWORD hsv_to_rgb(const game::HsvColor hsv)
{
DWORD rgb;
if (hsv.s == 0)
{
return RGB(hsv.v, hsv.v, hsv.v);
}
// converting to 16 bit to prevent overflow
const unsigned int h = hsv.h;
const unsigned int s = hsv.s;
const unsigned int v = hsv.v;
const auto region = static_cast<uint8_t>(h / 43);
const auto remainder = (h - (region * 43)) * 6;
const auto p = static_cast<uint8_t>((v * (255 - s)) >> 8);
const auto q = static_cast<uint8_t>(
(v * (255 - ((s * remainder) >> 8))) >> 8);
const auto t = static_cast<uint8_t>(
(v * (255 - ((s * (255 - remainder)) >> 8))) >> 8);
switch (region)
{
case 0:
rgb = RGB(v, t, p);
break;
case 1:
rgb = RGB(q, v, p);
break;
case 2:
rgb = RGB(p, v, t);
break;
case 3:
rgb = RGB(p, q, v);
break;
case 4:
rgb = RGB(t, p, v);
break;
default:
rgb = RGB(v, p, q);
break;
}
return rgb;
}
int color_index(const char c)
{
const auto index = c - 48;
return index >= 0xC ? 7 : index;
}
char add(const uint8_t r, const uint8_t g, const uint8_t b)
{
const char index = '0' + static_cast<char>(color_table.size());
color_table.push_back(RGB(r, g, b));
return index;
}
void com_clean_name_stub(const char* in, char* out, const int out_size)
{
strncpy_s(out, out_size, in, _TRUNCATE);
}
char* i_clean_str_stub(char* string)
{
utils::string::strip(string, string, std::strlen(string) + 1);
return string;
}
size_t get_client_name_stub(const int local_client_num, const int index, char* buf, const int size,
const size_t unk, const size_t unk2)
{
// CL_GetClientName (CL_GetClientNameAndClantag?)
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, size_t, size_t)>(0x1402CF790)(
local_client_num, index, buf, size, unk, unk2);
utils::string::strip(buf, buf, size);
return result;
}
void rb_lookup_color_stub(const char index, DWORD* color)
{
*color = RGB(255, 255, 255);
if (index == '8')
{
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD958, 0x1480E85BC));
}
else if (index == '9')
{
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD95C, 0x1480E85C0));
}
else if (index == ':')
{
*color = hsv_to_rgb({static_cast<uint8_t>((game::Sys_Milliseconds() / 100) % 256), 255, 255});
}
else if (index == ';')
{
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD964, 0x1480E85C8));
}
else
{
*color = color_table[color_index(index)];
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
if (!game::environment::is_sp())
{
// allows colored name in-game
utils::hook::jump(0x1404F5FC0, com_clean_name_stub);
// don't apply colors to overhead names
utils::hook::call(0x14025CE79, get_client_name_stub);
// patch I_CleanStr
utils::hook::jump(0x1404F63C0, i_clean_str_stub);
}
// force new colors
utils::hook::jump(SELECT_VALUE(0x14055DCC0, 0x14062AE80), rb_lookup_color_stub);
// add colors
add(0, 0, 0); // 0 - Black
add(255, 49, 49); // 1 - Red
add(134, 192, 0); // 2 - Green
add(255, 173, 34); // 3 - Yellow
add(0, 135, 193); // 4 - Blue
add(32, 197, 255); // 5 - Light Blue
add(151, 80, 221); // 6 - Pink
add(255, 255, 255); // 7 - White
add(0, 0, 0); // 8 - Team color (axis?)
add(0, 0, 0); // 9 - Team color (allies?)
add(0, 0, 0); // 10 - Rainbow (:)
add(0, 0, 0);
// 11 - Server color (;) - using that color in infostrings (e.g. your name) fails, ';' is an illegal character!
}
};
}
REGISTER_COMPONENT(colors::component)

View File

@ -0,0 +1,701 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "game_console.hpp"
#include "fastfiles.hpp"
#include <utils/io.hpp>
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/memory.hpp>
namespace command
{
namespace
{
constexpr auto CMD_MAX_NESTING = 8;
utils::hook::detour client_command_hook;
std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
void main_handler()
{
params params = {};
const auto command = utils::string::to_lower(params[0]);
if (const auto itr = handlers.find(command); itr != handlers.end())
{
itr->second(params);
}
}
void client_command(const int client_num)
{
if (game::mp::g_entities[client_num].client == nullptr)
{
// Client is not fully connected
return;
}
params_sv params = {};
const auto command = utils::string::to_lower(params[0]);
if (const auto itr = handlers_sv.find(command); itr != handlers_sv.end())
{
itr->second(client_num, params);
}
client_command_hook.invoke<void>(client_num);
}
// Shamelessly stolen from Quake3
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
void parse_command_line()
{
static auto parsed = false;
if (parsed)
{
return;
}
static std::string comand_line_buffer = GetCommandLineA();
auto* command_line = comand_line_buffer.data();
auto& com_num_console_lines = *reinterpret_cast<int*>(0x1445CFF98);
auto* com_console_lines = reinterpret_cast<char**>(0x1445CFFA0);
auto inq = false;
com_console_lines[0] = command_line;
com_num_console_lines = 0;
while (*command_line)
{
if (*command_line == '"')
{
inq = !inq;
}
// look for a + separating character
// if commandLine came from a file, we might have real line seperators
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
{
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
{
break;
}
com_console_lines[com_num_console_lines] = command_line + 1;
com_num_console_lines++;
*command_line = '\0';
}
command_line++;
}
parsed = true;
}
void parse_commandline_stub()
{
parse_command_line();
reinterpret_cast<void(*)()>(0x140413080)();
}
}
void read_startup_variable(const std::string& dvar)
{
// parse the commandline if it's not parsed
parse_command_line();
auto com_num_console_lines = *reinterpret_cast<int*>(0x1445CFF98);
auto* com_console_lines = reinterpret_cast<char**>(0x1445CFFA0);
for (int i = 0; i < com_num_console_lines; i++)
{
game::Com_TokenizeString(com_console_lines[i]);
// only +set dvar value
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
{
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
}
game::Com_EndTokenizeString();
}
}
params::params()
: nesting_(game::cmd_args->nesting)
{
assert(this->nesting_ < CMD_MAX_NESTING);
}
int params::size() const
{
return game::cmd_args->argc[this->nesting_];
}
const char* params::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::cmd_args->argv[this->nesting_][index];
}
std::string params::join(const int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
params_sv::params_sv()
: nesting_(game::sv_cmd_args->nesting)
{
assert(this->nesting_ < CMD_MAX_NESTING);
}
int params_sv::size() const
{
return game::sv_cmd_args->argc[this->nesting_];
}
const char* params_sv::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::sv_cmd_args->argv[this->nesting_][index];
}
std::string params_sv::join(const int index) const
{
std::string result = {};
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
void add_raw(const char* name, void (*callback)())
{
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
}
void add(const char* name, const std::function<void(const params&)>& callback)
{
const auto command = utils::string::to_lower(name);
if (!handlers.contains(command))
{
add_raw(name, main_handler);
}
handlers[command] = callback;
}
void add(const char* name, const std::function<void()>& callback)
{
add(name, [callback](const params&)
{
callback();
});
}
void add_sv(const char* name, const std::function<void(int, const params_sv&)>& callback)
{
// doing this so the sv command would show up in the console
add_raw(name, nullptr);
const auto command = utils::string::to_lower(name);
if (!handlers_sv.contains(command))
{
handlers_sv[command] = callback;
}
}
void execute(std::string command, const bool sync)
{
command += "\n";
if (sync)
{
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
}
else
{
game::Cbuf_AddText(0, command.data());
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
add_sp_commands();
}
else
{
utils::hook::call(0x14041213C, &parse_commandline_stub);
add_mp_commands();
}
add_commands_generic();
}
private:
static void add_commands_generic()
{
add("quit", game::Com_Quit);
add("crash", []
{
*reinterpret_cast<int*>(1) = 0x12345678;
});
add("dvarDump", [](const params& argument)
{
std::string filename;
if (argument.size() == 2)
{
filename = "iw6/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
console::info("================================ DVAR DUMP ========================================\n");
for (auto i = 0; i < *game::dvarCount; i++)
{
auto* dvar = game::sortedDvars[i];
if (dvar)
{
if (!filename.empty())
{
const auto line = std::format("{} \"{}\"\r\n", dvar->name, game::Dvar_ValueToString(dvar, dvar->current));
utils::io::write_file(filename, line, i != 0);
}
console::info("%s \"%s\"\n", dvar->name, game::Dvar_ValueToString(dvar, dvar->current));
}
}
console::info("\n%i dvars\n", *game::dvarCount);
console::info("================================ END DVAR DUMP ====================================\n");
});
add("commandDump", [](const params& argument)
{
std::string filename;
if (argument.size() == 2)
{
filename = "iw6/";
filename.append(argument[1]);
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
}
console::info("================================ COMMAND DUMP =====================================\n");
game::cmd_function_s* cmd = *game::cmd_functions;
auto i = 0;
while (cmd)
{
if (cmd->name)
{
if (!filename.empty())
{
const auto line = std::format("{}\r\n", cmd->name);
utils::io::write_file(filename, line, i != 0);
}
console::info("%s\n", cmd->name);
i++;
}
cmd = cmd->next;
}
console::info("\n%i commands\n", i);
console::info("================================ END COMMAND DUMP =================================\n");
});
add("consoleList", [](const params& params)
{
const std::string input = params.get(1);
std::vector<std::string> matches;
game_console::find_matches(input, matches, false);
for (auto& match : matches)
{
auto* dvar = game::Dvar_FindVar(match.c_str());
if (!dvar)
{
console::info("[CMD]\t %s\n", match.c_str());
}
else
{
console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current));
}
}
console::info("Total %i matches\n", matches.size());
});
add("listassetpool", [](const params& params)
{
if (params.size() < 2)
{
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
{
console::info("%d %s\n", i, game::g_assetNames[i]);
}
}
else
{
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
{
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
return;
}
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
auto total_assets = 0;
const std::string filter = params.get(2);
fastfiles::enum_assets(type, [type, &total_assets, filter](const game::XAssetHeader header)
{
const game::XAsset asset{ type, header };
const auto* const asset_name = game::DB_GetXAssetName(&asset);
//const auto* const entry = game::DB_FindXAssetEntry(type, asset_name);
//const char* zone_name;
total_assets++;
if (!filter.empty() && !game_console::match_compare(filter, asset_name, false))
{
return;
}
// TODO: in some cases returning garbage data
//if (game::environment::is_sp())
//{
// zone_name = game::sp::g_zones_0[entry->zoneIndex].name;
//}
//else
//{
// zone_name = game::mp::g_zones_0[entry->zoneIndex].name;
//}
console::info("%s\n", asset_name);
}, true);
console::info("Total %s assets: %d/%d", game::g_assetNames[type], total_assets, game::g_poolSize[type]);
}
});
add("vstr", [](const params& params)
{
if (params.size() < 2)
{
console::info("vstr <variablename> : execute a variable command\n");
return;
}
const auto* dvarName = params.get(1);
const auto* dvar = game::Dvar_FindVar(dvarName);
if (dvar == nullptr)
{
console::info("%s doesn't exist\n", dvarName);
return;
}
if (dvar->type != game::dvar_type::string
&& dvar->type != game::dvar_type::enumeration)
{
console::info("%s is not a string-based dvar\n", dvar->name);
return;
}
execute(dvar->current.string);
});
}
void add_sp_commands()
{
add("god", []
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= game::FL_GODMODE;
game::CG_GameMessage(0, utils::string::va("godmode %s",
game::sp::g_entities[0].flags & game::FL_GODMODE
? "^2on"
: "^1off"));
});
add("notarget", []
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].flags ^= game::FL_NOTARGET;
game::CG_GameMessage(0, utils::string::va("notarget %s",
game::sp::g_entities[0].flags & game::FL_NOTARGET
? "^2on"
: "^1off"));
});
add("noclip", []
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 1;
game::CG_GameMessage(0, utils::string::va("noclip %s",
game::sp::g_entities[0].client->flags & 1
? "^2on"
: "^1off"));
});
add("ufo", []
{
if (!game::SV_Loaded())
{
return;
}
game::sp::g_entities[0].client->flags ^= 2;
game::CG_GameMessage(0, utils::string::va("ufo %s",
game::sp::g_entities[0].client->flags & 2
? "^2on"
: "^1off"));
});
add("give", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
auto wp = game::G_GetWeaponForName(params.get(1));
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
}
});
add("take", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(0);
auto wp = game::G_GetWeaponForName(params.get(1));
game::G_TakePlayerWeapon(ps, wp);
});
}
static void add_mp_commands()
{
client_command_hook.create(0x1403929B0, &client_command);
add_sv("god", [&](const int client_num, const params_sv&)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].flags ^= game::FL_GODMODE;
game::SV_GameSendServerCommand(client_num, 1,
utils::string::va("f \"godmode %s\"",
game::mp::g_entities[client_num].flags & game::FL_GODMODE
? "^2on"
: "^1off"));
});
add_sv("notarget", [](const int client_num, const params_sv&)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET;
game::SV_GameSendServerCommand(client_num, 1,
utils::string::va("f \"notarget %s\"",
game::mp::g_entities[client_num].flags & game::FL_NOTARGET
? "^2on"
: "^1off"));
});
add_sv("noclip", [](const int client_num, const params_sv&)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 1;
game::SV_GameSendServerCommand(client_num, 1,
utils::string::va("f \"noclip %s\"",
game::mp::g_entities[client_num].client->flags & 1
? "^2on"
: "^1off"));
});
add_sv("ufo", [](const int client_num, const params_sv&)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
game::mp::g_entities[client_num].client->flags ^= 2;
game::SV_GameSendServerCommand(client_num, 1,
utils::string::va("f \"ufo %s\"",
game::mp::g_entities[client_num].client->flags & 2
? "^2on"
: "^1off"));
});
add_sv("setviewpos", [](const int client_num, const params_sv& params)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 4)
{
game::SV_GameSendServerCommand(client_num, 1,
"f \"You did not specify the correct number of coordinates\"");
return;
}
game::mp::g_entities[client_num].client->ps.origin[0] = std::strtof(params.get(1), nullptr);
game::mp::g_entities[client_num].client->ps.origin[1] = std::strtof(params.get(2), nullptr);
game::mp::g_entities[client_num].client->ps.origin[2] = std::strtof(params.get(3), nullptr);
});
add_sv("setviewang", [](const int client_num, const params_sv& params)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 4)
{
game::SV_GameSendServerCommand(client_num, 1,
"f \"You did not specify the correct number of coordinates\"");
return;
}
game::mp::g_entities[client_num].client->ps.delta_angles[0] = std::strtof(params.get(1), nullptr);
game::mp::g_entities[client_num].client->ps.delta_angles[1] = std::strtof(params.get(2), nullptr);
game::mp::g_entities[client_num].client->ps.delta_angles[2] = std::strtof(params.get(3), nullptr);
});
add_sv("give", [](const int client_num, const params_sv& params)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
auto wp = game::G_GetWeaponForName(params.get(1));
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(client_num, wp);
}
});
add_sv("take", [](const int client_num, const params_sv& params)
{
if (!dvars::sv_cheats->current.enabled)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
return;
}
if (params.size() < 2)
{
game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\"");
return;
}
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
auto wp = game::G_GetWeaponForName(params.get(1));
game::G_TakePlayerWeapon(ps, wp);
});
}
};
}
REGISTER_COMPONENT(command::component)

View File

@ -0,0 +1,50 @@
#pragma once
namespace command
{
class params
{
public:
params();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
const char* operator[](const int index) const
{
return this->get(index); //
}
private:
int nesting_;
};
class params_sv
{
public:
params_sv();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
const char* operator[](const int index) const
{
return this->get(index); //
}
private:
int nesting_;
};
void read_startup_variable(const std::string& dvar);
void add_raw(const char* name, void (*callback)());
void add(const char* name, const std::function<void(const params&)>& callback);
void add(const char* name, const std::function<void()>& callback);
void add_sv(const char* name, const std::function<void(int, const params_sv&)>& callback);
void execute(std::string command, bool sync = false);
}

View File

@ -0,0 +1,260 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include "command.hpp"
#include "game_console.hpp"
#include "rcon.hpp"
#include "scheduler.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include <utils/thread.hpp>
namespace console
{
namespace
{
using message_queue = std::queue<std::string>;
utils::concurrency::container<message_queue> message_queue_;
std::atomic_bool started_{false};
std::atomic_bool terminate_runner_{false};
void print_message(const char* message)
{
#ifdef _DEBUG
OutputDebugStringA(message);
#endif
if (game::is_headless())
{
std::fputs(message, stdout);
}
else
{
game::Conbuf_AppendText(message);
}
}
std::string format(va_list* ap, const char* message)
{
static thread_local char buffer[0x1000];
const auto count = vsnprintf_s(buffer, _TRUNCATE, message, *ap);
if (count < 0) return {};
return {buffer, static_cast<size_t>(count)};
}
void dispatch_message(const int type, const std::string& message)
{
if (rcon::message_redirect(message))
{
return;
}
game_console::print(type, message);
if (game::is_headless())
{
std::fputs(message.data(), stdout);
return;
}
message_queue_.access([&message](message_queue& queue)
{
queue.emplace(message);
});
}
message_queue empty_message_queue()
{
message_queue current_queue{};
message_queue_.access([&](message_queue& queue)
{
current_queue = std::move(queue);
queue = {};
});
return current_queue;
}
void print_stub(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
char buffer[4096]{};
const auto res = vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
(void)res;
print_message(buffer);
va_end(ap);
}
void append_text(const char* text)
{
dispatch_message(con_type_info, text);
}
}
class component final : public component_interface
{
public:
component()
{
if (game::is_headless())
{
if (!AttachConsole(ATTACH_PARENT_PROCESS))
{
AllocConsole();
AttachConsole(GetCurrentProcessId());
}
ShowWindow(GetConsoleWindow(), SW_SHOW);
FILE* fp;
freopen_s(&fp, "CONIN$", "r", stdin);
freopen_s(&fp, "CONOUT$", "w", stdout);
freopen_s(&fp, "CONOUT$", "w", stderr);
}
}
void post_unpack() override
{
// Redirect input (]command)
utils::hook::jump(SELECT_VALUE(0x14043DFA0, 0x140502A80), append_text);
utils::hook::jump(printf, print_stub);
if (game::is_headless())
{
return;
}
terminate_runner_ = false;
this->message_runner_ = utils::thread::create_named_thread("Console IO", []
{
while (!started_)
{
std::this_thread::sleep_for(10ms);
}
while (!terminate_runner_)
{
std::string message_buffer{};
auto current_queue = empty_message_queue();
while (!current_queue.empty())
{
const auto& msg = current_queue.front();
message_buffer.append(msg);
current_queue.pop();
}
if (!message_buffer.empty())
{
print_message(message_buffer.data());
}
std::this_thread::sleep_for(5ms);
}
});
this->console_runner_ = utils::thread::create_named_thread("Console Window", [this]
{
game::Sys_ShowConsole();
MSG msg{};
while (!terminate_runner_)
{
if (PeekMessageW(&msg, nullptr, NULL, NULL, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
command::execute("quit", false);
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
{
std::this_thread::sleep_for(5ms);
}
}
});
// Give the console a chance to open or we will lose some early messages
// like the ones printed from the filesystem component
scheduler::once([]() -> void
{
started_ = true;
}, scheduler::pipeline::main);
}
void pre_destroy() override
{
terminate_runner_ = true;
if (this->message_runner_.joinable())
{
this->message_runner_.join();
}
if (this->console_runner_.joinable())
{
this->console_runner_.join();
}
}
private:
std::thread console_runner_{};
std::thread message_runner_{};
};
HWND get_window()
{
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x145A7B490, 0x147AD1DB0)));
}
void set_title(std::string title)
{
if (game::is_headless())
{
SetConsoleTitleA(title.data());
}
else
{
SetWindowTextA(get_window(), title.data());
}
}
void set_size(const int width, const int height)
{
RECT rect;
GetWindowRect(get_window(), &rect);
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x145A7B4A0, 0x147AD1DC0));
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
}
void print(const int type, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
const auto result = format(&ap, fmt);
va_end(ap);
dispatch_message(type, result);
}
}
REGISTER_COMPONENT(console::component)

View File

@ -0,0 +1,35 @@
#pragma once
namespace console
{
HWND get_window();
void set_title(std::string title);
void set_size(int width, int height);
enum console_type
{
con_type_error = 1,
con_type_warning = 3,
con_type_info = 7
};
void print(int type, const char* fmt, ...);
template <typename... Args>
void error(const char* fmt, Args&&... args)
{
print(con_type_error, fmt, std::forward<Args>(args)...);
}
template <typename... Args>
void warn(const char* fmt, Args&&... args)
{
print(con_type_warning, fmt, std::forward<Args>(args)...);
}
template <typename... Args>
void info(const char* fmt, Args&&... args)
{
print(con_type_info, fmt, std::forward<Args>(args)...);
}
}

View File

@ -0,0 +1,75 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace cursor
{
namespace
{
bool show_cursor_next_frame = false;
int WINAPI show_cursor_stub(const BOOL show)
{
static auto counter = 0;
counter += show ? 1 : -1;
return counter;
}
void show_cursor(const bool show)
{
static auto last_state = false;
if (last_state == show)
{
return;
}
if (show)
{
while (ShowCursor(TRUE) < 0);
}
else
{
while (ShowCursor(FALSE) >= 0);
}
}
void draw_cursor_stub()
{
show_cursor_next_frame = true;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// This is the legacy cursor
// TODO: Find the new cursor drawing...
utils::hook::call(SELECT_VALUE(0x14040117E, 0x1404BB119), draw_cursor_stub);
scheduler::loop([]()
{
show_cursor(show_cursor_next_frame);
show_cursor_next_frame = false;
}, scheduler::pipeline::renderer);
show_cursor(true);
}
void* load_import(const std::string& library, const std::string& function) override
{
if (function == "ShowCursor")
{
return &show_cursor_stub;
}
return nullptr;
}
};
}
//REGISTER_COMPONENT(cursor::component)

View File

@ -0,0 +1,400 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
#include "dvars.hpp"
#include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
#include "server_list.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <version.hpp>
namespace dedicated
{
namespace
{
const game::dvar_t* sv_lanOnly;
void initialize()
{
command::execute("exec default_xboxlive.cfg", true);
command::execute("xstartprivatematch", true); // IW6 specific, doesn't work without
command::execute("onlinegame 1", true);
command::execute("xblive_privatematch 0", true);
}
void init_dedicated_server()
{
static bool initialized = false;
if (initialized) return;
initialized = true;
static const char* fastfiles[] =
{
"code_post_gfx_mp",
"ui_mp",
"code_nvidia_mp",
"common_mp",
nullptr, //"mp_character_room",
nullptr, //"mp_character_room_heads",
nullptr, //"mp_character_room_bodies_updated",
nullptr, //"mp_character_room_dlc_updated",
"techsets_common_core_mp",
"common_core_mp",
"common_core_dlc_updated_mp",
"techsets_common_alien_mp",
"common_alien_mp",
"common_alien_dlc_updated_mp",
nullptr
};
// load fastfiles
std::memcpy(reinterpret_cast<void*>(0x1480B1E40), &fastfiles, sizeof(fastfiles));
// R_LoadGraphicsAssets
reinterpret_cast<void(*)()>(0x1405E6F80)();
}
void start_map(const std::string& map_name)
{
if (game::Live_SyncOnlineDataFlags(0) != 0)
{
scheduler::on_game_initialized([map_name]
{
command::execute(std::format("map {}", map_name), false);
}, scheduler::pipeline::main, 1s);
return;
}
party::switch_gamemode_if_necessary(dvars::get_string("g_gametype"));
if (!game::environment::is_dedi())
{
party::perform_game_initialization();
}
const auto* current_mapname = game::Dvar_FindVar("mapname");
if (current_mapname && utils::string::to_lower(current_mapname->current.string) == utils::string::to_lower(map_name) && game::SV_Loaded())
{
console::info("Restarting map: %s\n", map_name.data());
command::execute("map_restart", false);
return;
}
console::info("Starting map: %s\n", map_name.data());
game::SV_StartMapForParty(0, map_name.data(), false, false);
}
void map_restart()
{
if (!game::SV_Loaded())
{
return;
}
*reinterpret_cast<int*>(0x144DB8C84) = 1; // sv_map_restart
*reinterpret_cast<int*>(0x144DB8C88) = 1; // sv_loadScripts
*reinterpret_cast<int*>(0x144DB8C8C) = 0; // sv_migrate
reinterpret_cast<void(*)()>(0x14046F3B0)(); // SV_CheckLoadGame
}
game::dvar_t* register_maxfps_stub(const char* name, int, int, int, unsigned int flags,
const char* desc)
{
return game::Dvar_RegisterInt(name, 0, 0, 0, game::DvarFlags::DVAR_FLAG_READ, desc);
}
void send_heartbeat()
{
if (sv_lanOnly->current.enabled)
{
return;
}
game::netadr_s target{};
if (server_list::get_master_server(target))
{
network::send(target, "heartbeat", "IW6");
}
}
std::vector<std::string>& get_startup_command_queue()
{
static std::vector<std::string> startup_command_queue;
return startup_command_queue;
}
void execute_startup_command(int /*client*/, int /*controllerIndex*/, const char* command)
{
if (game::Live_SyncOnlineDataFlags(0) == 0)
{
game::Cbuf_ExecuteBufferInternal(0, 0, command, game::Cmd_ExecuteSingleCommand);
}
else
{
get_startup_command_queue().emplace_back(command);
}
}
void execute_startup_command_queue()
{
const auto queue = get_startup_command_queue();
get_startup_command_queue().clear();
for (const auto& command : queue)
{
game::Cbuf_ExecuteBufferInternal(0, 0, command.data(), game::Cmd_ExecuteSingleCommand);
}
}
std::vector<std::string>& get_console_command_queue()
{
static std::vector<std::string> console_command_queue;
return console_command_queue;
}
void execute_console_command([[maybe_unused]] const int local_client_num, const char* command)
{
if (game::Live_SyncOnlineDataFlags(0) == 0)
{
command::execute(command);
}
else
{
get_console_command_queue().emplace_back(command);
}
}
void execute_console_command_queue()
{
const auto queue = get_console_command_queue();
get_console_command_queue().clear();
for (const auto& command : queue)
{
command::execute(command);
}
}
void sync_gpu_stub()
{
std::this_thread::sleep_for(1ms);
}
void gscr_is_using_match_rules_data_stub()
{
game::Scr_AddInt(0);
}
void glass_update()
{
if (*reinterpret_cast<void**>(0x14424C068))
{
reinterpret_cast<void(*)()>(0x140397450)();
}
}
HWND WINAPI set_focus_stub(const HWND hwnd)
{
return hwnd;
}
void sys_error_stub(const char* msg, ...)
{
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
scheduler::once([]()
{
command::execute("map_rotate");
}, scheduler::pipeline::main, 3s);
game::Com_Error(game::ERR_DROP, "%s", buffer);
}
void add_commands()
{
command::add("map", [](const command::params& params)
{
if (params.size() != 2)
{
return;
}
start_map(utils::string::to_lower(params[1]));
});
command::add("map_restart", map_restart);
command::add("fast_restart", []
{
if (game::SV_Loaded())
{
game::SV_FastRestart();
}
});
command::add("heartbeat", send_heartbeat);
}
}
class component final : public component_interface
{
public:
void* load_import(const std::string& library, const std::string& function) override
{
if (!game::environment::is_dedi()) return nullptr;
if (function == "SetFocus")
{
return set_focus_stub;
}
return nullptr;
}
void post_unpack() override
{
if (!game::environment::is_dedi())
{
return;
}
// Arxan error fix
utils::hook::call(0x1403A0AF9, glass_update);
// Add lanonly mode
sv_lanOnly = game::Dvar_RegisterBool("sv_lanOnly", false, game::DVAR_FLAG_NONE, "Don't send heartbeat");
// Make GScr_IsUsingMatchRulesData return 0 so the game doesn't override the cfg
utils::hook::jump(0x1403C9660, gscr_is_using_match_rules_data_stub);
// Patch "Server is on a different version"
utils::hook::set<uint8_t>(0x140471474, 0xEB);
// Patch checksum check
utils::hook::set<uint8_t>(0x1404714D4, 0xEB);
// Hook R_SyncGpu
utils::hook::jump(0x1405E8530, sync_gpu_stub);
//utils::hook::set<uint8_t>(0x1402C89A0, 0xC3); // R_Init caller
utils::hook::jump(0x1402C89A0, init_dedicated_server);
utils::hook::call(0x140413AD8, register_maxfps_stub);
// delay startup commands until the initialization is done
utils::hook::call(0x140412183, execute_startup_command);
// delay console commands until the initialization is done
utils::hook::call(0x140412FD3, execute_console_command);
utils::hook::nop(0x140412FE9, 5);
utils::hook::nop(0x1404DDC2E, 5); // don't load config file
utils::hook::set<uint8_t>(0x140416100, 0xC3); // don't save config file
utils::hook::set<uint8_t>(0x1402E5830, 0xC3); // disable self-registration
utils::hook::set<uint8_t>(0x1402C7935, 5); // make CL_Frame do client packets, even for game state 9
utils::hook::set<uint8_t>(0x140503FF0, 0xC3); // init sound system (1)
utils::hook::set<uint8_t>(0x140602380, 0xC3); // start render thread
utils::hook::set<uint8_t>(0x140658580, 0xC3); // init sound system (2)
//utils::hook::set<uint8_t>(0x49BC10, 0xC3); // Com_Frame audio processor?
utils::hook::set<uint8_t>(0x1402CF570, 0xC3); // called from Com_Frame, seems to do renderer stuff
utils::hook::set<uint8_t>(0x1402C49B0, 0xC3);
// CL_CheckForResend, which tries to connect to the local server constantly
utils::hook::set<uint8_t>(0x1405DAE1F, 0); // r_loadForRenderer default to 0
utils::hook::set<uint8_t>(0x1404FFCE2, 0xC3); // recommended settings check - TODO: Check hook
utils::hook::set<uint8_t>(0x140503420, 0xC3); // some mixer-related function called on shutdown
utils::hook::set<uint8_t>(0x1404BEC10, 0xC3); // dont load ui gametype stuff
//utils::hook::set<uint8_t>(0x611690, 0xC3); // some unknown function that seems to fail
utils::hook::nop(0x14047261C, 6); // unknown check in SV_ExecuteClientMessage
//utils::hook::nop(0x5751DF, 2); // don't spawn a DemonWare session for the dedicated server
//utils::hook::set<uint8_t>(0x5751E9, 0xEB); // ^
utils::hook::nop(0x140471B6B, 4); // allow first slot to be occupied
utils::hook::nop(0x1402CA0F5, 2); // properly shut down dedicated servers
utils::hook::nop(0x1402CA0B9, 2); // ^
utils::hook::nop(0x1402CA12D, 5); // don't shutdown renderer
utils::hook::set<uint8_t>(0x1405E87DE, 0xEB); // ignore world being in use
utils::hook::set<uint8_t>(0x1404FFCF0, 0xC3); // cpu detection stuff
utils::hook::set<uint8_t>(0x1405F0620, 0xC3); // gfx stuff during fastfile loading
utils::hook::set<uint8_t>(0x1405F0530, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405F05C0, 0xC3); // ^
utils::hook::set<uint8_t>(0x140324F00, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405F0580, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405B81A0, 0xC3); // directx stuff
utils::hook::set<uint8_t>(0x1405E0CF0, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405E1530, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405E3E50, 0xC3); // ^ - mutex
utils::hook::set<uint8_t>(0x1405E1050, 0xC3); // ^
// shaders
utils::hook::set<uint8_t>(0x140167E00, 0xC3); // ^
utils::hook::set<uint8_t>(0x140167D80, 0xC3); // ^
utils::hook::set<uint8_t>(0x1406492A0, 0xC3); // ^ - mutex
utils::hook::set<uint8_t>(0x1405047A0, 0xC3); // idk
utils::hook::set<uint8_t>(0x1405B8DB0, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405E7D20, 0xC3); // R_Shutdown
utils::hook::set<uint8_t>(0x1405B8BD0, 0xC3); // shutdown stuff
utils::hook::set<uint8_t>(0x1405E7DF0, 0xC3); // ^
utils::hook::set<uint8_t>(0x1405E76C0, 0xC3); // ^
utils::hook::set<uint8_t>(0x14065EA00, 0xC3); // sound crashes
utils::hook::set<uint8_t>(0x14047BE70, 0xC3); // disable host migration
utils::hook::set<uint8_t>(0x140423B20, 0xC3); // render synchronization lock
utils::hook::set<uint8_t>(0x140423A60, 0xC3); // render synchronization unlock
//utils::hook::set<uint8_t>(0x1405E3470, 0xC3); // some rendering stuff in R_UpdateDynamicMemory
//utils::hook::set<uint8_t>(0x1405E31A0, 0xC3); // ^
utils::hook::jump(0x140610EB6, 0x140610F15); // ^
utils::hook::nop(0x1404F8BD9, 5); // Disable sound pak file loading
utils::hook::nop(0x1404F8BE1, 2); // ^
utils::hook::set<uint8_t>(0x140328660, 0xC3); // Disable image pak file loading
// Stop crashing from sys_errors
utils::hook::jump(0x1404FF510, sys_error_stub);
// Reduce min required memory
utils::hook::set<uint64_t>(0x1404FA6BD, 0x80000000);
utils::hook::set<uint64_t>(0x1404FA76F, 0x80000000);
scheduler::on_game_initialized([]
{
initialize();
console::info("==================================\n");
console::info("Server started!\n");
console::info("==================================\n");
execute_startup_command_queue();
execute_console_command_queue();
}, scheduler::pipeline::main, 1s);
// Send heartbeat to dpmaster
scheduler::once(send_heartbeat, scheduler::pipeline::server);
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min);
add_commands();
}
};
}
REGISTER_COMPONENT(dedicated::component)

View File

@ -0,0 +1,70 @@
#include <std_include.hpp>
#include "console.hpp"
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include <utils\string.hpp>
namespace dedicated_info
{
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_dedi())
{
return;
}
scheduler::once([]()
{
console::set_title("iw6-mod Dedicated Server");
console::set_size(800, 600);
});
scheduler::loop([]()
{
auto* sv_running = game::Dvar_FindVar("sv_running");
if (!sv_running || !sv_running->current.enabled)
{
console::set_title("iw6-mod Dedicated Server");
return;
}
auto* const sv_hostname = game::Dvar_FindVar("sv_hostname");
auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients");
auto* const mapname = game::Dvar_FindVar("mapname");
auto client_count = 0;
auto bot_count = 0;
for (auto i = 0; i < sv_maxclients->current.integer; i++)
{
auto* client = &game::mp::svs_clients[i];
auto* self = &game::mp::g_entities[i];
if (client->header.state > game::CS_FREE && self && self->client)
{
client_count++;
if (game::SV_BotIsBot(i))
{
++bot_count;
}
}
}
std::string cleaned_hostname = sv_hostname->current.string;
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
cleaned_hostname.size() + 1);
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(),
mapname->current.string, client_count,
sv_maxclients->current.integer, bot_count));
}, scheduler::pipeline::main, 1s);
}
};
}
REGISTER_COMPONENT(dedicated_info::component)

View File

@ -0,0 +1,467 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "demonware.hpp"
#include "game_module.hpp"
#include <utils/hook.hpp>
#include <utils/nt.hpp>
#include <utils/cryptography.hpp>
#include <utils/thread.hpp>
#include "game/game.hpp"
#include "game/demonware/stun_server.hpp"
#include "game/demonware/service_server.hpp"
#include "game/demonware/services/bdLSGHello.hpp" // 7
#include "game/demonware/services/bdStorage.hpp" // 10
#include "game/demonware/services/bdDediAuth.hpp" // 12
#include "game/demonware/services/bdTitleUtilities.hpp" // 12
#include "game/demonware/services/bdBandwidthTest.hpp" // 18
#include "game/demonware/services/bdMatchMaking.hpp" // 21
#include "game/demonware/services/bdDediRSAAuth.hpp" // 26
#include "game/demonware/services/bdDML.hpp" // 27
#include "game/demonware/services/bdGroup.hpp" // 28
#include "game/demonware/services/bdSteamAuth.hpp" // 28
#include "game/demonware/services/bdAnticheat.hpp" // 38
#include "game/demonware/services/bdRelayService.hpp" // 86
#define TCP_BLOCKING true
#define UDP_BLOCKING false
namespace demonware
{
namespace
{
volatile bool terminate;
std::thread message_thread;
std::recursive_mutex server_mutex;
std::map<SOCKET, bool> blocking_sockets;
std::map<SOCKET, std::shared_ptr<service_server>> socket_links;
std::map<unsigned long, std::shared_ptr<service_server>> servers;
std::map<unsigned long, std::shared_ptr<stun_server>> stun_servers;
std::map<SOCKET, std::queue<std::pair<std::string, std::string>>> datagram_packets;
uint8_t encryption_key_[24];
uint8_t decryption_key_[24];
std::shared_ptr<service_server> find_server_by_address(const unsigned long address)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto server = servers.find(address);
if (server != servers.end())
{
return server->second;
}
return std::shared_ptr<service_server>();
}
std::shared_ptr<service_server> find_server_by_name(const std::string& name)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
return find_server_by_address(utils::cryptography::jenkins_one_at_a_time::compute(name));
}
std::shared_ptr<stun_server> find_stun_server_by_address(const unsigned long address)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto server = stun_servers.find(address);
if (server != stun_servers.end())
{
return server->second;
}
return std::shared_ptr<stun_server>();
}
std::shared_ptr<stun_server> find_stun_server_by_name(const std::string& name)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
return find_stun_server_by_address(utils::cryptography::jenkins_one_at_a_time::compute(name));
}
std::shared_ptr<service_server> find_server_by_socket(const SOCKET s)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto server = socket_links.find(s);
if (server != socket_links.end())
{
return server->second;
}
return std::shared_ptr<service_server>();
}
bool link_socket(const SOCKET s, const unsigned long address)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto server = find_server_by_address(address);
if (!server) return false;
socket_links[s] = server;
return true;
}
void unlink_socket(const SOCKET sock)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto server = socket_links.find(sock);
if (server != socket_links.end())
{
socket_links.erase(server);
}
const auto dgram_packets = datagram_packets.find(sock);
if (dgram_packets != datagram_packets.end())
{
datagram_packets.erase(dgram_packets);
}
}
bool is_blocking_socket(const SOCKET s, const bool def)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
if (blocking_sockets.find(s) != blocking_sockets.end())
{
return blocking_sockets[s];
}
return def;
}
int recv_datagam_packet(const SOCKET s, char* buf, const int len, sockaddr* from, int* fromlen)
{
std::unique_lock<std::recursive_mutex> lock(server_mutex);
auto queue = datagram_packets.find(s);
if (queue != datagram_packets.end())
{
const auto blocking = is_blocking_socket(s, UDP_BLOCKING);
lock.unlock();
while (blocking && queue->second.empty())
{
std::this_thread::sleep_for(1ms);
}
lock.lock();
if (!queue->second.empty())
{
auto [address, data] = queue->second.front();
queue->second.pop();
*fromlen = INT(address.size());
std::memcpy(from, address.data(), address.size());
const auto size = std::min(size_t(len), data.size());
std::memcpy(buf, data.data(), size);
return static_cast<int>(size);
}
WSASetLastError(WSAEWOULDBLOCK);
return -1;
}
return 0;
}
void remove_blocking_socket(const SOCKET s)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
const auto entry = blocking_sockets.find(s);
if (entry != blocking_sockets.end())
{
blocking_sockets.erase(entry);
}
}
void set_blocking_socket(const SOCKET s, const bool blocking)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
blocking_sockets[s] = blocking;
}
void server_thread()
{
terminate = false;
while (!terminate)
{
std::unique_lock<std::recursive_mutex> lock(server_mutex);
for (auto& server : servers)
{
server.second->run_frame();
}
lock.unlock();
std::this_thread::sleep_for(50ms);
}
}
void bd_logger_stub(int /*type*/, const char* const /*channelName*/, const char*, const char* const /*file*/,
const char* const function, const unsigned int /*line*/, const char* const msg, ...)
{
static const auto* bd_logger_enabled = game::Dvar_RegisterBool("bd_logger_enabled", false, game::DVAR_FLAG_NONE, "Enable bdLogger");
if (!bd_logger_enabled->current.enabled)
{
return;
}
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
printf("%s: %s\n", function, buffer);
va_end(ap);
}
namespace io
{
int WINAPI send_to(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to,
const int tolen)
{
if (tolen == sizeof(sockaddr_in))
{
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(to);
const auto server = find_stun_server_by_address(in_addr->sin_addr.s_addr);
if (server) return server->send(s, buf, len, to, tolen);
}
return sendto(s, buf, len, flags, to, tolen);
}
int WINAPI recv_from(const SOCKET s, char* buf, const int len, const int flags, sockaddr* from,
int* fromlen)
{
auto res = recv_datagam_packet(s, buf, len, from, fromlen);
if (res != 0) return res;
res = recvfrom(s, buf, len, flags, from, fromlen);
return res;
}
int WINAPI send(const SOCKET s, const char* buf, const int len, const int flags)
{
auto server = find_server_by_socket(s);
if (server) return server->send(buf, len);
return ::send(s, buf, len, flags);
}
int WINAPI recv(const SOCKET s, char* buf, const int len, const int flags)
{
auto server = find_server_by_socket(s);
if (server)
{
const auto blocking = is_blocking_socket(s, TCP_BLOCKING);
int result;
do
{
result = server->recv(buf, len);
if (blocking && result < 0) std::this_thread::sleep_for(1ms);
}
while (blocking && result < 0);
if (!blocking && result < 0)
{
WSASetLastError(WSAEWOULDBLOCK);
}
return result;
}
return ::recv(s, buf, len, flags);
}
int WINAPI connect(const SOCKET s, const sockaddr* addr, const int len)
{
if (len == sizeof(sockaddr_in))
{
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
if (link_socket(s, in_addr->sin_addr.s_addr)) return 0;
}
return ::connect(s, addr, len);
}
int WINAPI close_socket(const SOCKET s)
{
remove_blocking_socket(s);
unlink_socket(s);
return closesocket(s);
}
int WINAPI ioctl_socket(const SOCKET s, const long cmd, u_long* argp)
{
if (static_cast<unsigned long>(cmd) == (FIONBIO))
{
set_blocking_socket(s, *argp == 0);
}
return ioctlsocket(s, cmd, argp);
}
hostent* WINAPI get_host_by_name(char* name)
{
unsigned long addr = 0;
const auto server = find_server_by_name(name);
if (server) addr = server->get_address();
const auto stun_server = find_stun_server_by_name(name);
if (stun_server) addr = stun_server->get_address();
if (server || stun_server)
{
static thread_local in_addr address;
address.s_addr = addr;
static thread_local in_addr* addr_list[2];
addr_list[0] = &address;
addr_list[1] = nullptr;
static thread_local hostent host;
host.h_name = name;
host.h_aliases = nullptr;
host.h_addrtype = AF_INET;
host.h_length = sizeof(in_addr);
host.h_addr_list = reinterpret_cast<char**>(addr_list);
return &host;
}
#pragma warning(push)
#pragma warning(disable: 4996)
return gethostbyname(name);
#pragma warning(pop)
}
bool register_hook(const std::string& process, void* stub)
{
const auto game_module = game_module::get_game_module();
auto result = false;
result = result || utils::hook::iat(game_module, "wsock32.dll", process, stub);
result = result || utils::hook::iat(game_module, "WS2_32.dll", process, stub);
return result;
}
}
template <typename... Args>
std::shared_ptr<service_server> register_server(Args ... args)
{
std::lock_guard _(server_mutex);
auto server = std::make_shared<service_server>(args...);
servers[server->get_address()] = server;
return server;
}
std::shared_ptr<stun_server> register_stun_server(const std::string& name)
{
std::lock_guard _(server_mutex);
auto server = std::make_shared<stun_server>(name);
stun_servers[server->get_address()] = server;
return server;
}
void startup_dw()
{
register_stun_server("ghosts-stun.us.demonware.net");
register_stun_server("ghosts-stun.eu.demonware.net");
register_stun_server("ghosts-stun.jp.demonware.net");
register_stun_server("ghosts-stun.au.demonware.net");
auto lsg_server = register_server("ghosts-pc-lobby.prod.demonware.net");
auto auth_server = register_server("ghosts-pc-auth.prod.demonware.net");
auth_server->register_service<bdDediAuth>();
auth_server->register_service<bdSteamAuth>();
auth_server->register_service<bdDediRSAAuth>();
lsg_server->register_service<bdLSGHello>();
lsg_server->register_service<bdStorage>();
lsg_server->register_service<bdTitleUtilities>();
lsg_server->register_service<bdDML>();
lsg_server->register_service<bdMatchMaking>();
lsg_server->register_service<bdBandwidthTest>();
lsg_server->register_service<bdGroup>();
lsg_server->register_service<bdAnticheat>();
lsg_server->register_service<bdRelayService>();
}
}
void send_datagram_packet(const SOCKET s, const std::string& data, const sockaddr* to, const int tolen)
{
std::lock_guard<std::recursive_mutex> _(server_mutex);
datagram_packets[s].push({std::string(LPSTR(to), size_t(tolen)), data});
}
uint8_t* get_key(const bool encrypt)
{
return encrypt ? encryption_key_ : decryption_key_;
}
void set_key(const bool encrypt, uint8_t* key)
{
static_assert(sizeof encryption_key_ == sizeof decryption_key_);
std::memcpy(encrypt ? encryption_key_ : decryption_key_, key, sizeof encryption_key_);
}
class component final : public component_interface
{
public:
void post_load() override
{
startup_dw();
message_thread = utils::thread::create_named_thread("Demonware", server_thread);
io::register_hook("send", io::send);
io::register_hook("recv", io::recv);
io::register_hook("sendto", io::send_to);
io::register_hook("recvfrom", io::recv_from);
io::register_hook("connect", io::connect);
io::register_hook("closesocket", io::close_socket);
io::register_hook("ioctlsocket", io::ioctl_socket);
io::register_hook("gethostbyname", io::get_host_by_name);
}
void post_unpack() override
{
utils::hook::jump(SELECT_VALUE(0x140602230, 0x1406F54D0), bd_logger_stub);
}
void pre_destroy() override
{
std::lock_guard _(server_mutex);
terminate = true;
if (message_thread.joinable())
{
message_thread.join();
}
servers.clear();
stun_servers.clear();
socket_links.clear();
blocking_sockets.clear();
datagram_packets.clear();
}
};
}
REGISTER_COMPONENT(demonware::component)

View File

@ -0,0 +1,9 @@
#pragma once
namespace demonware
{
void send_datagram_packet(SOCKET s, const std::string& data, const sockaddr* to, int tolen);
uint8_t* get_key(const bool encrypt);
void set_key(bool encrypt, uint8_t* key);
}

View File

@ -0,0 +1,151 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
#include <utils/cryptography.hpp>
#include <utils/string.hpp>
#include <discord_rpc.h>
namespace discord
{
namespace
{
DiscordRichPresence discord_presence;
void join_game(const char* join_secret)
{
game::Cbuf_AddText(0, utils::string::va("connect %s\n", join_secret));
}
void join_request(const DiscordUser* request)
{
#ifdef _DEBUG
console::info("Discord: Join request from %s (%s)\n", request->username, request->userId);
#endif
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
}
void update_discord()
{
Discord_RunCallbacks();
if (!game::CL_IsCgameInitialized())
{
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
discord_presence.state = "Main Menu";
discord_presence.partySize = 0;
discord_presence.partyMax = 0;
discord_presence.startTimestamp = 0;
}
else
{
if (game::environment::is_sp()) return;
const auto* gametype = game::UI_LocalizeGametype(game::Dvar_FindVar("ui_gametype")->current.string);
const auto* map = game::UI_LocalizeMapname(game::Dvar_FindVar("ui_mapname")->current.string);
discord_presence.details = utils::string::va("%s on %s", gametype, map);
discord_presence.partySize = game::mp::cgArray->snap != nullptr
? game::mp::cgArray->snap->numClients
: 1;
if (game::Dvar_GetBool("xblive_privatematch"))
{
discord_presence.state = "Private Match";
discord_presence.partyMax = game::Dvar_GetInt("sv_maxclients");
}
else
{
auto* host_name = reinterpret_cast<char*>(0x14187EBC4);
utils::string::strip(host_name, host_name, std::strlen(host_name) + 1);
discord_presence.state = host_name;
discord_presence.partyMax = party::server_client_count();
std::hash<game::netadr_s> hash_fn;
static const auto nonce = utils::cryptography::random::get_integer();
const auto& address = party::get_target();
discord_presence.partyId = utils::string::va("%zu", hash_fn(address) ^ nonce);
discord_presence.joinSecret = network::net_adr_to_string(address);
}
if (!discord_presence.startTimestamp)
{
discord_presence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
}
}
discord_presence.largeImageKey = "main_logo";
Discord_UpdatePresence(&discord_presence);
}
}
class component final : public component_interface
{
public:
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
DiscordEventHandlers handlers;
ZeroMemory(&handlers, sizeof(handlers));
handlers.ready = ready;
handlers.errored = errored;
handlers.disconnected = errored;
handlers.joinGame = join_game;
handlers.spectateGame = nullptr;
handlers.joinRequest = join_request;
Discord_Initialize("1116670204148195428", &handlers, 1, nullptr);
scheduler::loop(update_discord, scheduler::pipeline::main, 20s);
initialized_ = true;
}
void pre_destroy() override
{
if (!initialized_)
{
return;
}
Discord_Shutdown();
}
private:
bool initialized_ = false;
static void ready(const DiscordUser* /*request*/)
{
ZeroMemory(&discord_presence, sizeof(discord_presence));
discord_presence.instance = 1;
Discord_UpdatePresence(&discord_presence);
}
static void errored(const int error_code, const char* message)
{
console::error("Discord: (%i) %s\n", error_code, message);
}
};
}
#ifndef DEV_BUILD
REGISTER_COMPONENT(discord::component)
#endif

View File

@ -0,0 +1,188 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "console.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace dvar_cheats
{
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
{
if (dvar && dvar->name == "sv_cheats"s)
{
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
if (game::environment::is_dedi() && source == game::DvarSetSource::DVAR_SOURCE_INTERNAL)
{
value->enabled = dvar->current.enabled;
}
// if sv_cheats was enabled and it changes to disabled, we need to reset all cheat dvars
else if (dvar->current.enabled && !value->enabled)
{
for (auto i = 0; i < *game::dvarCount; ++i)
{
const auto var = game::sortedDvars[i];
if (var && (var->flags & game::DvarFlags::DVAR_FLAG_CHEAT))
{
game::Dvar_Reset(var, game::DvarSetSource::DVAR_SOURCE_INTERNAL);
}
}
}
}
}
bool dvar_flag_checks(const game::dvar_t* dvar, const game::DvarSetSource source)
{
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_WRITE))
{
console::error("%s is write protected\n", dvar->name);
return false;
}
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_READ))
{
console::error("%s is read only\n", dvar->name);
return false;
}
// only check cheat/replicated values when the source is external
if (source == game::DvarSetSource::DVAR_SOURCE_EXTERNAL)
{
const auto cl_ingame = game::Dvar_FindVar("cl_ingame");
const auto sv_running = game::Dvar_FindVar("sv_running");
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_REPLICATED) && (cl_ingame && cl_ingame->current.enabled) && (
sv_running && !sv_running->current.enabled))
{
console::error("%s can only be changed by the server\n", dvar->name);
return false;
}
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && (sv_cheats && !sv_cheats->current.enabled))
{
console::error("%s is cheat protected\n", dvar->name);
return false;
}
}
// pass all the flag checks, allow dvar to be changed
return true;
}
const auto dvar_flag_checks_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto can_set_value = a.newLabel();
const auto zero_source = a.newLabel();
a.pushad64();
a.mov(r8, rdi);
a.mov(edx, esi);
a.mov(rcx, rbx);
a.call_aligned(apply_sv_cheats); //check if we are setting sv_cheats
a.popad64();
a.cmp(esi, 0);
a.jz(zero_source); //if the SetSource is 0 (INTERNAL) ignore flag checks
a.pushad64();
a.mov(edx, esi); //source
a.mov(rcx, rbx); //dvar
a.call_aligned(dvar_flag_checks); //protect read/write/cheat/replicated dvars
a.cmp(al, 1);
a.jz(can_set_value);
// if we get here, we are non-zero source and CANNOT set values
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
a.jmp(0x1404F0FC5);
// if we get here, we are non-zero source and CAN set values
a.bind(can_set_value);
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
a.jmp(0x1404F0D2E);
// if we get here, we are zero source and ignore flags
a.bind(zero_source);
a.jmp(0x1404F0D74);
});
void cg_set_client_dvar_from_server(const int local_client_num, game::mp::cg_s* cg, const char* dvar_id, const char* value)
{
if (dvar_id == "cg_fov"s || dvar_id == "com_maxfps"s)
{
return;
}
const auto* dvar = game::Dvar_FindVar(dvar_id);
if (dvar)
{
// If we send as string, it can't be set with source SERVERCMD because the game only allows that source on real server cmd dvars.
// Just use external instead as if it was being set by the console
game::Dvar_SetFromStringByNameFromSource(dvar_id, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL);
}
else
{
// Not a dvar name, assume it is an id and the game will handle normally
game::CG_SetClientDvarFromServer(local_client_num, cg, dvar_id, value);
}
}
void set_client_dvar_by_string(const int entity_num, const char* value)
{
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack
const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
game::SV_GameSendServerCommand(entity_num, 1, command);
}
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto set_by_string = a.newLabel();
a.pushad64();
// check if we didn't find a network dvar index
a.mov(ecx, dword_ptr(rsp, 0x8C8));
a.cmp(ecx, 0);
a.je(set_by_string);
// we found an index, handle normally
a.popad64();
a.mov(r8d, ptr(rsp, 0x848));
a.lea(r9, ptr(rsp, 0x30));
a.jmp(0x14038A5A7);
// no index, let's send the dvar as a string
a.bind(set_by_string);
a.movzx(ecx, word_ptr(rsp, 0x8C0)); //entity_num
a.lea(rdx, ptr(rsp, 0xB0)); //value
a.call_aligned(set_client_dvar_by_string);
a.popad64();
a.jmp(0x14038A5CD);
});
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp()) return;
utils::hook::nop(0x1404F0D13, 4); // let our stub handle zero-source sets
utils::hook::jump(0x1404F0D1A, dvar_flag_checks_stub, true); // check extra dvar flags when setting values
utils::hook::nop(0x14038A553, 5); // remove error in PlayerCmd_SetClientDvar if setting a non-network dvar
utils::hook::jump(0x14038A59A, player_cmd_set_client_dvar, true); // send non-network dvars as string
utils::hook::call(0x140287AED, cg_set_client_dvar_from_server); // check for dvars being sent as string before parsing ids
utils::hook::set<uint8_t>(0x14026B50E, 0xEB); // fov thing
dvars::sv_cheats = game::Dvar_RegisterBool("sv_cheats", false, game::DVAR_FLAG_REPLICATED, "Allow cheat commands and dvars on this server");
}
};
}
REGISTER_COMPONENT(dvar_cheats::component)

View File

@ -0,0 +1,86 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "dvars.hpp"
#include <utils/hook.hpp>
namespace dvars
{
struct dvar_base
{
unsigned int flags{};
};
struct dvar_bool : dvar_base
{
bool value{};
};
utils::hook::detour dvar_register_bool_hook;
template <typename T>
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name)
{
auto i = map.find(name);
if (i != map.end())
{
return &i->second;
}
return nullptr;
}
namespace override
{
static std::unordered_map<std::string, dvar_bool> register_bool_overrides;
void register_bool(const std::string& name, const bool value, const unsigned int flags)
{
dvar_bool values;
values.value = value;
values.flags = flags;
register_bool_overrides[name] = values;
}
}
std::string get_string(const std::string& dvar)
{
const auto* dvar_value = game::Dvar_FindVar(dvar.data());
if (dvar_value)
{
return {dvar_value->current.string};
}
return {};
}
const game::dvar_t* dvar_register_bool_stub(const char* name, bool value, unsigned int flags, const char* description)
{
const auto* var = find_dvar(override::register_bool_overrides, name);
if (var)
{
value = var->value;
flags = var->flags;
}
return dvar_register_bool_hook.invoke<const game::dvar_t*>(name, value, flags, description);
}
class component final : public component_interface
{
public:
void post_unpack() override
{
dvar_register_bool_hook.create(game::Dvar_RegisterBool, &dvar_register_bool_stub);
}
void pre_destroy() override
{
dvar_register_bool_hook.clear();
}
};
}
REGISTER_COMPONENT(dvars::component)

View File

@ -0,0 +1,11 @@
#pragma once
namespace dvars
{
namespace override
{
void register_bool(const std::string& name, bool value, unsigned int flags);
}
std::string get_string(const std::string& dvar);
}

View File

@ -0,0 +1,152 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "fastfiles.hpp"
#include "command.hpp"
#include "console.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/io.hpp>
namespace fastfiles
{
namespace
{
utils::hook::detour db_try_load_x_file_internal_hook;
utils::hook::detour db_find_x_asset_header_hook;
void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map)
{
console::info("Loading fastfile %s\n", zone_name);
return db_try_load_x_file_internal_hook.invoke<void>(zone_name, zone_flags, is_base_map);
}
void dump_gsc_script(const std::string& name, game::XAssetHeader header)
{
if (!dvars::g_dump_scripts->current.enabled)
{
return;
}
std::string buffer;
buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1);
buffer.append(reinterpret_cast<char*>(&header.scriptfile->compressedLen), 4);
buffer.append(reinterpret_cast<char*>(&header.scriptfile->len), 4);
buffer.append(reinterpret_cast<char*>(&header.scriptfile->bytecodeLen), 4);
buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen);
buffer.append(reinterpret_cast<char*>(header.scriptfile->bytecode), header.scriptfile->bytecodeLen);
const auto out_name = std::format("gsc_dump/{}.gscbin", name);
utils::io::write_file(out_name, buffer);
console::info("Dumped %s\n", out_name.data());
}
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{
const auto start = game::Sys_Milliseconds();
const auto result = db_find_x_asset_header_hook.invoke<game::XAssetHeader>(type, name, allow_create_default);
const auto diff = game::Sys_Milliseconds() - start;
if (type == game::ASSET_TYPE_SCRIPTFILE)
{
dump_gsc_script(name, result);
}
if (diff > 100)
{
console::print(
result.data == nullptr
? console::con_type_error
: console::con_type_warning,
"Waited %i msec for asset '%s' of type '%s'.\n",
diff,
name,
game::g_assetNames[type]
);
}
return result;
}
void reallocate_asset_pool(const game::XAssetType type, const unsigned int new_size)
{
const size_t element_size = game::DB_GetXAssetTypeSize(type);
auto* new_pool = utils::memory::get_allocator()->allocate(new_size * element_size);
std::memmove(new_pool, game::DB_XAssetPool[type], game::g_poolSize[type] * element_size);
game::DB_XAssetPool[type] = new_pool;
game::g_poolSize[type] = new_size;
}
void p_mem_free_stub(const char* name, game::PMem_Direction alloc_dir)
{
console::info("Unloaded fastfile %s\n", name);
game::PMem_Free(name, alloc_dir);
}
}
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool include_override)
{
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, include_override);
}
class component final : public component_interface
{
public:
void post_unpack() override
{
db_try_load_x_file_internal_hook.create(SELECT_VALUE(0x140275850, 0x1403237F0), &db_try_load_x_file_internal);
db_find_x_asset_header_hook.create(game::DB_FindXAssetHeader, db_find_x_asset_header_stub);
dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts to binary format");
utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub);
utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub);
command::add("loadzone", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("usage: loadzone <zone>\n");
return;
}
game::XZoneInfo info;
info.name = params.get(1);
info.allocFlags = 1;
info.freeFlags = 0;
game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_SYNC);
});
command::add("materiallist", [](const command::params& params)
{
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
{
if(header.material && header.material->name)
{
console::info("%s\n", header.material->name);
}
}, nullptr, false);
});
if (!game::environment::is_sp())
{
reallocate_asset_pool(game::ASSET_TYPE_WEAPON, 320);
}
}
};
}
REGISTER_COMPONENT(fastfiles::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace fastfiles
{
void enum_assets(game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, bool include_override);
}

View File

@ -0,0 +1,288 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "filesystem.hpp"
#include "game_module.hpp"
#include "console.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
namespace filesystem
{
namespace
{
bool initialized = false;
bool custom_path_registered = false;
std::deque<std::filesystem::path>& get_search_paths_internal()
{
static std::deque<std::filesystem::path> search_paths{};
return search_paths;
}
std::string get_binary_directory()
{
const auto dir = game_module::get_host_module().get_folder();
return utils::string::replace(dir, "/", "\\");
}
void register_custom_path_stub(const char* path, const char* dir)
{
if (!custom_path_registered)
{
custom_path_registered = true;
const auto launcher_dir = get_binary_directory();
game::FS_AddLocalizedGameDirectory(launcher_dir.data(), "data");
}
game::FS_AddLocalizedGameDirectory(path, dir);
}
void fs_startup_stub(const char* gamename)
{
console::info("[FS] Startup\n");
custom_path_registered = false;
game::FS_Startup(gamename);
}
std::vector<std::filesystem::path> get_paths(const std::filesystem::path& path)
{
std::vector<std::filesystem::path> paths{};
paths.push_back(path);
return paths;
}
bool can_insert_path(const std::filesystem::path& path)
{
for (const auto& path_ : get_search_paths_internal())
{
if (path_ == path)
{
return false;
}
}
return true;
}
void startup()
{
register_path("iw6");
register_path(get_binary_directory() + "\\data");
// game's search paths
register_path("devraw");
register_path("devraw_shared");
register_path("raw_shared");
register_path("raw");
register_path("main");
}
void check_for_startup()
{
if (!initialized)
{
initialized = true;
startup();
}
}
}
file::file(std::string name)
: name_(std::move(name))
{
char* buffer{};
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
if (size >= 0 && buffer)
{
this->valid_ = true;
this->buffer_.append(buffer, size);
game::FS_FreeFile(buffer);
}
}
bool file::exists() const
{
return this->valid_;
}
const std::string& file::get_buffer() const
{
return this->buffer_;
}
const std::string& file::get_name() const
{
return this->name_;
}
std::string read_file(const std::string& path)
{
check_for_startup();
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
return utils::io::read_file(path_.generic_string());
}
}
return {};
}
bool read_file(const std::string& path, std::string* data, std::string* real_path)
{
check_for_startup();
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::read_file(path_.generic_string(), data))
{
if (real_path != nullptr)
{
*real_path = path_.generic_string();
}
return true;
}
}
return false;
}
bool find_file(const std::string& path, std::string* real_path)
{
check_for_startup();
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
*real_path = path_.generic_string();
return true;
}
}
return false;
}
bool exists(const std::string& path)
{
check_for_startup();
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
return true;
}
}
return false;
}
void register_path(const std::filesystem::path& path)
{
const auto paths = get_paths(path);
for (const auto& path_ : paths)
{
if (can_insert_path(path_))
{
console::info("[FS] Registering path '%s'\n", path_.generic_string().data());
get_search_paths_internal().push_front(path_);
}
}
}
void unregister_path(const std::filesystem::path& path)
{
const auto paths = get_paths(path);
for (const auto& path_ : paths)
{
auto& search_paths = get_search_paths_internal();
for (auto i = search_paths.begin(); i != search_paths.end();)
{
if (*i == path_)
{
console::info("[FS] Unregistering path '%s'\n", path_.generic_string().data());
i = search_paths.erase(i);
}
else
{
++i;
}
}
}
}
std::vector<std::string> get_search_paths()
{
std::vector<std::string> paths{};
for (const auto& path : get_search_paths_internal())
{
paths.push_back(path.generic_string());
}
return paths;
}
std::vector<std::string> get_search_paths_rev()
{
std::vector<std::string> paths{};
const auto& search_paths = get_search_paths_internal();
for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i)
{
paths.push_back(i->generic_string());
}
return paths;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Set fs_basegame
utils::hook::inject(SELECT_VALUE(0x14041C053, 0x1404DDA13), "iw6");
if (game::environment::is_sp())
{
utils::hook::call(0x14041B744, fs_startup_stub);
utils::hook::call(0x14041CD00, register_custom_path_stub);
utils::hook::call(0x14041CD20, register_custom_path_stub);
utils::hook::call(0x14041CD5F, register_custom_path_stub);
}
else
{
utils::hook::call(0x1404DD704, fs_startup_stub);
utils::hook::call(0x1404DDB43, fs_startup_stub);
utils::hook::call(0x1404DE550, register_custom_path_stub);
utils::hook::call(0x1404DE570, register_custom_path_stub);
utils::hook::call(0x1404DE5AF, register_custom_path_stub);
}
}
};
}
REGISTER_COMPONENT(filesystem::component)

View File

@ -0,0 +1,30 @@
#pragma once
namespace filesystem
{
class file
{
public:
file(std::string name);
[[nodiscard]] bool exists() const;
[[nodiscard]] const std::string& get_buffer() const;
[[nodiscard]] const std::string& get_name() const;
private:
bool valid_ = false;
std::string name_;
std::string buffer_;
};
std::string read_file(const std::string& path);
bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr);
bool find_file(const std::string& path, std::string* real_path);
bool exists(const std::string& path);
void register_path(const std::filesystem::path& path);
void unregister_path(const std::filesystem::path& path);
std::vector<std::string> get_search_paths();
std::vector<std::string> get_search_paths_rev();
}

View File

@ -0,0 +1,183 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace fps
{
namespace
{
const game::dvar_t* cg_drawFPS;
const game::dvar_t* cg_drawPing;
float fps_color[4] = {0.6f, 1.0f, 0.0f, 1.0f};
float origin_color[4] = {1.0f, 0.67f, 0.13f, 1.0f};
float ping_color[4] = {1.0f, 1.0f, 1.0f, 0.65f};
struct cg_perf_data
{
std::chrono::time_point<std::chrono::steady_clock> perf_start;
std::int32_t current_ms{};
std::int32_t previous_ms{};
std::int32_t frame_ms{};
std::int32_t history[32]{};
std::int32_t count{};
std::int32_t index{};
std::int32_t instant{};
std::int32_t total{};
float average{};
float variance{};
std::int32_t min{};
std::int32_t max{};
};
cg_perf_data cg_perf = cg_perf_data();
void perf_calc_fps(cg_perf_data* data, const std::int32_t value)
{
data->history[data->index % 32] = value;
data->instant = value;
data->min = std::numeric_limits<int>::max();
data->max = 0;
data->average = 0.0f;
data->variance = 0.0f;
data->total = 0;
for (auto i = 0; i < data->count; ++i)
{
const std::int32_t idx = (data->index - i) % 32;
if (idx < 0)
{
break;
}
data->total += data->history[idx];
if (data->min > data->history[idx])
{
data->min = data->history[idx];
}
if (data->max < data->history[idx])
{
data->max = data->history[idx];
}
}
data->average = static_cast<float>(data->total) / static_cast<float>(data->count);
++data->index;
}
void perf_update()
{
cg_perf.count = 32;
cg_perf.current_ms = static_cast<std::int32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count());
cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms;
cg_perf.previous_ms = cg_perf.current_ms;
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
utils::hook::invoke<void>(SELECT_VALUE(0x1405806E0, 0x140658E30));
}
void cg_draw_fps()
{
if (cg_drawFPS && cg_drawFPS->current.integer != 0)
{
const auto fps = static_cast<std::int32_t>(static_cast<float>(1000.0f /
static_cast<float>(cg_perf.average)) + 9.313225746154785e-10);
auto* font = game::R_RegisterFont("fonts/normalfont");
if (!font) return;
const auto* const fps_string = utils::string::va("%i", fps);
const auto scale = 1.0f;
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) - game::R_TextWidth(
fps_string, std::numeric_limits<int>::max(), font) * scale;
const auto y = font->pixelHeight * 1.2f;
game::R_AddCmdDrawText(fps_string, std::numeric_limits<int>::max(), font, x, y, scale, scale, 0.0f, fps_color, 6);
if (game::mp::g_entities && cg_drawFPS->current.integer > 1 && game::SV_Loaded())
{
const auto* const origin_string = utils::string::va("%f, %f, %f",
game::mp::g_entities[0].client->ps.origin[0] *
1.0,
game::mp::g_entities[0].client->ps.origin[1] *
1.0,
game::mp::g_entities[0].client->ps.origin[2] *
1.0);
const auto origin_x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 10.0f) -
game::R_TextWidth(origin_string, std::numeric_limits<int>::max(), font) * scale;
game::R_AddCmdDrawText(origin_string, std::numeric_limits<int>::max(), font, origin_x, y + 50, scale, scale, 0.0f,
origin_color, 6);
}
}
}
void cg_draw_ping()
{
if (cg_drawPing->current.integer != 0 && game::CL_IsCgameInitialized())
{
const auto ping = *reinterpret_cast<int*>(0x1419E5100);
auto* font = game::R_RegisterFont("fonts/normalfont");
if (!font) return;
auto* const ping_string = utils::string::va("Ping: %i", ping);
const auto scale = 1.0f;
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 375.0f) - game::R_TextWidth(
ping_string, 0x7FFFFFFF, font) * scale;
const auto y = font->pixelHeight * 1.2f;
game::R_AddCmdDrawText(ping_string, std::numeric_limits<int>::max(), font, x, y, scale, scale, 0.0f, ping_color, 6);
}
}
const game::dvar_t* cg_draw_fps_register_stub(const char* dvar_name, const char** value_list, const int default_index, unsigned int /*flags*/, const char* description)
{
cg_drawFPS = game::Dvar_RegisterEnum(dvar_name, value_list, default_index, game::DVAR_FLAG_SAVED, description);
return cg_drawFPS;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
// fps setup
cg_perf.perf_start = std::chrono::high_resolution_clock::now();
utils::hook::call(SELECT_VALUE(0x140242C11, 0x1402CF457), &perf_update);
// change cg_drawfps flags to saved
utils::hook::call(SELECT_VALUE(0x1401F400A, 0x140272B98), &cg_draw_fps_register_stub);
cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping");
scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer);
scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer);
}
};
}
REGISTER_COMPONENT(fps::component)

View File

@ -0,0 +1,781 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game_console.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
#include <version.hpp>
#define console_font game::R_RegisterFont("fonts/consolefont")
#define material_white game::Material_RegisterHandle("white")
namespace game_console
{
namespace
{
struct console_globals
{
float x{};
float y{};
float left_x{};
float font_height{};
bool may_auto_complete{};
char auto_complete_choice[64]{};
int info_line_count{};
};
using output_queue = std::deque<std::string>;
struct ingame_console
{
char buffer[256]{};
int cursor{};
int font_height{};
int visible_line_count{};
int visible_pixel_width{};
float screen_min[2]{}; //left & top
float screen_max[2]{}; //right & bottom
console_globals globals{};
bool output_visible{};
int display_line_offset{};
int line_count{};
utils::concurrency::container<output_queue, std::recursive_mutex> output{};
};
ingame_console con{};
std::int32_t history_index = -1;
std::deque<std::string> history{};
std::string fixed_input{};
std::vector<std::string> matches{};
float color_white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float color_iw6[4] = { 0.0f, 0.7f, 1.0f, 1.0f };
void clear()
{
strncpy_s(con.buffer, "", sizeof(con.buffer));
con.cursor = 0;
fixed_input = "";
matches.clear();
}
void print_internal(const std::string& data)
{
con.output.access([&](output_queue& output)
{
if (con.visible_line_count > 0
&& con.display_line_offset == (output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
output.push_back(data);
if (output.size() > 512)
{
output.pop_front();
}
});
}
void toggle_console()
{
clear();
con.output_visible = false;
*game::keyCatchers ^= 1;
}
void toggle_console_output()
{
con.output_visible = con.output_visible == 0;
}
void check_resize()
{
con.screen_min[0] = 6.0f;
con.screen_min[1] = 6.0f;
con.screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 6.0f;
con.screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1] - 6.0f;
if (console_font)
{
con.font_height = console_font->pixelHeight;
con.visible_line_count = static_cast<int>((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2)
) -
24.0f) / con.font_height;
con.visible_pixel_width = static_cast<int>(((con.screen_max[0] - con.screen_min[0]) - 10.0f) - 18.0f);
}
else
{
con.font_height = 0;
con.visible_line_count = 0;
con.visible_pixel_width = 0;
}
}
void draw_box(const float x, const float y, const float w, const float h, float* color)
{
game::vec4_t dark_color;
dark_color[0] = color[0] * 0.5f;
dark_color[1] = color[1] * 0.5f;
dark_color[2] = color[2] * 0.5f;
dark_color[3] = color[3];
game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, material_white);
game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
material_white);
game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
material_white);
}
void draw_input_box(const int lines, float* color)
{
draw_box(
con.globals.x - 6.0f,
con.globals.y - 6.0f,
(con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]),
(lines * con.globals.font_height) + 12.0f,
color);
}
void draw_input_text_and_over(const char* str, float* color)
{
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
con.globals.y + con.globals.font_height, 1,
1, 0, color, 0);
con.globals.x = game::R_TextWidth(str, 0, console_font) + con.globals.x + 6.0f;
}
void draw_hint_box(const int lines, float* color, [[maybe_unused]] float offset_x = 0.0f,
[[maybe_unused]] float offset_y = 0.0f)
{
const auto _h = lines * con.globals.font_height + 12.0f;
const auto _y = con.globals.y - 3.0f + con.globals.font_height + 12.0f;
const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]);
draw_box(con.globals.x - 6.0f, _y, _w, _h, color);
}
void draw_hint_text(const int line, const char* text, float* color, const float offset = 0.0f)
{
const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f;
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color,
0);
}
void draw_input()
{
con.globals.font_height = static_cast<float>(console_font->pixelHeight);
con.globals.x = con.screen_min[0] + 6.0f;
con.globals.y = con.screen_min[1] + 6.0f;
con.globals.left_x = con.screen_min[0] + 6.0f;
draw_input_box(1, dvars::con_inputBoxColor->current.vector);
draw_input_text_and_over("iw6-mod: " VERSION ">", color_iw6);
con.globals.left_x = con.globals.x;
con.globals.auto_complete_choice[0] = 0;
game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, console_font, con.globals.x,
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0,
con.cursor, '|');
// check if using a prefixed '/' or not
const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\')
? std::string(con.buffer).substr(1)
: std::string(con.buffer);
if (!input.length())
{
return;
}
if (input != fixed_input)
{
matches.clear();
if (input.find(" ") != std::string::npos)
{
find_matches(input.substr(0, input.find(" ")), matches, true);
}
else
{
find_matches(input, matches, false);
}
fixed_input = input;
}
con.globals.may_auto_complete = false;
if (matches.size() > 24)
{
draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector);
draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()),
dvars::con_inputDvarMatchColor->current.vector);
}
else if (matches.size() == 1)
{
const auto dvar = game::Dvar_FindVar(matches[0].data());
const auto line_count = dvar ? 2 : 1;
draw_hint_box(line_count, dvars::con_inputHintBoxColor->current.vector);
draw_hint_text(0, matches[0].data(),
dvar
? dvars::con_inputDvarMatchColor->current.vector
: dvars::con_inputCmdMatchColor->current.vector);
if (dvar)
{
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current),
dvars::con_inputDvarValueColor->current.vector, offset);
draw_hint_text(1, " default", dvars::con_inputDvarInactiveValueColor->current.vector);
draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset),
dvars::con_inputDvarInactiveValueColor->current.vector, offset);
}
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice));
con.globals.may_auto_complete = true;
}
else if (matches.size() > 1)
{
draw_hint_box(static_cast<int>(matches.size()), dvars::con_inputHintBoxColor->current.vector);
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
for (size_t i = 0; i < matches.size(); i++)
{
const auto dvar = game::Dvar_FindVar(matches[i].data());
draw_hint_text(static_cast<int>(i), matches[i].data(),
dvar
? dvars::con_inputDvarMatchColor->current.vector
: dvars::con_inputCmdMatchColor->current.vector);
if (dvar)
{
draw_hint_text(static_cast<int>(i), game::Dvar_ValueToString(dvar, dvar->current),
dvars::con_inputDvarValueColor->current.vector, offset);
}
}
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice));
con.globals.may_auto_complete = true;
}
}
void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
{
const auto _x = (x + width) - 10.0f;
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
auto _height = height;
if (output.size() > con.visible_line_count)
{
const auto percentage = static_cast<float>(con.visible_line_count) / output.size();
_height *= percentage;
const auto remainingSpace = height - _height;
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (output.size() - con.
visible_line_count);
y = y + (remainingSpace * percentageAbove);
}
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
}
void draw_output_text(const float x, float y, output_queue& output)
{
const auto offset = output.size() >= con.visible_line_count
? 0.0f
: (con.font_height * (con.visible_line_count - output.size()));
for (auto i = 0; i < con.visible_line_count; i++)
{
y = console_font->pixelHeight + y;
const auto index = i + con.display_line_offset;
if (index >= output.size())
{
break;
}
game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
0.0f, color_white, 0);
}
}
void draw_output_window()
{
con.output.access([](output_queue& output)
{
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
const auto x = con.screen_min[0] + 6.0f;
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
game::R_AddCmdDrawText(game::Dvar_FindVar("version")->current.string, 0x7FFFFFFF, console_font, x,
((height - 12.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_iw6, 0);
draw_output_scrollbar(x, y, width, height, output);
draw_output_text(x, y, output);
});
}
void draw_console()
{
check_resize();
if (*game::keyCatchers & 1)
{
if (!(*game::keyCatchers & 1))
{
con.output_visible = false;
}
if (con.output_visible)
{
draw_output_window();
}
draw_input();
}
}
}
void print_internal(const char* fmt, ...)
{
char va_buffer[0x200] = { 0 };
va_list ap;
va_start(ap, fmt);
vsprintf_s(va_buffer, fmt, ap);
va_end(ap);
const auto formatted = std::string(va_buffer);
const auto lines = utils::string::split(formatted, '\n');
for (const auto& line : lines)
{
print_internal(line);
}
}
void print(const int type, const std::string& data)
{
try
{
if (game::environment::is_dedi())
{
return;
}
}
catch (std::exception&)
{
return;
}
const auto lines = utils::string::split(data, '\n');
for (const auto& line : lines)
{
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
}
}
bool console_char_event(const int localClientNum, const int key)
{
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{
return false;
}
if (*game::keyCatchers & 1)
{
if (key == game::keyNum_t::K_TAB) // tab (auto complete)
{
if (con.globals.may_auto_complete)
{
const auto firstChar = con.buffer[0];
clear();
if (firstChar == '\\' || firstChar == '/')
{
con.buffer[0] = firstChar;
con.buffer[1] = '\0';
}
strncat_s(con.buffer, con.globals.auto_complete_choice, 64);
con.cursor = static_cast<int>(std::string(con.buffer).length());
if (con.cursor != 254)
{
con.buffer[con.cursor++] = ' ';
con.buffer[con.cursor] = '\0';
}
}
}
if (key == 'v' - 'a' + 1) // paste
{
const auto clipboard = utils::string::get_clipboard_data();
if (clipboard.empty())
{
return false;
}
for (auto i = 0; i < clipboard.length(); i++)
{
console_char_event(localClientNum, clipboard[i]);
}
return false;
}
if (key == 'c' - 'a' + 1) // clear
{
clear();
con.line_count = 0;
con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1;
history.clear();
return false;
}
if (key == 'h' - 'a' + 1) // backspace
{
if (con.cursor > 0)
{
memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor,
strlen(con.buffer) + 1 - con.cursor);
con.cursor--;
}
return false;
}
if (key < 32)
{
return false;
}
if (con.cursor == 256 - 1)
{
return false;
}
memmove(con.buffer + con.cursor + 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor);
con.buffer[con.cursor] = static_cast<char>(key);
con.cursor++;
if (con.cursor == strlen(con.buffer) + 1)
{
con.buffer[con.cursor] = 0;
}
}
return true;
}
bool console_key_event(const int localClientNum, const int key, const int down)
{
if (key == game::keyNum_t::K_F10)
{
if(game::mp::svs_clients[localClientNum].header.state > game::CS_FREE)
{
return false;
}
game::Cmd_ExecuteSingleCommand(localClientNum, 0, "lui_open menu_systemlink_join\n");
}
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
{
if (!down)
{
return false;
}
if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down)
{
if (!(*game::keyCatchers & 1))
toggle_console();
toggle_console_output();
return false;
}
toggle_console();
return false;
}
if (*game::keyCatchers & 1)
{
if (down)
{
if (key == game::keyNum_t::K_UPARROW)
{
if (++history_index >= history.size())
{
history_index = static_cast<int>(history.size()) - 1;
}
clear();
if (history_index != -1)
{
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
con.cursor = static_cast<int>(strlen(con.buffer));
}
}
else if (key == game::keyNum_t::K_DOWNARROW)
{
if (--history_index < -1)
{
history_index = -1;
}
clear();
if (history_index != -1)
{
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
con.cursor = static_cast<int>(strlen(con.buffer));
}
}
if (key == game::keyNum_t::K_RIGHTARROW)
{
if (con.cursor < strlen(con.buffer))
{
con.cursor++;
}
return false;
}
if (key == game::keyNum_t::K_LEFTARROW)
{
if (con.cursor > 0)
{
con.cursor--;
}
return false;
}
//scroll through output
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
{
con.output.access([](output_queue& output)
{
if (output.size() > con.visible_line_count && con.display_line_offset > 0)
{
con.display_line_offset--;
}
});
}
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
{
con.output.access([](output_queue& output)
{
if (output.size() > con.visible_line_count
&& con.display_line_offset < (output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
});
}
if (key == game::keyNum_t::K_ENTER)
{
game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
if (history_index != -1)
{
const auto itr = history.begin() + history_index;
if (*itr == con.buffer)
{
history.erase(history.begin() + history_index);
}
}
history.push_front(con.buffer);
console::info("]%s\n", con.buffer);
if (history.size() > 10)
{
history.erase(history.begin() + 10);
}
history_index = -1;
clear();
}
}
}
return true;
}
bool match_compare(const std::string& input, const std::string& text, const bool exact)
{
if (exact && text == input) return true;
if (!exact && text.find(input) != std::string::npos) return true;
return false;
}
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
{
input = utils::string::to_lower(input);
for (int i = 0; i < *game::dvarCount; i++)
{
if (game::sortedDvars[i] && game::sortedDvars[i]->name)
{
std::string name = utils::string::to_lower(game::sortedDvars[i]->name);
if (match_compare(input, name, exact))
{
suggestions.push_back(game::sortedDvars[i]->name);
}
if (exact && suggestions.size() > 1)
{
return;
}
}
}
game::cmd_function_s* cmd = (*game::cmd_functions);
while (cmd)
{
if (cmd->name)
{
std::string name = utils::string::to_lower(cmd->name);
if (match_compare(input, name, exact))
{
suggestions.push_back(cmd->name);
}
if (exact && suggestions.size() > 1)
{
return;
}
}
cmd = cmd->next;
}
}
class component final : public component_interface
{
public:
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
scheduler::loop(draw_console, scheduler::pipeline::renderer);
}
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
// initialize our structs
con.cursor = 0;
con.visible_line_count = 0;
con.output_visible = false;
con.display_line_offset = 0;
con.line_count = 0;
strncpy_s(con.buffer, "", sizeof(con.buffer));
con.globals.x = 0.0f;
con.globals.y = 0.0f;
con.globals.left_x = 0.0f;
con.globals.font_height = 0.0f;
con.globals.may_auto_complete = false;
con.globals.info_line_count = 0;
strncpy_s(con.globals.auto_complete_choice, "", sizeof(con.globals.auto_complete_choice));
// add clear command
command::add("clear", [&]()
{
clear();
con.line_count = 0;
con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1;
history.clear();
});
// add our dvars
dvars::con_inputBoxColor = game::Dvar_RegisterVec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f,
game::DvarFlags::DVAR_FLAG_SAVED,
"color of console input box");
dvars::con_inputHintBoxColor = game::Dvar_RegisterVec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f,
0.0f, 1.0f,
game::DvarFlags::DVAR_FLAG_SAVED, "color of console input hint box");
dvars::con_outputBarColor = game::Dvar_RegisterVec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f,
1.0f, game::DvarFlags::DVAR_FLAG_SAVED,
"color of console output bar");
dvars::con_outputSliderColor = game::Dvar_RegisterVec4("con_outputSliderColor", 0.0f, 0.7f, 1.0f, 1.00f,
0.0f, 1.0f,
game::DvarFlags::DVAR_FLAG_SAVED, "color of console output slider");
dvars::con_outputWindowColor = game::Dvar_RegisterVec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f,
0.0f,
1.0f, game::DvarFlags::DVAR_FLAG_SAVED, "color of console output window");
dvars::con_inputDvarMatchColor = game::Dvar_RegisterVec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f,
0.0f,
1.0f, game::DvarFlags::DVAR_FLAG_SAVED, "color of console matched dvar");
dvars::con_inputDvarValueColor = game::Dvar_RegisterVec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f,
0.0f,
1.0f, game::DvarFlags::DVAR_FLAG_SAVED, "color of console matched dvar value");
dvars::con_inputDvarInactiveValueColor = game::Dvar_RegisterVec4(
"con_inputDvarInactiveValueColor", 0.8f, 0.8f,
0.8f, 1.0f, 0.0f, 1.0f, game::DvarFlags::DVAR_FLAG_SAVED,
"color of console inactive dvar value");
dvars::con_inputCmdMatchColor = game::Dvar_RegisterVec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
0.0f,
1.0f, game::DvarFlags::DVAR_FLAG_SAVED, "color of console matched command");
}
};
}
REGISTER_COMPONENT(game_console::component)

View File

@ -0,0 +1,12 @@
#pragma once
namespace game_console
{
void print(int type, const std::string& data);
bool console_char_event(int local_client_num, int key);
bool console_key_event(int local_client_num, int key, int down);
bool match_compare(const std::string& input, const std::string& text, const bool exact);
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
}

View File

@ -0,0 +1,116 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
#include "console.hpp"
#include "game_log.hpp"
#include "gsc/script_extension.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace game_log
{
namespace
{
void gscr_log_print()
{
char buf[1024]{};
std::size_t out_chars = 0;
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
{
const auto* value = game::Scr_GetString(i);
const auto len = std::strlen(value);
out_chars += len;
if (out_chars >= sizeof(buf))
{
break;
}
strncat_s(buf, value, _TRUNCATE);
}
g_log_printf("%s", buf);
}
}
void g_log_printf(const char* fmt, ...)
{
const auto* log = dvars::g_log->current.string;
if (*log == '\0')
{
return;
}
char buffer[0x400]{};
va_list ap;
va_start(ap, fmt);
vsprintf_s(buffer, fmt, ap);
va_end(ap);
const auto time = *game::level_time / 1000;
utils::io::write_file(log, utils::string::va("%3i:%i%i %s",
time / 60,
time % 60 / 10,
time % 60 % 10,
buffer
), true);
}
class component final : public component_interface
{
public:
void post_load() override
{
if (game::environment::is_sp())
{
return;
}
utils::hook::set<game::BuiltinFunction>(0x1409E8A20, gscr_log_print);
scheduler::once([]
{
dvars::g_log = game::Dvar_RegisterString("g_log", "logs/games_mp.log", game::DVAR_FLAG_NONE, "Log file name");
}, scheduler::pipeline::main);
scripting::on_init([]
{
console::info("------- Game Initialization -------\n");
console::info("gamename: IW6\n");
console::info("gamedate: " __DATE__ "\n");
const auto* log = dvars::g_log->current.string;
if (*log == '\0')
{
console::info("Not logging to disk.\n");
return;
}
console::info("Logging to disk: '%s'.\n", log);
g_log_printf("------------------------------------------------------------\n");
g_log_printf("InitGame\n");
});
scripting::on_shutdown([](int free_scripts)
{
console::info("==== ShutdownGame (%d) ====\n", free_scripts);
g_log_printf("ShutdownGame:\n");
g_log_printf("------------------------------------------------------------\n");
});
}
};
}
REGISTER_COMPONENT(game_log::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace game_log
{
void g_log_printf(const char* fmt, ...);
}

View File

@ -0,0 +1,122 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game_module.hpp"
#include <utils/hook.hpp>
namespace game_module
{
namespace
{
utils::hook::detour handle_a_hook;
utils::hook::detour handle_w_hook;
utils::hook::detour handle_ex_a_hook;
utils::hook::detour handle_ex_w_hook;
utils::hook::detour file_name_a_hook;
utils::hook::detour file_name_w_hook;
HMODULE __stdcall get_module_handle_a(const LPCSTR module_name)
{
if (!module_name)
{
return get_game_module();
}
return handle_a_hook.invoke<HMODULE>(module_name);
}
HMODULE __stdcall get_module_handle_w(const LPWSTR module_name)
{
if (!module_name)
{
return get_game_module();
}
return handle_w_hook.invoke<HMODULE>(module_name);
}
BOOL __stdcall get_module_handle_ex_a(const DWORD flags, const LPCSTR module_name, HMODULE* hmodule)
{
if (!module_name)
{
*hmodule = get_game_module();
return TRUE;
}
return handle_ex_a_hook.invoke<BOOL>(flags, module_name, hmodule);
}
BOOL __stdcall get_module_handle_ex_w(const DWORD flags, const LPCWSTR module_name, HMODULE* hmodule)
{
if (!module_name)
{
*hmodule = get_game_module();
return TRUE;
}
return handle_ex_w_hook.invoke<BOOL>(flags, module_name, hmodule);
}
DWORD __stdcall get_module_file_name_a(HMODULE hmodule, const LPSTR filename, const DWORD size)
{
if (!hmodule)
{
hmodule = get_game_module();
}
return file_name_a_hook.invoke<DWORD>(hmodule, filename, size);
}
DWORD __stdcall get_module_file_name_w(HMODULE hmodule, const LPWSTR filename, const DWORD size)
{
if (!hmodule || utils::nt::library(hmodule) == get_game_module())
{
hmodule = get_host_module();
}
return file_name_w_hook.invoke<DWORD>(hmodule, filename, size);
}
void hook_module_resolving()
{
handle_a_hook.create(&GetModuleHandleA, &get_module_handle_a);
handle_w_hook.create(&GetModuleHandleW, &get_module_handle_w);
handle_ex_w_hook.create(&GetModuleHandleExA, &get_module_handle_ex_a);
handle_ex_w_hook.create(&GetModuleHandleExW, &get_module_handle_ex_w);
file_name_a_hook.create(&GetModuleFileNameA, &get_module_file_name_a);
file_name_w_hook.create(&GetModuleFileNameW, &get_module_file_name_w);
}
}
utils::nt::library get_game_module()
{
static utils::nt::library game{HMODULE(0x140000000)};
return game;
}
utils::nt::library get_host_module()
{
static utils::nt::library host{};
return host;
}
class component final : public component_interface
{
public:
void post_start() override
{
get_host_module();
}
void post_load() override
{
#ifdef INJECT_HOST_AS_LIB
hook_module_resolving();
#else
assert(get_host_module() == get_game_module());
#endif
}
};
}
REGISTER_COMPONENT(game_module::component)

View File

@ -0,0 +1,9 @@
#pragma once
#include <utils/nt.hpp>
namespace game_module
{
utils::nt::library get_game_module();
utils::nt::library get_host_module();
}

View File

@ -0,0 +1,403 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace gameplay
{
namespace
{
template <typename T, typename R>
constexpr auto VectorScale(T v, R s, T out) { out[0] = v[0] * s; out[1] = v[1] * s; out[2] = v[2] * s; }
utils::hook::detour pm_weapon_use_ammo_hook;
int stuck_in_client_stub(void* self)
{
if (dvars::g_playerEjection->current.enabled)
{
return utils::hook::invoke<int>(0x140386950, self); // StuckInClient
}
return 0;
}
void cm_transformed_capsule_trace_stub(game::trace_t* results, const float* start, const float* end,
game::Bounds* bounds, game::Bounds* capsule, int contents, const float* origin, const float* angles)
{
if (dvars::g_playerCollision->current.enabled)
{
utils::hook::invoke<void>(0x1403F3050,
results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
}
}
const auto g_gravity_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(rax);
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::g_gravity)));
a.mov(eax, dword_ptr(rax, 0x10));
a.mov(dword_ptr(rbx, 0x5C), eax);
a.mov(eax, ptr(rbx, 0x33E8));
a.mov(ptr(rbx, 0x25C), eax);
a.pop(rax);
a.jmp(0x1403828D5);
});
const auto g_speed_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(rax);
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::g_speed)));
a.mov(eax, dword_ptr(rax, 0x10));
a.mov(dword_ptr(rdi, 0x60), eax);
a.pop(rax);
a.mov(eax, ptr(rdi, 0xEA4));
a.add(eax, ptr(rdi, 0xEA0));
a.jmp(0x140383796);
});
const auto pm_bouncing_stub_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto no_bounce = a.newLabel();
const auto loc_14046ED26 = a.newLabel();
a.push(rax);
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::pm_bouncing)));
a.mov(al, byte_ptr(rax, 0x10));
a.cmp(ptr(rbp, -0x40), al);
a.pop(rax);
a.jz(no_bounce);
a.jmp(0x14046EC7E);
a.bind(no_bounce);
a.cmp(ptr(rbp, -0x80), r13d);
a.jnz(loc_14046ED26);
a.jmp(0x14046EC6C);
a.bind(loc_14046ED26);
a.jmp(0x14046ED26);
});
const auto pm_bouncing_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto no_bounce = a.newLabel();
const auto loc_140228FB8 = a.newLabel();
a.push(rax);
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::pm_bouncing)));
a.mov(al, byte_ptr(rax, 0x10));
a.cmp(byte_ptr(rbp, -0x38), al);
a.pop(rax);
a.jz(no_bounce);
a.jmp(0x140229019);
a.bind(no_bounce);
a.cmp(dword_ptr(rbp, -0x70), 0);
a.jnz(loc_140228FB8);
a.jmp(0x14022900B);
a.bind(loc_140228FB8);
a.jmp(0x140228FB8);
});
const void pm_crashland_stub(void* ps, void* pml)
{
if (dvars::jump_enableFallDamage->current.enabled)
{
reinterpret_cast<void(*)(void*, void*)>(0x140220000)(ps, pml);
}
}
float get_jump_height_stub(void* pmove)
{
auto jump_height = reinterpret_cast<float(*)(void*)>(0x140213140)(pmove);
if (jump_height == 39.f)
{
jump_height = dvars::jump_height->current.value;
}
return jump_height;
}
void jump_apply_slowdown_stub(game::mp::playerState_s* ps)
{
assert(ps->pm_flags & game::PMF_JUMPING);
float scale = 1.0f;
if (ps->pm_time > 1800)
{
game::Jump_ClearState(ps);
scale = 0.65f;
}
else if (ps->pm_time == 0)
{
if (ps->jumpOriginZ + 18.0f <= ps->origin[2])
{
ps->pm_time = 1200;
scale = 0.5f;
}
else
{
ps->pm_time = 1800;
scale = 0.65f;
}
}
if (dvars::jump_slowdownEnable->current.enabled)
{
VectorScale(ps->velocity, scale, ps->velocity);
}
}
float jump_get_slowdown_friction(game::mp::playerState_s* ps)
{
assert(ps->pm_flags & game::PMF_JUMPING);
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
if (!dvars::jump_slowdownEnable->current.enabled)
{
return 1.0f;
}
if (ps->pm_time < 1700)
{
return static_cast<float>(ps->pm_time) * 1.5f * 0.00058823527f + 1.0f;
}
return 2.5f;
}
float jump_reduce_friction_stub(game::mp::playerState_s* ps)
{
float control;
assert(ps->pm_flags & game::PMF_JUMPING);
if (ps->pm_time > game::JUMP_LAND_SLOWDOWN_TIME)
{
game::Jump_ClearState(ps);
control = 1.0f;
}
else
{
control = jump_get_slowdown_friction(ps);
}
return control;
}
float jump_get_land_factor(game::mp::playerState_s* ps)
{
assert(ps->pm_flags & game::PMF_JUMPING);
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
if (!dvars::jump_slowdownEnable->current.enabled)
{
return 1.0f;
}
if (ps->pm_time < 1700)
{
return static_cast<float>(ps->pm_time) * 1.5f * 0.00058823527f + 1.0f;
}
return 2.5f;
}
void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float height)
{
static_assert(offsetof(game::mp::playerState_s, groundEntityNum) == 0x70);
static_assert(offsetof(game::mp::playerState_s, pm_time) == 0x8);
static_assert(offsetof(game::mp::playerState_s, sprintState.sprintButtonUpRequired) == 0x240);
static_assert(offsetof(game::pml_t, frametime) == 0x24);
static_assert(offsetof(game::pml_t, walking) == 0x2C);
static_assert(offsetof(game::pml_t, groundPlane) == 0x30);
static_assert(offsetof(game::pml_t, almostGroundPlane) == 0x34);
float factor;
float velocity_sqrd;
game::mp::playerState_s* ps;
ps = static_cast<game::mp::playerState_s*>(pm->ps);
assert(ps);
velocity_sqrd = (height * 2.0f) * static_cast<float>(ps->gravity);
if ((ps->pm_flags & game::PMF_JUMPING) != 0 && ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME)
{
factor = jump_get_land_factor(ps);
assert(factor);
velocity_sqrd = velocity_sqrd / factor;
}
pml->walking = 0;
pml->groundPlane = 0;
ps->groundEntityNum = game::ENTITYNUM_NONE;
ps->jumpTime = pm->cmd.serverTime;
ps->jumpOriginZ = ps->origin[2];
ps->velocity[2] = std::sqrtf(velocity_sqrd);
ps->pm_flags &= ~(game::PMF_UNK1 | game::PMF_UNK2);
ps->pm_flags |= game::PMF_JUMPING;
ps->pm_time = 0;
ps->sprintState.sprintButtonUpRequired = 0;
ps->aimSpreadScale = ps->aimSpreadScale + dvars::jump_spreadAdd->current.value;
if (ps->aimSpreadScale > 255.0f)
{
ps->aimSpreadScale = 255.0f;
}
}
const auto jump_push_off_ladder_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::jump_ladderPushVel)));
a.movaps(xmm8, dword_ptr(rax, 0x10));
a.mulss(xmm6, xmm8);
a.mulss(xmm7, xmm8);
a.jmp(0x140213494);
});
void pm_player_trace_stub(game::pmove_t* move, game::trace_t* trace, const float* f3,
const float* f4, const game::Bounds* bounds, int a6, int a7)
{
game::PM_playerTrace(move, trace, f3, f4, bounds, a6, a7);
if (dvars::g_enableElevators->current.enabled)
{
trace->startsolid = false;
}
}
void pm_trace_stub(const game::pmove_t* move, game::trace_t* trace, const float* f3,
const float* f4, const game::Bounds* bounds, int a6, int a7)
{
game::PM_trace(move, trace, f3, f4, bounds, a6, a7);
if (dvars::g_enableElevators->current.enabled)
{
trace->allsolid = false;
}
}
void pm_weapon_use_ammo_stub(game::playerState_s* ps, game::Weapon weapon,
bool is_alternate, int amount, game::PlayerHandIndex hand)
{
if (!dvars::player_sustainAmmo->current.enabled)
{
pm_weapon_use_ammo_hook.invoke<void>(ps, weapon, is_alternate, amount, hand);
}
}
game::mp::gentity_s* weapon_rocket_launcher_fire_stub(game::mp::gentity_s* ent, game::Weapon weapon, float spread, game::weaponParms* wp,
const float* gun_vel, game::mp::missileFireParms* fire_parms, bool magic_bullet)
{
auto* result = utils::hook::invoke<game::mp::gentity_s*>(0x1403DB8A0, ent, weapon, spread, wp, gun_vel, fire_parms, magic_bullet);
if (ent->client != nullptr && wp->weapDef->inventoryType != game::WEAPINVENTORY_EXCLUSIVE)
{
const auto scale = dvars::g_rocketPushbackScale->current.value;
ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * scale;
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * scale;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * scale;
}
return result;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Implement bouncing dvar
if (game::environment::is_sp())
{
utils::hook::nop(0x14046EC5C, 16);
}
utils::hook::jump(
SELECT_VALUE(0x14046EC5C, 0x140228FFF), SELECT_VALUE(pm_bouncing_stub_sp, pm_bouncing_stub_mp), true);
dvars::pm_bouncing = game::Dvar_RegisterBool("pm_bouncing", false,
game::DVAR_FLAG_REPLICATED, "Enable bouncing");
dvars::player_sustainAmmo = game::Dvar_RegisterBool("player_sustainAmmo", false,
game::DVAR_FLAG_REPLICATED, "Firing weapon will not decrease clip ammo.");
pm_weapon_use_ammo_hook.create(SELECT_VALUE(0x140479640, 0x140238A90), &pm_weapon_use_ammo_stub);
if (game::environment::is_sp()) return;
// Implement player ejection dvar
dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
utils::hook::call(0x140382C13, stuck_in_client_stub);
// Implement player collision dvar
dvars::g_playerCollision = game::Dvar_RegisterBool("g_playerCollision", true, game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off");
utils::hook::call(0x14048A49A, cm_transformed_capsule_trace_stub); // SV_ClipMoveToEntity
utils::hook::call(0x1402B5B88, cm_transformed_capsule_trace_stub); // CG_ClipMoveToEntity
// Implement gravity dvar
utils::hook::nop(0x1403828C8, 13);
utils::hook::jump(0x1403828C8, g_gravity_stub, true);
dvars::g_gravity = game::Dvar_RegisterInt("g_gravity", 800, 0, 1000, game::DVAR_FLAG_NONE,
"Game gravity in inches per second squared");
// Implement speed dvar
utils::hook::nop(0x140383789, 13);
utils::hook::jump(0x140383789, g_speed_stub, true);
dvars::g_speed = game::Dvar_RegisterInt("g_speed", 190, 0, 999, game::DVAR_FLAG_NONE, "Maximum player speed");
utils::hook::call(0x140225857, jump_apply_slowdown_stub);
utils::hook::call(0x1402210A2, jump_reduce_friction_stub);
utils::hook::call(0x140213015, jump_start_stub);
dvars::jump_slowdownEnable = game::Dvar_RegisterBool("jump_slowdownEnable", true,
game::DVAR_FLAG_REPLICATED,
"Slow player movement after jumping");
dvars::jump_spreadAdd = game::Dvar_RegisterFloat("jump_spreadAdd", 64.0f,
0.0f, 512.0f, game::DVAR_FLAG_REPLICATED,
"The amount of spread scale to add as a side effect of jumping");
utils::hook::call(0x1402219A5, pm_crashland_stub);
dvars::jump_enableFallDamage = game::Dvar_RegisterBool("jump_enableFallDamage", true,
game::DVAR_FLAG_REPLICATED,
"Enable fall damage");
utils::hook::call(0x140213007, get_jump_height_stub);
dvars::jump_height = game::Dvar_RegisterFloat("jump_height", 39.f, 0.f, 1024.f,
game::DVAR_FLAG_REPLICATED, "Jump height");
utils::hook::jump(0x140213484, jump_push_off_ladder_stub, true);
dvars::jump_ladderPushVel = game::Dvar_RegisterFloat("jump_ladderPushVel", 128.f, 0.f, 1024.f,
game::DVAR_FLAG_REPLICATED,
"Ladder push velocity");
utils::hook::call(0x140221F92, pm_player_trace_stub);
utils::hook::call(0x140221FFA, pm_player_trace_stub);
utils::hook::call(0x14021F0E3, pm_trace_stub);
dvars::g_enableElevators = game::Dvar_RegisterBool("g_enableElevators", false,
game::DVAR_FLAG_REPLICATED, "Enable Elevators");
utils::hook::call(0x1403D933E, weapon_rocket_launcher_fire_stub);
dvars::g_rocketPushbackScale = game::Dvar_RegisterFloat("g_rocketPushbackScale", 1.0f, 1.0f, std::numeric_limits<float>::max(),
game::DVAR_FLAG_REPLICATED, "The scale applied to the pushback force of a rocket");
}
};
}
REGISTER_COMPONENT(gameplay::component)

View File

@ -0,0 +1,319 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "script_extension.hpp"
#include "script_error.hpp"
#include "component/scripting.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
using namespace utils::string;
namespace gsc
{
namespace
{
std::array<const char*, 27> var_typename =
{
"undefined",
"object",
"string",
"localized string",
"vector",
"float",
"int",
"codepos",
"precodepos",
"function",
"builtin function",
"builtin method",
"stack",
"animation",
"pre animation",
"thread",
"thread",
"thread",
"thread",
"struct",
"removed entity",
"entity",
"array",
"removed thread",
"<free>",
"thread list",
"endon list",
};
utils::hook::detour scr_emit_function_hook;
unsigned int current_filename = 0;
std::string unknown_function_error;
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
{
current_filename = filename;
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
}
std::string get_filename_name()
{
const auto filename_str = game::SL_ConvertToString(current_filename);
const auto id = std::atoi(filename_str);
if (!id)
{
return filename_str;
}
return scripting::get_token(id);
}
void get_unknown_function_error(const char* code_pos)
{
const auto function = find_function(code_pos);
if (function.has_value())
{
const auto& pos = function.value();
unknown_function_error = std::format(
"while processing function '{}' in script '{}':\nunknown script '{}'", pos.first, pos.second, scripting::current_file
);
}
else
{
unknown_function_error = std::format("unknown script '{}'", scripting::current_file);
}
}
void get_unknown_function_error(unsigned int thread_name)
{
const auto filename = get_filename_name();
const auto name = scripting::get_token(thread_name);
unknown_function_error = std::format(
"while processing script '{}':\nunknown function '{}::{}'", scripting::current_file, filename, name
);
}
void compile_error_stub(const char* code_pos, [[maybe_unused]] const char* msg)
{
get_unknown_function_error(code_pos);
game::Com_Error(game::ERR_DROP, "script link error\n%s", unknown_function_error.data());
}
unsigned int find_variable_stub(unsigned int parent_id, unsigned int thread_name)
{
const auto res = game::FindVariable(parent_id, thread_name);
if (!res)
{
get_unknown_function_error(thread_name);
game::Com_Error(game::ERR_DROP, "script link error\n%s", unknown_function_error.data());
}
return res;
}
}
unsigned int scr_get_object(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_POINTER)
{
return value->u.pointerValue;
}
scr_error(va("Type %s is not an object", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
unsigned int scr_get_const_string(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (game::Scr_CastString(value))
{
assert(value->type == game::VAR_STRING);
return value->u.stringValue;
}
game::Scr_ErrorInternal();
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
unsigned int scr_get_const_istring(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_ISTRING)
{
return value->u.stringValue;
}
scr_error(va("Type %s is not a localized string", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
void scr_get_vector(unsigned int index, float* vector_value)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_VECTOR)
{
std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
return;
}
scr_error(va("Type %s is not a vector", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
}
int scr_get_int(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_INTEGER)
{
return value->u.intValue;
}
scr_error(va("Type %s is not an int", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
float scr_get_float(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_FLOAT)
{
return value->u.floatValue;
}
if (value->type == game::VAR_INTEGER)
{
return static_cast<float>(value->u.intValue);
}
scr_error(va("Type %s is not a float", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0.0f;
}
int scr_get_pointer_type(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
{
return game::GetObjectType((game::scr_VmPub->top - index)->u.intValue);
}
scr_error(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
int scr_get_type(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
return (game::scr_VmPub->top - index)->type;
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
const char* scr_get_type_name(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
return var_typename[(game::scr_VmPub->top - index)->type];
}
scr_error(va("Parameter %u does not exist", index + 1));
return nullptr;
}
std::optional<std::pair<std::string, std::string>> find_function(const char* pos)
{
for (const auto& file : scripting::script_function_table_sort)
{
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
{
const auto next = std::next(i);
if (pos >= i->second && pos < next->second)
{
return {std::make_pair(i->first, file.first)};
}
}
}
return {};
}
class error final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
scr_emit_function_hook.create(0x14042E150, &scr_emit_function_stub);
utils::hook::call(0x14042E0E4, compile_error_stub); // LinkFile
utils::hook::call(0x14042E138, compile_error_stub); // LinkFile
utils::hook::call(0x14042E22B, find_variable_stub); // Scr_EmitFunction
// Restore basic error messages to scr functions
utils::hook::jump(0x140438ED0, scr_get_object);
utils::hook::jump(0x140438AD0, scr_get_const_string);
utils::hook::jump(0x1404388B0, scr_get_const_istring);
utils::hook::jump(0x1404393D0, scr_get_vector);
utils::hook::jump(0x140438E10, scr_get_int);
utils::hook::jump(0x140438D60, scr_get_float);
utils::hook::jump(0x1404390B0, scr_get_pointer_type);
utils::hook::jump(0x140439280, scr_get_type);
utils::hook::jump(0x1404392F0, scr_get_type_name);
}
void pre_destroy() override
{
scr_emit_function_hook.clear();
}
};
}
REGISTER_COMPONENT(gsc::error)

View File

@ -0,0 +1,16 @@
#pragma once
namespace gsc
{
unsigned int scr_get_object(unsigned int index);
unsigned int scr_get_const_string(unsigned int index);
unsigned int scr_get_const_istring(unsigned int index);
void scr_get_vector(unsigned int index, float* vector_value);
int scr_get_int(unsigned int index);
float scr_get_float(unsigned int index);
int scr_get_pointer_type(unsigned int index);
int scr_get_type(unsigned int index);
const char* scr_get_type_name(unsigned int index);
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
}

View File

@ -0,0 +1,324 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/scripting/functions.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "component/console.hpp"
#include "component/scripting.hpp"
#include "component/notifies.hpp"
#include "component/command.hpp"
#include "script_extension.hpp"
#include "script_error.hpp"
#include "script_loading.hpp"
namespace gsc
{
std::uint16_t function_id_start = 0x25D;
std::uint16_t method_id_start = 0x8429;
void* func_table[0x1000];
const game::dvar_t* developer_script = nullptr;
namespace
{
#define RVA(ptr) static_cast<std::uint32_t>(reinterpret_cast<std::size_t>(ptr) - 0x140000000)
struct gsc_error : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
std::unordered_map<std::uint16_t, game::BuiltinFunction> functions;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
unsigned int scr_get_function_stub(const char** p_name, int* type)
{
const auto result = utils::hook::invoke<unsigned int>(0x1403CD9F0, p_name, type);
for (const auto& [name, func] : functions)
{
game::Scr_RegisterFunction(func, 0, name);
}
return result;
}
void execute_custom_function(game::BuiltinFunction function)
{
auto error = false;
try
{
function();
}
catch (const std::exception& ex)
{
error = true;
force_error_print = true;
gsc_error_msg = ex.what();
}
if (error)
{
game::Scr_ErrorInternal();
}
}
void vm_call_builtin_function(const std::uint32_t index)
{
const auto func = reinterpret_cast<game::BuiltinFunction>(scripting::get_function_by_index(index));
const auto custom = functions.contains(static_cast<std::uint16_t>(index));
if (!custom)
{
func();
}
else
{
execute_custom_function(func);
}
}
void builtin_call_error(const std::string& error)
{
const auto pos = game::scr_function_stack->pos;
const auto function_id = *reinterpret_cast<std::uint16_t*>(reinterpret_cast<std::size_t>(pos - 2));
if (function_id > 0x1000)
{
console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
}
else
{
console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
}
}
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
{
try
{
const auto index = gsc_ctx->opcode_enum(opcode);
return {gsc_ctx->opcode_name(index)};
}
catch (...)
{
return {};
}
}
void print_callstack()
{
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
{
const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos;
const auto function = find_function(frame->fs.pos);
if (function.has_value())
{
console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data());
}
else
{
console::warn("\tat unknown location %p\n", pos);
}
}
}
void vm_error_stub(int mark_pos)
{
if (!developer_script->current.enabled && !force_error_print)
{
utils::hook::invoke<void>(0x1404E4D00, mark_pos);
return;
}
console::warn("******* script runtime error ********\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x144D57840);
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA8 && opcode_id <= 0xAE))
{
builtin_call_error(error);
}
else
{
const auto opcode = get_opcode_name(opcode_id);
if (opcode.has_value())
{
console::warn("while processing instruction %s%s\n", opcode.value().data(), error.data());
}
else
{
console::warn("while processing instruction 0x%X%s\n", opcode_id, error.data());
}
}
force_error_print = false;
gsc_error_msg = {};
print_callstack();
console::warn("************************************\n");
utils::hook::invoke<void>(0x1404E4D00, mark_pos);
}
void inc_in_param()
{
game::Scr_ClearOutParams();
if (game::scr_VmPub->top == game::scr_VmPub->maxstack)
{
game::Sys_Error("Internal script stack overflow");
}
++game::scr_VmPub->top;
++game::scr_VmPub->inparamcount;
}
void add_code_pos(const char* pos)
{
inc_in_param();
game::scr_VmPub->top->type = game::VAR_FUNCTION;
game::scr_VmPub->top->u.codePosValue = pos;
}
void scr_print()
{
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
{
console::info("%s", game::Scr_GetString(i));
}
}
void scr_print_ln()
{
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
{
console::info("%s", game::Scr_GetString(i));
}
console::info("\n");
}
void assert_cmd()
{
if (!game::Scr_GetInt(0))
{
scr_error("Assert fail");
}
}
void assert_ex_cmd()
{
if (!game::Scr_GetInt(0))
{
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(1)));
}
}
void assert_msg_cmd()
{
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0)));
}
const char* get_code_pos(const int index)
{
if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount)
{
scr_error("Scr_GetCodePos: index is out of range");
return "";
}
const auto* value = &game::scr_VmPub->top[-index];
if (value->type != game::VAR_FUNCTION)
{
scr_error("Scr_GetCodePos requires a function as parameter");
return "";
}
return value->u.codePosValue;
}
}
void add_function(const std::string& name, game::BuiltinFunction function)
{
++function_id_start;
functions[function_id_start] = function;
gsc_ctx->func_add(name, function_id_start);
}
void scr_error(const char* error)
{
force_error_print = true;
gsc_error_msg = error;
game::Scr_ErrorInternal();
}
class extension final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print);
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln);
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D353C, 0x14042E33C), 0x1000); // Scr_RegisterFunction
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D3542 + 4, 0x14042E342 + 4), RVA(&func_table)); // Scr_RegisterFunction
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0
utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts
if (game::environment::is_sp())
{
return;
}
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
utils::hook::nop(0x14043BBBE + 5, 2);
utils::hook::call(0x14043BBBE, vm_call_builtin_function);
utils::hook::call(0x14043CEB1, vm_error_stub);
utils::hook::call(0x14042E76F, scr_get_function_stub);
utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd);
add_function("replacefunc", []
{
if (scr_get_type(0) != game::VAR_FUNCTION || scr_get_type(1) != game::VAR_FUNCTION)
{
throw gsc_error("Parameter must be a function");
}
notifies::set_gsc_hook(get_code_pos(0), get_code_pos(1));
});
add_function("executecommand", []
{
const auto* cmd = game::Scr_GetString(0);
command::execute(cmd);
});
add_function("isdedicated", []
{
game::Scr_AddInt(game::environment::is_dedi());
});
}
};
}
REGISTER_COMPONENT(gsc::extension)

View File

@ -0,0 +1,12 @@
#pragma once
namespace gsc
{
extern void* func_table[0x1000];
extern const game::dvar_t* developer_script;
void add_function(const std::string& name, game::BuiltinFunction function);
void scr_error(const char* error);
}

View File

@ -0,0 +1,369 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/io.hpp>
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/compression.hpp>
#include "component/filesystem.hpp"
#include "component/console.hpp"
#include "component/scripting.hpp"
#include "component/fastfiles.hpp"
#include "script_loading.hpp"
namespace gsc
{
std::unique_ptr<xsk::gsc::iw6_pc::context> gsc_ctx;
namespace
{
std::unordered_map<std::string, unsigned int> main_handles;
std::unordered_map<std::string, unsigned int> init_handles;
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
utils::memory::allocator script_allocator;
const game::dvar_t* developer_script;
void clear()
{
main_handles.clear();
init_handles.clear();
loaded_scripts.clear();
script_allocator.clear();
}
bool read_raw_script_file(const std::string& name, std::string* data)
{
if (filesystem::read_file(name, data))
{
return true;
}
// This will prevent 'fake' GSC raw files from being compiled.
// They are parsed by the game's own parser later as they are special files.
if (name.starts_with("maps/createfx") || name.starts_with("maps/createart") ||
(name.starts_with("maps/mp") && name.ends_with("_fx.gsc")))
{
return false;
}
const auto* name_str = name.data();
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
{
const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name.data(), false);
const auto len = game::DB_GetRawFileLen(asset.rawfile);
data->resize(len);
game::DB_GetRawBuffer(asset.rawfile, data->data(), len);
if (len > 0)
{
data->pop_back();
}
return true;
}
return false;
}
game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
{
if (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end())
{
return itr->second;
}
try
{
auto& compiler = gsc_ctx->compiler();
auto& assembler = gsc_ctx->assembler();
std::string source_buffer{};
if (!read_raw_script_file(real_name + ".gsc", &source_buffer))
{
return nullptr;
}
console::info("Compiling script '%s'\n", real_name.data());
std::vector<std::uint8_t> data;
data.assign(source_buffer.begin(), source_buffer.end());
const auto assembly_ptr = compiler.compile(real_name, data);
// Pair of two buffers. First is the byte code and second is the stack
const auto output_script = assembler.assemble(*assembly_ptr);
const auto script_file_ptr = static_cast<game::ScriptFile*>(script_allocator.allocate(sizeof(game::ScriptFile)));
script_file_ptr->name = file_name;
script_file_ptr->bytecodeLen = static_cast<int>(std::get<0>(output_script).size);
script_file_ptr->len = static_cast<int>(std::get<1>(output_script).size);
const auto byte_code_size = static_cast<std::uint32_t>(std::get<0>(output_script).size + 1);
const auto stack_size = static_cast<std::uint32_t>(std::get<1>(output_script).size + 1);
script_file_ptr->buffer = static_cast<char*>(script_allocator.allocate(stack_size));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), std::get<1>(output_script).data, std::get<1>(output_script).size);
script_file_ptr->bytecode = static_cast<std::uint8_t*>(game::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 1, game::PMEM_SOURCE_SCRIPT));
std::memcpy(script_file_ptr->bytecode, std::get<0>(output_script).data, std::get<0>(output_script).size);
script_file_ptr->compressedLen = 0;
loaded_scripts[real_name] = script_file_ptr;
return script_file_ptr;
}
catch (const std::exception& ex)
{
console::error("*********** script compile error *************\n");
console::error("failed to compile '%s':\n%s", real_name.data(), ex.what());
console::error("**********************************************\n");
return nullptr;
}
}
std::string get_script_file_name(const std::string& name)
{
const auto id = gsc_ctx->token_id(name);
if (!id)
{
return name;
}
return std::to_string(id);
}
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> read_compiled_script_file(const std::string& name, const std::string& real_name)
{
const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
if (!script_file)
{
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
}
console::info("Decompiling scriptfile '%s'\n", real_name.data());
const auto len = script_file->compressedLen;
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(len)};
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
std::vector<std::uint8_t> stack_data;
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
return {{script_file->bytecode, static_cast<std::uint32_t>(script_file->bytecodeLen)}, stack_data};
}
void load_script(const std::string& name)
{
if (!game::Scr_LoadScript(name.data()))
{
return;
}
const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("init"));
if (main_handle)
{
console::info("Loaded '%s::main'\n", name.data());
main_handles[name] = main_handle;
}
if (init_handle)
{
console::info("Loaded '%s::init'\n", name.data());
init_handles[name] = init_handle;
}
}
void load_scripts(const std::filesystem::path& root_dir)
{
const std::filesystem::path script_dir = root_dir / "scripts";
if (!utils::io::directory_exists(script_dir.generic_string()))
{
return;
}
const auto scripts = utils::io::list_files(script_dir.generic_string());
for (const auto& script : scripts)
{
if (!script.ends_with(".gsc"))
{
continue;
}
std::filesystem::path path(script);
const auto relative = path.lexically_relative(root_dir).generic_string();
const auto base_name = relative.substr(0, relative.size() - 4);
load_script(base_name);
}
}
int db_is_x_asset_default(game::XAssetType type, const char* name)
{
if (loaded_scripts.contains(name))
{
return 0;
}
return game::DB_IsXAssetDefault(type, name);
}
void gscr_load_game_type_script_stub()
{
utils::hook::invoke<void>(0x1403CCB10);
clear();
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path);
}
}
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
{
if (rawfile->len > 0 && rawfile->compressedLen == 0)
{
std::memset(buf, 0, size);
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
return;
}
game::DB_GetRawBuffer(rawfile, buf, size);
}
void g_load_structs_stub()
{
for (auto& function_handle : main_handles)
{
console::info("Executing '%s::main'\n", function_handle.first.data());
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
game::RemoveRefToObject(thread);
}
utils::hook::invoke<void>(0x1403D2CA0);
}
void scr_load_level_stub()
{
utils::hook::invoke<void>(0x1403CDC60);
for (auto& function_handle : init_handles)
{
console::info("Executing '%s::init'\n", function_handle.first.data());
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
game::RemoveRefToObject(thread);
}
}
void scr_begin_load_scripts_stub()
{
const auto comp_mode = developer_script->current.enabled ?
xsk::gsc::build::dev :
xsk::gsc::build::prod;
gsc_ctx->init(comp_mode, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
{
const auto script_name = std::filesystem::path(included_path).replace_extension().string();
std::string file_buffer;
if (!read_raw_script_file(included_path, &file_buffer) || file_buffer.empty())
{
const auto name = get_script_file_name(script_name);
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
{
return read_compiled_script_file(name, script_name);
}
throw std::runtime_error(std::format("Could not load gsc file '{}'", script_name));
}
std::vector<std::uint8_t> script_data;
script_data.assign(file_buffer.begin(), file_buffer.end());
return {{}, script_data};
});
utils::hook::invoke<void>(SELECT_VALUE(0x1403D3860, 0x14042E6B0));
}
void scr_end_load_scripts_stub()
{
gsc_ctx->cleanup();
utils::hook::invoke<void>(SELECT_VALUE(0x140603C00, 0x14042E7E0));
}
}
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default)
{
std::string real_name = name;
const auto id = static_cast<std::uint16_t>(std::strtol(name, nullptr, 10));
if (id)
{
real_name = gsc_ctx->token_name(id);
}
auto* script = load_custom_script(name, real_name);
if (script)
{
return script;
}
return game::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile;
}
class loading final : public component_interface
{
public:
loading()
{
gsc_ctx = std::make_unique<xsk::gsc::iw6_pc::context>();
}
void post_unpack() override
{
// Load our scripts with an uncompressed stack
utils::hook::call(SELECT_VALUE(0x1403DC8F0, 0x140437940), db_get_raw_buffer_stub);
utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts
utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
if (game::environment::is_sp())
{
return;
}
// ProcessScript
utils::hook::call(0x1404378D7, find_script);
utils::hook::call(0x1404378E7, db_is_x_asset_default);
// GScr_LoadScripts
utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub);
// Exec script handles
utils::hook::call(0x14039F64E, g_load_structs_stub);
utils::hook::call(0x14039F653, scr_load_level_stub);
scripting::on_shutdown([](int free_scripts)
{
if (free_scripts)
{
clear();
}
});
}
};
}
REGISTER_COMPONENT(gsc::loading)

View File

@ -0,0 +1,9 @@
#pragma once
#include <xsk/gsc/engine/iw6_pc.hpp>
namespace gsc
{
extern std::unique_ptr<xsk::gsc::iw6_pc::context> gsc_ctx;
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
}

View File

@ -0,0 +1,59 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game_console.hpp"
#include "server_list.hpp"
#include <utils/hook.hpp>
namespace input
{
namespace
{
utils::hook::detour cl_char_event_hook;
utils::hook::detour cl_key_event_hook;
void cl_char_event_stub(const int local_client_num, const int key)
{
if (!game_console::console_char_event(local_client_num, key))
{
return;
}
cl_char_event_hook.invoke<void>(local_client_num, key);
}
void cl_key_event_stub(const int local_client_num, const int key, const int down)
{
if (!game_console::console_key_event(local_client_num, key, down))
{
return;
}
if (game::environment::is_mp() && !server_list::sl_key_event(key, down))
{
return;
}
cl_key_event_hook.invoke<void>(local_client_num, key, down);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
cl_char_event_hook.create(SELECT_VALUE(0x14023CE50, 0x1402C2AE0), cl_char_event_stub);
cl_key_event_hook.create(SELECT_VALUE(0x14023D070, 0x1402C2CE0), cl_key_event_stub);
}
};
}
REGISTER_COMPONENT(input::component)

View File

@ -0,0 +1,66 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "localized_strings.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "game/game.hpp"
namespace localized_strings
{
namespace
{
utils::hook::detour seh_string_ed_get_string_hook;
std::unordered_map<std::string, std::string>& get_localized_overrides()
{
static std::unordered_map<std::string, std::string> overrides;
return overrides;
}
std::mutex& get_synchronization_mutex()
{
static std::mutex mutex;
return mutex;
}
const char* seh_string_ed_get_string(const char* reference)
{
std::lock_guard<std::mutex> _(get_synchronization_mutex());
auto& overrides = get_localized_overrides();
const auto entry = overrides.find(reference);
if (entry != overrides.end())
{
return utils::string::va("%s", entry->second.data());
}
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
}
unsigned int g_localized_string_index_stub(const char* name, /*ConfigString*/ unsigned int start,
unsigned int max, int create,
const char* errormsg)
{
create = 1;
return game::G_FindConfigstringIndex(name, start, max, create, errormsg);
}
}
void override(const std::string& key, const std::string& value)
{
std::lock_guard<std::mutex> _(get_synchronization_mutex());
get_localized_overrides()[key] = value;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Change some localized strings
seh_string_ed_get_string_hook.create(SELECT_VALUE(0x1403F42D0, 0x1404A5F60), &seh_string_ed_get_string);
}
};
}
REGISTER_COMPONENT(localized_strings::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace localized_strings
{
void override(const std::string& key, const std::string& value);
}

View File

@ -0,0 +1,159 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include <utils/hook.hpp>
namespace logger
{
namespace
{
utils::hook::detour com_error_hook;
const game::dvar_t* logger_dev = nullptr;
void print_com_error(int, const char* msg, ...)
{
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::error("%s", buffer);
}
void com_error_stub(const int error, const char* msg, ...)
{
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::error("Error: %s\n", buffer);
com_error_hook.invoke<void>(error, "%s", buffer);
}
void print_warning(const char* msg, ...)
{
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::warn("%s", buffer);
}
void print(const char* msg, ...)
{
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::info("%s", buffer);
}
void print_dev(const char* msg, ...)
{
if (!logger_dev->current.enabled)
{
return;
}
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::info("%s", buffer);
}
// nullsub_56 -> nullsub_16 (iw6)
void nullsub_16()
{
utils::hook::call(0x1401CCBD2, print_warning);
utils::hook::call(0x1401CCBDE, print_warning);
utils::hook::call(0x1401CCBE6, print_warning);
utils::hook::call(0x1401CCBF2, print_warning);
utils::hook::call(0x1401CCE48, print_warning);
utils::hook::call(0x1401CCE54, print_warning);
utils::hook::call(0x1401CCE5C, print_warning);
utils::hook::call(0x1401CCE68, print_warning);
utils::hook::call(0x1401CD0AD, print_warning);
utils::hook::call(0x1401CD0B9, print_warning);
utils::hook::call(0x1401CD0C1, print_warning);
utils::hook::call(0x1401CD0CD, print_warning);
utils::hook::call(0x1401CF46D, print_warning);
utils::hook::call(0x1401CF479, print_warning);
utils::hook::call(0x1401CF481, print_warning);
utils::hook::call(0x1401CF48D, print_warning);
utils::hook::call(0x1401D02B8, print_warning);
utils::hook::call(0x1401D02D8, print_warning);
utils::hook::call(0x1401D02E4, print_warning);
utils::hook::call(0x1401D0388, print_warning);
utils::hook::call(0x1401D03A8, print_warning);
utils::hook::call(0x1401D03B4, print_warning);
utils::hook::call(0x1401D03CD, print_warning);
utils::hook::call(0x1401D1884, print_warning);
utils::hook::call(0x1401D1A36, print_warning);
}
// sub_1400E7420 -> sub_1401DAA40
void sub_1401DAA40()
{
utils::hook::call(0x1401C9CAE, print_warning);
utils::hook::call(0x1401CC7F5, print_warning);
utils::hook::call(0x1401CC97E, print_warning);
utils::hook::call(0x1401CE43C, print_dev); // lua start up
utils::hook::call(0x1401CF7E4, print_dev);
utils::hook::call(0x1401D15BA, print_dev); // lua memory used
utils::hook::call(0x1401D24BC, print_dev); // lui shutting down
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_mp())
{
nullsub_16();
sub_1401DAA40();
}
if (!game::environment::is_sp())
{
utils::hook::call(0x140501AE3, print_com_error);
}
com_error_hook.create(game::Com_Error, com_error_stub);
// Make havok script's print function actually print
utils::hook::jump(SELECT_VALUE(0x1406283A4, 0x140732184), print);
logger_dev = game::Dvar_RegisterBool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff");
}
};
}
REGISTER_COMPONENT(logger::component)

View File

@ -0,0 +1,292 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "map_rotation.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace map_rotation
{
namespace
{
rotation_data dedicated_rotation;
const game::dvar_t* sv_map_rotation;
const game::dvar_t* sv_map_rotation_current;
const game::dvar_t* sv_random_map_rotation;
void set_gametype(const std::string& gametype)
{
assert(!gametype.empty());
auto* g_gametype = game::Dvar_FindVar("g_gametype");
game::Dvar_SetString(g_gametype, gametype.data());
}
void launch_map(const std::string& mapname)
{
assert(!mapname.empty());
command::execute(utils::string::va("map %s", mapname.data()), false);
}
void launch_default_map()
{
auto* mapname = game::Dvar_FindVar("mapname");
if (mapname && *mapname->current.string)
{
launch_map(mapname->current.string);
}
else
{
launch_map("mp_prisonbreak");
}
}
void apply_rotation(rotation_data& rotation)
{
assert(!rotation.empty());
std::size_t i = 0;
while (i < rotation.get_entries_size())
{
const auto& entry = rotation.get_next_entry();
if (entry.first == "map"s)
{
console::info("Loading new map: '%s'\n", entry.second.data());
launch_map(entry.second);
// Map was found so we exit the loop
break;
}
if (entry.first == "gametype"s)
{
console::info("Applying new gametype: '%s'\n", entry.second.data());
set_gametype(entry.second);
}
++i;
}
if (i == rotation.get_entries_size())
{
console::error("Map rotation does not contain any map. Restarting\n");
launch_default_map();
}
}
void load_rotation(const std::string& data)
{
static auto loaded = false;
if (loaded)
{
return;
}
loaded = true;
try
{
dedicated_rotation.parse(data);
}
catch (const std::exception& ex)
{
console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name);
}
#ifdef _DEBUG
console::info("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size());
#endif
}
void load_map_rotation()
{
const std::string map_rotation = sv_map_rotation->current.string;
if (!map_rotation.empty())
{
#ifdef _DEBUG
console::info("%s is not empty. Parsing...\n", sv_map_rotation->name);
#endif
load_rotation(map_rotation);
}
}
void apply_map_rotation_current(const std::string& data)
{
assert(!data.empty());
rotation_data rotation_current;
try
{
rotation_current.parse(data);
}
catch (const std::exception& ex)
{
console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation_current->name);
}
game::Dvar_SetString(sv_map_rotation_current, "");
if (rotation_current.empty())
{
console::warn("%s is empty or contains invalid data\n", sv_map_rotation_current->name);
launch_default_map();
return;
}
apply_rotation(rotation_current);
}
void randomize_map_rotation()
{
if (sv_random_map_rotation->current.enabled)
{
console::info("Randomizing the map rotation\n");
dedicated_rotation.randomize();
}
}
void perform_map_rotation()
{
if (game::Live_SyncOnlineDataFlags(0) != 0)
{
scheduler::on_game_initialized(perform_map_rotation, scheduler::pipeline::main, 1s);
return;
}
console::info("Rotating map...\n");
// This takes priority because of backwards compatibility
const std::string map_rotation_current = sv_map_rotation_current->current.string;
if (!map_rotation_current.empty())
{
#ifdef _DEBUG
console::info("Applying %s\n", sv_map_rotation_current->name);
#endif
apply_map_rotation_current(map_rotation_current);
return;
}
load_map_rotation();
if (dedicated_rotation.empty())
{
console::warn("%s is empty or contains invalid data. Restarting map\n", sv_map_rotation->name);
launch_default_map();
return;
}
randomize_map_rotation();
apply_rotation(dedicated_rotation);
}
void trigger_map_rotation()
{
scheduler::schedule([]
{
if (game::CL_IsCgameInitialized())
{
return scheduler::cond_continue;
}
command::execute("map_rotate", false);
return scheduler::cond_end;
}, scheduler::pipeline::main, 1s);
}
}
rotation_data::rotation_data()
: index_(0)
{
}
void rotation_data::randomize()
{
std::random_device rd;
std::mt19937 gen(rd());
std::ranges::shuffle(this->rotation_entries_, gen);
}
void rotation_data::add_entry(const std::string& key, const std::string& value)
{
this->rotation_entries_.emplace_back(std::make_pair(key, value));
}
bool rotation_data::contains(const std::string& key, const std::string& value) const
{
return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry)
{
return entry.first == key && entry.second == value;
});
}
bool rotation_data::empty() const noexcept
{
return this->rotation_entries_.empty();
}
std::size_t rotation_data::get_entries_size() const noexcept
{
return this->rotation_entries_.size();
}
rotation_data::rotation_entry& rotation_data::get_next_entry()
{
const auto index = this->index_;
++this->index_ %= this->rotation_entries_.size();
return this->rotation_entries_.at(index);
}
void rotation_data::parse(const std::string& data)
{
const auto tokens = utils::string::split(data, ' ');
for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2)
{
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
if (key == "map"s || key == "gametype"s)
{
this->add_entry(key, value);
}
else
{
throw map_rotation_parse_error();
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_dedi())
{
return;
}
scheduler::once([]
{
sv_map_rotation = game::Dvar_RegisterString("sv_mapRotation", "", game::DvarFlags::DVAR_FLAG_NONE, "");
sv_map_rotation_current = game::Dvar_RegisterString("sv_mapRotationCurrent", "", game::DvarFlags::DVAR_FLAG_NONE, "");
}, scheduler::pipeline::main);
sv_random_map_rotation = game::Dvar_RegisterBool("sv_randomMapRotation", false, game::DVAR_FLAG_NONE, "Randomize map rotation");
command::add("map_rotate", &perform_map_rotation);
// Hook SV_MatchEnd in GScr_ExitLevel
utils::hook::jump(0x1403CBC30, &trigger_map_rotation);
//utils::hook::call(0x1403CBC6C, &trigger_map_rotation);
}
};
}
REGISTER_COMPONENT(map_rotation::component)

View File

@ -0,0 +1,34 @@
#pragma once
namespace map_rotation
{
struct map_rotation_parse_error : public std::exception
{
[[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Error"; }
};
class rotation_data
{
public:
using rotation_entry = std::pair<std::string, std::string>;
rotation_data();
void randomize();
// In case a new way to enrich the map rotation is added (other than sv_mapRotation)
// this method should be called to add a new entry (gamemode/map & value)
void add_entry(const std::string& key, const std::string& value);
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] std::size_t get_entries_size() const noexcept;
[[nodiscard]] rotation_entry& get_next_entry();
void parse(const std::string& data);
private:
std::vector<rotation_entry> rotation_entries_;
std::size_t index_;
};
}

View File

@ -0,0 +1,49 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game_module.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace musical_talent
{
namespace
{
bool is_main_menu_music(game::StreamedSound* sound)
{
return sound->filename.fileIndex == 1 && sound->filename.info.packed.offset == 0xBBAD000;
}
void open_sound_handle_stub(HANDLE* handle, game::StreamedSound* sound)
{
if (is_main_menu_music(sound))
{
const auto folder = game_module::get_host_module().get_folder();
const auto file = folder + "/data/sound/patch-3-music.flac";
*handle = CreateFileA(file.data(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr);
}
else
{
return reinterpret_cast<void(*)(HANDLE*, game::StreamedSound*)>(0x1404F8B90)(handle, sound);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
utils::hook::call(0x1404903BD, open_sound_handle_stub);
}
};
}
REGISTER_COMPONENT(musical_talent::component)

View File

@ -0,0 +1,291 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "network.hpp"
#include "console.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace network
{
namespace
{
std::unordered_map<std::string, callback>& get_callbacks()
{
static std::unordered_map<std::string, callback> callbacks{};
return callbacks;
}
bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message)
{
const auto cmd_string = utils::string::to_lower(command);
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())
{
return false;
}
const std::string data(message->data + offset, message->cursize - offset);
handler->second(*address, data);
return true;
}
void handle_command_stub(utils::hook::assembler& a)
{
const auto return_unhandled = a.newLabel();
a.pushad64();
a.mov(r8, rsi); // message
a.mov(rdx, rdi); // command
a.mov(rcx, r14); // netaddr
a.call_aligned(handle_command);
a.test(al, al);
a.jz(return_unhandled);
// Command handled
a.popad64();
a.jmp(0x1402C6DE8);
a.bind(return_unhandled);
a.popad64();
a.jmp(0x1402C64EE);
}
int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
{
if (a1->type == a2->type)
{
switch (a1->type)
{
case game::netadrtype_t::NA_BOT:
case game::netadrtype_t::NA_LOOPBACK:
return a1->port == a2->port;
case game::netadrtype_t::NA_IP:
return !memcmp(a1->ip, a2->ip, 4);
case game::netadrtype_t::NA_BROADCAST:
return true;
default:
break;
}
}
return false;
}
int net_compare_address(const game::netadr_s* a1, const game::netadr_s* a2)
{
return net_compare_base_address(a1, a2) && a1->port == a2->port;
}
void reconnect_migratated_client(game::mp::client_t*, game::netadr_s* from, const int, const int, const char*,
const char*, bool)
{
// This happens when a client tries to rejoin after being recently disconnected, OR by a duplicated guid
// We don't want this to do anything. It decides to crash seemingly randomly
// Rather than try and let the player in, just tell them they are a duplicate player and reject connection
game::NET_OutOfBandPrint(game::NS_SERVER, from, "error\nYou are already connected to the server.");
}
}
void on(const std::string& command, const callback& callback)
{
get_callbacks()[utils::string::to_lower(command)] = callback;
}
int dw_send_to_stub(const int size, const char* src, game::netadr_s* addr)
{
sockaddr s = {};
game::NetadrToSockadr(addr, &s);
return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0;
}
void send(const game::netadr_s& address, const std::string& command, const std::string& data, const char separator)
{
std::string packet = "\xFF\xFF\xFF\xFF";
packet.append(command);
packet.push_back(separator);
packet.append(data);
send_data(address, packet);
}
void send_data(const game::netadr_s& address, const std::string& data)
{
if (address.type == game::NA_LOOPBACK)
{
game::NET_SendLoopPacket(game::NS_CLIENT1, static_cast<int>(data.size()), data.data(), &address);
}
else
{
game::Sys_SendPacket(static_cast<int>(data.size()), data.data(), &address);
}
}
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b)
{
return net_compare_address(&a, &b);
}
const char* net_adr_to_string(const game::netadr_s& a)
{
if (a.type == game::netadrtype_t::NA_LOOPBACK)
{
return "loopback";
}
if (a.type == game::netadrtype_t::NA_BOT)
{
return "bot";
}
if (a.type == game::netadrtype_t::NA_IP || a.type == game::netadrtype_t::NA_BROADCAST)
{
if (a.port)
{
return utils::string::va("%u.%u.%u.%u:%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3], htons(a.port));
}
return utils::string::va("%u.%u.%u.%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);
}
return "bad";
}
void set_xuid_config_string_stub(utils::hook::assembler& a)
{
const auto return_regular = a.newLabel();
a.mov(rax, ptr(rsp));
a.mov(r9, 0x140477715); // This is the evil one :(
a.cmp(rax, r9);
a.jne(return_regular);
// Do the original work
a.call_aligned(return_regular);
// Jump to success branch
a.mov(rax, 0x14047771E);
a.mov(ptr(rsp), rax);
a.ret();
a.bind(return_regular);
a.sub(rsp, 0x38);
a.mov(eax, ptr(rcx));
a.mov(r9d, ptr(rcx, 4));
a.mov(r10, rdx);
a.jmp(0x14041DFBD);
}
game::dvar_t* register_netport_stub(const char* dvarName, int value, int min, int max, unsigned int flags,
const char* description)
{
auto dvar = game::Dvar_RegisterInt("net_port", 27016, 0, 0xFFFFu, game::DVAR_FLAG_LATCHED, "Network port");
// read net_port from command line
command::read_startup_variable("net_port");
return dvar;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
{
if (game::environment::is_sp())
{
return;
}
// redirect dw_sendto to raw socket
utils::hook::jump(0x140501A00, dw_send_to_stub);
// intercept command handling
utils::hook::jump(0x1402C64CB, utils::hook::assemble(handle_command_stub), true);
// handle xuid without secure connection
utils::hook::jump(0x14041DFB0, utils::hook::assemble(set_xuid_config_string_stub), true);
utils::hook::jump(0x14041D010, net_compare_address);
utils::hook::jump(0x14041D060, net_compare_base_address);
// don't establish secure conenction
utils::hook::set<uint8_t>(0x1402ECF1D, 0xEB);
utils::hook::set<uint8_t>(0x1402ED02A, 0xEB);
utils::hook::set<uint8_t>(0x1402ED34D, 0xEB);
utils::hook::set<uint8_t>(0x1402C4A1F, 0xEB); //
// ignore unregistered connection
utils::hook::jump(0x140471AAC, reinterpret_cast<void*>(0x140471A50));
utils::hook::set<uint8_t>(0x140471AF0, 0xEB);
// disable xuid verification
utils::hook::set<uint8_t>(0x140154AA9, 0xEB);
utils::hook::set<uint8_t>(0x140154AC3, 0xEB);
// disable xuid verification
utils::hook::nop(0x140474584, 2);
utils::hook::set<uint8_t>(0x1404745DB, 0xEB);
// ignore configstring mismatch
utils::hook::set<uint8_t>(0x1402CCCC7, 0xEB);
// ignore dw handle in SV_PacketEvent
utils::hook::set<uint8_t>(0x14047A17A, 0xEB);
utils::hook::call(0x14047A16E, &net_compare_address);
// ignore dw handle in SV_FindClientByAddress
utils::hook::set<uint8_t>(0x1404799AD, 0xEB);
utils::hook::call(0x1404799A1, &net_compare_address);
// ignore dw handle in SV_DirectConnect
utils::hook::set<uint8_t>(0x1404717BA, 0xEB);
utils::hook::set<uint8_t>(0x1404719DF, 0xEB);
utils::hook::call(0x1404717AD, &net_compare_address);
utils::hook::call(0x1404719D2, &net_compare_address);
// increase cl_maxpackets
utils::hook::set<uint8_t>(0x1402C8083, 125);
utils::hook::set<uint8_t>(0x1402C808C, 125);
// ignore impure client
utils::hook::jump(0x140472671, reinterpret_cast<void*>(0x1404726F9));
// don't send checksum
utils::hook::set<uint8_t>(0x140501A63, 0);
// don't read checksum
utils::hook::jump(0x1405019CB, 0x1405019F3);
// don't try to reconnect client
utils::hook::call(0x14047197E, reconnect_migratated_client);
// allow server owner to modify net_port before the socket bind
utils::hook::call(0x140500FD0, register_netport_stub);
// ignore built in "print" oob command and add in our own
utils::hook::set<std::uint8_t>(0x1402C6AA4, 0xEB);
on("print", [](const game::netadr_s&, const std::string& data)
{
console::info("%s", data.data());
});
}
}
};
}
REGISTER_COMPONENT(network::component)

View File

@ -0,0 +1,47 @@
#pragma once
namespace network
{
using callback = std::function<void(const game::netadr_s&, const std::string&)>;
void on(const std::string& command, const callback& callback);
void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' ');
void send_data(const game::netadr_s& address, const std::string& data);
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b);
const char* net_adr_to_string(const game::netadr_s& a);
}
inline bool operator==(const game::netadr_s& a, const game::netadr_s& b)
{
return network::are_addresses_equal(a, b); //
}
inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b)
{
return !(a == b); //
}
namespace std
{
template <>
struct equal_to<game::netadr_s>
{
using result_type = bool;
bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const
{
return network::are_addresses_equal(lhs, rhs);
}
};
template <>
struct hash<game::netadr_s>
{
size_t operator()(const game::netadr_s& x) const noexcept
{
return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&x.ip[0])) ^ hash<uint16_t>()(x.port);
}
};
}

View File

@ -0,0 +1,197 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "game_log.hpp"
#include "notifies.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
#include <utils/hook.hpp>
namespace notifies
{
bool hook_enabled = true;
namespace
{
struct gsc_hook
{
const char* target_pos{};
};
std::unordered_map<const char*, gsc_hook> vm_execute_hooks;
const char* target_function = nullptr;
void client_command_stub(const int client_num)
{
if (game::mp::g_entities[client_num].client == nullptr)
{
return;
}
command::params_sv params;
if (params[0] == "say"s || params[0] == "say_team"s)
{
std::string message(game::ConcatArgs(1));
auto msg_index = 0;
if (message[msg_index] == '\x1F')
{
msg_index = 1;
}
auto hidden = false;
if (message[msg_index] == '/')
{
hidden = true;
if (msg_index == 1)
{
// Overwrite / with \x1F only if present
message[msg_index] = message[msg_index - 1];
}
// Skip over the first character
message.erase(message.begin());
}
scheduler::once([params, message, client_num]
{
const auto* guid = game::SV_GetGuid(client_num);
const auto* name = game::mp::svs_clients[client_num].name;
game_log::g_log_printf("%s;%s;%i;%s;%s\n",
params.get(0),
guid,
client_num,
name,
message.data()
);
}, scheduler::pipeline::server);
if (hidden)
{
return;
}
}
// ClientCommand
utils::hook::invoke<void>(0x1403929B0, client_num);
}
bool execute_vm_hook(const char* pos)
{
if (!vm_execute_hooks.contains(pos))
{
hook_enabled = true;
return false;
}
if (!hook_enabled && pos > reinterpret_cast<char*>(vm_execute_hooks.size()))
{
hook_enabled = true;
return false;
}
const auto hook = vm_execute_hooks[pos];
target_function = hook.target_pos;
return true;
}
void vm_execute_stub(utils::hook::assembler& a)
{
const auto replace = a.newLabel();
const auto end = a.newLabel();
a.pushad64();
a.mov(rcx, r14);
a.call_aligned(execute_vm_hook);
a.cmp(al, 0);
a.jne(replace);
a.popad64();
a.jmp(end);
a.bind(end);
a.movzx(r15d, byte_ptr(r14));
a.inc(r14);
a.lea(eax, dword_ptr(r15, -0x17));
a.mov(dword_ptr(rbp, 0x60), r15d);
a.jmp(0x14043A593);
a.bind(replace);
a.popad64();
a.mov(rax, qword_ptr(reinterpret_cast<std::int64_t>(&target_function)));
a.mov(r14, rax);
a.jmp(end);
}
}
void clear_callbacks()
{
vm_execute_hooks.clear();
}
void enable_vm_execute_hook()
{
hook_enabled = true;
}
void disable_vm_execute_hook()
{
hook_enabled = false;
}
void set_gsc_hook(const char* source, const char* target)
{
gsc_hook hook;
hook.target_pos = target;
vm_execute_hooks[source] = hook;
}
void clear_hook(const char* pos)
{
vm_execute_hooks.erase(pos);
}
std::size_t get_hook_count()
{
return vm_execute_hooks.size();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
utils::hook::call(0x1404724DD, client_command_stub);
utils::hook::jump(0x14043A584, utils::hook::assemble(vm_execute_stub), true);
scripting::on_shutdown([](const bool free_scripts)
{
if (free_scripts)
{
vm_execute_hooks.clear();
}
});
}
};
}
REGISTER_COMPONENT(notifies::component)

View File

@ -0,0 +1,15 @@
#pragma once
namespace notifies
{
extern bool hook_enabled;
void set_gsc_hook(const char* source, const char* target);
void clear_hook(const char* pos);
std::size_t get_hook_count();
void clear_callbacks();
void enable_vm_execute_hook();
void disable_vm_execute_hook();
}

View File

@ -0,0 +1,494 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "dvars.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include "server_list.hpp"
#include "party.hpp"
#include "steam/steam.hpp"
#include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
#include <utils/hook.hpp>
#include <version.hpp>
namespace party
{
namespace
{
struct
{
game::netadr_s host{};
std::string challenge{};
bool hostDefined{false};
} connect_state;
utils::hook::detour didyouknow_hook;
std::string sv_motd;
int sv_maxclients;
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype)
{
if (game::environment::is_sp())
{
return;
}
if (game::Live_SyncOnlineDataFlags(0))
{
scheduler::once([=]()
{
connect_to_party(target, mapname, gametype);
}, scheduler::pipeline::main, 1s);
return;
}
switch_gamemode_if_necessary(gametype);
perform_game_initialization();
// CL_ConnectFromParty
char session_info[0x100] = {};
reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x1402C5700)(
0, session_info, &target, mapname.data(), gametype.data());
}
void disconnect_stub()
{
if (game::CL_IsCgameInitialized())
{
// CL_ForwardCommandToServer
reinterpret_cast<void (*)(int, const char*)>(0x1402C76C0)(0, "disconnect");
// CL_WritePacket
reinterpret_cast<void (*)(int)>(0x1402C1E70)(0);
}
// CL_Disconnect
reinterpret_cast<void (*)(int)>(0x1402C6240)(0);
}
const auto drop_reason_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.mov(rdx, rdi);
a.mov(ecx, 2);
a.jmp(0x1402C617D);
});
}
void switch_gamemode_if_necessary(const std::string& gametype)
{
const auto target_mode = gametype == "aliens" ? game::CODPLAYMODE_ALIENS : game::CODPLAYMODE_CORE;
const auto current_mode = game::Com_GetCurrentCoDPlayMode();
if (current_mode != target_mode)
{
switch (target_mode)
{
case game::CODPLAYMODE_CORE:
game::SwitchToCoreMode();
break;
case game::CODPLAYMODE_ALIENS:
game::SwitchToAliensMode();
break;
}
}
}
void perform_game_initialization()
{
// This fixes several crashes and impure client stuff
command::execute("onlinegame 1", true);
command::execute("exec default_xboxlive.cfg", true);
command::execute("xstartprivateparty", true);
command::execute("xblive_privatematch 0", true);
command::execute("startentitlements", true);
}
int server_client_count()
{
return sv_maxclients;
}
int get_client_count()
{
auto count = 0;
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{
if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED)
{
++count;
}
}
return count;
}
int get_bot_count()
{
auto count = 0;
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{
if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED &&
game::mp::svs_clients[i].testClient != game::TC_NONE)
{
++count;
}
}
return count;
}
game::netadr_s& get_target()
{
return connect_state.host;
}
void reset_connect_state()
{
connect_state = {};
}
int get_client_num_from_name(const std::string& name)
{
for (auto i = 0; !name.empty() && i < *game::mp::svs_clientCount; ++i)
{
if (game::mp::g_entities[i].client)
{
char client_name[16] = {0};
strncpy_s(client_name, game::mp::g_entities[i].client->sess.cs.name, sizeof(client_name));
game::I_CleanStr(client_name);
if (client_name == name)
{
return i;
}
}
}
return -1;
}
void connect(const game::netadr_s& target)
{
if (game::environment::is_sp())
{
return;
}
command::execute("lui_open popup_acceptinginvite", false);
connect_state.host = target;
connect_state.challenge = utils::cryptography::random::get_challenge();
connect_state.hostDefined = true;
network::send(target, "getInfo", connect_state.challenge);
}
void didyouknow_stub(game::dvar_t* dvar, const char* string)
{
if (dvar->name == "didyouknow"s && !party::sv_motd.empty())
{
string = party::sv_motd.data();
}
return didyouknow_hook.invoke<void>(dvar, string);
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
// hook disconnect command function
utils::hook::jump(0x1402C6370, disconnect_stub);
if (game::environment::is_mp())
{
// show custom drop reason
utils::hook::nop(0x1402C6100, 13);
utils::hook::jump(0x1402C6100, drop_reason_stub, true);
}
// enable custom kick reason in GScr_KickPlayer
utils::hook::set<uint8_t>(0x1403CBFD0, 0xEB);
didyouknow_hook.create(game::Dvar_SetString, didyouknow_stub);
command::add("reconnect", []([[maybe_unused]] const command::params& params)
{
if (!connect_state.hostDefined)
{
console::info("Cannot connect to server.\n");
return;
}
if (game::CL_IsCgameInitialized())
{
command::execute("disconnect");
command::execute("reconnect");
}
else
{
connect(connect_state.host);
}
});
command::add("connect", [](const command::params& params)
{
if (params.size() != 2)
{
return;
}
game::netadr_s target{};
if (game::NET_StringToAdr(params[1], &target))
{
connect(target);
}
});
command::add("clientkick", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("usage: clientkick <num>, <reason>(optional)\n");
return;
}
if (!game::SV_Loaded())
{
return;
}
std::string reason;
if (params.size() > 2)
{
reason = params.join(2);
}
if (reason.empty())
{
reason = "EXE_PLAYERKICKED";
}
const auto client_num = atoi(params.get(1));
if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
{
return;
}
scheduler::once([client_num, reason]
{
game::SV_KickClientNum(client_num, reason.data());
}, scheduler::pipeline::server);
});
command::add("kick", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("usage: kick <name>, <reason>(optional)\n");
return;
}
if (!game::SV_Loaded())
{
return;
}
std::string reason;
if (params.size() > 2)
{
reason = params.join(2);
}
if (reason.empty())
{
reason = "EXE_PLAYERKICKED";
}
const std::string name = params.get(1);
if (name == "all"s)
{
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{
scheduler::once([i, reason]
{
game::SV_KickClientNum(i, reason.data());
}, scheduler::pipeline::server);
}
return;
}
const auto client_num = get_client_num_from_name(name);
if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
{
return;
}
scheduler::once([client_num, reason]
{
game::SV_KickClientNum(client_num, reason.data());
}, scheduler::pipeline::server);
});
scheduler::once([]
{
game::Dvar_RegisterString("sv_sayName", "console", game::DVAR_FLAG_NONE,
"The name to pose as for 'say' commands");
game::Dvar_RegisterString("didyouknow", "", game::DVAR_FLAG_NONE, "");
}, scheduler::pipeline::main);
command::add("tell", [](const command::params& params)
{
if (params.size() < 3)
{
return;
}
const auto client_num = atoi(params.get(1));
const auto message = params.join(2);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
console::info("%s -> %i: %s\n", name, client_num, message.data());
});
command::add("tellraw", [](const command::params& params)
{
if (params.size() < 3)
{
return;
}
const auto client_num = atoi(params.get(1));
const auto message = params.join(2);
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s\"", 84, message.data()));
console::info("%i: %s\n", client_num, message.data());
});
command::add("say", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto message = params.join(1);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
console::info("%s: %s\n", name, message.data());
});
command::add("sayraw", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto message = params.join(1);
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data()));
console::info("%s\n", message.data());
});
network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
{
utils::info_string info;
info.set("challenge", data);
info.set("gamename", "IW6");
info.set("hostname", dvars::get_string("sv_hostname"));
info.set("gametype", dvars::get_string("g_gametype"));
info.set("sv_motd", dvars::get_string("sv_motd"));
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", dvars::get_string("mapname"));
info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1");
info.set("clients", std::to_string(get_client_count()));
info.set("bots", std::to_string(get_bot_count()));
info.set("sv_maxclients", std::to_string(*game::mp::svs_clientCount));
info.set("protocol", std::to_string(PROTOCOL));
info.set("shortversion", SHORTVERSION);
network::send(target, "infoResponse", info.build(), '\n');
});
if (game::environment::is_dedi())
{
return;
}
network::on("infoResponse", [](const game::netadr_s& target, const std::string& data)
{
const utils::info_string info(data);
server_list::handle_info_response(target, info);
if (connect_state.host != target)
{
return;
}
if (info.get("challenge") != connect_state.challenge)
{
console::info("Invalid challenge.\n");
return;
}
const auto mapname = info.get("mapname");
if (mapname.empty())
{
console::info("Invalid map.\n");
return;
}
const auto game_type = info.get("gametype");
if (game_type.empty())
{
console::info("Invalid gametype.\n");
return;
}
const auto game_name = info.get("gamename");
if (game_name != "IW6"s)
{
console::info("Invalid gamename.\n");
return;
}
const auto is_private = info.get("isPrivate");
if (is_private == "1"s && dvars::get_string("password").empty())
{
console::info("Password is not set.\n");
return;
}
sv_motd = info.get("sv_motd");
try
{
sv_maxclients = std::stoi(info.get("sv_maxclients"));
}
catch ([[maybe_unused]] const std::exception& ex)
{
sv_maxclients = 1;
}
connect_to_party(target, mapname, game_type);
});
}
};
}
REGISTER_COMPONENT(party::component)

View File

@ -0,0 +1,19 @@
#pragma once
namespace party
{
void switch_gamemode_if_necessary(const std::string& gametype);
void perform_game_initialization();
void reset_connect_state();
void connect(const game::netadr_s& target);
void map_restart();
[[nodiscard]] int server_client_count();
[[nodiscard]] int get_client_count();
[[nodiscard]] int get_bot_count();
[[nodiscard]] game::netadr_s& get_target();
}

View File

@ -0,0 +1,412 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "dvars.hpp"
#include "filesystem.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/nt.hpp>
namespace patches
{
namespace
{
utils::hook::detour live_get_local_client_name_hook;
const char* live_get_local_client_name()
{
return game::Dvar_FindVar("name")->current.string;
}
utils::hook::detour sv_kick_client_num_hook;
void sv_kick_client_num(const int clientNum, const char* reason)
{
// Don't kick bot to equalize team balance.
if (reason == "EXE_PLAYERKICKED_BOT_BALANCE"s)
{
return;
}
return sv_kick_client_num_hook.invoke<void>(clientNum, reason);
}
std::string get_login_username()
{
char username[UNLEN + 1];
DWORD username_len = UNLEN + 1;
if (!GetUserNameA(username, &username_len))
{
return "Unknown Soldier";
}
return std::string{ username, username_len - 1 };
}
utils::hook::detour com_register_dvars_hook;
void com_register_dvars_stub()
{
if (game::environment::is_mp())
{
// Make name save
game::Dvar_RegisterString("name", get_login_username().data(), game::DVAR_FLAG_SAVED, "Player name.");
}
return com_register_dvars_hook.invoke<void>();
}
utils::hook::detour dvar_register_int_hook;
game::dvar_t* dvar_register_int(const char* name, int value, const int min, const int max,
const unsigned int flags,
const char* description)
{
// enable map selection in extinction
if (!strcmp(name, "extinction_map_selection_enabled"))
{
value = true;
}
// enable extra loadouts
else if (!strcmp(name, "extendedLoadoutsEnable"))
{
value = true;
}
// show all in-game store items
else if (strstr(name, "igs_"))
{
value = true;
}
return dvar_register_int_hook.invoke<game::dvar_t*>(name, value, min, max, flags, description);
}
game::dvar_t* register_fovscale_stub(const char* name, float /*value*/, float /*min*/, float /*max*/,
unsigned int /*flags*/,
const char* desc)
{
// changed max value from 2.0f -> 5.0f and min value from 0.5f -> 0.1f
return game::Dvar_RegisterFloat(name, 1.0f, 0.1f, 5.0f, game::DvarFlags::DVAR_FLAG_SAVED, desc);
}
game::dvar_t* register_cg_gun_dvars(const char* name, float /*value*/, float /*min*/, float /*max*/,
unsigned int /*flags*/, const char* desc)
{
if (name == "cg_gun_x"s)
{
return game::Dvar_RegisterFloat(name, 0.0f, -1.0f, 2.0f, game::DvarFlags::DVAR_FLAG_SAVED, desc);
}
else
{
return game::Dvar_RegisterFloat(name, 0.0f, 0.0f, 0.0f, game::DvarFlags::DVAR_FLAG_NONE, desc);
}
}
game::dvar_t* register_network_fps_stub(const char* name, int, int, int, unsigned int flags,
const char* desc)
{
return game::Dvar_RegisterInt(name, 1000, 20, 1000, flags, desc);
}
bool cmd_exec_patch()
{
const command::params exec_params{};
if (exec_params.size() == 2)
{
std::string file_name = exec_params.get(1);
if (file_name.find(".cfg") == std::string::npos)
file_name.append(".cfg");
const auto file = filesystem::file(file_name);
if (file.exists())
{
game::Cbuf_ExecuteBufferInternal(0, 0, file.get_buffer().data(), game::Cmd_ExecuteSingleCommand);
return true;
}
}
return false;
}
auto cmd_exec_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto success = a.newLabel();
a.pushad64();
a.call_aligned(cmd_exec_patch);
a.test(al, al);
a.popad64();
a.jz(success);
a.mov(edx, 0x18000);
a.jmp(0x1403F7530);
a.bind(success);
a.jmp(0x1403F7574);
});
auto cmd_exec_stub_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto success = a.newLabel();
a.pushad64();
a.call_aligned(cmd_exec_patch);
a.test(al, al);
a.popad64();
a.jz(success);
a.mov(edx, 0x18000);
a.jmp(0x1403B39C0);
a.bind(success);
a.jmp(0x1403B3A04);
});
int dvar_command_patch() // game makes this return an int and compares with eax instead of al -_-
{
const command::params args{};
if (args.size() <= 0)
return 0;
auto* dvar = game::Dvar_FindVar(args.get(0));
if (dvar)
{
if (args.size() == 1)
{
const auto current = game::Dvar_ValueToString(dvar, dvar->current);
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset);
console::info("\"%s\" is: \"%s^7\" default: \"%s^7\"\n", dvar->name, current, reset);
console::info(" %s\n", dvars::dvar_get_domain(dvar->type, dvar->domain).data());
}
else
{
char command[0x1000] = {0};
game::Dvar_GetCombinedString(command, 1);
game::Dvar_SetCommand(args.get(0), command);
}
return 1;
}
return 0;
}
game::Font_s* get_chat_font_handle()
{
return game::R_RegisterFont("fonts/bigFont");
}
void aim_assist_add_to_target_list(void* a1, void* a2)
{
if (!dvars::aimassist_enabled->current.enabled)
return;
game::AimAssist_AddToTargetList(a1, a2);
}
game::dvar_t* register_cg_fov_stub(const char* name, float value, float min, float /*max*/,
const unsigned int flags,
const char* description)
{
return game::Dvar_RegisterFloat(name, value, min, 160, flags | 1, description);
}
void bsp_sys_error_stub(const char* error, const char* arg1)
{
if (game::environment::is_dedi())
{
game::Sys_Error(error, arg1);
}
else
{
scheduler::once([]()
{
command::execute("reconnect");
}, scheduler::pipeline::main, 1s);
game::Com_Error(game::ERR_DROP, error, arg1);
}
}
utils::hook::detour cmd_lui_notify_server_hook;
void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
{
command::params_sv params{};
const auto menu_id = atoi(params.get(1));
const auto client = &game::mp::svs_clients[ent->s.clientNum];
// 9 => "end_game"
if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK)
{
return;
}
cmd_lui_notify_server_hook.invoke<void>(ent);
}
void update_last_seen_players_stub(utils::hook::assembler& a)
{
const auto safe_continue = a.newLabel();
// (game's code)
a.mov(rax, ptr(rbx)); // g_entities pointer
// Avoid crash if pointer is nullptr
a.test(rax, rax);
a.jz(safe_continue);
// Jump back in (game's code)
a.mov(dword_ptr(rax, 0x34F8), 0);
// Continue to next iter in this loop
a.bind(safe_continue);
a.jmp(0x1403A1A10);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Increment ref-count on these
LoadLibraryA("PhysXDevice64.dll");
LoadLibraryA("PhysXUpdateLoader64.dll");
// Unlock fps in main menu
utils::hook::set<BYTE>(SELECT_VALUE(0x140242DDB, 0x1402CF58B), 0xEB);
// Unlock cg_fov
utils::hook::call(SELECT_VALUE(0x1401F3E96, 0x14027273C), register_cg_fov_stub);
if (game::environment::is_sp())
{
utils::hook::call(0x1401F3EC7, register_cg_fov_stub);
}
// set it to 3 to display both voice dlc announcers did only show 1
game::Dvar_RegisterInt("igs_announcer", 3, 3, 3, game::DvarFlags::DVAR_FLAG_NONE,
"Show Announcer Packs. (Bitfield representing which announcer paks to show)");
// changed max value from 85 -> 1000
if (!game::environment::is_dedi())
{
game::Dvar_RegisterInt("com_maxfps", 85, 0, 1000, game::DvarFlags::DVAR_FLAG_SAVED, "Cap frames per second");
}
if (!game::environment::is_sp())
{
//increased max limit for sv_network_fps, the lower limit is the default one. Original range is from 20 to 200 times a second.
utils::hook::call(0x140476F4F, register_network_fps_stub);
}
// register cg_gun_ dvars with new values and flags
// maybe _x can stay usable within a reasonable range? it can make scoped weapons DRASTICALLY better on high FOVs
utils::hook::call(SELECT_VALUE(0x140228DDE, 0x1402AB04C), register_cg_gun_dvars);
utils::hook::call(SELECT_VALUE(0x140228E0E, 0x1402AB07C), register_cg_gun_dvars);
utils::hook::call(SELECT_VALUE(0x140228E3E, 0x1402AB0AC), register_cg_gun_dvars);
// Register cg_fovscale with new params
utils::hook::call(SELECT_VALUE(0x140317079, 0x140272777), register_fovscale_stub);
// Patch Dvar_Command to print out values how CoD4 does it
utils::hook::jump(SELECT_VALUE(0x1403BFCB0, 0x140416A60), dvar_command_patch);
// Allow executing custom cfg files with the "exec" command
utils::hook::jump(SELECT_VALUE(0x1403B39BB, 0x1403F752B), SELECT_VALUE(0x1403B3A12, 0x1403F7582));
//Use a relative jump to empty memory first
utils::hook::jump(SELECT_VALUE(0x1403B3A12, 0x1403F7582), SELECT_VALUE(cmd_exec_stub_sp, cmd_exec_stub_mp),
true);
// Use empty memory to go to our stub first (can't do close jump, so need space for 12 bytes)
// Fix mouse lag
utils::hook::nop(SELECT_VALUE(0x14043E6CB, 0x140504A2B), 6);
scheduler::loop([]()
{
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
}, scheduler::pipeline::main);
// Allow kbam input when gamepad is enabled
utils::hook::nop(SELECT_VALUE(0x14023D490, 0x1402C3099), 2);
utils::hook::nop(SELECT_VALUE(0x14023B3AC, 0x1402C0CE0), 6);
if (game::environment::is_sp())
{
patch_sp();
}
else
{
patch_mp();
}
}
static void patch_mp()
{
// Register dvars
com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub);
// Use name dvar and add "saved" flags to it
utils::hook::set<uint8_t>(0x1402C836D, 0x01);
live_get_local_client_name_hook.create(0x1404FDAA0, &live_get_local_client_name);
// Patch SV_KickClientNum
sv_kick_client_num_hook.create(0x14046F730, &sv_kick_client_num);
// Block changing name in-game
utils::hook::set<uint8_t>(0x140470300, 0xC3);
// Unlock all DLC items
utils::hook::set(0x1404EAC50, 0xC301B0); // Content_DoWeHaveDLCPackByName
utils::hook::set(0x140599890, 0xC301B0); // Entitlements_IsIDUnlocked
// Enable DLC items, extra loadouts and map selection in extinction
dvar_register_int_hook.create(0x1404EE270, &dvar_register_int);
// Patch game chat on resolutions higher than 1080p to use the right font
utils::hook::call(0x14025C825, get_chat_font_handle);
utils::hook::call(0x1402BC42F, get_chat_font_handle);
utils::hook::call(0x1402C3699, get_chat_font_handle);
dvars::aimassist_enabled = game::Dvar_RegisterBool("aimassist_enabled", true,
game::DvarFlags::DVAR_FLAG_SAVED,
"Enables aim assist for controllers");
// Client side aim assist dvar
utils::hook::call(0x14013B9AC, aim_assist_add_to_target_list);
// Patch "Couldn't find the bsp for this map." error to not be fatal in mp
utils::hook::call(0x14031E8AB, bsp_sys_error_stub);
// isProfanity
utils::hook::set(0x1402F61B0, 0xC3C033);
// Don't register every replicated dvar as a network dvar
utils::hook::nop(0x1403E984E, 5); // Dvar_ForEach
// Prevent clients from ending the game as non host by sending 'end_game' lui notification
cmd_lui_notify_server_hook.create(0x1403926A0, cmd_lui_notify_server_stub);
// Remove cheat protection from r_umbraExclusive
dvars::override::register_bool("r_umbraExclusive", false, game::DVAR_FLAG_NONE);
// Patch crash caused by the server trying to kick players for 'invalid password'
utils::hook::jump(0x1403A1A03, utils::hook::assemble(update_last_seen_players_stub), true);
utils::hook::nop(0x1403A1A0F, 1);
// ^^
utils::hook::nop(0x1403A072F, 5); // LiveStorage_RecordMovementInMatchdata
}
static void patch_sp()
{
// SP doesn't initialize WSA
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
}
};
}
REGISTER_COMPONENT(patches::component)

View File

@ -0,0 +1,109 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "dvars.hpp"
#include <utils/hook.hpp>
#include <utils/flags.hpp>
namespace ranked
{
namespace
{
utils::hook::detour bg_bot_system_enabled_hook;
utils::hook::detour bg_ai_system_enabled_hook;
utils::hook::detour bg_bot_fast_file_enabled_hook;
utils::hook::detour bg_bots_using_team_difficulty_hook;
int bg_bot_system_enabled_stub()
{
const auto* game_type = game::Dvar_FindVar("g_gametype")->current.string;
if (!std::strcmp(game_type, "aliens") || !std::strcmp(game_type, "horde"))
{
return bg_bot_system_enabled_hook.invoke<int>();
}
return 1;
}
int bg_ai_system_enabled_stub()
{
const auto* game_type = game::Dvar_FindVar("g_gametype")->current.string;
if (!std::strcmp(game_type, "aliens") || !std::strcmp(game_type, "horde"))
{
return bg_ai_system_enabled_hook.invoke<int>();
}
return 1;
}
int bg_bot_fast_file_enabled_stub()
{
const auto* game_type = game::Dvar_FindVar("g_gametype")->current.string;
if (!std::strcmp(game_type, "aliens") || !std::strcmp(game_type, "horde"))
{
return bg_bot_fast_file_enabled_hook.invoke<int>();
}
return 1;
}
int bg_bots_using_team_difficulty_stub()
{
const auto* game_type = game::Dvar_FindVar("g_gametype")->current.string;
if (!std::strcmp(game_type, "aliens") || !std::strcmp(game_type, "horde"))
{
return bg_bots_using_team_difficulty_hook.invoke<int>();
}
return 1;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
if (game::environment::is_mp())
{
// This must be registered as 'true' to avoid crash when starting a private match
dvars::override::register_bool("xblive_privatematch", true, game::DVAR_FLAG_REPLICATED);
}
if (game::environment::is_dedi() && !utils::flags::has_flag("unranked"))
{
dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_WRITE);
// Some dvar used in gsc
game::Dvar_RegisterBool("force_ranking", true, game::DVAR_FLAG_WRITE, "Force ranking");
// Fix sessionteam always returning none (SV_HasAssignedTeam_Internal)
utils::hook::set(0x140479CF0, 0xC300B0);
}
// Always run bots, even if xblive_privatematch is 0
bg_bot_system_enabled_hook.create(0x140217020, &bg_bot_system_enabled_stub);
bg_ai_system_enabled_hook.create(0x140216DC0, &bg_ai_system_enabled_stub);
bg_bot_fast_file_enabled_hook.create(0x140216F70, &bg_bot_fast_file_enabled_stub);
bg_bots_using_team_difficulty_hook.create(0x1402170E0, &bg_bots_using_team_difficulty_stub);
}
void pre_destroy() override
{
bg_bot_system_enabled_hook.clear();
bg_ai_system_enabled_hook.clear();
bg_bot_fast_file_enabled_hook.clear();
bg_bots_using_team_difficulty_hook.clear();
}
};
}
REGISTER_COMPONENT(ranked::component)

View File

@ -0,0 +1,198 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <hidusage.h>
namespace raw_mouse
{
namespace
{
int mouse_raw_x = 0;
int mouse_raw_y = 0;
const game::dvar_t* cl_rawInput = nullptr;
LRESULT wnd_proc_hook(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_INPUT)
{
std::uint32_t size = sizeof(RAWINPUT);
static BYTE lpb[sizeof(RAWINPUT)];
GetRawInputData(
reinterpret_cast<HRAWINPUT>(lparam),
RID_INPUT, lpb, &size,
sizeof(RAWINPUTHEADER)
);
auto* raw = reinterpret_cast<RAWINPUT*>(lpb);
if (raw->header.dwType == RIM_TYPEMOUSE)
{
// Is there's really absolute mouse on earth?
if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
{
mouse_raw_x = raw->data.mouse.lLastX;
mouse_raw_y = raw->data.mouse.lLastY;
}
else
{
mouse_raw_x += raw->data.mouse.lLastX;
mouse_raw_y += raw->data.mouse.lLastY;
}
}
return TRUE;
}
return game::MainWndProc(hwnd, msg, wparam, lparam);
}
void in_clamp_mouse_move(tagPOINT &current_position)
{
tagRECT rc;
GetWindowRect(game::g_wv->hWnd, &rc);
auto is_clamped = false;
if (current_position.x >= rc.left)
{
if (current_position.x >= rc.right)
{
current_position.x = rc.right - 1;
is_clamped = true;
}
}
else
{
current_position.x = rc.left;
is_clamped = true;
}
if (current_position.y >= rc.top)
{
if (current_position.y >= rc.bottom)
{
current_position.y = rc.bottom - 1;
is_clamped = true;
}
}
else
{
current_position.y = rc.top;
is_clamped = true;
}
if (is_clamped)
{
SetCursorPos(current_position.x, current_position.y);
}
}
void in_raw_mouse_move()
{
static const auto* r_displayMode = game::Dvar_FindVar("r_displayMode");
if (GetForegroundWindow() == game::g_wv->hWnd)
{
static tagPOINT current_position;
GetCursorPos(&current_position);
// is fullscreen
if (r_displayMode->current.integer == 0)
{
in_clamp_mouse_move(current_position);
}
static auto old_x = 0, old_y = 0;
const auto need_reset = game::s_wmv->oldPos.x == -100000;
const auto delta_x = need_reset ? 0 : mouse_raw_x - old_x;
const auto delta_y = need_reset ? 0 : mouse_raw_y - old_y;
old_x = mouse_raw_x;
old_y = mouse_raw_y;
game::s_wmv->oldPos = current_position;
ScreenToClient(game::g_wv->hWnd, &current_position);
current_position.x = current_position.x * game::vidConfigOut->displayWidth / game::vidConfigOut->monitorWidth;
current_position.y = current_position.y * game::vidConfigOut->displayHeight / game::vidConfigOut->monitorHeight;
game::g_wv->recenterMouse = game::CL_MouseEvent(current_position.x, current_position.y, delta_x, delta_y);
if (game::g_wv->recenterMouse && (delta_x || delta_y || need_reset))
{
game::IN_RecenterMouse();
}
}
}
void in_mouse_move()
{
if (cl_rawInput->current.enabled)
{
in_raw_mouse_move();
}
else
{
game::IN_MouseMove();
}
}
void in_raw_mouse_init(HWND hwnd)
{
RAWINPUTDEVICE rid[1];
rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
RegisterRawInputDevices(rid, ARRAYSIZE(rid), sizeof(rid[0]));
}
HWND CALLBACK create_window_ex_a_stub(DWORD dw_ex_style, LPCSTR lp_class_name, LPCSTR lp_window_name,
DWORD dw_style, int x, int y, int n_width, int n_height, HWND h_wnd_parent, HMENU h_menu, HINSTANCE h_instance, LPVOID lp_param)
{
const auto hwnd = CreateWindowExA(dw_ex_style, lp_class_name, lp_window_name,
dw_style, x, y, n_width, n_height, h_wnd_parent, h_menu, h_instance, lp_param);
in_raw_mouse_init(hwnd);
return hwnd;
}
ATOM CALLBACK register_class_ex_a_stub(WNDCLASSEXA* wnd_class)
{
wnd_class->lpfnWndProc = wnd_proc_hook;
return RegisterClassExA(wnd_class);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
utils::hook::call(SELECT_VALUE(0x14043845F, 0x1404FC3FB), &in_mouse_move);
utils::hook::call(SELECT_VALUE(0x140519F71, 0x1405E69A1), &create_window_ex_a_stub);
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x140519F76, 0x1405E69A6), 0x90);
utils::hook::call(SELECT_VALUE(0x14043BF04, 0x140500824), &register_class_ex_a_stub);
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x14043BF09, 0x140500829), 0x90);
cl_rawInput = game::Dvar_RegisterBool("cl_rawInput", true,
game::DVAR_FLAG_SAVED, "Use Raw Input for mouse input. This fixes mouse acceleration issue");
}
};
}
REGISTER_COMPONENT(raw_mouse::component)

View File

@ -0,0 +1,202 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
#include "network.hpp"
#include "scheduler.hpp"
namespace rcon
{
namespace
{
bool is_redirecting_ = false;
game::netadr_s redirect_target_ = {};
std::recursive_mutex redirect_lock;
void setup_redirect(const game::netadr_s& target)
{
std::lock_guard<std::recursive_mutex> $(redirect_lock);
is_redirecting_ = true;
redirect_target_ = target;
}
void clear_redirect()
{
std::lock_guard<std::recursive_mutex> $(redirect_lock);
is_redirecting_ = false;
redirect_target_ = {};
}
std::string build_status_buffer()
{
const auto* sv_maxclients = game::Dvar_FindVar("sv_maxclients");
const auto* mapname = game::Dvar_FindVar("mapname");
std::string buffer;
buffer.append(utils::string::va("map: %s\n", mapname->current.string));
buffer.append("num score bot ping guid name address qport\n");
buffer.append("--- ----- --- ---- -------------------------------- ---------------- --------------------- -----\n");
for (int i = 0; i < sv_maxclients->current.integer; i++)
{
const auto client = &game::mp::svs_clients[i];
auto self = &game::mp::g_entities[i];
char clean_name[32] = {0};
strncpy_s(clean_name, self->client->sess.cs.name, sizeof(clean_name));
game::I_CleanStr(clean_name);
if (client->header.state > game::CS_FREE && self && self->client)
{
buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n",
i,
self->client->sess.scores.score,
game::SV_BotIsBot(i) ? "Yes" : "No",
(client->header.state == game::CS_RECONNECTING) ? "CNCT" : (client->header.state == game::CS_ZOMBIE) ? "ZMBI" : utils::string::va("%4i", client->ping),
game::SV_GetGuid(i),
clean_name,
network::net_adr_to_string(client->header.netchan.remoteAddress),
client->header.netchan.remoteAddress.port)
);
}
}
return buffer;
}
void send_rcon_command(const std::string& password, const std::string& data)
{
// If you are the server, don't bother with rcon and just execute the command
if (game::Dvar_FindVar("sv_running")->current.enabled)
{
game::Cbuf_AddText(0, data.data());
return;
}
if (password.empty())
{
console::info("You must login first to use RCON\n");
return;
}
if (*reinterpret_cast<std::int32_t*>(0x1419E1AE0) >= 5) //clientUIActive.connectionState >= CA_CONNECTED
{
const auto target = *reinterpret_cast<game::netadr_s*>(0x141CB535C);
const auto buffer = password + " " + data;
network::send(target, "rcon", buffer);
}
else
{
console::warn("You need to be connected to a server!\n");
}
}
}
bool message_redirect(const std::string& message)
{
std::lock_guard<std::recursive_mutex> $(redirect_lock);
if (is_redirecting_)
{
network::send(redirect_target_, "print\n", message);
return true;
}
return false;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp()) return;
scheduler::once([]()
{
game::Dvar_RegisterString("rcon_password", "", game::DvarFlags::DVAR_FLAG_NONE,
"The password for remote console");
}, scheduler::pipeline::main);
command::add("status", []()
{
const auto sv_running = game::Dvar_FindVar("sv_running");
if (!sv_running || !sv_running->current.enabled)
{
console::error("Server is not running\n");
return;
}
const auto status = build_status_buffer();
console::info("%s", status.data());
});
if (!game::environment::is_dedi())
{
command::add("rcon", [](const command::params& params)
{
static std::string rcon_password{};
if (params.size() < 2) return;
const auto operation = params.get(1);
if (operation == "login"s)
{
if (params.size() < 3)
return;
rcon_password = params.get(2);
}
else if (operation == "logout"s)
{
rcon_password.clear();
}
else
{
send_rcon_command(rcon_password, params.join(1));
}
});
}
else
{
network::on("rcon", [](const game::netadr_s& addr, const std::string& data)
{
const auto pos = data.find_first_of(" ");
if (pos == std::string::npos)
{
console::info("Invalid RCon request from %s\n", network::net_adr_to_string(addr));
return;
}
const auto password = data.substr(0, pos);
const auto command = data.substr(pos + 1);
const auto rcon_password = game::Dvar_FindVar("rcon_password");
if (command.empty() || !rcon_password || !*rcon_password->current.string)
{
return;
}
setup_redirect(addr);
if (password != rcon_password->current.string)
{
console::error("Invalid rcon password\n");
}
else
{
command::execute(command, true);
}
clear_redirect();
});
}
}
};
}
REGISTER_COMPONENT(rcon::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace rcon
{
bool message_redirect(const std::string& message);
}

View File

@ -0,0 +1,74 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game_module.hpp"
#include <utils/nt.hpp>
#include <utils/string.hpp>
namespace redirect
{
namespace
{
void launch_complementary_game(const bool singleplayer)
{
const auto self = game_module::get_host_module();
STARTUPINFOA startup_info;
PROCESS_INFORMATION process_info;
ZeroMemory(&startup_info, sizeof(startup_info));
ZeroMemory(&process_info, sizeof(process_info));
startup_info.cb = sizeof(startup_info);
auto* arguments = const_cast<char*>(utils::string::va("%s%s", self.get_path().data(),
(singleplayer ? " -singleplayer" : " -multiplayer")));
CreateProcessA(self.get_path().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
&startup_info, &process_info);
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
{
CloseHandle(process_info.hThread);
}
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
{
CloseHandle(process_info.hProcess);
}
}
HINSTANCE shell_execute_a(const HWND hwnd, const LPCSTR operation, const LPCSTR file, const LPCSTR parameters,
const LPCSTR directory, const INT show_cmd)
{
if (utils::string::starts_with(file, "steam://run/209160/"))
{
launch_complementary_game(true);
return HINSTANCE(33);
}
else if (utils::string::starts_with(file, "steam://run/209170/"))
{
launch_complementary_game(false);
return HINSTANCE(33);
}
return ShellExecuteA(hwnd, operation, file, parameters, directory, show_cmd);
}
}
class component final : public component_interface
{
public:
void* load_import(const std::string& library, const std::string& function) override
{
if (library == "SHELL32.dll")
{
if (function == "ShellExecuteA")
{
return shell_execute_a;
}
}
return nullptr;
}
};
}
REGISTER_COMPONENT(redirect::component)

View File

@ -0,0 +1,59 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "utils/hook.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
namespace renderer
{
namespace
{
utils::hook::detour r_init_draw_method_hook;
utils::hook::detour r_update_front_end_dvar_options_hook;
void r_init_draw_method_stub()
{
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : game::TECHNIQUE_LIT;
game::gfxDrawMethod->emissiveTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : game::TECHNIQUE_EMISSIVE;
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : 0x19E;
}
bool r_update_front_end_dvar_options_stub()
{
if (dvars::r_fullbright->modified)
{
dvars::r_fullbright->modified = false;
game::R_SyncRenderThread();
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : game::TECHNIQUE_LIT;
game::gfxDrawMethod->emissiveTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : game::TECHNIQUE_EMISSIVE;
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? game::TECHNIQUE_UNLIT : 0x19E;
}
return r_update_front_end_dvar_options_hook.invoke<bool>();
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
dvars::r_fullbright = game::Dvar_RegisterBool("r_fullbright", false, game::DvarFlags::DVAR_FLAG_SAVED,
"Toggles rendering without lighting");
r_init_draw_method_hook.create(SELECT_VALUE(0x1404FF600, 0x1405CB470), &r_init_draw_method_stub);
r_update_front_end_dvar_options_hook.create(
SELECT_VALUE(0x140535FF0, 0x140603240), &r_update_front_end_dvar_options_stub);
}
};
}
REGISTER_COMPONENT(renderer::component)

View File

@ -0,0 +1,70 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/nt.hpp>
namespace resources
{
namespace
{
HICON icon;
HANDLE splash, logo;
HANDLE WINAPI load_image_a(const HINSTANCE handle, LPCSTR name, const UINT type, const int cx, const int cy,
const UINT load)
{
const utils::nt::library self;
if (!IS_INTRESOURCE(name) && name == "logo.bmp"s) return logo;
if (self.get_handle() == handle && name == LPCSTR(0x64)) return splash;
return LoadImageA(handle, name, type, cx, cy, load);
}
HICON WINAPI load_icon_a(const HINSTANCE handle, const LPCSTR name)
{
const utils::nt::library self;
if (self.get_handle() == handle && name == LPCSTR(2)) return icon;
return LoadIconA(handle, name);
}
}
class component final : public component_interface
{
public:
~component() override
{
if (icon) DestroyIcon(icon);
if (logo) DeleteObject(logo);
if (splash) DeleteObject(splash);
}
void post_start() override
{
const utils::nt::library self;
icon = LoadIconA(self.get_handle(), MAKEINTRESOURCEA(ID_ICON));
logo = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_LOGO), 0, 0, 0, LR_COPYFROMRESOURCE);
splash = LoadImageA(self.get_handle(), MAKEINTRESOURCEA(IMAGE_SPLASH), 0, 0, 0, LR_COPYFROMRESOURCE);
}
void* load_import(const std::string& library, const std::string& function) override
{
if (library == "USER32.dll")
{
if (function == "LoadIconA")
{
return load_icon_a;
}
if (function == "LoadImageA")
{
return load_image_a;
}
}
return nullptr;
}
};
}
REGISTER_COMPONENT(resources::component)

View File

@ -0,0 +1,197 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include <utils/thread.hpp>
namespace scheduler
{
namespace
{
struct task
{
std::function<bool()> handler{};
std::chrono::milliseconds interval{};
std::chrono::high_resolution_clock::time_point last_call{};
};
using task_list = std::vector<task>;
class task_pipeline
{
public:
void add(task&& task)
{
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
}
void execute()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->interval)
{
++i;
continue;
}
i->last_call = now;
const auto res = i->handler();
if (res == cond_end)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
private:
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
void merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
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()));
new_tasks = {};
});
});
}
};
volatile bool kill = false;
std::thread thread;
task_pipeline pipelines[pipeline::count];
utils::hook::detour r_end_frame_hook;
void execute(const pipeline type)
{
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
}
void r_end_frame_stub()
{
execute(pipeline::renderer);
r_end_frame_hook.invoke<void>();
}
void server_frame_stub()
{
game::G_Glass_Update();
execute(pipeline::server);
}
void main_frame_stub()
{
execute(pipeline::main);
game::Com_Frame_Try_Block_Function();
}
}
void schedule(const std::function<bool()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
assert(type >= 0 && type < pipeline::count);
task task;
task.handler = callback;
task.interval = delay;
task.last_call = std::chrono::high_resolution_clock::now();
pipelines[type].add(std::move(task));
}
void loop(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_continue;
}, type, delay);
}
void once(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([callback]()
{
callback();
return cond_end;
}, type, delay);
}
void on_game_initialized(const std::function<void()>& callback, const pipeline type,
const std::chrono::milliseconds delay)
{
schedule([=]()
{
const auto dw_init = game::environment::is_sp() || game::Live_SyncOnlineDataFlags(0) == 0;
if (dw_init && game::Sys_IsDatabaseReady2())
{
once(callback, type, delay);
return cond_end;
}
return cond_continue;
}, pipeline::main);
}
class component final : public component_interface
{
public:
void post_start() override
{
thread = utils::thread::create_named_thread("Async Scheduler", []()
{
while (!kill)
{
execute(pipeline::async);
std::this_thread::sleep_for(10ms);
}
});
}
void post_unpack() override
{
r_end_frame_hook.create(SELECT_VALUE(0x140534860, 0x140601AA0), scheduler::r_end_frame_stub);
utils::hook::call(SELECT_VALUE(0x1403BC922, 0x140413142), scheduler::main_frame_stub);
utils::hook::call(SELECT_VALUE(0x1403185FD, 0x1403A0AF9), scheduler::server_frame_stub);
}
void pre_destroy() override
{
kill = true;
if (thread.joinable())
{
thread.join();
}
}
};
}
REGISTER_COMPONENT(scheduler::component)

View File

@ -0,0 +1,33 @@
#pragma once
namespace scheduler
{
enum pipeline
{
// Asynchronuous pipeline, disconnected from the game
async = 0,
// The game's rendering pipeline
renderer,
// The game's server thread
server,
// The game's main thread
main,
count,
};
static const bool cond_continue = false;
static const bool cond_end = true;
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async,
std::chrono::milliseconds delay = 0ms);
void loop(const std::function<void()>& callback, pipeline type = pipeline::async,
std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type = pipeline::async,
std::chrono::milliseconds delay = 0ms);
void on_game_initialized(const std::function<void()>& callback, pipeline type = pipeline::async,
std::chrono::milliseconds delay = 0ms);
}

View File

@ -0,0 +1,215 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/scripting/functions.hpp"
#include "scripting.hpp"
#include "gsc/script_loading.hpp"
#include <utils/hook.hpp>
namespace scripting
{
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
std::string current_file;
namespace
{
utils::hook::detour scr_load_level_hook;
utils::hook::detour g_shutdown_game_hook;
utils::hook::detour scr_set_thread_position_hook;
utils::hook::detour process_script_hook;
utils::hook::detour sl_get_canonical_string_hook;
utils::hook::detour g_find_config_string_index;
std::string current_script_file;
std::uint32_t current_file_id = 0;
std::unordered_map<unsigned int, std::string> canonical_string_table;
std::vector<std::function<void(int)>> shutdown_callbacks;
std::vector<std::function<void()>> init_callbacks;
void scr_load_level_stub()
{
scr_load_level_hook.invoke<void>();
for (const auto& callback : init_callbacks)
{
callback();
}
}
void g_shutdown_game_stub(const int free_scripts)
{
if (free_scripts)
{
script_function_table_sort.clear();
script_function_table.clear();
script_function_table_rev.clear();
canonical_string_table.clear();
}
for (const auto& callback : shutdown_callbacks)
{
callback(free_scripts);
}
return g_shutdown_game_hook.invoke<void>(free_scripts);
}
void process_script_stub(const char* filename)
{
current_script_file = filename;
const auto file_id = std::atoi(filename);
if (file_id)
{
current_file_id = file_id;
}
else
{
current_file_id = 0;
current_file = filename;
}
process_script_hook.invoke<void>(filename);
}
unsigned int sl_get_canonical_string_stub(const char* str)
{
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
canonical_string_table[result] = str;
return result;
}
void add_function_sort(unsigned int id, const char* pos)
{
std::string filename = current_file;
if (current_file_id)
{
filename = get_token(current_file_id);
}
if (!script_function_table_sort.contains(filename))
{
auto* script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file.data(), false);
if (script)
{
const auto* end = &script->bytecode[script->bytecodeLen];
script_function_table_sort[filename].emplace_back("__end__", reinterpret_cast<const char*>(end));
}
}
const auto name = get_token(id);
auto& itr = script_function_table_sort[filename];
itr.insert(itr.end() - 1, {name, pos});
}
void add_function(const std::string& file, unsigned int id, const char* pos)
{
const auto name = get_token(id);
script_function_table[file][name] = pos;
script_function_table_rev[pos] = { file, name };
}
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
{
add_function_sort(thread_name, code_pos);
if (current_file_id)
{
const auto name = get_token(current_file_id);
add_function(name, thread_name, code_pos);
}
else
{
add_function(current_file, thread_name, code_pos);
}
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
}
int has_config_string_index(const unsigned int csIndex)
{
const auto* s_constantConfigStringTypes = reinterpret_cast<uint8_t*>(0x141721F80);
return csIndex < 0xDC4 && s_constantConfigStringTypes[csIndex] < 0x18u;
}
int is_pre_main_stub()
{
return game::SV_Loaded();
}
unsigned int g_find_config_string_index_stub(const char* name, const int start, const unsigned int max, const int create, const char* errormsg)
{
const auto* sv_running = game::Dvar_FindVar("sv_running");
return g_find_config_string_index.invoke<unsigned int>(name, start, max, sv_running->current.enabled, errormsg);
}
}
std::string get_token(unsigned int id)
{
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
{
return itr->second;
}
return find_token(id);
}
void on_shutdown(const std::function<void(int)>& callback)
{
shutdown_callbacks.push_back(callback);
}
void on_init(const std::function<void()>& callback)
{
init_callbacks.push_back(callback);
}
std::optional<std::string> get_canonical_string(const unsigned int id)
{
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
{
return {itr->second};
}
return {};
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// SP address is wrong, but should be ok
scr_load_level_hook.create(SELECT_VALUE(0x14013D5D0, 0x1403C4E60), &scr_load_level_stub);
g_shutdown_game_hook.create(SELECT_VALUE(0x140318C10, 0x1403A0DF0), &g_shutdown_game_stub);
scr_set_thread_position_hook.create(SELECT_VALUE(0x1403D3560, 0x14042E360), &scr_set_thread_position_stub);
process_script_hook.create(SELECT_VALUE(0x1403DC870, 0x1404378C0), &process_script_stub);
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, &sl_get_canonical_string_stub);
if (!game::environment::is_sp())
{
// Make some room for pre_main hook
utils::hook::jump(0x1402084A0, has_config_string_index);
// Allow precaching anytime
utils::hook::jump(0x1402084A5, is_pre_main_stub);
utils::hook::set<uint16_t>(0x1402084D0, 0xD3EB); // jump to 0x1402084A5
g_find_config_string_index.create(0x140161F90, &g_find_config_string_index_stub);
}
}
};
}
REGISTER_COMPONENT(scripting::component)

View File

@ -0,0 +1,16 @@
#pragma once
namespace scripting
{
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
extern std::string current_file;
void on_shutdown(const std::function<void(int)>& callback);
void on_init(const std::function<void()>& callback);
std::optional<std::string> get_canonical_string(unsigned int id);
std::string get_token(unsigned int id);
}

View File

@ -0,0 +1,51 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include <utils/hook.hpp>
namespace security
{
namespace
{
void set_cached_playerdata_stub(const int localclient, const int index1, const int index2)
{
if (index1 >= 0 && index1 < 18 && index2 >= 0 && index2 < 42)
{
reinterpret_cast<void(*)(int, int, int)>(0x1405834B0)(localclient, index1, index2);
}
}
void sv_execute_client_message_stub(game::mp::client_t* client, game::msg_t* msg)
{
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
{
client->reliableAcknowledge = client->reliableSequence;
console::info("Negative reliableAcknowledge from %s - cl->reliableSequence is %i, reliableAcknowledge is %i\n",
client->name, client->reliableSequence, client->reliableAcknowledge);
return;
}
utils::hook::invoke<void>(0x140472500, client, msg);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp()) return;
// Patch vulnerability in PlayerCards_SetCachedPlayerData
utils::hook::call(0x140287C5C, set_cached_playerdata_stub);
// It is possible to make the server hang if left unchecked
utils::hook::call(0x14047A29A, sv_execute_client_message_stub);
}
};
}
REGISTER_COMPONENT(security::component)

View File

@ -0,0 +1,486 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game/ui_scripting/execution.hpp"
#include "server_list.hpp"
#include "console.hpp"
#include "command.hpp"
#include "dvars.hpp"
#include "localized_strings.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include "party.hpp"
#include <utils/cryptography.hpp>
#include <utils/string.hpp>
#include <utils/hook.hpp>
namespace server_list
{
namespace
{
struct server_info
{
// gotta add more to this
int clients;
int max_clients;
int bots;
int ping;
bool is_private;
std::string host_name;
std::string map_name;
std::string game_type;
char in_game;
game::netadr_s address;
};
struct
{
game::netadr_s address{};
volatile bool requesting = false;
std::unordered_map<game::netadr_s, int> queued_servers{};
} master_state;
volatile bool update_server_list = false;
std::mutex mutex;
std::vector<server_info> servers;
size_t server_list_index = 0;
void lui_open_menu_stub(int /*controllerIndex*/, const char* /*menu*/, int /*a3*/, int /*a4*/,
unsigned int /*a5*/)
{
game::Cmd_ExecuteSingleCommand(0, 0, "lui_open menu_systemlink_join\n");
}
void refresh_server_list()
{
{
std::lock_guard<std::mutex> _(mutex);
servers.clear();
master_state.queued_servers.clear();
server_list_index = 0;
}
party::reset_connect_state();
if (get_master_server(master_state.address))
{
master_state.requesting = true;
network::send(master_state.address, "getservers", utils::string::va("IW6 %i full empty", PROTOCOL));
}
}
void join_server(int, int, const int index)
{
std::lock_guard<std::mutex> _(mutex);
const auto i = static_cast<size_t>(index) + server_list_index;
if (i < servers.size())
{
static size_t last_index = 0xFFFFFFFF;
if (last_index != i)
{
last_index = i;
}
else
{
console::info("Connecting to (%d - %zu): %s\n", index, i, servers[i].host_name.data());
party::connect(servers[i].address);
}
}
}
void trigger_refresh()
{
update_server_list = true;
}
bool server_list_refresher()
{
if (update_server_list)
{
update_server_list = false;
}
return false;
}
int ui_feeder_count()
{
std::lock_guard<std::mutex> _(mutex);
return static_cast<int>(servers.size() - server_list_index);
}
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const int index,
const int column)
{
std::lock_guard<std::mutex> _(mutex);
const auto i = server_list_index + index;
if (i >= servers.size())
{
return "";
}
if (column == 0)
{
return utils::string::va("%s", servers[i].host_name.data());
}
if (column == 1)
{
return utils::string::va("%s", servers[i].map_name.data());
}
if (column == 2)
{
const auto client_count = servers[i].clients - servers[i].bots;
return utils::string::va("%d/%d [%d]", client_count, servers[i].max_clients, servers[i].bots);
}
if (column == 3)
{
return utils::string::va("%s", servers[i].game_type.data());
}
if (column == 4)
{
return utils::string::va("%d", servers[i].ping);
}
if (column == 5)
{
return utils::string::va("%d", servers[i].is_private);
}
return "";
}
void sort_serverlist()
{
std::ranges::stable_sort(servers, [](const server_info& a, const server_info& b)
{
const auto a_players = a.clients - a.bots;
const auto b_players = b.clients - b.bots;
if (a_players == b_players)
{
return a.ping < b.ping;
}
return a_players > b_players;
});
}
void insert_server(server_info&& server)
{
std::lock_guard<std::mutex> _(mutex);
servers.emplace_back(std::move(server));
sort_serverlist();
trigger_refresh();
}
void do_frame_work()
{
auto& queue = master_state.queued_servers;
if (queue.empty())
{
return;
}
std::lock_guard<std::mutex> _(mutex);
size_t queried_servers = 0;
const size_t query_limit = 3;
for (auto i = queue.begin(); i != queue.end();)
{
if (i->second)
{
const auto now = game::Sys_Milliseconds();
if (now - i->second > 10'000)
{
i = queue.erase(i);
continue;
}
}
else if (queried_servers++ < query_limit)
{
i->second = game::Sys_Milliseconds();
network::send(i->first, "getInfo", utils::cryptography::random::get_challenge());
}
++i;
}
}
bool is_server_list_open()
{
return game::Menu_IsMenuOpenAndVisible(0, "menu_systemlink_join");
}
bool scroll_down()
{
if (!is_server_list_open())
{
return false;
}
if (server_list_index + 16 < servers.size())
{
++server_list_index;
trigger_refresh();
}
return true;
}
bool scroll_up()
{
if (!is_server_list_open())
{
return false;
}
if (server_list_index > 0)
{
--server_list_index;
trigger_refresh();
}
return true;
}
int get_client_count()
{
std::lock_guard<std::mutex> _(mutex);
auto count = 0;
for (const auto& server : servers)
{
count += server.clients;
}
return count;
}
int get_bot_count()
{
std::lock_guard<std::mutex> _(mutex);
auto count = 0;
for (const auto& server : servers)
{
count += server.bots;
}
return count;
}
int get_max_clients_count()
{
std::lock_guard<std::mutex> _(mutex);
auto count = 0;
for (const auto& server : servers)
{
count += server.max_clients;
}
return count;
}
int get_total_active_players_count_stub(game::hks::lua_State* s, void* a2)
{
const auto clients = get_client_count();
const auto bots = get_bot_count();
const auto max = get_max_clients_count();
const auto str = utils::string::va("%d/%d [%d]", clients, max, bots);
ui_scripting::push_value(str);
return 1;
}
}
bool get_master_server(game::netadr_s& address)
{
return game::NET_StringToAdr("server.alterware.dev:20810", &address);
}
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
{
int start_time{};
const auto now = game::Sys_Milliseconds();
{
std::lock_guard<std::mutex> _(mutex);
const auto entry = master_state.queued_servers.find(address);
if (entry == master_state.queued_servers.end() || !entry->second)
{
return;
}
start_time = entry->second;
master_state.queued_servers.erase(entry);
}
if (dvars::get_string("ui_customModeName") == "mp"s)
{
if (info.get("gametype") == "aliens"s)
{
return;
}
}
else if (dvars::get_string("ui_customModeName") == "aliens"s)
{
if (info.get("gametype") != "aliens"s)
{
return;
}
}
if (dvars::get_string("ui_mapvote_entrya_gametype") != "any"s && dvars::get_string("ui_mapvote_entrya_gametype") != info.get("gametype"))
{
return;
}
if (dvars::get_string("ui_mapvote_entrya_mapname") != "any"s && dvars::get_string("ui_mapvote_entrya_mapname") != info.get("mapname"))
{
return;
}
server_info server{};
server.address = address;
server.host_name = info.get("hostname");
server.map_name = game::UI_LocalizeMapname(info.get("mapname").data());
server.game_type = game::UI_LocalizeGametype(info.get("gametype").data());
server.clients = atoi(info.get("clients").data());
server.max_clients = atoi(info.get("sv_maxclients").data());
server.bots = atoi(info.get("bots").data());
server.ping = now - start_time;
server.is_private = info.get("isPrivate") == "1"s;
server.in_game = 1;
if (server.host_name.size() > 50)
{
server.host_name.resize(50);
}
insert_server(std::move(server));
}
bool sl_key_event(const int key, const int down)
{
if (down)
{
if (key == game::keyNum_t::K_MWHEELUP)
{
return !scroll_up();
}
if (key == game::keyNum_t::K_MWHEELDOWN)
{
return !scroll_down();
}
}
return true;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp()) return;
localized_strings::override("PLATFORM_SYSTEM_LINK_TITLE", "SERVER LIST");
localized_strings::override("LUA_MENU_STORE_CAPS", "SERVER LIST");
localized_strings::override("LUA_MENU_STORE_DESC", "Browse available servers.");
localized_strings::override("MENU_NUMPLAYERS", "Players");
// hook LUI_OpenMenu to show server list instead of store popup
utils::hook::call(0x1404FE840, &lui_open_menu_stub);
// refresh server list when needed
utils::hook::call(0x1402F7480, &server_list_refresher);
// replace UI_RunMenuScript call in LUI_CoD_LuaCall_RefreshServerList to our refresh_servers
utils::hook::call(0x1401E7171, &refresh_server_list);
utils::hook::call(0x1401E7616, &join_server);
utils::hook::nop(0x1401E7635, 5);
// do feeder stuff
utils::hook::call(0x1401E7225, &ui_feeder_count);
utils::hook::call(0x1401E7405, &ui_feeder_item_text);
utils::hook::jump(0x1401EBE30, &get_total_active_players_count_stub, true);
scheduler::once(refresh_server_list, scheduler::pipeline::main);
command::add("lui_open", [](const command::params& params)
{
if (params.size() <= 1)
{
console::info("usage: lui_open <name>\n");
return;
}
game::LUI_OpenMenu(0, params[1], 1, 0, 0);
});
scheduler::loop(do_frame_work, scheduler::pipeline::main);
network::on("getServersResponse", [](const game::netadr_s& target, const std::string& data)
{
{
std::lock_guard<std::mutex> _(mutex);
if (!master_state.requesting || master_state.address != target)
{
return;
}
master_state.requesting = false;
std::optional<size_t> start{};
for (std::size_t i = 0; i + 6 < data.size(); ++i)
{
if (data[i + 6] == '\\')
{
start.emplace(i);
break;
}
}
if (!start.has_value())
{
return;
}
for (auto i = start.value(); i + 6 < data.size(); i += 7)
{
if (data[i + 6] != '\\')
{
break;
}
game::netadr_s address{};
address.type = game::NA_IP;
address.localNetID = game::NS_CLIENT1;
std::memcpy(&address.ip[0], data.data() + i + 0, 4);
std::memcpy(&address.port, data.data() + i + 4, 2);
master_state.queued_servers[address] = 0;
}
}
});
}
};
}
REGISTER_COMPONENT(server_list::component)

View File

@ -0,0 +1,10 @@
#pragma once
#include <utils/info_string.hpp>
namespace server_list
{
bool get_master_server(game::netadr_s& address);
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
bool sl_key_event(int key, int down);
}

View File

@ -0,0 +1,74 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace slowmotion
{
namespace
{
int delay = 0;
utils::hook::detour com_timescale_msec_hook;
uint64_t com_timescale_msec(const int msec)
{
if (delay <= 0)
{
return com_timescale_msec_hook.invoke<uint64_t>(msec);
}
else
{
delay -= msec;
return 0;
}
}
void scr_cmd_set_slow_motion()
{
if (game::Scr_GetNumParam() < 1)
return;
int duration = 1000;
float end = 1.0f;
const float start = game::Scr_GetFloat(0);
if (game::Scr_GetNumParam() >= 2)
end = game::Scr_GetFloat(1);
if (game::Scr_GetNumParam() >= 3)
duration = static_cast<int>(game::Scr_GetFloat(2) * 1000.0f);
const auto _delay = (start > end) ? 150 : 0;
game::SV_SetConfigstring(game::CS_TIMESCALE,
utils::string::va("%i %i %g %g", *game::mp::gameTime, duration, start, end));
game::Com_SetSlowMotion(start, end, duration - _delay);
delay = _delay;
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; i++)
{
auto* client = &game::mp::svs_clients[i];
client->nextSnapshotTime = *game::mp::serverTime - 1;
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_dedi()) return;
utils::hook::jump(0x1403B4A10, scr_cmd_set_slow_motion);
// Detour used here instead of call hook because Com_TimeScaleMsec is called from arxan encrypted function
com_timescale_msec_hook.create(0x140415D50, com_timescale_msec);
}
};
}
REGISTER_COMPONENT(slowmotion::component)

View File

@ -0,0 +1,141 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game_module.hpp"
#include <utils/nt.hpp>
#include <utils/hook.hpp>
namespace splash
{
class component final : public component_interface
{
public:
void post_start() override
{
const utils::nt::library self;
image_ = LoadImageA(self, MAKEINTRESOURCE(IMAGE_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
}
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
this->show();
}
void post_unpack() override
{
// Disable native splash screen
utils::hook::nop(SELECT_VALUE(0x14043C15B, 0x140500ADB), 5);
utils::hook::jump(SELECT_VALUE(0x14043D5A0, 0x140502000), destroy_stub);
utils::hook::jump(SELECT_VALUE(0x14043D5E0, 0x140502040), destroy_stub);
}
void pre_destroy() override
{
this->destroy();
MSG msg;
while (this->window_ && IsWindow(this->window_))
{
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
std::this_thread::sleep_for(1ms);
}
}
this->window_ = nullptr;
}
private:
HWND window_{};
HANDLE image_{};
static void destroy_stub()
{
component_loader::get<component>()->destroy();
}
void destroy() const
{
if (this->window_ && IsWindow(this->window_))
{
ShowWindow(this->window_, SW_HIDE);
DestroyWindow(this->window_);
UnregisterClassA("iw6-mod Splash Screen", utils::nt::library{});
}
}
void show()
{
WNDCLASSA wnd_class;
const auto self = game_module::get_host_module();
wnd_class.style = CS_DROPSHADOW;
wnd_class.cbClsExtra = 0;
wnd_class.cbWndExtra = 0;
wnd_class.lpszMenuName = nullptr;
wnd_class.lpfnWndProc = DefWindowProcA;
wnd_class.hInstance = self;
wnd_class.hIcon = LoadIconA(self, reinterpret_cast<LPCSTR>(102));
wnd_class.hCursor = LoadCursorA(nullptr, IDC_APPSTARTING);
wnd_class.hbrBackground = reinterpret_cast<HBRUSH>(6);
wnd_class.lpszClassName = "iw6-mod Splash Screen";
if (RegisterClassA(&wnd_class))
{
const auto x_pixels = GetSystemMetrics(SM_CXFULLSCREEN);
const auto y_pixels = GetSystemMetrics(SM_CYFULLSCREEN);
if (image_)
{
this->window_ = CreateWindowExA(WS_EX_APPWINDOW, "iw6-mod Splash Screen", "iw6-mod",
WS_POPUP | WS_SYSMENU,
(x_pixels - 320) / 2, (y_pixels - 100) / 2, 320, 100, nullptr,
nullptr,
self, nullptr);
if (this->window_)
{
auto* const image_window = CreateWindowExA(0, "Static", nullptr, WS_CHILD | WS_VISIBLE | 0xEu,
0, 0,
320, 100, this->window_, nullptr, self, nullptr);
if (image_window)
{
RECT rect;
SendMessageA(image_window, 0x172u, 0, reinterpret_cast<LPARAM>(image_));
GetWindowRect(image_window, &rect);
const int width = rect.right - rect.left;
rect.left = (x_pixels - width) / 2;
const int height = rect.bottom - rect.top;
rect.top = (y_pixels - height) / 2;
rect.right = rect.left + width;
rect.bottom = rect.top + height;
AdjustWindowRect(&rect, WS_CHILD | WS_VISIBLE | 0xEu, 0);
SetWindowPos(this->window_, nullptr, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOZORDER);
ShowWindow(this->window_, SW_SHOW);
UpdateWindow(this->window_);
}
}
}
}
}
};
}
REGISTER_COMPONENT(splash::component)

View File

@ -0,0 +1,404 @@
#include <std_include.hpp>
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
namespace stats
{
namespace
{
const int controller_index = 0;
//Checks if it's a valid reserved lookup string otherwise it crashes the client.
bool is_reserved_lookup_string(const char* lookup_string)
{
const std::vector<std::string> reserved_lookup_strings
{
//EXTINCTION MODE RESERVED LOOKUP STRINGS
"extinction_purchase_flags",
"extinction_tokens",
"extinction_crafted_flags",
"upgrades_enabled_flags",
"relics_enabled_flags",
"bonus_pool_size",
"bonus_pool_deadline",
//MULTIPLAYER
"prestigeLevel",
//COMMON
"mp_announcer_type"
};
return std::find(reserved_lookup_strings.begin(), reserved_lookup_strings.end(), lookup_string)
!= reserved_lookup_strings.end();
}
game::StatsGroup get_statsgroup_for_reserved_lookup_string(const char* lookup_string)
{
enum game::StatsGroup statsgroup{ game::STATSGROUP_COOP };
if (!strcmp(lookup_string, "prestigeLevel"))
{
statsgroup = game::STATSGROUP_RANKED;
}
else if (!strcmp(lookup_string, "mp_announcer_type"))
{
statsgroup = game::STATSGROUP_COMMON;
}
return statsgroup;
}
game::StatsGroup get_statsgroup_for_lookup_string(const char* lookup_string)
{
const auto asset{ game::StructuredDataDef_GetAsset("mp/playerdata.def", 0x93FCu) };
bool found_param{ false };
enum game::StatsGroup statsgroup{ game::STATSGROUP_RANKED };
for (int i = 0; i < asset->structs[23].propertyCount; ++i)
{
if (!strcmp(game::SL_ConvertToString(asset->structs[23].properties[i].name), lookup_string))
{
statsgroup = game::STATSGROUP_COMMON;
found_param = true;
break;
}
}
//no need to search it again if we already found it at the common strings
if (!found_param)
{
for (int i = 0; i < asset->structs[22].propertyCount; ++i)
{
if (!strcmp(game::SL_ConvertToString(asset->structs[22].properties[i].name), lookup_string))
{
statsgroup = game::STATSGROUP_COOP;
break;
}
}
}
return statsgroup;
}
std::vector<unsigned int> parse_params_to_lookup_strings(const command::params& params, bool setData)
{
std::vector<unsigned int> lookup_strings_vector{};
int amount_lookup_strings{ params.size() };
//last param is a value if we're setting data.
if (setData)
{
--amount_lookup_strings;
}
for (int i = 1; i < amount_lookup_strings; ++i)
{
if (utils::string::is_numeric(params.get(i)))
{
lookup_strings_vector.push_back(game::SL_GetStringForInt(atoi(params.get(i))));
}
else
{
lookup_strings_vector.push_back(game::SL_FindString(params.get(i)));
}
}
return lookup_strings_vector;
}
void set_player_data_for_lookup_string(const char* lookup_string,
const std::vector<std::tuple<const char*, const int>>& data, game::StatsGroup statsgroup)
{
unsigned int nav_strings[2]{};
nav_strings[0] = game::SL_FindString(lookup_string);
for (int i = 0; i < data.size(); ++i)
{
nav_strings[1] = game::SL_FindString(std::get<0>(data[i]));
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 2, std::get<1>(data[i]), statsgroup);
}
}
void register_squadmembers_purchase()
{
unsigned int nav_strings[2]{};
const game::StringTable* unlock_table{ nullptr };
game::StringTable_GetAsset("mp/unlocktable.csv", &unlock_table);
if (unlock_table)
{
//squad purchases challenge is defined at line 112 and ends at 152.
//it's defined 4 times each hence the jumps of 4
for (int i = 112; i < 152; i += 4)
{
// Register squad purchases for calling cards
auto squadmember_purchase = game::StringTable_GetColumnValueForRow(unlock_table, i, 3);
nav_strings[0] = game::SL_FindString("challengeState");
nav_strings[1] = game::SL_FindString(squadmember_purchase);
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 2, 2, game::STATSGROUP_RANKED);
nav_strings[0] = game::SL_FindString("challengeProgress");
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 2, 1, game::STATSGROUP_RANKED);
}
}
}
void unlock_all_squadmember(const int squadmember_index)
{
unsigned int nav_strings[5]{};
const int amount_regular_loadouts{ 6 };
const game::StringTable* squad_unlocktable{ nullptr };
game::StringTable_GetAsset("mp/squadunlocktable.csv", &squad_unlocktable);
//select specific squadmember
nav_strings[0] = game::SL_FindString("squadMembers");
nav_strings[1] = game::SL_GetStringForInt(squadmember_index);
//unlocks squadmember
nav_strings[2] = game::SL_FindString("inUse");
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 3, 1, game::STATSGROUP_RANKED);
//choose a default loadout otherwise unlocking loadouts won't work
nav_strings[2] = game::SL_FindString("defaultSet");
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 3, 1, game::STATSGROUP_RANKED);
//set max xp squad member
nav_strings[2] = game::SL_FindString("squadMemXP");
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 3, 1230080, game::STATSGROUP_RANKED);
//unlocks loadouts
nav_strings[2] = game::SL_FindString("loadouts");
nav_strings[4] = game::SL_FindString("inUse");
for (int j = 0; j < amount_regular_loadouts; ++j)
{
nav_strings[3] = game::SL_GetStringForInt(j);
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 5, 2, game::STATSGROUP_RANKED);
}
//unlocks all weapons, reticles and killstreaks
nav_strings[2] = game::SL_FindString("challengeState");
if (squad_unlocktable)
{
for (int j = 0; j < squad_unlocktable->rowCount; ++j)
{
auto unlock_item = game::StringTable_GetColumnValueForRow(squad_unlocktable, j, 3);
if (unlock_item[0] == '\0')
continue;
nav_strings[3] = game::SL_FindString(unlock_item);
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 4, 2, game::STATSGROUP_RANKED);
}
}
}
void unlock_all_extinction()
{
const auto persistent_data_buffer{ game::LiveStorage_GetPersistentDataBuffer(controller_index) };
const std::vector<std::tuple<const char*, const int>> data{
{"prestige", 25},
{"experience", 1845000},
//not sure if necessary
{"rank", 31},
{"revives", 100},
{"kills", 10000},
{"escaped", 10},
//unlocks background with the challenges "Beat any Extinction map with x Relic(s) Active"
{"headShots", 5}
};
set_player_data_for_lookup_string("alienPlayerStats", data, game::STATSGROUP_COOP);
game::LiveStorage_PlayerDataSetReservedInt(
persistent_data_buffer, "extinction_purchase_flags", -1, 0, game::STATSGROUP_COOP);
game::LiveStorage_PlayerDataSetReservedInt(
persistent_data_buffer, "extinction_tokens", 5000, 0, game::STATSGROUP_COOP);
}
void unlock_all_multiplayer()
{
const int amount_squad_members{ 10 };
const auto persistent_data_buffer{ game::LiveStorage_GetPersistentDataBuffer(controller_index) };
for (int i = 0; i < amount_squad_members; ++i)
{
unlock_all_squadmember(i);
}
register_squadmembers_purchase();
//not the correct way to set each squadmembers prestige
//but i couldn't figure it out how it's done.
for (int i = 0; i < amount_squad_members; ++i)
{
utils::hook::set<int>(0x1445A2B20 + (i * 4), i);
}
//set the players prestige to 10th prestige
game::LiveStorage_PlayerDataSetReservedInt(
persistent_data_buffer, "prestigeLevel", 10, 0, game::STATSGROUP_RANKED);
//gives unlock points but not really necassary since everything is already unlocked
game::LiveStorage_PlayerDataSetIntByName(controller_index, game::SL_FindString("unlockPoints"), 5000, game::StatsGroup::STATSGROUP_RANKED);
}
void unlock_all_challenges()
{
unsigned int nav_strings[2]{};
const game::StringTable* challenges_table{ nullptr };
game::StringTable_GetAsset("mp/allchallengestable.csv", &challenges_table);
if (challenges_table)
{
for (int i = 0; i < challenges_table->rowCount; ++i)
{
// Find challenge
auto challenge{ game::StringTable_GetColumnValueForRow(challenges_table, i, 0) };
int max_state{ 0 };
int max_progress{ 0 };
// Find correct tier and progress
for (int j = 0; j < 8; ++j)
{
int progress{ atoi(game::StringTable_GetColumnValueForRow(challenges_table, i, 9 + j * 2)) };
if (!progress) break;
max_state = j + 2;
max_progress = progress;
}
nav_strings[0] = game::SL_FindString("challengeState");
nav_strings[1] = game::SL_FindString(challenge);
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 2, max_state, game::STATSGROUP_RANKED);
nav_strings[0] = game::SL_FindString("challengeProgress");
game::LiveStorage_PlayerDataSetIntByNameArray(
controller_index, nav_strings, 2, max_progress, game::STATSGROUP_RANKED);
}
}
}
void unlock_past_title_backgrounds()
{
const std::vector<std::tuple<const char*, const int>> data{
{"playedmw3", 1},
{"playedblackops2", 1},
{"mw3prestige", 20},
{"blackops2prestige", 11}
};
set_player_data_for_lookup_string("pastTitleData", data, game::STATSGROUP_COMMON);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
command::add("setReservedPlayerDataInt", [](const command::params& params)
{
if (params.size() < 3 || !is_reserved_lookup_string(params.get(1)))
{
console::info("usage: setReservedPlayerDataInt <lookup_string>, <value>\n");
return;
}
const auto persistent_data_buffer{ game::LiveStorage_GetPersistentDataBuffer(controller_index) };
const char* first_param{ params.get(1) };
const auto value{ atoi(params.get(2)) };
const auto statsgroup{ get_statsgroup_for_reserved_lookup_string(first_param) };
game::LiveStorage_PlayerDataSetReservedInt(
persistent_data_buffer, first_param, value, 0, statsgroup);
});
command::add("getReservedPlayerDataInt", [](const command::params& params)
{
if (params.size() < 2 || !is_reserved_lookup_string(params.get(1)))
{
console::info("usage: getReservedPlayerDataInt <lookup_string>\n");
return;
}
const auto persistent_data_buffer{ game::LiveStorage_GetPersistentDataBuffer(controller_index) };
const char* first_param{ params.get(1) };
const auto statsgroup{ get_statsgroup_for_reserved_lookup_string(first_param) };
const auto result{ game::LiveStorage_PlayerDataGetReservedInt(
persistent_data_buffer, first_param, statsgroup) };
console::info("%d\n", result);
});
command::add("setPlayerDataInt", [](const command::params& params)
{
if (params.size() < 3)
{
console::info("usage: setPlayerDataInt <lookup_string>, ... , <lookup_string>, <value>\n");
return;
}
const std::vector lookup_strings_vector{parse_params_to_lookup_strings(params, true)};
const auto value = atoi(params.get(params.size() - 1));
const auto statsgroup{get_statsgroup_for_lookup_string(params.get(1))};
game::LiveStorage_PlayerDataSetIntByNameArray(controller_index, &lookup_strings_vector[0], params.size() - 2, value, statsgroup);
//This is necessary for the stats to stick after closing the game
game::LiveStorage_StatsWriteNeeded(controller_index);
});
command::add("getPlayerDataInt", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("usage: getPlayerDataInt <lookup_string>, ... , <lookup_string>\n");
return;
}
const std::vector lookup_strings_vector{parse_params_to_lookup_strings(params, false)};
const auto statsgroup{ get_statsgroup_for_lookup_string(params.get(1)) };
const auto result{game::LiveStorage_PlayerDataGetIntByNameArray(controller_index, &lookup_strings_vector[0], params.size() - 1, statsgroup)};
console::info("%d\n", result);
});
command::add("unlockstats", []()
{
unlock_all_extinction();
unlock_all_multiplayer();
unlock_all_challenges();
unlock_past_title_backgrounds();
//This is necessary for the stats to stick after closing the game
game::LiveStorage_StatsWriteNeeded(controller_index);
});
}
};
}
REGISTER_COMPONENT(stats::component)

View File

@ -0,0 +1,210 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include <utils/nt.hpp>
#include <utils/string.hpp>
#include <utils/binary_resource.hpp>
#include "game/game.hpp"
#include "steam/interface.hpp"
#include "steam/steam.hpp"
namespace steam_proxy
{
namespace
{
enum class ownership_state
{
success,
unowned,
nosteam,
error,
};
ownership_state state_;
utils::binary_resource runner_file(RUNNER, "runner.exe");
}
class component final : public component_interface
{
public:
void post_load() override
{
if (game::environment::is_dedi())
{
return;
}
this->load_client();
this->clean_up_on_error();
#ifndef DEV_BUILD
try
{
const std::string game_type = game::environment::is_sp() ? "singleplayer" : "multiplayer";
state_ = this->start_mod("\xF0\x9F\x92\x8E" " iw6-mod: " + (game_type), game::environment::is_sp() ? 209160 : 209170);
}
catch (const std::exception& ex)
{
state_ = ownership_state::error;
printf("Steam: %s\n", ex.what());
}
#endif
(void)state_;
}
void pre_destroy() override
{
if (this->steam_client_module_)
{
if (this->steam_pipe_)
{
if (this->global_user_)
{
this->steam_client_module_.invoke<void>("Steam_ReleaseUser", this->steam_pipe_, this->global_user_);
}
this->steam_client_module_.invoke<bool>("Steam_BReleaseSteamPipe", this->steam_pipe_);
}
}
}
[[nodiscard]] const utils::nt::library& get_overlay_module() const
{
return steam_overlay_module_;
}
private:
utils::nt::library steam_client_module_{};
utils::nt::library steam_overlay_module_{};
steam::interface client_engine_{};
steam::interface client_user_{};
steam::interface client_utils_{};
void* steam_pipe_ = nullptr;
void* global_user_ = nullptr;
[[nodiscard]] void* load_client_engine() const
{
if (!this->steam_client_module_) return nullptr;
for (auto i = 1; i < 1000; ++i)
{
const auto* name = utils::string::va("CLIENTENGINE_INTERFACE_VERSION%03i", i);
auto* const client_engine = this->steam_client_module_.invoke<void*>("CreateInterface", name, nullptr);
if (client_engine) return client_engine;
}
return nullptr;
}
void load_client()
{
const std::filesystem::path steam_path = steam::SteamAPI_GetSteamInstallPath();
if (steam_path.empty()) return;
utils::nt::library::load(steam_path / "tier0_s64.dll");
utils::nt::library::load(steam_path / "vstdlib_s64.dll");
this->steam_overlay_module_ = utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll");
this->steam_client_module_ = utils::nt::library::load(steam_path / "steamclient64.dll");
if (!this->steam_client_module_) return;
this->client_engine_ = load_client_engine();
if (!this->client_engine_) return;
this->steam_pipe_ = this->steam_client_module_.invoke<void*>("Steam_CreateSteamPipe");
this->global_user_ = this->steam_client_module_.invoke<void*>("Steam_ConnectToGlobalUser", this->steam_pipe_);
this->client_user_ = this->client_engine_.invoke<void*>(8, this->steam_pipe_, this->global_user_);
// GetIClientUser
this->client_utils_ = this->client_engine_.invoke<void*>(14, this->steam_pipe_); // GetIClientUtils
}
ownership_state start_mod(const std::string& title, const size_t app_id)
{
__try
{
return this->start_mod_unsafe(title, app_id);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
this->do_cleanup();
return ownership_state::error;
}
}
ownership_state start_mod_unsafe(const std::string& title, size_t app_id)
{
if (!this->client_utils_ || !this->client_user_)
{
return ownership_state::nosteam;
}
if (!this->client_user_.invoke<bool>("BIsSubscribedApp", app_id))
{
#ifdef _DEBUG
app_id = 480; // Spacewar
#else
return ownership_state::unowned;
#endif
}
this->client_utils_.invoke<void>("SetAppIDForCurrentPipe", app_id, false);
char our_directory[MAX_PATH]{};
GetCurrentDirectoryA(sizeof(our_directory), our_directory);
const auto path = runner_file.get_extracted_file();
const auto* cmdline = utils::string::va("\"%s\" -proc %d", path.data(), GetCurrentProcessId());
steam::game_id game_id;
game_id.raw.type = 1; // k_EGameIDTypeGameMod
game_id.raw.app_id = app_id & 0xFFFFFF;
const auto* mod_id = "iw6-mod";
game_id.raw.mod_id = *reinterpret_cast<const unsigned int*>(mod_id) | 0x80000000;
this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmdline, our_directory,
&game_id.bits, title.data(), 0, 0, 0);
return ownership_state::success;
}
void do_cleanup()
{
this->client_engine_ = nullptr;
this->client_user_ = nullptr;
this->client_utils_ = nullptr;
this->steam_pipe_ = nullptr;
this->global_user_ = nullptr;
this->steam_client_module_ = utils::nt::library{ nullptr };
}
void clean_up_on_error()
{
scheduler::schedule([this]()
{
if (this->steam_client_module_
&& this->steam_pipe_
&& this->global_user_
&& this->steam_client_module_.invoke<bool>("Steam_BConnected", this->global_user_, this->steam_pipe_)
&& this->steam_client_module_.invoke<bool>("Steam_BLoggedOn", this->global_user_, this->steam_pipe_)
)
{
return scheduler::cond_continue;
}
this->do_cleanup();
return scheduler::cond_end;
});
}
};
}
REGISTER_COMPONENT(steam_proxy::component)

View File

@ -0,0 +1,99 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "system_check.hpp"
#include "game/game.hpp"
#include <utils/io.hpp>
#include <utils/cryptography.hpp>
namespace system_check
{
namespace
{
std::string read_zone(const std::string& name)
{
std::string data{};
if (utils::io::read_file(name, &data))
{
return data;
}
if (utils::io::read_file("zone/" + name, &data))
{
return data;
}
return {};
}
std::string hash_zone(const std::string& name)
{
const auto data = read_zone(name);
return utils::cryptography::sha256::compute(data, true);
}
bool verify_hashes(const std::unordered_map<std::string, std::string>& zone_hashes)
{
for (const auto& zone_hash : zone_hashes)
{
const auto hash = hash_zone(zone_hash.first);
if (hash != zone_hash.second)
{
return false;
}
}
return true;
}
bool is_system_valid()
{
static std::unordered_map<std::string, std::string> mp_zone_hashes =
{
{"patch_common_mp.ff", "F1F08BFD03D0496199FAFD49CAA3E5786B70084A4C3C8841ACC4A7B7616D226C"},
};
static std::unordered_map<std::string, std::string> sp_zone_hashes =
{
{"patch_common.ff", "883DB33A1E386420EC6EF19F25C0D8081D01C1945BA2BCAD9FBD5460A201D6AA"},
{"patch_common_alien_mp.ff", "78B00BFF961F69F9A45446D40638A5A7F5C3462F1AF05A833066772C62FB5DB2"},
};
return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes));
}
void verify_binary_version()
{
const auto value = *reinterpret_cast<DWORD*>(0x140001337);
if (value != 0xDB0A33E7 && value != 0xA6D147E7)
{
throw std::runtime_error("Unsupported Call of Duty: Ghosts version");
}
}
}
bool is_valid()
{
static auto valid = is_system_valid();
return valid;
}
class component final : public component_interface
{
public:
void post_load() override
{
verify_binary_version();
if (!is_valid())
{
game::show_error("Your game files are outdated or unsupported.\n"
"Please get the latest officially supported Call of Duty: Ghosts files, or you will get random crashes and issues.",
"Invalid game files!");
}
}
};
}
REGISTER_COMPONENT(system_check::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace system_check
{
bool is_valid();
}

View File

@ -0,0 +1,59 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include <utils/thread.hpp>
namespace thread_names
{
namespace
{
void set_thread_names()
{
static std::unordered_map<int, std::string> thread_names =
{
{game::THREAD_CONTEXT_MAIN, "Main"},
{game::THREAD_CONTEXT_BACKEND, "Backend"}, // Renderer
{game::THREAD_CONTEXT_WORKER0, "Worker0"},
{game::THREAD_CONTEXT_WORKER1, "Worker1"},
{game::THREAD_CONTEXT_WORKER2, "Worker2"},
{game::THREAD_CONTEXT_WORKER3, "Worker3"},
{game::THREAD_CONTEXT_WORKER4, "Worker4"},
{game::THREAD_CONTEXT_WORKER5, "Worker5"},
{game::THREAD_CONTEXT_WORKER6, "Worker6"},
{game::THREAD_CONTEXT_WORKER7, "Worker7"},
{game::THREAD_CONTEXT_SERVER, "Server"},
{game::THREAD_CONTEXT_CINEMATIC, "Cinematic"},
{game::THREAD_CONTEXT_DATABASE, "Database"},
{game::THREAD_CONTEXT_STREAM, "Stream"},
{game::THREAD_CONTEXT_SNDSTREAMPACKETCALLBACK, "Snd stream packet callback"},
{game::THREAD_CONTEXT_STATS_WRITE, "Stats write"},
};
for (const auto& thread_name : thread_names)
{
const auto id = game::threadIds[thread_name.first];
if (id)
{
utils::thread::set_name(id, thread_name.second);
}
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
set_thread_names();
scheduler::once(set_thread_names, scheduler::pipeline::main);
scheduler::once(set_thread_names, scheduler::pipeline::renderer);
scheduler::once(set_thread_names, scheduler::pipeline::server);
}
};
}
REGISTER_COMPONENT(thread_names::component)

View File

@ -0,0 +1,391 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "localized_strings.hpp"
#include "console.hpp"
#include "game_module.hpp"
#include "game/ui_scripting/execution.hpp"
#include "ui_scripting.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
namespace ui_scripting
{
namespace
{
std::unordered_map<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>> converted_functions;
utils::hook::detour hks_start_hook;
utils::hook::detour hks_shutdown_hook;
utils::hook::detour hks_package_require_hook;
struct script
{
std::string name;
std::string root;
};
struct globals
{
std::string in_require_script;
std::vector<script> loaded_scripts;
bool load_raw_script{};
std::string raw_script_name{};
};
globals globals;
bool is_loaded_script(const std::string& name)
{
return std::ranges::any_of(globals.loaded_scripts, [name](const auto& loaded_script)
{
return loaded_script.name == name;
});
}
std::string get_root_script(const std::string& name)
{
for (const auto& loaded_script : globals.loaded_scripts)
{
if (loaded_script.name == name)
{
return loaded_script.root;
}
}
return {};
}
table get_globals()
{
const auto state = *game::hks::lua_state;
return state->globals.v.table;
}
void print_error(const std::string& error)
{
console::error("************** LUI script execution error **************\n");
console::error("%s\n", error.data());
console::error("********************************************************\n");
}
void print_loading_script(const std::string& name)
{
console::info("Loading LUI script '%s'\n", name.data());
}
std::string get_current_script()
{
const auto state = *game::hks::lua_state;
game::hks::lua_Debug info{};
game::hks::hksi_lua_getstack(state, 1, &info);
game::hks::hksi_lua_getinfo(state, "nSl", &info);
return info.short_src;
}
int load_buffer(const std::string& name, const std::string& data)
{
const auto state = *game::hks::lua_state;
const auto sharing_mode = state->m_global->m_bytecodeSharingMode;
state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON;
const auto _0 = gsl::finally([&]
{
state->m_global->m_bytecodeSharingMode = sharing_mode;
});
game::hks::HksCompilerSettings compiler_settings{};
return game::hks::hksi_hksL_loadbuffer(state, &compiler_settings, data.data(), data.size(), name.data());
}
void load_script(const std::string& name, const std::string& data)
{
globals.loaded_scripts.push_back({name, name});
const auto lua = get_globals();
const auto load_results = lua["loadstring"](data, name);
if (load_results[0].is<function>())
{
const auto results = lua["pcall"](load_results);
if (!results[0].as<bool>())
{
print_error(results[1].as<std::string>());
}
}
else if (load_results[1].is<std::string>())
{
print_error(load_results[1].as<std::string>());
}
}
void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir))
{
return;
}
const auto scripts = utils::io::list_files(script_dir);
for (const auto& script : scripts)
{
std::string data;
if (std::filesystem::is_directory(script) && utils::io::read_file(script + "/__init__.lua", &data))
{
print_loading_script(script);
load_script(script + "/__init__.lua", data);
}
}
}
void setup_functions()
{
const auto lua = get_globals();
using game = table;
auto game_type = game();
lua["game"] = game_type;
game_type["issingleplayer"] = [](const game&)
{
return ::game::environment::is_sp();
};
game_type["ismultiplayer"] = [](const game&)
{
return ::game::environment::is_mp();
};
game_type["addlocalizedstring"] = [](const game&, const std::string& string,
const std::string& value)
{
localized_strings::override(string, value);
};
}
void enable_globals()
{
const auto lua = get_globals();
const std::string code =
"local g = getmetatable(_G)\n"
"if not g then\n"
"g = {}\n"
"setmetatable(_G, g)\n"
"end\n"
"g.__newindex = nil\n";
(void)lua["loadstring"](code)[0]();
}
void start()
{
globals = {};
const auto lua = get_globals();
enable_globals();
setup_functions();
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x14017B120)); // hks::base_print
lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua;
load_scripts(game_module::get_host_module().get_folder() + "/data/ui_scripts/");
load_scripts("iw6/ui_scripts/");
}
void try_start()
{
try
{
start();
}
catch (const std::exception& ex)
{
console::error("Failed to load LUI scripts: %s\n", ex.what());
}
}
void* hks_start_stub(char a1)
{
const auto _0 = gsl::finally(&try_start);
return hks_start_hook.invoke<void*>(a1);
}
void hks_shutdown_stub()
{
converted_functions.clear();
globals = {};
return hks_shutdown_hook.invoke<void>();
}
void* hks_package_require_stub(game::hks::lua_State* state)
{
const auto script = get_current_script();
const auto root = get_root_script(script);
globals.in_require_script = root;
return hks_package_require_hook.invoke<void*>(state);
}
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{
game::XAssetHeader header {.luaFile = nullptr};
if (!is_loaded_script(globals.in_require_script))
{
return game::DB_FindXAssetHeader(type, name, allow_create_default);
}
const auto folder = globals.in_require_script.substr(0, globals.in_require_script.find_last_of("/\\"));
const std::string name_ = name;
const std::string target_script = folder + "/" + name_ + ".lua";
if (utils::io::file_exists(target_script))
{
globals.load_raw_script = true;
globals.raw_script_name = target_script;
header.luaFile = reinterpret_cast<game::LuaFile*>(1);
}
else if (name_.starts_with("ui/LUI/"))
{
return game::DB_FindXAssetHeader(type, name, allow_create_default);
}
return header;
}
int hks_load_stub(game::hks::lua_State* state, void* compiler_options, void* reader, void* reader_data, const char* chunk_name)
{
if (globals.load_raw_script)
{
globals.load_raw_script = false;
globals.loaded_scripts.push_back({globals.raw_script_name, globals.in_require_script});
return load_buffer(globals.raw_script_name, utils::io::read_file(globals.raw_script_name));
}
return utils::hook::invoke<int>(0x140198B00, state, compiler_options, reader, reader_data, chunk_name);
}
void* lua_atpanic_stub(void* l, [[maybe_unused]] void* panicf)
{
return utils::hook::invoke<void*>(SELECT_VALUE(0x1401851F0, 0x1401A4730), l, SELECT_VALUE(0x1401789A0, 0x140197BC0));
}
int luaopen_stub([[maybe_unused]] void* l)
{
return 0;
}
int hks_base_stub([[maybe_unused]] void* l)
{
return 0;
}
}
int main_handler(game::hks::lua_State* state)
{
const auto value = state->m_apistack.base[-1];
if (value.t != game::hks::TCFUNCTION)
{
return 0;
}
const auto closure = value.v.cClosure;
if (!converted_functions.contains(closure))
{
return 0;
}
const auto& function = converted_functions[closure];
try
{
const auto args = get_return_values();
const auto results = function(args);
for (const auto& result : results)
{
push_value(result);
}
return static_cast<int>(results.size());
}
catch (const std::exception& ex)
{
game::hks::hksi_luaL_error(state, ex.what());
}
return 0;
}
template <typename F>
game::hks::cclosure* convert_function(F f)
{
const auto state = *game::hks::lua_state;
const auto closure = game::hks::cclosure_Create(state, main_handler, 0, 0, 0);
converted_functions[closure] = wrap_function(f);
return closure;
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Disable debug breakpoints in the assembly of hksDefaultPanic
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x140178C5E, 0x140197E7E), 0x90);
// Restore hksDefaultPanic for complete error messages
utils::hook::call(SELECT_VALUE(0x1401B0028, 0x1401CE6C8), lua_atpanic_stub);
// Do not allow the HKS vm to open LUA's libraries
utils::hook::jump(SELECT_VALUE(0x14015BD10, 0x14017E040), luaopen_stub); // io
utils::hook::jump(SELECT_VALUE(0x14015C3D0, 0x14017E700), luaopen_stub); // os
utils::hook::jump(SELECT_VALUE(0x14015D400, 0x14017F730), luaopen_stub); // serialize
utils::hook::jump(SELECT_VALUE(0x14015D3D0, 0x14017F700), luaopen_stub); // havokscript
utils::hook::jump(SELECT_VALUE(0x14015C930, 0x14017EC60), luaopen_stub); // debug
utils::hook::nop(SELECT_VALUE(0x14015B639, 0x14017D969), 5); // coroutine
// Disable unsafe functions
utils::hook::jump(SELECT_VALUE(0x140158C20, 0x14017AF50), hks_base_stub); // base_loadfile
utils::hook::jump(SELECT_VALUE(0x140158B10, 0x14017AE40), hks_base_stub); // base_dofile
utils::hook::jump(SELECT_VALUE(0x14015D7E0, 0x14017FB10), hks_base_stub); // base_load
utils::hook::jump(SELECT_VALUE(0x1401569D0, 0x140178CF0), hks_base_stub); // package_loadlib
utils::hook::jump(SELECT_VALUE(0x140163CD0, 0x140177E00), hks_base_stub); // package_c_loader
utils::hook::jump(SELECT_VALUE(0x140163DE0, 0x140177F10), hks_base_stub); // package_all_in_one_loader
utils::hook::jump(SELECT_VALUE(0x140157DA0, 0x14017A0D0), hks_base_stub); // string_dump
utils::hook::jump(SELECT_VALUE(0x1401606A0, 0x1401747D0), hks_base_stub); // os_execute
if (!game::environment::is_mp())
{
return;
}
utils::hook::call(0x1401C7F57, db_find_x_asset_header_stub);
utils::hook::call(0x1401C7F24, hks_load_stub);
hks_package_require_hook.create(0x140178020, hks_package_require_stub);
hks_start_hook.create(0x1401D8E90, hks_start_stub);
hks_shutdown_hook.create(0x1401D24A0, hks_shutdown_stub);
command::add("lui_restart", []
{
utils::hook::invoke<void>(0x1401D24A0);
utils::hook::invoke<void>(0x1401D9FB0);
});
}
};
}
REGISTER_COMPONENT(ui_scripting::component)

View File

@ -0,0 +1,47 @@
#pragma once
namespace ui_scripting
{
template <class... Args, std::size_t... I>
auto wrap_function(const std::function<void(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
f(args[I]...);
return arguments{{}};
};
}
template <class... Args, std::size_t... I>
auto wrap_function(const std::function<arguments(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return f(args[I]...);
};
}
template <typename R, class... Args, std::size_t... I>
auto wrap_function(const std::function<R(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return arguments{f(args[I]...)};
};
}
template <typename R, class... Args>
auto wrap_function(const std::function<R(Args...)>& f)
{
return wrap_function(f, std::index_sequence_for<Args...> {});
}
template <class F>
auto wrap_function(F f)
{
return wrap_function(std::function(f));
}
template <typename F>
game::hks::cclosure* convert_function(F f);
}

View File

@ -0,0 +1,238 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace ultrawide
{
namespace
{
game::dvar_t* r_aspectRatio;
game::dvar_t* dvar_register_aspect_ratio(const char* name, const char**, int default_index, unsigned int flags,
const char* description)
{
static const char* values[] =
{
"auto",
"standard",
"wide 16:10",
"wide 16:9",
"custom",
nullptr
};
// register custom aspect ratio dvar
dvars::r_aspectRatioCustom = game::Dvar_RegisterFloat("r_aspectRatioCustom", 16.0f / 9.0f, 4.0f / 3.0f,
63.0f / 9.0f, flags,
"Screen aspect ratio. Divide width by height to get the aspect ratio value. For example: 21 / 9 = 2,33");
// register r_aspectRatio dvar
r_aspectRatio = game::Dvar_RegisterEnum(name, values, default_index, flags, description);
return r_aspectRatio;
}
struct wnd_struct
{
char pad[0x1C];
float aspect_ratio;
};
float hud_aspect_ratio = 1.77778f;
float hud_aspect_ratio_inv = -1.77778f;
void ultrawide_patch(wnd_struct* wnd)
{
if (r_aspectRatio != nullptr
&& r_aspectRatio->current.integer == 4 && dvars::r_aspectRatioCustom != nullptr)
{
hud_aspect_ratio = dvars::r_aspectRatioCustom->current.value;
hud_aspect_ratio_inv = -hud_aspect_ratio;
if (wnd)
{
wnd->aspect_ratio = hud_aspect_ratio;
}
}
}
// general aspect ratio
auto ultrawide_spawn_window_stub_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.call_aligned(0x140519430); // og - setup hwnd struct
a.lea(rcx, ptr(rsp, 0x0A0));
a.call_aligned(ultrawide_patch);
a.mov(edi, ptr(rsp, 0x0B0)); // mov edi, [rsp+0B0h]
a.jmp(0x140519ED3);
});
auto ultrawide_spawn_window_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.call_aligned(0x1405E5E70); // og - setup hwnd struct
a.lea(rcx, ptr(rsp, 0x0A0));
a.call_aligned(ultrawide_patch);
a.mov(edi, ptr(rsp, 0x0B0)); // mov edi, [rsp+0B0h]
a.jmp(0x1405E6903);
});
// menu aspect ratio + left offset
auto ultrawide_menu_stub_01_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.divss(xmm0, xmm5); // divss xmm0, cs:FLOAT_1_77778 // -> custom aspect ratio
a.subss(xmm1, xmm0); // subss xmm1, xmm0
a.movaps(xmm0, xmm3); // movaps xmm0, xmm3
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio_inv)));
a.movd(xmm5, eax);
a.mulss(xmm0, xmm5); // mulss xmm0, cs:dword_14083D47C // -1.77778 // -> left menu offset
a.pop(eax);
a.jmp(0x1401CD772);
});
auto ultrawide_menu_stub_01_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.divss(xmm0, xmm5); // divss xmm0, cs:FLOAT_1_77778 // -> custom aspect ratio
a.subss(xmm1, xmm0); // subss xmm1, xmm0
a.movaps(xmm0, xmm3); // movaps xmm0, xmm3
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio_inv)));
a.movd(xmm5, eax);
a.mulss(xmm0, xmm5); // mulss xmm0, cs:dword_14083D47C // -1.77778 // -> left menu offset
a.pop(eax);
a.jmp(0x1402008D2);
});
// menu aspect ratio right offset
auto ultrawide_menu_stub_02_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.mulss(xmm0, xmm5); // mulss xmm0, cs:FLOAT_1_77778 // -> custom aspect ratio
a.mulss(xmm0, xmm1); // mulss xmm0, xmm1
a.pop(eax);
a.jmp(0x1401CD79F);
});
auto ultrawide_menu_stub_02_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.mulss(xmm0, xmm5); // mulss xmm0, cs:FLOAT_1_77778 // -> custom aspect ratio
a.mulss(xmm0, xmm1); // mulss xmm0, xmm1
a.pop(eax);
a.jmp(0x1402008FF);
});
// fix loadscreen and in-game hud aspect ratio
auto ultrawide_hud_stub_sp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.divss(xmm6, xmm5); // divss xmm6, cs:FLOAT_1_77778
a.mulss(xmm6, xmm3); // mulss xmm6, xmm3
a.pop(eax);
a.jmp(0x14024D32F);
});
auto ultrawide_hud_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(eax);
a.mov(eax, dword_ptr(reinterpret_cast<int64_t>(&hud_aspect_ratio)));
a.movd(xmm5, eax);
a.divss(xmm6, xmm5); // divss xmm6, cs:FLOAT_1_77778
a.mulss(xmm6, xmm3); // mulss xmm6, xmm3
a.pop(eax);
a.jmp(0x1402F704F);
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// ultrawide patches
utils::hook::call(SELECT_VALUE(0x140510D0E, 0x1405DCF58), dvar_register_aspect_ratio);
// register r_aspectRatioCustom
utils::hook::jump(
SELECT_VALUE(0x140519EC7, 0x1405E68F7),
SELECT_VALUE(ultrawide_spawn_window_stub_sp, ultrawide_spawn_window_stub_mp), true);
// apply general aspect ratio
utils::hook::jump(
SELECT_VALUE(0x1401CD75B, 0x1402008BB),
SELECT_VALUE(ultrawide_menu_stub_01_sp, ultrawide_menu_stub_01_mp), true);
// fix menu aspect ratio and left offset
utils::hook::jump(
SELECT_VALUE(0x1401CD793, 0x1402008F3),
SELECT_VALUE(ultrawide_menu_stub_02_sp, ultrawide_menu_stub_02_mp), true);
// fix menu aspect ratio right offset
utils::hook::jump(
SELECT_VALUE(0x14024D323, 0x1402F7043), SELECT_VALUE(ultrawide_hud_stub_sp, ultrawide_hud_stub_mp),
true); // fix aspect ratio for loadscreen and general in-game hud
// safeArea_adjusted dvars :: disable resets (in-game hud padding)
utils::hook::nop(SELECT_VALUE(0x14024AEF2, 0x1402D4FB2), 5); // safeArea_adjusted_horizontal
utils::hook::nop(SELECT_VALUE(0x14024D655, 0x1402F7375), 5); // safeArea_adjusted_horizontal
utils::hook::nop(SELECT_VALUE(0x14024AF19, 0x1402D4FD9), 5);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024AF19, 0x1402D4FD9), 0xC3);
// safeArea_adjusted_vertical :: return instead of jumping to Dvar_SetFloat
utils::hook::nop(SELECT_VALUE(0x14024D664, 0x1402F7384), 5); // safeArea_adjusted_vertical
// safeArea_ dvars :: remove cheat protection + add saved flag
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D19F + 4, 0x1402F6EBF + 4), 0x1);
// register safeArea_horizontal (mov dword ptr [rsp+20h], 4)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D1D7 + 4, 0x1402F6EF7 + 4), 0x1);
// register safeArea_vertical (mov dword ptr [rsp+20h], 4)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D207 + 5, 0x1402F6F27 + 5), 0x0);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D207 + 4, 0x1402F6F27 + 4), 0x1);
// register safeArea_adjusted_horizontal (mov dword ptr [rsp+20h], 2000h)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D237 + 5, 0x1402F6F57 + 5), 0x0);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D237 + 4, 0x1402F6F57 + 4), 0x1);
// register safeArea_adjusted_vertical (mov dword ptr [rsp+20h], 2000h)
}
};
}
REGISTER_COMPONENT(ultrawide::component)

View File

@ -0,0 +1,182 @@
#include <std_include.hpp>
#include "bit_buffer.hpp"
namespace demonware
{
bool bit_buffer::read_bytes(const unsigned int bytes, unsigned char* output)
{
return this->read(bytes * 8, output);
}
bool bit_buffer::read_bool(bool* output)
{
if (!this->read_data_type(1))
{
return false;
}
return this->read(1, output);
}
bool bit_buffer::read_uint32(unsigned int* output)
{
if (!this->read_data_type(8))
{
return false;
}
return this->read(32, output);
}
bool bit_buffer::read_data_type(const char expected)
{
char data_type = 0;
if (!this->use_data_types_) return true;
if (this->read(5, &data_type))
{
return (data_type == expected);
}
return false;
}
bool bit_buffer::write_bytes(const size_t bytes, const char* data)
{
return this->write_bytes(bytes, reinterpret_cast<const unsigned char*>(data));
}
bool bit_buffer::write_bytes(const size_t bytes, const unsigned char* data)
{
return this->write(static_cast<unsigned>(bytes) * 8, data);
}
bool bit_buffer::write_bool(bool data)
{
if (this->write_data_type(1))
{
return this->write(1, &data);
}
return false;
}
bool bit_buffer::write_int32(int data)
{
if (this->write_data_type(7))
{
return this->write(32, &data);
}
return false;
}
bool bit_buffer::write_uint32(unsigned int data)
{
if (this->write_data_type(8))
{
return this->write(32, &data);
}
return false;
}
bool bit_buffer::write_data_type(char data)
{
if (!this->use_data_types_)
{
return true;
}
return this->write(5, &data);
}
bool bit_buffer::read(unsigned int bits, void* output)
{
if (bits == 0) return false;
if ((this->current_bit_ + bits) > (this->buffer_.size() * 8)) return false;
int cur_byte = this->current_bit_ >> 3;
auto cur_out = 0;
const char* bytes = this->buffer_.data();
const auto output_bytes = reinterpret_cast<unsigned char*>(output);
while (bits > 0)
{
const int min_bit = (bits < 8) ? bits : 8;
const auto this_byte = bytes[cur_byte++] & 0xFF;
const int remain = this->current_bit_ & 7;
if ((min_bit + remain) <= 8)
{
output_bytes[cur_out] = BYTE((0xFF >> (8 - min_bit)) & (this_byte >> remain));
}
else
{
output_bytes[cur_out] = BYTE(
(0xFF >> (8 - min_bit)) & (bytes[cur_byte] << (8 - remain)) | (this_byte >> remain));
}
cur_out++;
this->current_bit_ += min_bit;
bits -= min_bit;
}
return true;
}
bool bit_buffer::write(const unsigned int bits, const void* data)
{
if (bits == 0) return false;
this->buffer_.resize(this->buffer_.size() + (bits >> 3) + 1);
int bit = bits;
const auto bytes = const_cast<char*>(this->buffer_.data());
const auto* input_bytes = reinterpret_cast<const unsigned char*>(data);
while (bit > 0)
{
const int bit_pos = this->current_bit_ & 7;
auto rem_bit = 8 - bit_pos;
const auto this_write = (bit < rem_bit) ? bit : rem_bit;
const BYTE mask = ((0xFF >> rem_bit) | (0xFF << (bit_pos + this_write)));
const int byte_pos = this->current_bit_ >> 3;
const BYTE temp_byte = (mask & bytes[byte_pos]);
const BYTE this_bit = ((bits - bit) & 7);
const auto this_byte = (bits - bit) >> 3;
auto this_data = input_bytes[this_byte];
const auto next_byte = (((bits - 1) >> 3) > this_byte) ? input_bytes[this_byte + 1] : 0;
this_data = BYTE((next_byte << (8 - this_bit)) | (this_data >> this_bit));
const BYTE out_byte = (~mask & (this_data << bit_pos) | temp_byte);
bytes[byte_pos] = out_byte;
this->current_bit_ += this_write;
bit -= this_write;
}
return true;
}
void bit_buffer::set_use_data_types(const bool use_data_types)
{
this->use_data_types_ = use_data_types;
}
unsigned int bit_buffer::size() const
{
return this->current_bit_ / 8 + (this->current_bit_ % 8 ? 1 : 0);
}
std::string& bit_buffer::get_buffer()
{
this->buffer_.resize(this->size());
return this->buffer_;
}
}

View File

@ -0,0 +1,40 @@
#pragma once
namespace demonware
{
class bit_buffer final
{
public:
bit_buffer() = default;
explicit bit_buffer(std::string buffer) : buffer_(std::move(buffer))
{
}
bool read_bytes(unsigned int bytes, unsigned char* output);
bool read_bool(bool* output);
bool read_uint32(unsigned int* output);
bool read_data_type(char expected);
bool write_bytes(size_t bytes, const char* data);
bool write_bytes(size_t bytes, const unsigned char* data);
bool write_bool(bool data);
bool write_int32(int data);
bool write_uint32(unsigned int data);
bool write_data_type(char data);
bool read(unsigned int bits, void* output);
bool write(unsigned int bits, const void* data);
void set_use_data_types(bool use_data_types);
unsigned int size() const;
std::string& get_buffer();
private:
std::string buffer_{};
unsigned int current_bit_ = 0;
bool use_data_types_ = true;
};
}

View File

@ -0,0 +1,313 @@
#include <std_include.hpp>
#include "byte_buffer.hpp"
namespace demonware
{
bool byte_buffer::read_byte(unsigned char* output)
{
if (!this->read_data_type(3)) return false;
return this->read(1, output);
}
bool byte_buffer::read_bool(bool* output)
{
if (!this->read_data_type(1)) return false;
return this->read(1, output);
}
bool byte_buffer::read_int16(short* output)
{
if (!this->read_data_type(5)) return false;
return this->read(2, output);
}
bool byte_buffer::read_uint16(unsigned short* output)
{
if (!this->read_data_type(6)) return false;
return this->read(2, output);
}
bool byte_buffer::read_int32(int* output)
{
if (!this->read_data_type(7)) return false;
return this->read(4, output);
}
bool byte_buffer::read_uint32(unsigned int* output)
{
if (!this->read_data_type(8)) return false;
return this->read(4, output);
}
bool byte_buffer::read_int64(__int64* output)
{
if (!this->read_data_type(9)) return false;
return this->read(8, output);
}
bool byte_buffer::read_uint64(unsigned __int64* output)
{
if (!this->read_data_type(10)) return false;
return this->read(8, output);
}
bool byte_buffer::read_float(float* output)
{
if (!this->read_data_type(13)) return false;
return this->read(4, output);
}
bool byte_buffer::read_string(std::string* output)
{
char* out_data;
if (this->read_string(&out_data))
{
output->clear();
output->append(out_data);
return true;
}
return false;
}
bool byte_buffer::read_string(char** output)
{
if (!this->read_data_type(16)) return false;
*output = const_cast<char*>(this->buffer_.data()) + this->current_byte_;
this->current_byte_ += strlen(*output) + 1;
return true;
}
bool byte_buffer::read_string(char* output, const int length)
{
if (!this->read_data_type(16)) return false;
strcpy_s(output, length, const_cast<char*>(this->buffer_.data()) + this->current_byte_);
this->current_byte_ += strlen(output) + 1;
return true;
}
bool byte_buffer::read_blob(std::string* output)
{
char* out_data;
int length;
if (this->read_blob(&out_data, &length))
{
output->clear();
output->append(out_data, length);
return true;
}
return false;
}
bool byte_buffer::read_blob(char** output, int* length)
{
if (!this->read_data_type(0x13))
{
return false;
}
unsigned int size;
this->read_uint32(&size);
*output = const_cast<char*>(this->buffer_.data()) + this->current_byte_;
*length = static_cast<int>(size);
this->current_byte_ += size;
return true;
}
bool byte_buffer::read_data_type(const char expected)
{
if (!this->use_data_types_) return true;
char type;
this->read(1, &type);
if (type != expected)
{
//throw std::runtime_error("Data type mismatch!");
}
return type == expected;
}
bool byte_buffer::read_array_header(const unsigned char expected, unsigned int* element_count,
unsigned int* element_size)
{
if (element_count) *element_count = 0;
if (element_size) *element_size = 0;
if (!this->read_data_type(expected + 100)) return false;
uint32_t array_size, el_count;
if (!this->read_uint32(&array_size)) return false;
this->set_use_data_types(false);
this->read_uint32(&el_count);
this->set_use_data_types(true);
if (element_count) *element_count = el_count;
if (element_size) *element_size = array_size / el_count;
return true;
}
bool byte_buffer::write_byte(char data)
{
this->write_data_type(3);
return this->write(1, &data);
}
bool byte_buffer::write_bool(bool data)
{
this->write_data_type(1);
return this->write(1, &data);
}
bool byte_buffer::write_int16(short data)
{
this->write_data_type(5);
return this->write(2, &data);
}
bool byte_buffer::write_uint16(unsigned short data)
{
this->write_data_type(6);
return this->write(2, &data);
}
bool byte_buffer::write_int32(int data)
{
this->write_data_type(7);
return this->write(4, &data);
}
bool byte_buffer::write_uint32(unsigned int data)
{
this->write_data_type(8);
return this->write(4, &data);
}
bool byte_buffer::write_int64(__int64 data)
{
this->write_data_type(9);
return this->write(8, &data);
}
bool byte_buffer::write_uint64(unsigned __int64 data)
{
this->write_data_type(10);
return this->write(8, &data);
}
bool byte_buffer::write_data_type(char data)
{
if (!this->use_data_types_) return true;
return this->write(1, &data);
}
bool byte_buffer::write_float(float data)
{
this->write_data_type(13);
return this->write(4, &data);
}
bool byte_buffer::write_string(const std::string& data)
{
return this->write_string(data.data());
}
bool byte_buffer::write_string(const char* data)
{
this->write_data_type(16);
return this->write(static_cast<int>(strlen(data)) + 1, data);
}
bool byte_buffer::write_blob(const std::string& data)
{
return this->write_blob(data.data(), INT(data.size()));
}
bool byte_buffer::write_blob(const char* data, const int length)
{
this->write_data_type(0x13);
this->write_uint32(length);
return this->write(length, data);
}
bool byte_buffer::write_array_header(const unsigned char type, const unsigned int element_count,
const unsigned int element_size)
{
const auto using_types = this->is_using_data_types();
this->set_use_data_types(false);
auto result = this->write_byte(type + 100);
this->set_use_data_types(true);
result &= this->write_uint32(element_count * element_size);
this->set_use_data_types(false);
result &= this->write_uint32(element_count);
this->set_use_data_types(using_types);
return result;
}
bool byte_buffer::read(const size_t bytes, void* output)
{
if (bytes + this->current_byte_ > this->buffer_.size()) return false;
std::memmove(output, this->buffer_.data() + this->current_byte_, bytes);
this->current_byte_ += bytes;
return true;
}
bool byte_buffer::write(const size_t bytes, const void* data)
{
this->buffer_.append(static_cast<const char*>(data), bytes);
this->current_byte_ += bytes;
return true;
}
bool byte_buffer::write(const std::string& data)
{
return this->write(data.size(), data.data());
}
void byte_buffer::set_use_data_types(const bool use_data_types)
{
this->use_data_types_ = use_data_types;
}
size_t byte_buffer::size() const
{
return this->buffer_.size();
}
bool byte_buffer::is_using_data_types() const
{
return use_data_types_;
}
std::string& byte_buffer::get_buffer()
{
return this->buffer_;
}
std::string byte_buffer::get_remaining()
{
return std::string(this->buffer_.begin() + this->current_byte_, this->buffer_.end());
}
bool byte_buffer::has_more_data() const
{
return this->buffer_.size() > this->current_byte_;
}
}

View File

@ -0,0 +1,69 @@
#pragma once
namespace demonware
{
class byte_buffer final
{
public:
byte_buffer() = default;
explicit byte_buffer(std::string buffer) : buffer_(std::move(buffer))
{
}
bool read_byte(unsigned char* output);
bool read_bool(bool* output);
bool read_int16(short* output);
bool read_uint16(unsigned short* output);
bool read_int32(int* output);
bool read_uint32(unsigned int* output);
bool read_int64(__int64* output);
bool read_uint64(unsigned __int64* output);
bool read_float(float* output);
bool read_string(char** output);
bool read_string(char* output, int length);
bool read_string(std::string* output);
bool read_blob(char** output, int* length);
bool read_blob(std::string* output);
bool read_data_type(char expected);
bool read_array_header(unsigned char expected, unsigned int* element_count,
unsigned int* element_size = nullptr);
bool write_byte(char data);
bool write_bool(bool data);
bool write_int16(short data);
bool write_uint16(unsigned short data);
bool write_int32(int data);
bool write_uint32(unsigned int data);
bool write_int64(__int64 data);
bool write_uint64(unsigned __int64 data);
bool write_data_type(char data);
bool write_float(float data);
bool write_string(const char* data);
bool write_string(const std::string& data);
bool write_blob(const char* data, int length);
bool write_blob(const std::string& data);
bool write_array_header(unsigned char type, unsigned int element_count, unsigned int element_size);
bool read(size_t bytes, void* output);
bool write(size_t bytes, const void* data);
bool write(const std::string& data);
void set_use_data_types(bool use_data_types);
size_t size() const;
bool is_using_data_types() const;
std::string& get_buffer();
std::string get_remaining();
bool has_more_data() const;
private:
std::string buffer_;
size_t current_byte_ = 0;
bool use_data_types_ = true;
};
}

View File

@ -0,0 +1,449 @@
#pragma once
#include "i_server.hpp"
#include "game/structs.hpp"
namespace demonware
{
class bdFileData final : public i_serializable
{
public:
std::string file_data;
explicit bdFileData(std::string buffer) : file_data(std::move(buffer))
{
}
void serialize(byte_buffer* buffer) override
{
buffer->write_blob(this->file_data);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_blob(&this->file_data);
}
};
class bdFileInfo final : public i_serializable
{
public:
uint64_t file_id;
uint32_t create_time;
uint32_t modified_time;
bool priv;
uint64_t owner_id;
std::string filename;
uint32_t file_size;
void serialize(byte_buffer* buffer) override
{
buffer->write_uint32(this->file_size);
buffer->write_uint64(this->file_id);
buffer->write_uint32(this->create_time);
buffer->write_uint32(this->modified_time);
buffer->write_bool(this->priv);
buffer->write_uint64(this->owner_id);
buffer->write_string(this->filename);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_uint32(&this->file_size);
buffer->read_uint64(&this->file_id);
buffer->read_uint32(&this->create_time);
buffer->read_uint32(&this->modified_time);
buffer->read_bool(&this->priv);
buffer->read_uint64(&this->owner_id);
buffer->read_string(&this->filename);
}
};
class bdGroupCount final : public i_serializable
{
public:
uint32_t group_id;
uint32_t group_count;
bdGroupCount()
{
this->group_id = 0;
this->group_count = 0;
}
void serialize(byte_buffer* buffer) override
{
buffer->write_uint32(this->group_id);
buffer->write_uint32(this->group_count);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_uint32(&this->group_id);
buffer->read_uint32(&this->group_count);
}
};
class bdTimeStamp final : public i_serializable
{
public:
uint32_t unix_time;
void serialize(byte_buffer* buffer) override
{
buffer->write_uint32(this->unix_time);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_uint32(&this->unix_time);
}
};
class bdDMLInfo : public i_serializable
{
public:
std::string country_code; // Char [3]
std::string country; // Char [65]
std::string region; // Char [65]
std::string city; // Char [129]
float latitude;
float longitude;
void serialize(byte_buffer* buffer) override
{
buffer->write_string(this->country_code);
buffer->write_string(this->country);
buffer->write_string(this->region);
buffer->write_string(this->city);
buffer->write_float(this->latitude);
buffer->write_float(this->longitude);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_string(&this->country_code);
buffer->read_string(&this->country);
buffer->read_string(&this->region);
buffer->read_string(&this->city);
buffer->read_float(&this->latitude);
buffer->read_float(&this->longitude);
}
};
class bdDMLRawData final : public bdDMLInfo
{
public:
uint32_t asn; // Autonomous System Number.
std::string timezone;
void serialize(byte_buffer* buffer) override
{
bdDMLInfo::serialize(buffer);
buffer->write_uint32(this->asn);
buffer->write_string(this->timezone);
}
void deserialize(byte_buffer* buffer) override
{
bdDMLInfo::deserialize(buffer);
buffer->read_uint32(&this->asn);
buffer->read_string(&this->timezone);
}
};
class bdSessionID final : public i_serializable
{
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<uint64_t*>(data);
}
}
};
class bdMatchmakingInfo : public i_serializable
{
public:
bdSessionID session_id{};
std::string host_addr{};
uint32_t game_type{};
uint32_t max_players{};
uint32_t num_players{};
bool symmetric = false;
void serialize(byte_buffer* buffer) override
{
buffer->write_blob(this->host_addr);
this->session_id.serialize(buffer);
buffer->write_uint32(this->game_type);
buffer->write_uint32(this->max_players);
buffer->write_uint32(this->num_players);
}
void deserialize(byte_buffer* buffer) override
{
buffer->read_blob(&this->host_addr);
if (this->symmetric) this->session_id.deserialize(buffer);
buffer->read_uint32(&this->game_type);
buffer->read_uint32(&this->max_players);
if (this->symmetric) buffer->read_uint32(&this->num_players);
}
};
class MatchMakingInfo final : public bdMatchmakingInfo
{
public:
int32_t playlist_number{};
int32_t playlist_version{};
int32_t netcode_version{};
int32_t map_packs{};
int32_t slots_needed_on_team{};
int32_t skill{};
uint32_t country_code{};
uint32_t asn{};
float latitude{};
float longitude{};
int32_t max_reserved_slots{};
int32_t used_reserved_slots{};
std::string game_security_key{}; // 16 bytes.
std::string platform_session_id{}; // 16 bytes.
uint32_t data_centres{};
uint32_t coop_state{};
void serialize(byte_buffer* buffer) override
{
bdMatchmakingInfo::serialize(buffer);
buffer->write_int32(this->playlist_number);
buffer->write_int32(this->playlist_version);
buffer->write_int32(this->netcode_version);
buffer->write_int32(this->map_packs);
buffer->write_int32(this->slots_needed_on_team);
buffer->write_int32(this->skill);
buffer->write_uint32(this->country_code);
buffer->write_uint32(this->asn);
buffer->write_float(this->latitude);
buffer->write_float(this->longitude);
buffer->write_int32(this->max_reserved_slots);
buffer->write_int32(this->used_reserved_slots);
buffer->write_blob(this->game_security_key);
buffer->write_blob(this->platform_session_id);
buffer->write_uint32(this->data_centres);
buffer->write_uint32(this->coop_state);
}
void deserialize(byte_buffer* buffer) override
{
bdMatchmakingInfo::deserialize(buffer);
buffer->read_int32(&this->playlist_number);
buffer->read_int32(&this->playlist_version);
buffer->read_int32(&this->netcode_version);
buffer->read_int32(&this->map_packs);
buffer->read_int32(&this->slots_needed_on_team);
buffer->read_int32(&this->skill);
buffer->read_uint32(&this->country_code);
buffer->read_uint32(&this->asn);
buffer->read_float(&this->latitude);
buffer->read_float(&this->longitude);
buffer->read_int32(&this->max_reserved_slots);
buffer->read_int32(&this->used_reserved_slots);
buffer->read_blob(&this->game_security_key);
buffer->read_blob(&this->platform_session_id);
buffer->read_uint32(&this->data_centres);
buffer->read_uint32(&this->coop_state);
}
};
class bdPerformanceValue final : public i_serializable
{
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);
}
};
struct bdSockAddr final
{
bdSockAddr() : in_un(), m_family(AF_INET)
{
}
union
{
struct
{
char m_b1;
char m_b2;
char m_b3;
char m_b4;
} m_caddr;
unsigned int m_iaddr;
struct
{
unsigned __int16 m_w1;
unsigned __int16 m_w2;
unsigned __int16 m_w3;
unsigned __int16 m_w4;
unsigned __int16 m_w5;
unsigned __int16 m_w6;
unsigned __int16 m_w7;
unsigned __int16 m_w8;
} m_caddr6;
char m_iaddr6[16];
char m_sockaddr_storage[128];
} in_un;
unsigned __int16 m_family;
};
struct bdInetAddr final : i_serializable
{
bdSockAddr m_addr;
bool is_valid() const
{
return (this->m_addr.m_family == AF_INET /*|| this->m_addr.m_family == AF_INET6*/);
}
void serialize(byte_buffer* buffer) override
{
const auto data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
if (this->m_addr.m_family == AF_INET)
{
buffer->write(4, &this->m_addr.in_un.m_caddr);
}
buffer->set_use_data_types(data_types);
}
void deserialize(byte_buffer* buffer) override
{
const auto data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
if (this->m_addr.m_family == AF_INET)
{
buffer->read(4, &this->m_addr.in_un.m_caddr);
}
buffer->set_use_data_types(data_types);
}
};
struct bdAddr final : i_serializable
{
bdInetAddr m_address;
unsigned __int16 m_port{};
void serialize(byte_buffer* buffer) override
{
const bool data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
this->m_address.serialize(buffer);
buffer->write_uint16(this->m_port);
buffer->set_use_data_types(data_types);
}
void deserialize(byte_buffer* buffer) override
{
const auto data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
this->m_address.deserialize(buffer);
buffer->read_uint16(&this->m_port);
buffer->set_use_data_types(data_types);
}
};
struct bdCommonAddr : i_serializable
{
bdAddr m_local_addrs[5];
bdAddr m_public_addr;
game::bdNATType m_nat_type;
unsigned int m_hash;
bool m_is_loopback;
void serialize(byte_buffer* buffer) override
{
const auto data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
auto valid = true;
for (uint32_t i = 0; i < 5 && i < ARRAYSIZE(this->m_local_addrs) && valid; ++i)
{
this->m_local_addrs[i].serialize(buffer);
valid = this->m_local_addrs[i].m_address.is_valid();
}
if (valid)
{
this->m_public_addr.serialize(buffer);
buffer->write_byte(this->m_nat_type);
}
buffer->set_use_data_types(data_types);
}
void deserialize(byte_buffer* buffer) override
{
const auto data_types = buffer->is_using_data_types();
buffer->set_use_data_types(false);
auto valid = true;
for (uint32_t i = 0; i < ARRAYSIZE(this->m_local_addrs) && valid; ++i)
{
bdAddr addr;
addr.deserialize(buffer);
this->m_local_addrs[i] = addr;
valid = this->m_local_addrs[i].m_address.is_valid();
}
if (valid)
{
this->m_public_addr.deserialize(buffer);
buffer->read_byte(reinterpret_cast<uint8_t*>(&this->m_nat_type));
}
buffer->set_use_data_types(data_types);
}
};
}

View File

@ -0,0 +1,201 @@
#pragma once
#include "bit_buffer.hpp"
#include "byte_buffer.hpp"
namespace demonware
{
class reply
{
public:
virtual ~reply() = default;
virtual std::string get_data() = 0;
};
class raw_reply : public reply
{
public:
raw_reply() = default;
explicit raw_reply(std::string data) : buffer_(std::move(data))
{
}
virtual std::string get_data() override
{
return this->buffer_;
}
protected:
std::string buffer_;
};
class typed_reply : public raw_reply
{
public:
typed_reply(uint8_t _type) : type_(_type)
{
}
protected:
uint8_t get_type() const { return this->type_; }
private:
uint8_t type_;
};
class encrypted_reply final : public typed_reply
{
public:
encrypted_reply(const uint8_t type, bit_buffer* bbuffer) : typed_reply(type)
{
this->buffer_.append(bbuffer->get_buffer());
}
encrypted_reply(const uint8_t type, byte_buffer* bbuffer) : typed_reply(type)
{
this->buffer_.append(bbuffer->get_buffer());
}
virtual std::string get_data() override;
};
class unencrypted_reply final : public typed_reply
{
public:
unencrypted_reply(uint8_t _type, bit_buffer* bbuffer) : typed_reply(_type)
{
this->buffer_.append(bbuffer->get_buffer());
}
unencrypted_reply(uint8_t _type, byte_buffer* bbuffer) : typed_reply(_type)
{
this->buffer_.append(bbuffer->get_buffer());
}
virtual std::string get_data() override;
};
class remote_reply;
class service_reply;
class i_server
{
public:
virtual ~i_server() = default;
virtual int send(const char* buf, int len) = 0;
virtual int recv(char* buf, int len) = 0;
virtual void send_reply(reply* reply) = 0;
virtual std::shared_ptr<remote_reply> create_message(uint8_t type)
{
auto reply = std::make_shared<remote_reply>(this, type);
return reply;
}
virtual std::shared_ptr<service_reply> create_reply(uint8_t type,
uint32_t error = 0 /*Game::bdLobbyErrorCode::BD_NO_ERROR*/)
{
auto reply = std::make_shared<service_reply>(this, type, error);
return reply;
}
};
class remote_reply final
{
public:
remote_reply(i_server* server, uint8_t _type) : type_(_type), server_(server)
{
}
template <typename BufferType>
void send(BufferType* buffer, const bool encrypted)
{
std::unique_ptr<typed_reply> reply;
if (encrypted) reply = std::make_unique<encrypted_reply>(this->type_, buffer);
else reply = std::make_unique<unencrypted_reply>(this->type_, buffer);
this->server_->send_reply(reply.get());
}
uint8_t get_type() const { return this->type_; }
private:
uint8_t type_;
i_server* server_;
};
class i_serializable
{
public:
virtual ~i_serializable() = default;
virtual void serialize(byte_buffer* /*buffer*/)
{
}
virtual void deserialize(byte_buffer* /*buffer*/)
{
}
};
class service_reply final
{
public:
service_reply(i_server* _server, uint8_t _type, uint32_t _error) : type_(_type), error_(_error),
reply_(_server, 1)
{
}
uint64_t send()
{
static uint64_t id = 0x8000000000000001;
const auto transaction_id = ++id;
byte_buffer buffer;
buffer.write_uint64(transaction_id);
buffer.write_uint32(this->error_);
buffer.write_byte(this->type_);
if (!this->error_)
{
buffer.write_uint32(uint32_t(this->objects_.size()));
if (!this->objects_.empty())
{
buffer.write_uint32(uint32_t(this->objects_.size()));
for (auto& object : this->objects_)
{
object->serialize(&buffer);
}
this->objects_.clear();
}
}
else
{
buffer.write_uint64(transaction_id);
}
this->reply_.send(&buffer, true);
return transaction_id;
}
void add(const std::shared_ptr<i_serializable>& object)
{
this->objects_.push_back(object);
}
void add(i_serializable* object)
{
this->add(std::shared_ptr<i_serializable>(object));
}
private:
uint8_t type_;
uint32_t error_;
remote_reply reply_;
std::vector<std::shared_ptr<i_serializable>> objects_;
};
}

View File

@ -0,0 +1,82 @@
#pragma once
#include "i_server.hpp"
namespace demonware
{
class i_service
{
public:
virtual ~i_service() = default;
i_service() = default;
// Copying or moving a service object won't work
// as the callbacks are bound to the initial object pointer
// Therefore, you should never declare copy/move
// constructors when inheriting from IService!
i_service(i_service&&) = delete;
i_service(const i_service&) = delete;
i_service& operator=(const i_service&) = delete;
typedef std::function<void(i_server*, byte_buffer*)> callback;
virtual uint16_t getType() = 0;
virtual void call_service(i_server* server, const std::string& data)
{
std::lock_guard _(this->mutex_);
byte_buffer buffer(data);
buffer.read_byte(&this->sub_type_);
#ifdef DEBUG
printf("DW: Handling subservice of type %d\n", this->sub_type_);
#endif
const auto callback = this->callbacks_.find(this->sub_type_);
if (callback != this->callbacks_.end())
{
callback->second(server, &buffer);
}
else
{
#ifdef DEBUG
printf("DW: Missing subservice %d for type %d\n", this->sub_type_, this->getType());
#endif
}
}
protected:
std::map<uint8_t, callback> callbacks_{};
template <typename Class, typename T, typename... Args>
void register_service(const uint8_t type, T (Class::*callback)(Args ...) const)
{
this->callbacks_[type] = [this, callback](Args ... args) -> T
{
return (reinterpret_cast<Class*>(this)->*callback)(args...);
};
}
template <typename Class, typename T, typename... Args>
void register_service(const uint8_t type, T (Class::*callback)(Args ...))
{
this->callbacks_[type] = [this, callback](Args ... args) -> T
{
return (reinterpret_cast<Class*>(this)->*callback)(args...);
};
}
uint8_t get_sub_type() const { return this->sub_type_; }
private:
std::mutex mutex_;
uint8_t sub_type_{};
};
template <uint16_t Type>
class i_generic_service : public i_service
{
public:
uint16_t getType() override { return Type; }
};
}

View File

@ -0,0 +1,206 @@
#include <std_include.hpp>
#include "component/demonware.hpp"
#include "game/demonware/service_server.hpp"
#include <utils/cryptography.hpp>
namespace demonware
{
std::string unencrypted_reply::get_data()
{
byte_buffer result;
result.set_use_data_types(false);
result.write_int32(static_cast<int>(this->buffer_.size()) + 2);
result.write_bool(false);
result.write_byte(this->get_type());
result.write(this->buffer_);
return result.get_buffer();
}
std::string encrypted_reply::get_data()
{
byte_buffer result;
result.set_use_data_types(false);
byte_buffer enc_buffer;
enc_buffer.set_use_data_types(false);
enc_buffer.write_int32(0xDEADBEEF);
enc_buffer.write_byte(this->get_type());
enc_buffer.write(this->buffer_);
auto data = enc_buffer.get_buffer();
auto size = enc_buffer.size();
size = ~7 & (size + 7); // 8 byte align
data.resize(size);
result.write_int32(static_cast<int>(size) + 5);
result.write_byte(true);
auto seed = 0x13371337;
result.write_int32(seed);
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&seed), 4));
const std::string key(reinterpret_cast<char*>(get_key(true)), 24);
result.write(utils::cryptography::des3::encrypt(data, iv, key));
return result.get_buffer();
}
service_server::service_server(std::string _name) : name_(std::move(_name))
{
this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_);
}
unsigned long service_server::get_address() const
{
return this->address_;
}
int service_server::send(const char* buf, const int len)
{
if (len <= 3) return -1;
std::lock_guard<std::recursive_mutex> _(this->mutex_);
this->incoming_queue_.push(std::string(buf, len));
return len;
}
int service_server::recv(char* buf, int len)
{
if (len > 0 && !this->outgoing_queue_.empty())
{
std::lock_guard<std::recursive_mutex> _(this->mutex_);
len = std::min(len, static_cast<int>(this->outgoing_queue_.size()));
for (auto i = 0; i < len; ++i)
{
buf[i] = this->outgoing_queue_.front();
this->outgoing_queue_.pop();
}
return len;
}
return SOCKET_ERROR;
}
void service_server::send_reply(reply* data)
{
if (!data) return;
std::lock_guard<std::recursive_mutex> _(this->mutex_);
this->reply_sent_ = true;
const auto buffer = data->get_data();
for (const auto& byte : buffer)
{
this->outgoing_queue_.push(byte);
}
}
void service_server::call_handler(const uint8_t type, const std::string& data)
{
if (this->services_.find(type) != this->services_.end())
{
this->services_[type]->call_service(this, data);
}
else
{
#ifdef DEBUG
printf("DW: Missing handler of type %d\n", type);
#endif
}
}
void service_server::run_frame()
{
if (!this->incoming_queue_.empty())
{
std::lock_guard<std::recursive_mutex> _(this->mutex_);
const auto packet = this->incoming_queue_.front();
this->incoming_queue_.pop();
this->parse_packet(packet);
}
}
void service_server::parse_packet(const std::string& packet)
{
byte_buffer buffer(packet);
buffer.set_use_data_types(false);
try
{
while (buffer.has_more_data())
{
int size;
buffer.read_int32(&size);
if (size <= 0)
{
const std::string zero("\x00\x00\x00\x00", 4);
raw_reply reply(zero);
this->send_reply(&reply);
return;
}
else if (size == 200) // Connection id
{
byte_buffer bbufer;
bbufer.write_uint64(0x00000000000000FD);
auto reply = this->create_message(4);
reply->send(&bbufer, false);
return;
}
if (buffer.size() < size_t(size)) return;
byte_buffer p_buffer;
p_buffer.set_use_data_types(false);
p_buffer.get_buffer().resize(size);
buffer.read(size, p_buffer.get_buffer().data());
bool enc;
p_buffer.read_bool(&enc);
if (enc)
{
int iv;
p_buffer.read_int32(&iv);
auto iv_hash = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&iv), 4));
const std::string key(reinterpret_cast<char*>(get_key(false)), 24);
p_buffer = byte_buffer{utils::cryptography::des3::decrypt(p_buffer.get_remaining(), iv_hash, key)};
p_buffer.set_use_data_types(false);
int checksum;
p_buffer.read_int32(&checksum);
}
uint8_t type;
p_buffer.read_byte(&type);
#ifdef DEBUG
printf("DW: Handling message of type %d (encrypted: %d)\n", type, enc);
#endif
this->reply_sent_ = false;
this->call_handler(type, p_buffer.get_remaining());
if (!this->reply_sent_ && type != 7)
{
this->create_reply(type)->send();
}
}
}
catch (...)
{
}
}
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "i_service.hpp"
namespace demonware
{
class service_server final : public i_server
{
public:
explicit service_server(std::string name);
template <typename T>
void register_service()
{
static_assert(std::is_base_of<i_service, T>::value, "Service must inherit from IService");
auto service = std::make_unique<T>();
const uint16_t type = service->getType();
this->services_[type] = std::move(service);
}
unsigned long get_address() const;
int send(const char* buf, int len) override;
int recv(char* buf, int len) override;
void send_reply(reply* data) override;
void call_handler(uint8_t type, const std::string& data);
void run_frame();
private:
std::string name_;
std::recursive_mutex mutex_;
std::queue<char> outgoing_queue_;
std::queue<std::string> incoming_queue_;
std::map<uint16_t, std::unique_ptr<i_service>> services_;
unsigned long address_ = 0;
bool reply_sent_ = false;
void parse_packet(const std::string& packet);
};
}

View File

@ -0,0 +1,18 @@
#include <std_include.hpp>
#include "bdAnticheat.hpp"
#include "../data_types.hpp"
namespace demonware
{
bdAnticheat::bdAnticheat()
{
this->register_service(4, &bdAnticheat::report_console_details);
}
void bdAnticheat::report_console_details(i_server* server, [[maybe_unused]] byte_buffer* buffer) const
{
// TODO: Read data as soon as needed
auto reply = server->create_reply(this->get_sub_type());
reply->send();
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdAnticheat final : public i_generic_service<38>
{
public:
bdAnticheat();
private:
void report_console_details(i_server* server, byte_buffer* buffer) const;
};
}

View File

@ -0,0 +1,24 @@
#include <std_include.hpp>
#include "bdBandwidthTest.hpp"
#include "../data_types.hpp"
namespace demonware
{
static uint8_t bandwidth_iw6[51] =
{
0x0F, 0xC1, 0x1C, 0x37, 0xB8, 0xEF, 0x7C, 0xD6, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00, 0xD0, 0x07,
0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x88, 0x13, 0x00, 0x00, 0xF4, 0x01,
0x00, 0x00, 0x02, 0x0C, 0x88, 0xB3, 0x04, 0x65, 0x89, 0xBF, 0xC3, 0x6A,
0x27, 0x94, 0xD4, 0x8F
};
void bdBandwidthTest::call_service(i_server* server, const std::string& /*data*/)
{
byte_buffer buffer;
buffer.write(sizeof bandwidth_iw6, bandwidth_iw6);
auto reply = server->create_message(5);
reply->send(&buffer, true);
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdBandwidthTest final : public i_generic_service<18>
{
public:
void call_service(i_server* server, const std::string& data) override;
};
}

View File

@ -0,0 +1,29 @@
#include <std_include.hpp>
#include "bdDML.hpp"
#include "../data_types.hpp"
namespace demonware
{
bdDML::bdDML()
{
this->register_service(2, &bdDML::get_user_raw_data);
}
void bdDML::get_user_raw_data(i_server* server, byte_buffer* /*buffer*/) const
{
auto result = new bdDMLRawData;
result->country_code = "US";
result->country_code = "'Murica";
result->region = "New York";
result->city = "New York";
result->latitude = 0;
result->longitude = 0;
result->asn = 0x2119;
result->timezone = "+01:00";
auto reply = server->create_reply(this->get_sub_type());
reply->add(result);
reply->send();
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdDML final : public i_generic_service<27>
{
public:
bdDML();
private:
void get_user_raw_data(i_server* server, byte_buffer* buffer) const;
};
}

View File

@ -0,0 +1,63 @@
#include <std_include.hpp>
#include "bdDediAuth.hpp"
#include "game/game.hpp"
#include "steam/steam.hpp"
#include <utils/cryptography.hpp>
namespace demonware
{
void bdDediAuth::call_service(i_server* server, const std::string& data)
{
bit_buffer buffer(data);
bool more_data;
buffer.set_use_data_types(false);
buffer.read_bool(&more_data);
buffer.set_use_data_types(true);
uint32_t seed, title_id, ticket_size;
buffer.read_uint32(&seed);
buffer.read_uint32(&title_id);
uint8_t ticket[1024];
buffer.read_bytes(std::min(ticket_size, static_cast<uint32_t>(sizeof(ticket))), ticket);
game::bdAuthTicket auth_ticket{};
std::memset(&auth_ticket, 0xA, sizeof auth_ticket);
auth_ticket.m_magicNumber = 0x0EFBDADDE;
auth_ticket.m_type = 0;
auth_ticket.m_titleID = title_id;
auth_ticket.m_userID = steam::SteamUser()->GetSteamID().bits;
auth_ticket.m_licenseID = 4;
auto key = utils::cryptography::tiger::compute(SERVER_CD_KEY);
strcpy_s(auth_ticket.m_username, "iw6-mod server");
std::memcpy(auth_ticket.m_sessionKey, key.data(), 24);
auth_ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
uint8_t lsg_ticket[128];
ZeroMemory(&lsg_ticket, sizeof lsg_ticket);
std::memcpy(lsg_ticket, key.data(), 24);
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&seed), 4));
const std::string enc_key(reinterpret_cast<char*>(&ticket[32]), 24);
auto enc_ticket = utils::cryptography::des3::encrypt(
std::string(reinterpret_cast<char*>(&auth_ticket), sizeof(auth_ticket)), iv, enc_key);
bit_buffer response;
response.set_use_data_types(false);
response.write_bool(false);
response.write_uint32(700);
response.write_uint32(seed);
response.write_bytes(enc_ticket.size(), enc_ticket.data());
response.write_bytes(sizeof(lsg_ticket), lsg_ticket);
auto reply = server->create_message(29);
reply->send(&response, false);
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdDediAuth final : public i_generic_service<12>
{
public:
void call_service(i_server* server, const std::string& data) override;
};
}

View File

@ -0,0 +1,73 @@
#include <std_include.hpp>
#include "bdDediRSAAuth.hpp"
#include "game/game.hpp"
#include "steam/steam.hpp"
#include <utils/cryptography.hpp>
namespace demonware
{
void bdDediRSAAuth::call_service(i_server* server, const std::string& data)
{
bit_buffer buffer(data);
bool more_data;
buffer.set_use_data_types(false);
buffer.read_bool(&more_data);
buffer.set_use_data_types(true);
uint32_t seed, title_id, ticket_size;
buffer.read_uint32(&seed);
buffer.read_uint32(&title_id);
unsigned char rsa_key[140];
buffer.read_bytes(sizeof(rsa_key), rsa_key);
uint8_t ticket[1024];
buffer.read_bytes(std::min(ticket_size, static_cast<uint32_t>(sizeof(ticket))), ticket);
game::bdAuthTicket auth_ticket{};
std::memset(&auth_ticket, 0xA, sizeof auth_ticket);
auth_ticket.m_magicNumber = 0x0EFBDADDE;
auth_ticket.m_type = 0;
auth_ticket.m_titleID = title_id;
auth_ticket.m_userID = steam::SteamUser()->GetSteamID().bits;
auto key = utils::cryptography::tiger::compute(SERVER_CD_KEY);
strcpy_s(auth_ticket.m_username, "iw6-mod server");
std::memcpy(auth_ticket.m_sessionKey, key.data(), 24);
auth_ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
uint8_t lsg_ticket[128];
ZeroMemory(&lsg_ticket, sizeof lsg_ticket);
std::memcpy(lsg_ticket, key.data(), 24);
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&seed), 4));
const std::string enc_key(reinterpret_cast<char*>(&ticket[32]), 24);
auto enc_ticket = utils::cryptography::des3::encrypt(
std::string(reinterpret_cast<char*>(&auth_ticket), sizeof(auth_ticket)), iv, enc_key);
register_hash(&sha1_desc);
register_prng(&yarrow_desc);
auto encrypted_key = utils::cryptography::rsa::encrypt(std::string(SERVER_CD_KEY, 24),
std::string("DW-RSAENC", 10),
std::string(PCHAR(rsa_key), sizeof(rsa_key)));
bit_buffer response;
response.set_use_data_types(false);
response.write_bool(false);
response.write_uint32(700);
response.write_uint32(seed);
response.write_bytes(enc_ticket.size(), enc_ticket.data());
response.write_bytes(sizeof(lsg_ticket), lsg_ticket);
response.write_bytes(encrypted_key.size(), encrypted_key.data());
auto reply = server->create_message(29);
reply->send(&response, false);
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdDediRSAAuth final : public i_generic_service<26>
{
public:
void call_service(i_server* server, const std::string& data) override;
};
}

View File

@ -0,0 +1,49 @@
#include <std_include.hpp>
#include "bdGroup.hpp"
#include "../data_types.hpp"
namespace demonware
{
bdGroup::bdGroup()
{
this->register_service(1, &bdGroup::set_groups);
this->register_service(4, &bdGroup::get_groups);
}
void bdGroup::set_groups(i_server* server, byte_buffer* /*buffer*/) const
{
//uint32_t groupCount;
// TODO: Implement array reading
auto reply = server->create_reply(this->get_sub_type());
reply->send();
}
void bdGroup::get_groups(i_server* server, byte_buffer* buffer)
{
uint32_t group_count;
buffer->read_array_header(8, &group_count);
auto reply = server->create_reply(this->get_sub_type());
buffer->set_use_data_types(false);
for (uint32_t i = 0; i < group_count; ++i)
{
auto* count = new bdGroupCount;
buffer->read_uint32(&count->group_id);
if (count->group_id < ARRAYSIZE(this->groups))
{
this->groups[count->group_id] = 999;
count->group_count = this->groups[count->group_id];
}
reply->add(count);
}
buffer->set_use_data_types(true);
reply->send();
}
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdGroup final : public i_generic_service<28>
{
public:
bdGroup();
private:
void set_groups(i_server* server, byte_buffer* buffer) const;
void get_groups(i_server* server, byte_buffer* buffer);
uint32_t groups[512]{};
};
}

View File

@ -0,0 +1,26 @@
#include <std_include.hpp>
#include "bdLSGHello.hpp"
#include "component/demonware.hpp"
namespace demonware
{
void bdLSGHello::call_service(i_server* server, const std::string& data)
{
bit_buffer buffer(data);
bool more_data;
buffer.set_use_data_types(false);
buffer.read_bool(&more_data);
buffer.set_use_data_types(true);
uint32_t seed, title_id;
buffer.read_uint32(&title_id);
buffer.read_uint32(&seed);
uint8_t ticket[128];
buffer.read_bytes(sizeof(ticket), ticket);
set_key(true, ticket);
set_key(false, ticket);
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "../i_service.hpp"
namespace demonware
{
class bdLSGHello final : public i_generic_service<7>
{
public:
void call_service(i_server* server, const std::string& data) override;
};
}

Some files were not shown because too many files have changed in this diff Show More