20 Commits
Author SHA1 Message Date
Future b54b3f51af fix: add back this patch cuz idk 2026-09-10 08:24:14 +02:00
Future 02ba3b3663 feat: admen abooze 2026-09-09 21:14:45 +02:00
Future 043f8f07c0 Merge branch 'master' of https://git.alterware.dev/alterware/iw6-mod 2026-09-09 17:38:19 +02:00
Future 582e8e05be feat: fart out new fixes (cc: Wanted) DO NOT TELL PYRO PETE 2026-09-09 17:37:47 +02:00
Future 6ff635498d maint: update Premake5 (beta8) 2026-01-28 11:03:18 +00:00
Future 67c0b5ddfd feat: slowmotion command 2026-01-19 15:34:46 +01:00
Future 1b78912b33 chore: update 2025-11-16 21:38:02 +01:00
Future deafda9061 chore: remove WinToast 2025-06-04 10:58:19 +02:00
Future 61f0374d10 chore: update 2025-05-21 18:47:56 +02:00
Future 9052daab5c maint: update Premake5 2025-04-11 11:10:05 +02:00
Future a299874dd2 fix: remove _s poison 2025-02-06 12:34:59 +01:00
Future 27d5985574 fix noob team 2025-02-03 20:02:50 +01:00
Future b60692f0d7 feat: more arxan patches 2025-01-30 13:14:21 +01:00
Future 6f2c293970 chore: update deps 2025-01-29 13:25:42 +01:00
Future 03b44731a8 fix(gsc): I got one big silly goose in me 2025-01-29 08:21:56 +00:00
Future ea8d2e483f fix(gameplay): fix gun game 2025-01-28 10:38:57 +01:00
Future 833e25700c feat(gsc): make bots join a server automatically 2025-01-28 08:57:47 +01:00
Future bc0372a249 feat(mods): add LUI menu 2025-01-27 22:28:55 +01:00
3d77a21f3d assets(weapons): cache weapons from code (Code quaK/H1-Mod probably took from Pluto xD)
Co-authored-by: H1-Mod Team <[email protected]>
Co-authored-by: HighTechRedNeck <[email protected]>
2025-01-24 13:59:57 +01:00
FutureandAnomaly 199206ee5a maint: January Update
Co-authored-by: Anomaly <[email protected]>
2025-01-11 10:20:05 +01:00
120 changed files with 1468 additions and 7595 deletions
-3
View File
@@ -29,9 +29,6 @@
[submodule "deps/udis86"] [submodule "deps/udis86"]
path = deps/udis86 path = deps/udis86
url = https://github.com/vmt/udis86.git url = https://github.com/vmt/udis86.git
[submodule "deps/WinToast"]
path = deps/WinToast
url = https://github.com/mohabouje/WinToast.git
[submodule "deps/zlib"] [submodule "deps/zlib"]
path = deps/zlib path = deps/zlib
url = https://github.com/madler/zlib.git url = https://github.com/madler/zlib.git
Vendored
+1 -1
Submodule deps/GSL updated: 272463043e...543d0dd3fe
-1
Submodule deps/WinToast deleted from a78ce469b4
Vendored
+1 -1
+1 -1
+1 -1
-32
View File
@@ -1,32 +0,0 @@
wintoast = {
source = path.join(dependencies.basePath, "WinToast"),
}
function wintoast.import()
links { "WinToast" }
wintoast.includes()
end
function wintoast.includes()
includedirs {
path.join(wintoast.source, "include"),
}
end
function wintoast.project()
project "WinToast"
language "C++"
wintoast.includes()
rapidjson.import();
files {
path.join(wintoast.source, "include/wintoastlib.h"),
path.join(wintoast.source, "src/wintoastlib.cpp"),
}
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, wintoast)
+1 -1
Vendored
+1 -1
Submodule deps/zlib updated: ef24c4c750...5a82f71ed1
+2 -2
View File
@@ -257,7 +257,7 @@ if os.getenv("CI") then
defines {"CI"} defines {"CI"}
end end
flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"} multiprocessorcompile "On"
filter "platforms:x64" filter "platforms:x64"
defines {"_WINDOWS", "WIN32"} defines {"_WINDOWS", "WIN32"}
@@ -268,7 +268,7 @@ filter "configurations:Release"
buildoptions {"/GL"} buildoptions {"/GL"}
linkoptions {"/IGNORE:4702", "/LTCG"} linkoptions {"/IGNORE:4702", "/LTCG"}
defines {"NDEBUG"} defines {"NDEBUG"}
flags {"FatalCompileWarnings"} fatalwarnings {"All"}
filter {} filter {}
filter "configurations:Debug" filter "configurations:Debug"
+1 -5
View File
@@ -103,9 +103,7 @@ namespace arxan
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context) BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
{ {
if (!game::environment::is_sp() if (game::dwGetLogOnStatus(0) == game::DW_LIVE_CONNECTED && context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
&& game::dwGetLogOnStatus(0) == game::DW_LIVE_CONNECTED
&& context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
{ {
return TRUE; return TRUE;
} }
@@ -142,8 +140,6 @@ namespace arxan
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp()) return;
utils::hook::jump(0x1404FE1E0, 0x1404FE2D0); // idk utils::hook::jump(0x1404FE1E0, 0x1404FE2D0); // idk
utils::hook::jump(0x140558C20, 0x140558CB0); // dwNetPump utils::hook::jump(0x140558C20, 0x140558CB0); // dwNetPump
utils::hook::jump(0x140591850, 0x1405918E0); // dwLobbyPump utils::hook::jump(0x140591850, 0x1405918E0); // dwLobbyPump
+59
View File
@@ -0,0 +1,59 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "component/console.hpp"
#include <utils/hook.hpp>
namespace weapons
{
namespace
{
void g_setup_level_weapon_def_stub()
{
game::G_SetupLevelWeaponDef();
// The count on most maps is well below 200
std::array<game::WeaponCompleteDef*, 256> weapons{};
const auto count = game::DB_GetAllXAssetOfType_FastFile(game::ASSET_TYPE_WEAPON, (void**)weapons.data(), static_cast<int>(weapons.max_size()));
std::sort(weapons.begin(), weapons.begin() + count, [](game::WeaponCompleteDef* weapon1, game::WeaponCompleteDef* weapon2)
{
assert(weapon1->szInternalName);
assert(weapon2->szInternalName);
return std::strcmp(weapon1->szInternalName, weapon2->szInternalName) < 0;
});
#ifdef _DEBUG
console::info("Found %i weapons to precache\n", count);
#endif
for (auto i = 0; i < count; ++i)
{
#ifdef _DEBUG
console::info("Precaching weapon \"%s\"\n", weapons[i]->szInternalName);
#endif
(void)game::G_GetWeaponForName(weapons[i]->szInternalName);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// Kill Scr_PrecacheItem (We are going to do this from code)
utils::hook::nop(0x1403BDB10, 4);
utils::hook::set<std::uint8_t>(0x1403BDB10, 0xC3);
// Load weapons from the DB
utils::hook::call(0x14039F382, g_setup_level_weapon_def_stub);
utils::hook::call(0x1403AE31A, g_setup_level_weapon_def_stub);
}
};
}
REGISTER_COMPONENT(weapons::component)
+75 -26
View File
@@ -36,11 +36,8 @@ namespace auth
auto hw_profile_path = (utils::properties::get_appdata_path() / "iw6-guid.dat").generic_string(); auto hw_profile_path = (utils::properties::get_appdata_path() / "iw6-guid.dat").generic_string();
if (utils::io::file_exists(hw_profile_path)) if (utils::io::file_exists(hw_profile_path))
{ {
std::string hw_profile_info; // Migration
if (utils::io::read_file(hw_profile_path, &hw_profile_info) && !hw_profile_info.empty()) utils::io::remove_file(hw_profile_path);
{
return hw_profile_info;
}
} }
HW_PROFILE_INFO info; HW_PROFILE_INFO info;
@@ -50,8 +47,6 @@ namespace auth
} }
auto hw_profile_info = std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) }; auto hw_profile_info = std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) };
utils::io::write_file(hw_profile_path, hw_profile_info);
return hw_profile_info; return hw_profile_info;
} }
@@ -67,7 +62,7 @@ namespace auth
return {}; return {};
} }
const auto size = std::min(data_out.cbData, 52ul); const auto size = std::min<DWORD>(data_out.cbData, 52);
std::string result{ reinterpret_cast<char*>(data_out.pbData), size }; std::string result{ reinterpret_cast<char*>(data_out.pbData), size };
LocalFree(data_out.pbData); LocalFree(data_out.pbData);
@@ -91,17 +86,80 @@ namespace auth
return entropy; return entropy;
} }
utils::cryptography::ecc::key& get_key() bool load_key(utils::cryptography::ecc::key& key)
{ {
static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); std::string data{};
auto key_path = (utils::properties::get_appdata_path() / "iw6-private.key").generic_string();
if (!utils::io::read_file(key_path, &data))
{
return false;
}
key.deserialize(data);
if (!key.is_valid())
{
console::error("Loaded key is invalid!\n");
return false;
}
return true;
}
utils::cryptography::ecc::key generate_key()
{
auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy());
if (!key.is_valid())
{
throw std::runtime_error("Failed to generate cryptographic key!");
}
auto key_path = (utils::properties::get_appdata_path() / "iw6-private.key").generic_string();
if (!utils::io::write_file(key_path, key.serialize()))
{
console::error("Failed to write cryptographic key!\n");
}
console::info("Generated cryptographic key: %llX\n", key.get_hash());
return key; return key;
} }
int send_connect_data_stub(game::netsrc_t sock, game::netadr_t* adr, const char* format, const int len) utils::cryptography::ecc::key load_or_generate_key()
{
utils::cryptography::ecc::key key{};
if (load_key(key))
{
console::info("Loaded cryptographic key: %llX\n", key.get_hash());
return key;
}
return generate_key();
}
utils::cryptography::ecc::key get_key_internal()
{
auto key = load_or_generate_key();
auto key_path = (utils::properties::get_appdata_path() / "iw6-public.key").generic_string();
if (!utils::io::write_file(key_path, key.get_public_key()))
{
console::error("Failed to write public key!\n");
}
return key;
}
const utils::cryptography::ecc::key& get_key()
{
static auto key = get_key_internal();
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); std::string connect_string(format, len);
game::SV_Cmd_TokenizeString(connect_string.data()); game::SV_Cmd_TokenizeString(connect_string.data());
const auto _ = gsl::finally([]() const auto _0 = gsl::finally([]()
{ {
game::SV_Cmd_EndTokenizedString(); game::SV_Cmd_EndTokenizedString();
}); });
@@ -124,14 +182,14 @@ namespace auth
proto::network::connect_info info; proto::network::connect_info info;
info.set_publickey(get_key().get_public_key()); info.set_publickey(get_key().get_public_key());
info.set_signature(sign_message(get_key(), challenge)); info.set_signature(utils::cryptography::ecc::sign_message(get_key(), challenge));
info.set_infostring(connect_string); info.set_infostring(connect_string);
network::send(*adr, "connect", info.SerializeAsString()); network::send(*adr, "connect", info.SerializeAsString());
return true; return true;
} }
void direct_connect(game::netadr_t* from, game::msg_t* msg) void direct_connect(game::netadr_s* from, game::msg_t* msg)
{ {
const auto offset = sizeof("connect") + 4; const auto offset = sizeof("connect") + 4;
@@ -165,14 +223,14 @@ namespace auth
utils::cryptography::ecc::key key; utils::cryptography::ecc::key key;
key.set(info.publickey()); key.set(info.publickey());
const auto xuid = strtoull(steam_id.data(), nullptr, 16); const auto xuid = std::strtoull(steam_id.data(), nullptr, 16);
if (xuid != key.get_hash()) if (xuid != key.get_hash())
{ {
network::send(*from, "error", "XUID doesn't match the certificate!", '\n'); network::send(*from, "error", "XUID doesn't match the certificate!", '\n');
return; return;
} }
if (!key.is_valid() || !verify_message(key, challenge, info.signature())) if (!key.is_valid() || !utils::cryptography::ecc::verify_message(key, challenge, info.signature()))
{ {
network::send(*from, "error", "Challenge signature was invalid!", '\n'); network::send(*from, "error", "Challenge signature was invalid!", '\n');
return; return;
@@ -214,14 +272,6 @@ namespace auth
void post_unpack() override void post_unpack() override
{ {
// Patch steam id bit check // 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(0x140585410, 0x140585466);
utils::hook::jump(0x140142252, 0x140142293); utils::hook::jump(0x140142252, 0x140142293);
utils::hook::jump(0x140142334, 0x140142389); utils::hook::jump(0x140142334, 0x140142389);
@@ -230,9 +280,8 @@ namespace auth
utils::hook::jump(0x140479636, get_direct_connect_stub(), true); utils::hook::jump(0x140479636, get_direct_connect_stub(), true);
utils::hook::call(0x1402C4F8E, send_connect_data_stub); utils::hook::call(0x1402C4F8E, send_connect_data_stub);
}
command::add("guid", [] command::add("guid", []() -> void
{ {
console::info("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits); console::info("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits);
}); });
+5 -9
View File
@@ -25,9 +25,7 @@ namespace binding
if (value && value < 100) if (value && value < 100)
{ {
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used), const auto len = game::Com_sprintf(&buffer[bytes_used], (buffer_size_align - bytes_used), "bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
"bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
if (len < 0) if (len < 0)
{ {
return bytes_used; return bytes_used;
@@ -40,9 +38,7 @@ namespace binding
value -= 100; value -= 100;
if (static_cast<size_t>(value) < custom_binds.size() && !custom_binds[value].empty()) 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), const auto len = game::Com_sprintf(&buffer[bytes_used], (buffer_size_align - bytes_used), "bind %s \"%s\"\n", key_button, custom_binds[value].data());
"bind %s \"%s\"\n", key_button, custom_binds[value].data());
if (len < 0) if (len < 0)
{ {
return bytes_used; return bytes_used;
@@ -119,13 +115,13 @@ namespace binding
} }
// write all bindings to config file // write all bindings to config file
utils::hook::call(SELECT_VALUE(0x14023DF2B, 0x1402C465B), key_write_bindings_to_buffer_stub); utils::hook::call(0x1402C465B, key_write_bindings_to_buffer_stub);
// links a custom command to an index // links a custom command to an index
utils::hook::jump(SELECT_VALUE(0x14023D6E0, 0x1402C3E50), key_get_binding_for_cmd_stub); utils::hook::jump(0x1402C3E50, key_get_binding_for_cmd_stub);
// execute custom binds // execute custom binds
cl_execute_key_hook.create(SELECT_VALUE(0x140239970, 0x1402BF0E0), &cl_execute_key_stub); cl_execute_key_hook.create(0x1402BF0E0, &cl_execute_key_stub);
} }
}; };
} }
+9 -12
View File
@@ -13,6 +13,8 @@
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#define ALLOW_CUSTOM_BOT_NAMES
namespace bots namespace bots
{ {
namespace namespace
@@ -21,7 +23,7 @@ namespace bots
bool can_add() bool can_add()
{ {
return party::get_client_count() < game::mp::svs->clientCount; return party::get_client_count() < *game::mp::svs_clientCount;
} }
void bot_team_join(const int entity_num) void bot_team_join(const int entity_num)
@@ -29,14 +31,14 @@ namespace bots
// schedule the select team call // schedule the select team call
scheduler::once([entity_num]() scheduler::once([entity_num]()
{ {
game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num], game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value), utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value),
false); false);
// scheduler the select class call // scheduler the select class call
scheduler::once([entity_num]() scheduler::once([entity_num]()
{ {
game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num], game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
utils::string::va("lui 5 %i %i", (rand() % 5) + 10, utils::string::va("lui 5 %i %i", (rand() % 5) + 10,
*game::mp::sv_serverId_value), false); *game::mp::sv_serverId_value), false);
}, scheduler::pipeline::server, 1s); }, scheduler::pipeline::server, 1s);
@@ -144,7 +146,7 @@ namespace bots
{ {
bot_names_received = false; bot_names_received = false;
game::netadr_t master{}; game::netadr_s master{};
if (server_list::get_master_server(master)) if (server_list::get_master_server(master))
{ {
console::info("Getting bots...\n"); console::info("Getting bots...\n");
@@ -158,11 +160,6 @@ namespace bots
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp())
{
return;
}
get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name); get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name);
command::add("spawnBot", [](const command::params& params) command::add("spawnBot", [](const command::params& params)
@@ -175,7 +172,7 @@ namespace bots
num_bots = std::strtoul(params.get(1), nullptr, 10); num_bots = std::strtoul(params.get(1), nullptr, 10);
} }
num_bots = std::min(num_bots, static_cast<std::size_t>(game::mp::svs->clientCount)); num_bots = std::min(num_bots, static_cast<std::size_t>(*game::mp::svs_clientCount));
console::info("Spawning %zu %s\n", num_bots, (num_bots == 1 ? "bot" : "bots")); console::info("Spawning %zu %s\n", num_bots, (num_bots == 1 ? "bot" : "bots"));
@@ -198,9 +195,9 @@ namespace bots
parse_bot_names_from_file(); parse_bot_names_from_file();
} }
network::on("getbotsResponse", [](const game::netadr_t& target, const std::string& data) network::on("getbotsResponse", [](const game::netadr_s& target, const std::string& data)
{ {
game::netadr_t master{}; game::netadr_s master{};
if (server_list::get_master_server(master) && !bot_names_received && target == master) if (server_list::get_master_server(master) && !bot_names_received && target == master)
{ {
bot_names = utils::string::split(data, '\n'); bot_names = utils::string::split(data, '\n');
+2 -2
View File
@@ -40,8 +40,8 @@ namespace branding
localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "iw6-mod: " VERSION " by AlterWare.\n"); localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "iw6-mod: " VERSION " by AlterWare.\n");
utils::hook::call(SELECT_VALUE(0x1403BDABA, 0x140414424), dvar_set_string_stub); utils::hook::call(0x140414424, dvar_set_string_stub);
ui_get_formatted_build_number_hook.create(game::LiveStorage_FetchFFotD, ui_get_formatted_build_number_stub); ui_get_formatted_build_number_hook.create(0x1404D7C00, ui_get_formatted_build_number_stub);
scheduler::loop([]() scheduler::loop([]()
{ {
-5
View File
@@ -28,11 +28,6 @@ namespace bullet
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp())
{
return;
}
dvars::bg_surfacePenetration = game::Dvar_RegisterFloat("bg_surfacePenetration", 0.0f, dvars::bg_surfacePenetration = game::Dvar_RegisterFloat("bg_surfacePenetration", 0.0f,
0.0f, std::numeric_limits<float>::max(), game::DVAR_FLAG_SAVED, 0.0f, std::numeric_limits<float>::max(), game::DVAR_FLAG_SAVED,
"Set to a value greater than 0 to override the surface penetration depth"); "Set to a value greater than 0 to override the surface penetration depth");
+33 -22
View File
@@ -76,7 +76,16 @@ namespace colors
void com_clean_name_stub(const char* in, char* out, const int out_size) void com_clean_name_stub(const char* in, char* out, const int out_size)
{ {
strncpy_s(out, out_size, in, _TRUNCATE); // Check that the name is at least 3 char without colors
char name[16]{};
game::I_strncpyz(out, in, std::min<int>(out_size, sizeof(name)));
utils::string::strip(out, name, std::strlen(out) + 1);
if (std::strlen(name) < 3)
{
game::I_strncpyz(out, "UnnamedPlayer", std::min<int>(out_size, sizeof(name)));
}
} }
char* i_clean_str_stub(char* string) char* i_clean_str_stub(char* string)
@@ -86,26 +95,29 @@ namespace colors
return string; return string;
} }
size_t get_client_name_stub(const int local_client_num, const int index, char* name_buf, int cl_get_client_name_and_clan_tag_stub(const int local_client_num, const int index, char* name_buf, const int name_size, char* clan_tag_buf, int clan_tag_size)
const int max_name_size, char* clan_buf, const int max_clan_size)
{ {
// CL_GetClientNameAndClanTag (wrapper for CL_GetClientNameAndClanTagColorize) // CL_GetClientNameAndClanTag -> CL_GetClientNameAndClanTagColorize
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, char*, int)>(0x1402CF790)( const auto result = utils::hook::invoke<int>(0x1402CF790, local_client_num, index, name_buf, name_size, clan_tag_buf, clan_tag_size);
local_client_num, index, name_buf, max_name_size, clan_buf, max_clan_size);
utils::string::strip(name_buf, name_buf, static_cast<size_t>(max_name_size)); utils::string::strip(name_buf, name_buf, name_size);
return result; return result;
} }
int clean_agent_name_stub(char* out, int max_size, const char* fmt, const char* in) int com_sprintf_stub(char* dest, int size, const char* fmt, const char* name)
{ {
// format agent overhead name like [%s] const auto len = game::Com_sprintf(dest, size, fmt, name);
const auto length = sprintf_s(out, max_size, fmt, in); if (len < 0)
{
game::I_strncpyz(dest, "UnnamedAgent", size);
return len;
}
utils::string::strip(in, out, std::min(static_cast<size_t>(length), static_cast<size_t>(max_size))); // This should inherit the name of the owner (a player) which already passed the length check in Com_CleanName
utils::string::strip(dest, dest, len + 1);
return length; return len;
} }
void rb_lookup_color_stub(const char index, DWORD* color) void rb_lookup_color_stub(const char index, DWORD* color)
@@ -114,11 +126,11 @@ namespace colors
if (index == '8') if (index == '8')
{ {
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD958, 0x1480E85BC)); *color = *reinterpret_cast<DWORD*>(0x1480E85BC);
} }
else if (index == '9') else if (index == '9')
{ {
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD95C, 0x1480E85C0)); *color = *reinterpret_cast<DWORD*>(0x1480E85C0);
} }
else if (index == ':') else if (index == ':')
{ {
@@ -126,7 +138,7 @@ namespace colors
} }
else if (index == ';') else if (index == ';')
{ {
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x145FFD964, 0x1480E85C8)); *color = *reinterpret_cast<DWORD*>(0x1480E85C8);
} }
else else
{ {
@@ -145,23 +157,22 @@ namespace colors
return; return;
} }
if (!game::environment::is_sp())
{
// allows colored name in-game // allows colored name in-game
utils::hook::jump(0x1404F5FC0, com_clean_name_stub); utils::hook::call(0x1403881CF, com_clean_name_stub);
utils::hook::call(0x140388224, com_clean_name_stub);
// don't apply colors to overhead names // don't apply colors to overhead names
utils::hook::call(0x14025CE79, get_client_name_stub); utils::hook::call(0x14025CE79, cl_get_client_name_and_clan_tag_stub);
// don't apply colors to overhead names of agents (like dogs or juggernauts) // don't apply colors to overhead names of agents (like dogs or juggernauts)
utils::hook::call(0x1402CF760, clean_agent_name_stub); // hook Com_sprintf in CL_GetAgentName
utils::hook::call(0x1402CF760, com_sprintf_stub);
// patch I_CleanStr // patch I_CleanStr
utils::hook::jump(0x1404F63C0, i_clean_str_stub); utils::hook::jump(0x1404F63C0, i_clean_str_stub);
}
// force new colors // force new colors
utils::hook::jump(SELECT_VALUE(0x14055DCC0, 0x14062AE80), rb_lookup_color_stub); utils::hook::jump(0x14062AE80, rb_lookup_color_stub);
// add colors // add colors
add(0, 0, 0); // 0 - Black add(0, 0, 0); // 0 - Black
+22 -140
View File
@@ -2,6 +2,7 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "game/engine/sv_game.hpp"
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
@@ -194,7 +195,7 @@ namespace command
void add_raw(const char* name, void (*callback)()) void add_raw(const char* name, void (*callback)())
{ {
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_t>()); 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) void add(const char* name, const std::function<void(const params&)>& callback)
@@ -251,17 +252,10 @@ namespace command
{ {
public: public:
void post_unpack() override void post_unpack() override
{
if (game::environment::is_sp())
{
add_sp_commands();
}
else
{ {
utils::hook::call(0x14041213C, &parse_commandline_stub); utils::hook::call(0x14041213C, &parse_commandline_stub);
add_mp_commands(); add_mp_commands();
}
add_commands_generic(); add_commands_generic();
} }
@@ -321,7 +315,7 @@ namespace command
} }
console::info("================================ COMMAND DUMP =====================================\n"); console::info("================================ COMMAND DUMP =====================================\n");
game::cmd_function_t* cmd = *game::cmd_functions; game::cmd_function_s* cmd = *game::cmd_functions;
auto i = 0; auto i = 0;
while (cmd) while (cmd)
{ {
@@ -449,105 +443,6 @@ namespace command
}); });
} }
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() static void add_mp_commands()
{ {
client_command_hook.create(0x1403929B0, &client_command); client_command_hook.create(0x1403929B0, &client_command);
@@ -556,77 +451,64 @@ namespace command
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
game::mp::g_entities[client_num].flags ^= game::FL_GODMODE; game::mp::g_entities[client_num].flags ^= game::FL_GODMODE;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, utils::string::va("f \"godmode %s\"", game::mp::g_entities[client_num].flags & game::FL_GODMODE ? "^2on" : "^1off"));
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&) add_sv("notarget", [](const int client_num, const params_sv&)
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET; game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"notarget %s\"", utils::string::va("f \"notarget %s\"", game::mp::g_entities[client_num].flags & game::FL_NOTARGET ? "^2on" : "^1off"));
game::mp::g_entities[client_num].flags & game::FL_NOTARGET
? "^2on"
: "^1off"));
}); });
add_sv("noclip", [](const int client_num, const params_sv&) add_sv("noclip", [](const int client_num, const params_sv&)
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
game::mp::g_entities[client_num].client->flags ^= 1; game::mp::g_entities[client_num].client->flags ^= 1;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"noclip %s\"", utils::string::va("f \"noclip %s\"", game::mp::g_entities[client_num].client->flags & 1 ? "^2on" : "^1off"));
game::mp::g_entities[client_num].client->flags & 1
? "^2on"
: "^1off"));
}); });
add_sv("ufo", [](const int client_num, const params_sv&) add_sv("ufo", [](const int client_num, const params_sv&)
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
game::mp::g_entities[client_num].client->flags ^= 2; game::mp::g_entities[client_num].client->flags ^= 2;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
utils::string::va("f \"ufo %s\"", utils::string::va("f \"ufo %s\"", game::mp::g_entities[client_num].client->flags & 2 ? "^2on" : "^1off"));
game::mp::g_entities[client_num].client->flags & 2
? "^2on"
: "^1off"));
}); });
add_sv("setviewpos", [](const int client_num, const params_sv& params) add_sv("setviewpos", [](const int client_num, const params_sv& params)
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
if (params.size() < 4) if (params.size() < 4)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify the correct number of coordinates\""); "f \"You did not specify the correct number of coordinates\"");
return; return;
} }
@@ -640,13 +522,13 @@ namespace command
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
if (params.size() < 4) if (params.size() < 4)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
"f \"You did not specify the correct number of coordinates\""); "f \"You did not specify the correct number of coordinates\"");
return; return;
} }
@@ -660,13 +542,13 @@ namespace command
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
if (params.size() < 2) if (params.size() < 2)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\"");
return; return;
} }
@@ -683,13 +565,13 @@ namespace command
{ {
if (!dvars::sv_cheats->current.enabled) if (!dvars::sv_cheats->current.enabled)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
return; return;
} }
if (params.size() < 2) if (params.size() < 2)
{ {
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\""); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\"");
return; return;
} }
+8 -9
View File
@@ -84,15 +84,14 @@ namespace console
void print_stub(const char* fmt, ...) void print_stub(const char* fmt, ...)
{ {
char buffer[4096]{};
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
[[maybe_unused]] const auto len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
char buffer[4096]{};
const auto res = vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
(void)res;
print_message(buffer);
va_end(ap); va_end(ap);
print_message(buffer);
} }
void append_text(const char* text) void append_text(const char* text)
@@ -126,7 +125,7 @@ namespace console
void post_unpack() override void post_unpack() override
{ {
// Redirect input (]command) // Redirect input (]command)
utils::hook::jump(SELECT_VALUE(0x14043DFA0, 0x140502A80), append_text); utils::hook::jump(0x140502A80, append_text);
utils::hook::jump(printf, print_stub); utils::hook::jump(printf, print_stub);
@@ -220,7 +219,7 @@ namespace console
HWND get_window() HWND get_window()
{ {
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x145A7B490, 0x147AD1DB0))); return *reinterpret_cast<HWND*>(0x147AD1DB0);
} }
void set_title(std::string title) void set_title(std::string title)
@@ -242,7 +241,7 @@ namespace console
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0); SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x145A7B4A0, 0x147AD1DC0)); auto* const logo_window = *reinterpret_cast<HWND*>(0x147AD1DC0);
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0); SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
} }
+1 -1
View File
@@ -49,7 +49,7 @@ namespace cursor
{ {
// This is the legacy cursor // This is the legacy cursor
// TODO: Find the new cursor drawing... // TODO: Find the new cursor drawing...
utils::hook::call(SELECT_VALUE(0x14040117E, 0x1404BB119), draw_cursor_stub); utils::hook::call(0x1404BB119, draw_cursor_stub);
scheduler::loop([]() scheduler::loop([]()
{ {
+1 -23
View File
@@ -110,7 +110,7 @@ namespace dedicated
return; return;
} }
game::netadr_t target{}; game::netadr_s target{};
if (server_list::get_master_server(target)) if (server_list::get_master_server(target))
{ {
network::send(target, "heartbeat", "IW6"); network::send(target, "heartbeat", "IW6");
@@ -198,25 +198,6 @@ namespace dedicated
return 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() void add_commands()
{ {
command::add("map", [](const command::params& params) command::add("map", [](const command::params& params)
@@ -364,9 +345,6 @@ namespace dedicated
utils::hook::nop(0x1404F8BE1, 2); // ^ utils::hook::nop(0x1404F8BE1, 2); // ^
utils::hook::set<uint8_t>(0x140328660, 0xC3); // Disable image pak file loading 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 // Reduce min required memory
utils::hook::set<uint64_t>(0x1404FA6BD, 0x80000000); utils::hook::set<uint64_t>(0x1404FA6BD, 0x80000000);
utils::hook::set<uint64_t>(0x1404FA76F, 0x80000000); utils::hook::set<uint64_t>(0x1404FA76F, 0x80000000);
+3 -4
View File
@@ -41,7 +41,7 @@ namespace dedicated_info
for (auto i = 0; i < sv_maxclients->current.integer; ++i) for (auto i = 0; i < sv_maxclients->current.integer; ++i)
{ {
auto* client = &game::mp::svs->clients[i]; auto* client = &game::mp::svs_clients[i];
auto* self = &game::mp::g_entities[i]; auto* self = &game::mp::g_entities[i];
if (client->header.state > game::CS_FREE && self && self->client) if (client->header.state > game::CS_FREE && self && self->client)
@@ -56,10 +56,9 @@ namespace dedicated_info
std::string cleaned_hostname = sv_hostname->current.string; std::string cleaned_hostname = sv_hostname->current.string;
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(), utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(), cleaned_hostname.size() + 1);
cleaned_hostname.size() + 1);
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.c_str(), console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(),
mapname->current.string, client_count, mapname->current.string, client_count,
sv_maxclients->current.integer, bot_count)); sv_maxclients->current.integer, bot_count));
}, scheduler::pipeline::main, 1s); }, scheduler::pipeline::main, 1s);
-947
View File
@@ -1,947 +0,0 @@
#include "std_include.hpp"
#include "loader/component_loader.hpp"
#include "demo_playback.hpp"
#include "demo_utils.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "utils/command_line.hpp"
#include "utils/hook.hpp"
using namespace demo_utils;
namespace // demo class
{
class demo_playback_t
{
public:
demo_playback_t();
[[nodiscard]] bool is_demo_playing() const;
[[nodiscard]] bool is_demo_repeating() const;
[[nodiscard]] const std::optional<gamestate_t>& get_gamestate() const;
[[nodiscard]] bool rewind_demo(std::optional<std::uint32_t> msec);
[[nodiscard]] bool start(std::filesystem::path& path, bool repeat);
[[nodiscard]] std::optional<std::reference_wrapper<std::filesystem::path>> restart();
template <typename Func, typename... Args> [[nodiscard]] auto use_predicted_data(Func func, Args&&... args);
template <typename Func, typename... Args> [[nodiscard]] auto handle_fast_forward(Func func, Args&&... args);
void parse_demo_wrapper();
void stop();
private:
void repeat();
bool repeat_{};
bool skip_timeout_{};
bool retain_timescale_{};
std::size_t post_map_header_file_offset_{};
std::size_t pred_data_index_{};
std::array<predicted_data_t, 256> pred_data_{};
std::optional<std::int32_t> rewind_snap_svr_time_;
std::optional<gamestate_t> gs_;
std::vector<std::uint8_t> buffer_;
std::filesystem::path path_;
std::ifstream file_;
} demo;
demo_playback_t::demo_playback_t()
{
buffer_.reserve(MAX_SIZE);
}
bool demo_playback_t::is_demo_playing() const
{
return file_.good() && file_.is_open();
}
bool demo_playback_t::is_demo_repeating() const
{
return repeat_;
}
const std::optional<gamestate_t>& demo_playback_t::get_gamestate() const
{
return gs_;
}
template <typename Func, typename ...Args>
auto demo_playback_t::use_predicted_data(Func func, Args&& ...args)
{
return func(pred_data_, pred_data_index_, std::forward<Args>(args)...);
}
template <typename Func, typename ...Args>
auto demo_playback_t::handle_fast_forward(Func func, Args&& ...args)
{
return func(rewind_snap_svr_time_, skip_timeout_, std::forward<Args>(args)...);
}
bool demo_playback_t::rewind_demo(std::optional<std::uint32_t> msec)
{
if (!is_demo_playing())
{
return false;
}
// set demo end-of-file which will repeat the demo
repeat_ = true;
file_.seekg(0, std::ios::end);
if (msec)
{
retain_timescale_ = true;
const auto snap_svr_time = game::mp::cl->snap.serverTime;
if (const auto delta_time = snap_svr_time - static_cast<std::int32_t>(*msec) - 50; delta_time > 0)
{
rewind_snap_svr_time_ = delta_time;
}
}
// workaround to get rewinding working without delay at low timescales
fast_forward_demo(1000);
return true;
}
bool demo_playback_t::start(std::filesystem::path& path, bool repeat)
{
// check if the file can be opened before taking any action with the demo file stream
std::ifstream file(path, std::ios::binary);
const auto can_open = file.good() && file.is_open();
if (!can_open)
{
return false;
}
stop();
file_ = std::move(file);
assert(file_.good() && file_.is_open());
// (re)set data
repeat_ = repeat;
gs_.reset();
path_ = std::move(path);
get_persistent_data() = {};
if (auto* demo_timescale = game::Dvar_FindVar("demotimescale"); demo_timescale)
{
demo_timescale->current.value = 1.0f;
}
if (game::Live_SyncOnlineDataFlags(0))
{
console::info("the demo should start to load in a few seconds\n");
}
return true;
}
std::optional<std::reference_wrapper<std::filesystem::path>> demo_playback_t::restart()
{
if (!start(path_, repeat_))
{
return path_;
}
return std::nullopt;
}
void demo_playback_t::parse_demo_wrapper()
{
if (!parse_demo(file_, buffer_, post_map_header_file_offset_, pred_data_, pred_data_index_, gs_))
{
if (is_demo_repeating() && *game::mp::connstate != game::CA_DISCONNECTED)
{
repeat();
}
else
{
// don't repeat demo if it was stopped manually
stop();
}
}
assert(*game::mp::connstate != game::CA_DISCONNECTED || !is_demo_playing());
}
void demo_playback_t::stop()
{
skip_timeout_ = {};
retain_timescale_ = {};
post_map_header_file_offset_ = {};
pred_data_index_ = {};
pred_data_.fill({});
rewind_snap_svr_time_ = {};
gs_.reset();
buffer_.clear();
file_.clear();
file_.close();
// ensure that sv_cheats is always disabled after demo playback
if (auto* sv_cheats = game::Dvar_FindVar("sv_cheats"); sv_cheats)
{
sv_cheats->current.enabled = false;
}
}
void demo_playback_t::repeat()
{
assert(post_map_header_file_offset_ < 256);
if (auto& connstate = *game::mp::connstate; connstate == game::CA_ACTIVE)
{
// prevent old snapshots from being processed; would mess up the server command sequences
connstate = game::CA_PRIMED;
}
// reset game data
game::mp::clc->serverCommandSequence = {};
game::mp::clc->lastExecutedServerCommand = {};
game::mp::cgs->serverCommandSequence = {};
get_persistent_data() = {};
if (auto* demo_timescale = game::Dvar_FindVar("demotimescale"); demo_timescale)
{
if (!retain_timescale_)
{
demo_timescale->current.value = 1.0f;
}
}
// reset class data
skip_timeout_ = {};
retain_timescale_ = {};
pred_data_index_ = {};
file_.clear();
file_.seekg(post_map_header_file_offset_, std::ios::beg);
}
}
namespace // hooks
{
utils::hook::detour CG_UpdateOmnvars_hook;
utils::hook::detour CG_VisionSetStartLerp_To_hook;
utils::hook::detour CL_GetPredictedPlayerInformationForServerTime_hook;
utils::hook::detour CL_GetPredictedVehicleForServerTime_hook;
utils::hook::detour CL_SetCGameTime_hook;
utils::hook::detour CL_WritePacket_hook;
utils::hook::detour GetRemoteEyeValues_hook;
utils::hook::detour LUI_IntermissionBegan_hook;
utils::hook::detour VehicleCam_UpdatePlayerControlCam_hook;
game::dvar_t* demo_vision;
static constexpr std::array VISION_SETS{
"disabled", "no vision", "default",
"ac130", "ac130_enhanced_mp", "ac130_inverted", "aftermath", "aftermath_glow", "aftermath_post", "black_bw",
"cheat_bw", "coup_sunblind", "default_night", "default_night_mp", "end_game", "missilecam", "mpintro",
"mpnuke", "mpnuke_aftermath", "mpoutro", "near_death", "near_death_mp", "nuke_global_flash", "thermal_mp", "thermal_snowlevel_mp",
"aftermath_map", static_cast<const char*>(nullptr)
};
static_assert(
std::string_view(VISION_SETS[0]) == "disabled" &&
std::string_view(VISION_SETS[1]) == "no vision" &&
std::string_view(VISION_SETS[2]) == "default" &&
std::string_view(VISION_SETS[VISION_SETS.size() - 2]) == "aftermath_map" &&
VISION_SETS.back() == nullptr // enum dvars require a nullptr as last value
);
bool handle_vision_override(game::mp::ClientVisionSetData& cvs_data, std::int32_t index, const char* vision_name, std::int32_t duration, std::int32_t time)
{
if (demo.is_demo_playing())
{
const auto vision_set = static_cast<std::size_t>((demo_vision) ? demo_vision->current.integer : 0);
if (vision_set == 1)
{
// enable 'post apply' vision
cvs_data.visionSetLerpData[index].style = 1;
cvs_data.postApplyLerpDest = 1.0f;
return true;
}
else if (vision_set >= 2 && vision_set + 2 <= VISION_SETS.size())
{
// enable 'post apply' vision
cvs_data.visionSetLerpData[index].style = 0;
cvs_data.postApplyLerpDest = 1.0f;
if (vision_set + 2 == VISION_SETS.size())
{
if (const auto mapname = get_mapname(false); mapname.empty())
{
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, "aftermath_post", duration, time);
}
else
{
// a small number of maps have a vision called 'aftermath_mp_mapname'
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, std::format("aftermath_{}", mapname).c_str(), duration, time);
}
}
else
{
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, VISION_SETS[vision_set], duration, time);
}
}
}
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, vision_name, duration, time);
}
void handle_vision()
{
if (demo_vision && demo_vision->modified)
{
const auto vision_set = demo_vision->current.integer;
for (auto i = 0; i < game::CS_VISIONSET_COUNT; ++i)
{
const std::string_view vision_name = (!vision_set)
? game::mp::cg->cvsData.visionName[i]
: "";
handle_vision_override(game::mp::cg->cvsData, i, vision_name.data(), 1, game::mp::cl->serverTime);
}
if (!vision_set)
{
// disable 'post apply' vision if the game hasn't used it yet
if (game::mp::cg->cvsData.visionName[5][0] == '\0')
{
game::mp::cg->cvsData.postApplyLerpDest = 0.0f;
}
}
else if (vision_set == 1)
{
std::memset(&game::mp::cg->cvsData.visionSetCurrent, 0, sizeof(game::mp::cg->cvsData.visionSetCurrent));
}
// reset modified flag to avoid doing the same work each frame
demo_vision->modified = false;
}
}
void handle_fast_forward(auto old_connstate)
{
if (const auto new_connstate = *game::mp::connstate; new_connstate == game::CA_ACTIVE)
{
const auto server_time = game::mp::cl->serverTime;
const auto snap_svr_time = game::mp::cl->snap.serverTime;
demo.handle_fast_forward([old_connstate, server_time, snap_svr_time](
std::optional<std::int32_t>& rewind_snap_svr_time, bool& skip_timeout)
{
if (old_connstate == game::CA_PRIMED)
{
if (rewind_snap_svr_time)
{
// handle fast forward after rewinding
if (*rewind_snap_svr_time > snap_svr_time)
{
fast_forward_demo(*rewind_snap_svr_time - snap_svr_time);
}
rewind_snap_svr_time = {};
}
}
else if (old_connstate == game::CA_ACTIVE && !skip_timeout)
{
if (const auto delta_time = snap_svr_time - server_time; delta_time >= 1000)
{
// skip timeout between first and second snapshot (for user demos)
fast_forward_demo(delta_time);
skip_timeout = true;
}
}
});
}
}
void handle_demo_patches()
{
// set cg_t->demoType to DEMO_TYPE_CLIENT to disable mouse input and avoid frame interpolation issues
game::mp::cg->demoType = game::mp::DEMO_TYPE_CLIENT;
// ensure that sv_cheats is always enabled during demo playback
if (auto* sv_cheats = game::Dvar_FindVar("sv_cheats"); sv_cheats)
{
sv_cheats->current.enabled = true;
}
// sometimes the hud isn't updated after a weapon change; force update the weapon name and ammo (clip) when changing weapons
// various other Call of Duty games are also affected by this bug
if (game::mp::cg->weaponSelect.data != static_cast<std::size_t>(game::mp::cg->ps.weapon))
{
game::CG_SelectWeapon(0, game::mp::cg->ps.weapon, 0);
}
}
void handle_demo_reading(const std::int32_t client_num)
{
if (game::Live_SyncOnlineDataFlags(0) || !demo.is_demo_playing())
{
// set demoType to default value
// normally the game does this unconditionally, but that code has been removed
game::mp::cg->demoType = game::mp::DEMO_TYPE_NONE;
CL_SetCGameTime_hook.invoke<void>(client_num);
}
else
{
const auto old_connstate = *game::mp::connstate;
if (old_connstate == game::CA_ACTIVE)
{
// reset cl->newSnapshots to prevent CL_SetCGameTime from making a call to CL_AdjustTimeDelta,
// which interferes with the timescale
game::mp::cl->newSnapshots = 0;
}
CL_SetCGameTime_hook.invoke<void>(client_num);
handle_fast_forward(old_connstate);
handle_vision();
handle_demo_patches();
demo.parse_demo_wrapper();
}
}
void write_packet(const std::int32_t client_num)
{
if (!demo.is_demo_playing())
{
CL_WritePacket_hook.invoke<void>(client_num);
}
}
bool get_predicted_player_data(const game::mp::clientActive_t& cl, std::int32_t ps_time, game::mp::playerState_t& ps)
{
if (!demo.is_demo_playing())
{
return CL_GetPredictedPlayerInformationForServerTime_hook.invoke<bool>(&cl, ps_time, &ps);
}
// hide regular hud when showing special hud for killstreak reward
// must not unset this 15th bit when dead to avoid a crash in CG_SimulateBulletFire
ps.otherFlags = (ps.pm_type == 7 || ps.remoteControlEnt != 2047 || ps.vehicleState.entity != 2047)
? ps.otherFlags | 0x4000
: ps.otherFlags & ~0x4000;
demo.use_predicted_data([&ps, ps_time](const std::array<predicted_data_t, 256>& pred_data, std::size_t pred_data_index)
{
static constexpr auto pred_data_size = std::tuple_size_v<std::remove_reference_t<decltype(pred_data)>>;
// find most recent predicted player data
for (std::size_t count = 0, index = pred_data_index - 1; count < pred_data_size; ++count, --index)
{
const auto& p_data = pred_data[index % pred_data_size];
if (p_data.cad.serverTime <= ps_time && ps_time - p_data.cad.serverTime < 500)
{
std::memcpy(&ps.origin[0], &p_data.cad.origin[0], sizeof(ps.origin));
std::memcpy(&ps.velocity[0], &p_data.cad.velocity[0], sizeof(ps.velocity));
std::memcpy(&ps.viewangles[0], &p_data.viewangles[0], sizeof(ps.viewangles));
ps.bobCycle = p_data.cad.bobCycle;
ps.movementDir = p_data.cad.movementDir;
break;
}
}
});
// must return true otherwise branch is taken that undoes the work of this function
return true;
}
bool get_predicted_vehicle_data(const game::mp::clientActive_t& cl, std::int32_t ps_time, game::PlayerVehicleState& ps_vehicle)
{
if (!demo.is_demo_playing())
{
return CL_GetPredictedVehicleForServerTime_hook.invoke<bool>(&cl, ps_time, &ps_vehicle);
}
demo.use_predicted_data([&ps_vehicle, ps_time](const std::array<predicted_data_t, 256>& pred_data, std::size_t pred_data_index)
{
static constexpr auto pred_data_size = std::tuple_size_v<std::remove_reference_t<decltype(pred_data)>>;
// find most recent predicted vehicle data
for (std::size_t count = 0, index = pred_data_index - 1; count < pred_data_size; ++count, --index)
{
const auto& p_data = pred_data[index % pred_data_size];
if (p_data.cad.serverTime <= ps_time && ps_time - p_data.cad.serverTime < 500)
{
const auto org_entity = ps_vehicle.entity;
ps_vehicle = p_data.cad.playerVehStateClientArchive;
ps_vehicle.entity = org_entity;
break;
}
}
});
// must return true otherwise branch is taken that undoes the work of this function
return true;
}
bool show_intermission_menu(const std::int32_t client_num, void* lua_vm)
{
// it appeared easier to prevent the intermission menu from being shown instead of closing or hiding it
if (!demo.is_demo_playing() || !demo.is_demo_repeating())
{
return LUI_IntermissionBegan_hook.invoke<bool>(client_num, lua_vm);
}
// rewind demo and don't display intermission menu for repeated demos
static_cast<void>(demo.rewind_demo(std::nullopt));
return false;
}
void control_ui_element(const std::int32_t client_num, const game::mp::snapshot_t& old_snap, game::mp::snapshot_t& new_snap)
{
// it appeared easier to prevent these menus from being shown instead of closing or hiding them
if (demo.is_demo_playing())
{
// skip team selection menu (and loadout selection menu)
assert(std::string_view(*reinterpret_cast<const char**>(game::Omnvar_GetDef(103))) == "ui_options_menu");
new_snap.omnvars[103] = {};
if (demo.is_demo_repeating())
{
// skip extinction mode end-of-game score / stats menu
assert(std::string_view(*reinterpret_cast<const char**>(game::Omnvar_GetDef(194))) == "ui_alien_show_eog_score");
new_snap.omnvars[194] = {};
}
}
CG_UpdateOmnvars_hook.invoke<void>(client_num, &old_snap, &new_snap);
}
std::int32_t update_gamestate_config_strings(game::msg_t& msg)
{
if (demo.is_demo_playing())
{
// update command sequences and overwrite config strings if that data is available
if (const auto& gs = demo.get_gamestate(); gs)
{
game::mp::cls->gameState = gs->data;
game::mp::clc->serverCommandSequence = gs->svr_cmd_seq;
game::mp::clc->lastExecutedServerCommand = gs->svr_cmd_seq;
game::mp::cgs->serverCommandSequence = gs->svr_cmd_seq;
}
else
{
assert(is_gamestate_valid(game::mp::cls->gameState, false));
game::mp::clc->lastExecutedServerCommand = game::mp::clc->serverCommandSequence;
game::mp::cgs->serverCommandSequence = game::mp::clc->serverCommandSequence;
}
}
// call MSG_ReadLong that this hook replaces
return game::MSG_ReadLong(&msg);
}
void ignore_mouse_input_killstreak_reward_1(const std::int32_t client_num, game::mp::cg_t& cg, game::vec3_t& out_view_origin, game::vec3_t& out_view_angles)
{
auto& in_killcam = cg.inKillCam;
if (!demo.is_demo_playing() || in_killcam)
{
VehicleCam_UpdatePlayerControlCam_hook.invoke<void>(client_num, &cg, &out_view_origin, &out_view_angles);
}
else
{
// required for e.g. the Gryphon killstreak reward
in_killcam = 1;
VehicleCam_UpdatePlayerControlCam_hook.invoke<void>(client_num, &cg, &out_view_origin, &out_view_angles);
in_killcam = 0;
}
}
bool ignore_mouse_input_killstreak_reward_2(const std::int32_t client_num)
{
auto& other_flags = game::mp::cg->ps.otherFlags;
if (!demo.is_demo_playing() || !(other_flags & 0x4000))
{
return GetRemoteEyeValues_hook.invoke<bool>(client_num);
}
else
{
// required for e.g. the Trinity Rocket killstreak reward
other_flags &= ~0x4000;
const auto result = GetRemoteEyeValues_hook.invoke<bool>(client_num);
other_flags |= 0x4000;
return result;
}
}
}
namespace // command execution
{
bool execute_playback_command_internal(std::filesystem::path& file_path, bool repeat, bool print_error)
{
if (demo.start(file_path, repeat))
{
return true;
}
if (print_error)
{
console::error("could not play demo %s\n", file_path.string().c_str());
}
return false;
}
bool execute_playback_command_internal(std::string_view file_name, bool repeat)
{
const auto fs_basepath = get_base_path(false);
if (fs_basepath.empty())
{
console::error("could not find fs_basepath\n");
return false;
}
const auto mod = get_mod_directory();
auto file_path1 = std::filesystem::path(fs_basepath) / std::format("demos/client/{2}/user/{0}{1}", file_name, DEMO_EXTENSION, mod);
if (execute_playback_command_internal(file_path1, repeat, false))
{
return true;
}
auto file_path2 = std::filesystem::path(fs_basepath) / std::format("demos/client/{2}/auto/{0}{1}", file_name, DEMO_EXTENSION, mod);
if (execute_playback_command_internal(file_path2, repeat, false))
{
return true;
}
auto file_path3 = std::filesystem::path(fs_basepath) / std::format("demos/client/{0}{1}", file_name, DEMO_EXTENSION);
if (execute_playback_command_internal(file_path3, repeat, false))
{
return true;
}
auto file_path4 = std::filesystem::path(fs_basepath) / std::format("demos/server/{2}/{0}{1}", file_name, DEMO_EXTENSION, mod);
if (execute_playback_command_internal(file_path4, repeat, false))
{
return true;
}
auto file_path5 = std::filesystem::path(fs_basepath) / std::format("demos/server/{0}{1}", file_name, DEMO_EXTENSION);
if (execute_playback_command_internal(file_path5, repeat, false))
{
return true;
}
auto file_path6 = std::filesystem::path(fs_basepath) / std::format("demos/{0}{1}", file_name, DEMO_EXTENSION);
if (execute_playback_command_internal(file_path6, repeat, false))
{
return true;
}
console::error("could not play demo %s\n", file_path1.string().c_str());
console::error("could not play demo %s\n", file_path2.string().c_str());
console::error("could not play demo %s\n", file_path3.string().c_str());
console::error("could not play demo %s\n", file_path4.string().c_str());
console::error("could not play demo %s\n", file_path5.string().c_str());
console::error("could not play demo %s\n", file_path6.string().c_str());
return false;
}
template <bool force_playback>
bool execute_playback_command(const command::params& params)
{
struct args_t
{
bool fullpath{};
bool repeat{};
};
const auto parsed_arguments = [&params]() -> std::optional<args_t>
{
if (params.size() == 3)
{
if (std::string_view(params.get(2)) == "fullpath")
{
return args_t{ true, false };
}
else if (std::string_view(params.get(2)) == "repeat")
{
return args_t{ false, true };
}
else
{
return {};
}
}
else if (params.size() == 4)
{
if (std::string_view(params.get(2)) == "fullpath" && std::string_view(params.get(3)) == "repeat")
{
return args_t{ true, true };
}
else
{
return {};
}
}
else
{
return args_t{ false, false };
}
}();
if (params.size() < 2 || params.size() > 4 || !parsed_arguments)
{
console::info("usage: demoplay <file_name>, fullpath(optional), repeat(optional)\n");
return false;
}
if constexpr (!force_playback)
{
if (!demo.is_demo_playing() && game::CL_IsCgameInitialized())
{
console::warn("could not play demo; please disconnect first\n");
return false;
}
}
if (!parsed_arguments->fullpath)
{
return execute_playback_command_internal(params.get(1), parsed_arguments->repeat);
}
else
{
auto file_path = std::filesystem::path(params.get(1));
return execute_playback_command_internal(file_path, parsed_arguments->repeat, true);
}
}
void execute_playback_instant_play_command(const command::params& params)
{
// the demo recording code (demo_recording_t::shutdown_watcher) should take care of properly closing any recording demo files!
const auto demo_playing = demo.is_demo_playing();
if (execute_playback_command<true>(params))
{
if (auto* sv_running = game::Dvar_FindVar("sv_running"); sv_running && sv_running->current.enabled)
{
// demo cannot play if local server is still considered to be running
sv_running->current.enabled = false;
}
if (!demo_playing)
{
if (auto& connstate = *game::mp::connstate; connstate == game::CA_ACTIVE)
{
// prevent old snapshots from being processed,
// would mess up the server command sequences and server times
connstate = game::CA_PRIMED;
}
}
}
}
void execute_playback_replay_command(const command::params& params)
{
if (params.size() != 1)
{
console::info("usage: demoreplay\n");
return;
}
if (const auto path = demo.restart(); path)
{
console::error("could not play demo %s\n", path->get().string().c_str());
}
}
void execute_playback_rewind_command(const command::params& params)
{
if (params.size() < 1 || params.size() > 2)
{
console::info("usage: demorewind <milliseconds>(optional)\n");
return;
}
if (params.size() == 2)
{
const auto msec = std::atoi(params.get(1));
if (msec <= 0)
{
console::error("demorewind invalid value %s\n", params.get(1));
return;
}
if (!demo.rewind_demo(msec))
{
console::error("demo must be playing to rewind\n");
}
}
else if (!demo.rewind_demo(std::nullopt))
{
console::error("demo must be playing to rewind\n");
}
}
void execute_playback_fast_forward_command(const command::params& params)
{
if (params.size() != 2)
{
console::info("usage: demoforward <milliseconds>\n");
return;
}
const auto msec = std::atoi(params.get(1));
if (msec <= 0)
{
console::error("demoforward invalid value %s\n", params.get(1));
return;
}
if (demo.is_demo_playing())
{
fast_forward_demo(static_cast<std::uint32_t>(msec));
}
else
{
console::error("demo must be playing to fast forward\n");
}
}
}
namespace demo_playback
{
bool playing()
{
return demo.is_demo_playing();
}
bool startplay(std::string_view file_name, bool repeat)
{
return execute_playback_command_internal(file_name, repeat);
}
bool startplay(std::filesystem::path& file_name, bool repeat)
{
return execute_playback_command_internal(file_name, repeat, true);
}
void stopplay()
{
demo.stop();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
// check run-time address assertions
check_address_assertions();
// primary hook for demo playback; read demo data after server time has been updated
CL_SetCGameTime_hook.create(game::CL_SetCGameTime, handle_demo_reading);
// don't write packets when playing a demo
CL_WritePacket_hook.create(game::CL_WritePacket, write_packet);
// replace functions to get predicted data, because they skip the viewangles
CL_GetPredictedPlayerInformationForServerTime_hook.create(
game::CL_GetPredictedPlayerInformationForServerTime, get_predicted_player_data);
CL_GetPredictedVehicleForServerTime_hook.create(
game::CL_GetPredictedVehicleForServerTime, get_predicted_vehicle_data);
// ignore mouse input when updating the camera for certain killstreak rewards
VehicleCam_UpdatePlayerControlCam_hook.create(
game::VehicleCam_UpdatePlayerControlCam, ignore_mouse_input_killstreak_reward_1);
GetRemoteEyeValues_hook.create(
game::GetRemoteEyeValues, ignore_mouse_input_killstreak_reward_2);
// optional skip of intermission menu
LUI_IntermissionBegan_hook.create(game::LUI_IntermissionBegan, show_intermission_menu);
// don't open any menu when demo playback has started
CG_UpdateOmnvars_hook.create(game::CG_UpdateOmnvars, control_ui_element);
// allow user to override visual settings
CG_VisionSetStartLerp_To_hook.create(0x1402A3200, handle_vision_override);
demo_vision = game::Dvar_RegisterEnum(
"demovision", const_cast<const char**>(VISION_SETS.data()), 0, game::DVAR_FLAG_NONE, "Override vision set for demos");
// update the gamestate configs strings with 'live' data from the demo (in CL_ParseGamestate)
utils::hook::call(0x1402CCD56, update_gamestate_config_strings);
// don't let the game set cg_t->demoType in CG_DrawActiveFrame,
// otherwise demo playback will have mouse input and suffer from frame interpolation issues
utils::hook::nop(0x14029981C, 6);
// replace thread local storage lookup for cg.bgs in function CG_EmissiveBlendCommand with hardcoded address
// when CG_ExecuteNewServerCommands is called manually (e.g. from demo_utils::process_server_commands),
// this address in tls may be a nullptr and the game could crash here (in extinction mode at least)
static constexpr auto patch_address = 0x14028844B;
static constexpr std::array<std::uint8_t, 7> patch{ 0x48, 0x8D, 0x1D, 0x68, 0xBC, 0x59, 0x01 };
utils::hook::nop(patch_address, 0xE);
utils::hook::nop(patch_address + 0x1C, 4);
utils::hook::nop(patch_address + 0x22, 4);
utils::hook::copy(patch_address + 0xE, patch.data(), patch.size()); // lea rbx, [0x1418240C8] (point RBX to cg.bgs)
assert(std::bit_cast<std::size_t>(&game::mp::cg->bgs) ==
patch_address + 0xE + patch.size() + *reinterpret_cast<const std::uint32_t*>(patch_address + 0x11));
// add console command support to play demos
command::add("demoplay", execute_playback_command<false>);
// add experimental command console support to play demos,
// skip disconnecting from local or online server and skip map (re)loading
command::add("demoinstantplay", execute_playback_instant_play_command);
// replay most recent demo (if any)
command::add("demoreplay", execute_playback_replay_command);
// rewind demo (by N msec)
command::add("demorewind", execute_playback_rewind_command);
// fast forward demo by N msec
command::add("demoforward", execute_playback_fast_forward_command);
// add command line support to play demos; this makes it possible to double click on a demo file to play it
const auto& args = utils::command_line::get_args();
for (const auto& arg : args)
{
if (arg.extension() == DEMO_EXTENSION)
{
scheduler::on_game_initialized([cmd = std::format(R"(demoplay "{}" fullpath repeat)", arg.string())]() mutable
{
command::execute(std::move(cmd), true);
}, scheduler::main);
console::info("the demo should start to load in a few seconds\n");
break;
}
}
}
};
}
REGISTER_COMPONENT(demo_playback::component)
-9
View File
@@ -1,9 +0,0 @@
#pragma once
namespace demo_playback
{
[[nodiscard]] bool playing();
[[nodiscard]] bool startplay(std::string_view file_name, bool repeat = true);
[[nodiscard]] bool startplay(std::filesystem::path& file_path, bool repeat = true);
[[nodiscard]] void stopplay();
}
-714
View File
@@ -1,714 +0,0 @@
#include "std_include.hpp"
#include "loader/component_loader.hpp"
#include "demo_playback.hpp"
#include "demo_recording.hpp"
#include "demo_utils.hpp"
#include "command.hpp"
#include "console.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include "utils/hook.hpp"
using namespace demo_utils;
namespace // demo class
{
game::dvar_t* demo_autorecord;
class demo_recording_t
{
static constexpr auto NON_DELTA_SNAPSHOT_COUNT = 4;
struct stored_times_t
{
std::int32_t flush_time{};
std::int32_t predicted_data_time{};
std::int32_t cur_svr_time{};
std::int32_t first_svr_time_user_file{};
std::int32_t first_svr_time_auto_file{};
};
public:
demo_recording_t() = default;
~demo_recording_t();
demo_recording_t(const demo_recording_t&) = delete;
demo_recording_t(demo_recording_t&&) noexcept = delete;
demo_recording_t& operator=(const demo_recording_t&) = delete;
demo_recording_t& operator=(demo_recording_t&&) noexcept = delete;
[[nodiscard]] std::uint32_t get_demo_count() const;
[[nodiscard]] std::int32_t get_demo_length() const;
[[nodiscard]] bool is_recording() const;
[[nodiscard]] bool non_delta_data_requested() const;
[[nodiscard]] bool start(const std::filesystem::path& user_path, const std::filesystem::path& auto_path);
[[nodiscard]] bool stop();
void shutdown_watcher();
void process_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data);
void process_predicted_data(auto old_connstate, auto new_connstate);
private:
[[nodiscard]] bool is_auto_recording() const;
[[nodiscard]] bool non_delta_data_request_acknowledged();
[[nodiscard]] bool start_user_file(const std::filesystem::path& path);
[[nodiscard]] bool start_auto_file(const std::filesystem::path& path);
[[nodiscard]] static bool stop_internal(std::ofstream& file, std::int32_t first_svr_time, std::int32_t last_svr_time);
void flush_filestreams();
void check_auto_recording();
void process_old_data();
void process_new_gamestate(std::span<const std::uint8_t> network_data);
void process_first_snapshot(std::span<const std::uint8_t> network_data);
void process_special_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data);
void process_helo_pilot_turret_fire();
std::uint32_t demo_count_{};
std::uint32_t non_delta_count_{};
stored_times_t times_;
buffer_t crit_data_;
std::ofstream user_file_;
buffer_t auto_buffer_;
std::ofstream auto_file_;
} demo;
demo_recording_t::~demo_recording_t()
{
process_old_data();
}
std::uint32_t demo_recording_t::get_demo_count() const
{
return demo_count_;
}
std::int32_t demo_recording_t::get_demo_length() const
{
return (times_.cur_svr_time - times_.first_svr_time_user_file) / 1000;
}
bool demo_recording_t::is_recording() const
{
return user_file_.good() && user_file_.is_open();
}
bool demo_recording_t::is_auto_recording() const
{
return auto_file_.good() && auto_file_.is_open();
}
bool demo_recording_t::non_delta_data_requested() const
{
return non_delta_count_ > 0;
}
bool demo_recording_t::non_delta_data_request_acknowledged()
{
if (non_delta_data_requested())
{
const auto delta_num = game::mp::cl->snap.deltaNum;
if (static constexpr auto no_delta = -1; delta_num == no_delta)
{
--non_delta_count_;
return true;
}
}
return false;
}
bool demo_recording_t::start_user_file(const std::filesystem::path& path)
{
const auto crit_data = crit_data_.get();
if (!crit_data.size())
{
return false;
}
std::ofstream file(path, std::ios::binary);
if (!file.good() || !file.is_open())
{
return false;
}
write_general_header(file);
// store mod name so if there's mod support the mod can be loaded
write_mod_header(file);
// store map name and game type so that the map can be preloaded
if (!write_map_header(file))
{
return false;
}
// store the current gamestate config strings so that the original strings are overwritten if they were modified
if (!write_gamestate_data(file))
{
return false;
}
// required for demos (and live matches) to function at all
file.write(reinterpret_cast<const char*>(crit_data.data()), crit_data.size());
// set the non-delta snapshot count, the demo won't be valid otherwise
non_delta_count_ = NON_DELTA_SNAPSHOT_COUNT;
// store first server time so that demo length can be tracked
times_.first_svr_time_user_file = game::mp::cl->snap.serverTime;
// increase demo count for the text render function to work properly
++demo_count_;
user_file_ = std::move(file);
return true;
}
bool demo_recording_t::start_auto_file(const std::filesystem::path& path)
{
std::ofstream file(path, std::ios::binary);
if (!file.good() || !file.is_open())
{
return false;
}
const auto buffer = auto_buffer_.get();
file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
auto_file_ = std::move(file);
auto_buffer_.clear();
return true;
}
bool demo_recording_t::start(const std::filesystem::path& user_path, const std::filesystem::path& auto_path)
{
if (!is_recording())
{
if (!is_auto_recording() && !start_auto_file(auto_path))
{
return false;
}
if (!start_user_file(user_path))
{
return false;
}
return true;
}
return false;
}
bool demo_recording_t::stop_internal(std::ofstream& file, std::int32_t first_svr_time, std::int32_t last_svr_time)
{
write_general_footer(file, first_svr_time, last_svr_time);
write_end_of_file(file);
file.flush();
file.close();
return file.good() && !file.is_open();
}
bool demo_recording_t::stop()
{
return is_recording() && stop_internal(user_file_, times_.first_svr_time_user_file, times_.cur_svr_time);
}
void demo_recording_t::process_old_data()
{
if (is_recording() && !stop())
{
console::error("could not stop user demo\n");
}
if (is_auto_recording() && !stop_internal(auto_file_, times_.first_svr_time_auto_file, times_.cur_svr_time))
{
console::error("could not close auto demo\n");
}
demo_count_ = {};
non_delta_count_ = {};
times_ = {};
crit_data_.clear();
auto_buffer_.clear();
assert(!is_recording());
assert(!is_auto_recording());
}
void demo_recording_t::shutdown_watcher()
{
auto execute_on_disconnect = [this, cgame_init = false]() mutable
{
if (demo_playback::playing())
{
if (is_recording() || is_auto_recording())
{
// this is only relevant when playing back demos without disconnecting from the server (instant play)
cgame_init = false;
process_old_data();
}
return;
}
if (!cgame_init && game::CL_IsCgameInitialized())
{
cgame_init = true;
}
if (cgame_init && !game::CL_IsCgameInitialized())
{
cgame_init = false;
process_old_data();
}
};
scheduler::loop(execute_on_disconnect, scheduler::main);
}
void demo_recording_t::flush_filestreams()
{
const auto real_time = game::mp::cls->realtime;
if (real_time >= times_.flush_time + 15'000)
{
times_.flush_time = real_time;
if (is_recording())
{
user_file_.flush();
}
if (is_auto_recording())
{
auto_file_.flush();
}
}
}
void demo_recording_t::check_auto_recording()
{
if (!is_auto_recording() && times_.first_svr_time_auto_file && demo_autorecord && demo_autorecord->current.enabled)
{
assert(!game::Live_SyncOnlineDataFlags(0) && game::CL_IsCgameInitialized());
const auto opt_auto_dir = create_directory_auto_demo();
if (!opt_auto_dir)
{
console::error("could not create demo auto directory\n");
return;
}
const auto opt_auto_path = create_path_auto_demo(*opt_auto_dir);
if (!opt_auto_path)
{
console::error("could not create demo auto file\n");
return;
}
if (!start_auto_file(*opt_auto_path))
{
// turn auto recording off to prevent spamming the console with errors
demo_autorecord->current.enabled = false;
console::error("could not create demo auto file %s\n", opt_auto_path->string().c_str());
return;
}
}
}
void demo_recording_t::process_new_gamestate(std::span<const std::uint8_t> network_data)
{
process_old_data();
// store gamestate for user file
write_network_data(crit_data_, network_data);
// reserve memory; use smaller buffer size when auto recording is enabled,
// since the data is written to disk almost right away.
const auto reserve_size = (demo_autorecord && demo_autorecord->current.enabled) ? 8192 : 1024 * 1024;
auto_buffer_.reserve_memory(reserve_size);
// write headers to auto buffer (ahead of the gamestate itself)
write_general_header(auto_buffer_);
write_mod_header(auto_buffer_);
write_map_header(auto_buffer_);
}
void demo_recording_t::process_first_snapshot(std::span<const std::uint8_t> network_data)
{
assert(!is_recording() && crit_data_.size()
&& !times_.cur_svr_time && !times_.first_svr_time_user_file && !times_.first_svr_time_auto_file);
// store first snapshot for user file
write_network_data(crit_data_, network_data);
times_.first_svr_time_auto_file = game::mp::cl->snap.serverTime;
}
void demo_recording_t::process_special_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data)
{
assert(new_connstate >= game::CA_PRIMED);
if (old_connstate < game::CA_LOADING)
{
assert(!game::mp::cl->snap.valid && !game::mp::cl->snap.serverTime);
process_new_gamestate(network_data);
}
else if (old_connstate >= game::CA_PRIMED)
{
if (game::mp::cl->snap.valid && game::mp::cl->snap.serverTime && !times_.cur_svr_time)
{
process_first_snapshot(network_data);
}
times_.cur_svr_time = game::mp::cl->snap.serverTime;
}
}
void demo_recording_t::process_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data)
{
if (new_connstate < game::CA_PRIMED)
{
return;
}
process_special_network_data(old_connstate, new_connstate, network_data);
check_auto_recording();
if (is_recording())
{
if (!non_delta_data_requested() || non_delta_data_request_acknowledged())
{
write_network_data(user_file_, network_data);
}
}
if (is_auto_recording())
{
write_network_data(auto_file_, network_data);
}
else
{
write_network_data(auto_buffer_, network_data);
}
flush_filestreams();
}
void demo_recording_t::process_helo_pilot_turret_fire()
{
const auto turret_fire = cpy_cast<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(0x1419A9394));
if (turret_fire > 0)
{
if (is_recording())
{
write_helo_pilot_turret_fire(user_file_, turret_fire);
}
if (is_auto_recording())
{
write_helo_pilot_turret_fire(auto_file_, turret_fire);
}
else
{
write_helo_pilot_turret_fire(auto_buffer_, turret_fire);
}
}
}
void demo_recording_t::process_predicted_data(auto old_connstate, auto new_connstate)
{
if (old_connstate < game::CA_ACTIVE || new_connstate < game::CA_ACTIVE)
{
return;
}
process_helo_pilot_turret_fire();
if (is_recording())
{
write_predicted_data(user_file_);
}
if (is_auto_recording())
{
write_predicted_data(auto_file_);
}
else
{
write_predicted_data(auto_buffer_);
}
}
}
namespace // hooks
{
utils::hook::detour CL_ParseServerMessage_hook;
void capture_data(const std::int32_t client_num, game::msg_t& msg)
{
const auto old_connstate = *game::mp::connstate;
CL_ParseServerMessage_hook.invoke(client_num, &msg);
const auto new_connstate = *game::mp::connstate;
if (demo_playback::playing())
{
return;
}
if (new_connstate < game::CA_DISCONNECTED || new_connstate > game::CA_ACTIVE)
{
console::warn("invalid connection state after processing data in CL_PacketEvent\n");
return;
}
if (!msg.data || msg.overflowed || static_cast<std::size_t>(msg.cursize) > MAX_SIZE)
{
if (demo.is_recording())
{
console::warn("invalid data in CL_PacketEvent\n");
}
return;
}
demo.process_predicted_data(old_connstate, new_connstate);
demo.process_network_data(old_connstate, new_connstate,
std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(msg.data), msg.cursize));
}
void request_non_delta_data(game::msg_t& msg, std::int32_t value, std::uint32_t bits)
{
if (!demo_playback::playing() && demo.non_delta_data_requested())
{
// request a non-delta snapshot from the server
// to have a valid start for a new (user) demo
value = 1;
}
// call MSG_WriteBits that this hook replaces
game::MSG_WriteBits(&msg, value, bits);
}
}
namespace // command execution
{
void add_recording_text_to_hud(const std::filesystem::path& path)
{
const auto* font = game::R_RegisterFont("fonts/normalfont");
if (!font)
{
console::error("could not register font\n");
return;
}
const auto* view_placement = game::ScrPlace_GetViewPlacement();
if (!view_placement)
{
console::error("could not find view placement\n");
return;
}
auto create_resources = [&path, font, view_placement]()
{
struct resources_t
{
const game::Font_t* font{};
float y{};
std::int32_t demo_length{};
std::uint32_t demo_count{};
std::uint32_t org_text_size{};
std::string text;
};
resources_t res{
.font = font,
.y = view_placement->realViewportSize[1],
.demo_length = demo.get_demo_length(),
.demo_count = demo.get_demo_count(),
.text = "recording: " + path.stem().string()
};
res.org_text_size = static_cast<std::uint32_t>(res.text.size());
res.text.reserve(res.text.size() + 12);
return res;
};
scheduler::schedule([res = create_resources()]() mutable
{
if (!demo.is_recording() || res.demo_count != demo.get_demo_count() || !game::CL_IsCgameInitialized())
{
return scheduler::cond_end;
}
if (res.demo_length != demo.get_demo_length())
{
res.demo_length = demo.get_demo_length();
// resize to original size and append demo length in seconds
res.text.resize(res.org_text_size);
res.text += std::format(" ({} sec)", res.demo_length);
}
static constexpr std::array<float, 4> color_red{ 178.0f / 255.0f, 34.0f / 255.0f, 34.0f / 255.0f, 1.0f };
// draw in bottom left corner
game::R_AddCmdDrawText(res.text.c_str(), std::numeric_limits<std::int32_t>::max(),
res.font, 10.0f, res.y - 5.0f, 0.75f, 0.75f, 0.0f, color_red.data(), 0);
return scheduler::cond_continue;
}, scheduler::renderer);
}
bool execute_demo_start_command_internal(std::string_view file_name, bool overwrite)
{
if (game::Live_SyncOnlineDataFlags(0) || !game::CL_IsCgameInitialized() || *game::mp::connstate != game::CA_ACTIVE)
{
console::warn("must be in a match to record a demo\n");
return false;
}
if (demo.is_recording())
{
console::warn("must stop demo recording before starting to record\n");
return false;
}
if (demo_playback::playing())
{
console::warn("cannot record a demo during demo playback\n");
return false;
}
const auto opt_user_dir = create_directory_user_demo();
if (!opt_user_dir)
{
console::error("could not create demo user directory\n");
return false;
}
const auto opt_auto_dir = create_directory_auto_demo();
if (!opt_auto_dir)
{
console::error("could not create demo auto directory\n");
return false;
}
const auto opt_user_path = (file_name.empty())
? create_path_user_demo(*opt_user_dir)
: create_path_user_demo(*opt_user_dir, file_name, overwrite);
if (!opt_user_path)
{
console::error("could not create demo user file\n");
return false;
}
const auto opt_auto_path = create_path_auto_demo(*opt_auto_dir);
if (!opt_auto_path)
{
console::error("could not create demo auto file\n");
return false;
}
if (!demo.start(*opt_user_path, *opt_auto_path))
{
console::error("could not create demo user file %s\n", opt_user_path->string().c_str());
console::error("could not create demo auto file %s\n", opt_auto_path->string().c_str());
return false;
}
add_recording_text_to_hud(*opt_user_path);
return true;
}
void execute_demo_start_command(const command::params& params)
{
if (params.size() < 1 || params.size() > 3 || (params.size() == 3 && std::string_view(params.get(2)) != "overwrite"))
{
console::info("usage: demostart <file_name>(optional), overwrite(optional)\n");
return;
}
assert(params.size() != 3 || (params.size() == 3 && std::string_view(params.get(2)) == "overwrite"));
execute_demo_start_command_internal((params.size() >= 2) ? params.get(1) : std::string_view{}, (params.size() == 3));
}
void execute_demo_stop_command(const command::params& params)
{
if (params.size() != 1)
{
console::info("usage: demostop\n");
return;
}
if (demo.is_recording() && !demo.stop())
{
console::error("could not stop demo\n");
}
}
}
namespace demo_recording
{
bool recording()
{
return demo.is_recording();
}
bool startrecord(std::string_view file_name, bool overwrite)
{
return execute_demo_start_command_internal(file_name, overwrite);
}
bool demo_recording::stoprecord()
{
return demo.stop();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
// check run-time address assertions
check_address_assertions();
// capture incoming packets
CL_ParseServerMessage_hook.create(game::CL_ParseServerMessage, capture_data);
// request the server for no delta data (in CL_WritePacket)
utils::hook::call(0x1402C2026, request_non_delta_data);
// add support to auto record
demo_autorecord = game::Dvar_RegisterBool(
"demoautorecord", false, game::DVAR_FLAG_NONE, "Automatically start recording a demo.");
// add console command support to record demos
command::add("demostart", execute_demo_start_command);
command::add("demostop", execute_demo_stop_command);
scheduler::on_game_initialized([]()
{
demo.shutdown_watcher();
// check if demo directories exist / could be created
if (!can_create_demo_directories())
{
console::error("could not create demo directories\n");
}
}, scheduler::main);
}
};
}
REGISTER_COMPONENT(demo_recording::component)
-8
View File
@@ -1,8 +0,0 @@
#pragma once
namespace demo_recording
{
[[nodiscard]] bool recording();
[[nodiscard]] bool startrecord(std::string_view file_name = {}, bool overwrite = {});
[[nodiscard]] bool stoprecord();
}
-668
View File
@@ -1,668 +0,0 @@
#include "std_include.hpp"
#include "loader/component_loader.hpp"
#include "demo_sv_recording.hpp"
#include "demo_utils.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "utils/hook.hpp"
#include "utils/string.hpp"
using namespace demo_utils;
namespace // checked client num class
{
inline constexpr std::size_t SV_MAX_CLIENTS =
sizeof(game::mp::serverStatic_t::clients) / sizeof(game::mp::serverStatic_t::clients[0]);
class sv_client_num_t
{
public:
explicit sv_client_num_t(std::size_t client_num);
explicit sv_client_num_t(std::string_view client_id);
[[nodiscard]] bool valid() const;
[[nodiscard]] std::uint8_t value() const;
private:
[[nodiscard]] static std::optional<std::uint8_t> check_client_num(std::size_t client_num);
[[nodiscard]] static std::optional<std::uint8_t> parse_client_id(std::string_view str);
const std::optional<std::uint8_t> client_num_;
};
sv_client_num_t::sv_client_num_t(std::size_t client_num)
: client_num_(check_client_num(client_num))
{
assert(valid());
}
sv_client_num_t::sv_client_num_t(std::string_view client_id)
: client_num_(parse_client_id(client_id))
{}
bool sv_client_num_t::valid() const
{
return client_num_.has_value();
}
std::uint8_t sv_client_num_t::value() const
{
assert(valid());
return client_num_.value();
}
std::optional<std::uint8_t> sv_client_num_t::check_client_num(std::size_t client_num)
{
if (client_num >= SV_MAX_CLIENTS)
{
return {};
}
if (client_num >= static_cast<std::size_t>(game::mp::svs->clientCount))
{
return {};
}
assert(game::mp::svs->clientCount == game::Dvar_FindVar("sv_maxclients")->current.integer);
assert(client_num == static_cast<std::size_t>(game::mp::svs->clients[client_num].gentity->s.number));
return static_cast<std::uint8_t>(client_num);
}
std::optional<std::uint8_t> sv_client_num_t::parse_client_id(std::string_view str)
{
if (str.starts_with("pid"))
{
const auto pid = std::string_view(str.begin() + 3, str.end());
const auto all_digits = std::all_of(pid.begin(), pid.end(), [](unsigned char c)
{
return std::isdigit(c);
});
if (all_digits)
{
auto value = std::numeric_limits<std::size_t>::max();
const auto ec = std::from_chars(pid.data(), pid.data() + pid.size(), value).ec;
if (ec == std::errc{})
{
const auto client_num = check_client_num(value);
if (client_num)
{
return client_num;
}
}
}
}
for (const auto& cl : game::mp::svs->clients)
{
if (cl.header.state != game::CS_ACTIVE || str != utils::string::strip(cl.name, true))
{
continue;
}
const auto client_num = check_client_num(cl.gentity->s.number);
if (client_num)
{
return client_num;
}
}
return {};
}
}
namespace // server demos class
{
game::dvar_t* sv_demos;
game::dvar_t* sv_demo_autorecord;
bool sv_execute_demo_start_command_internal(sv_client_num_t client_num, std::string_view file_name, bool overwrite);
class server_rotation_t
{
struct data_t
{
bool svr_restart_bit{};
std::size_t map_hash{};
std::size_t gametype_hash{};
};
public:
[[nodiscard]] bool has_changed()
{
const auto svr_restart_bit = static_cast<bool>(game::mp::svs->snapFlagServerBit & 4);
const auto has_value = data_.has_value();
if (!has_value || data_->svr_restart_bit != svr_restart_bit)
{
const auto dvar_mapname = get_mapname(false);
const auto dvar_gametype = get_gametype(false);
if (dvar_mapname.empty() || dvar_gametype.empty())
{
console::error("the server demo recording code may not function properly if dvars mapname or g_gametype are not available\n");
}
const auto map_hash = std::hash<std::string_view>()(dvar_mapname);
const auto gametype_hash = std::hash<std::string_view>()(dvar_gametype);
if (!has_value || (data_->map_hash != map_hash || data_->gametype_hash != gametype_hash))
{
data_.emplace(data_t{
.svr_restart_bit = svr_restart_bit,
.map_hash = map_hash,
.gametype_hash = gametype_hash
});
return has_value;
}
// server has not rotated despite restart bit change
data_->svr_restart_bit = svr_restart_bit;
}
return false;
}
private:
std::optional<data_t> data_;
};
struct client_data_t
{
private:
struct stored_times_t
{
std::int32_t first_svr_time{};
std::int32_t cur_svr_time{};
std::optional<std::int32_t> last_conn_time;
};
public:
[[nodiscard]] bool is_buffer_active() const
{
return buffer_active;
}
[[nodiscard]] bool is_recording() const
{
return file_active;
}
bool buffer_active{};
bool file_active{};
stored_times_t times;
buffer_t buffer;
std::ofstream file;
};
struct sv_demo_recordings_t
{
public:
sv_demo_recordings_t() = default;
~sv_demo_recordings_t();
sv_demo_recordings_t(const sv_demo_recordings_t&) = delete;
sv_demo_recordings_t(sv_demo_recordings_t&&) noexcept = delete;
sv_demo_recordings_t& operator=(const sv_demo_recordings_t&) = delete;
sv_demo_recordings_t& operator=(sv_demo_recordings_t&&) noexcept = delete;
[[nodiscard]] bool is_recording(sv_client_num_t client_num) const;
[[nodiscard]] bool start(sv_client_num_t client_num, const std::filesystem::path& path);
[[nodiscard]] bool stop(sv_client_num_t client_num);
void write_data(sv_client_num_t client_num, const game::mp::client_t& cl,
std::span<const std::uint8_t> network_data, bool client_loading);
void shutdown_watcher();
private:
[[nodiscard]] const client_data_t& get_client(sv_client_num_t client_num) const;
[[nodiscard]] client_data_t& get_client(sv_client_num_t client_num);
bool stop_internal(client_data_t& client, bool reset_client);
bool update_state(sv_client_num_t client_num, std::int32_t last_conn_time, bool client_loading);
void process_old_data();
server_rotation_t svr_rotation_data_;
std::array<client_data_t, SV_MAX_CLIENTS> client_data_{};
} demos;
sv_demo_recordings_t::~sv_demo_recordings_t()
{
process_old_data();
}
const client_data_t& sv_demo_recordings_t::get_client(sv_client_num_t client_num) const
{
assert(client_num.valid());
return client_data_[client_num.value()];
}
client_data_t& sv_demo_recordings_t::get_client(sv_client_num_t client_num)
{
assert(client_num.valid());
return client_data_[client_num.value()];
}
bool sv_demo_recordings_t::is_recording(sv_client_num_t client_num) const
{
return client_num.valid() && get_client(client_num).is_recording();
}
bool sv_demo_recordings_t::start(sv_client_num_t client_num, const std::filesystem::path& path)
{
if (!client_num.valid())
{
return false;
}
auto& client = get_client(client_num);
if (client.is_buffer_active())
{
if (!client.is_recording())
{
std::ofstream file(path, std::ios::binary);
if (file.good() && file.is_open())
{
const auto buffer = client.buffer.get();
client.file = std::move(file);
client.file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
client.file.flush();
client.file_active = true;
return true;
}
}
}
else
{
console::error("client pid %d cannot be recorded at this time; sv_demos was possibly enabled too late!\n", client_num.value());
}
return false;
}
bool sv_demo_recordings_t::stop_internal(client_data_t& client, bool reset_client)
{
if (client.is_recording())
{
write_general_footer(client.file, client.times.first_svr_time, client.times.cur_svr_time);
write_end_of_file(client.file);
client.file.flush();
client.file.close();
client.file_active = {};
}
if (reset_client)
{
client.buffer_active = {};
client.times = {};
client.buffer.clear();
}
return client.file.good() && !client.file.is_open();
}
bool sv_demo_recordings_t::stop(sv_client_num_t client_num)
{
return client_num.valid() && stop_internal(get_client(client_num), false);
}
void sv_demo_recordings_t::process_old_data()
{
for (auto& client : client_data_)
{
stop_internal(client, true);
}
svr_rotation_data_ = {};
}
void sv_demo_recordings_t::shutdown_watcher()
{
auto execute_on_shutdown = [this, sv_loaded = false]() mutable
{
if (!sv_loaded && game::SV_Loaded())
{
sv_loaded = true;
}
if (sv_loaded && !game::SV_Loaded())
{
sv_loaded = false;
process_old_data();
}
};
scheduler::loop(execute_on_shutdown, scheduler::main);
}
bool sv_demo_recordings_t::update_state(sv_client_num_t client_num, std::int32_t last_conn_time, bool client_loading)
{
// handle server map change: close files and reset data for all clients; should ignore fast restarts
if (svr_rotation_data_.has_changed())
{
process_old_data();
}
auto& client = get_client(client_num);
if (!client_loading && !client.is_buffer_active())
{
// sv_demos was possibly enabled too late for this player for this match!
return false;
}
// handle client last connect time change: close file and reset data
if (!client.times.last_conn_time || *client.times.last_conn_time != last_conn_time)
{
if (client.times.last_conn_time)
{
stop_internal(client, true);
}
client.times.last_conn_time = last_conn_time;
}
// write general, mod and map headers to the buffer once
if (!client.is_buffer_active())
{
// reserve memory up front to minimize further (re)allocations
client.buffer.reserve_memory(1024 * 1024);
sv_write_general_header(client.buffer, game::mp::svs->clients[client_num.value()].name);
write_mod_header(client.buffer);
if (write_map_header(client.buffer))
{
client.buffer_active = true;
}
}
// store first and current server times to keep track of demo length
client.times.cur_svr_time = game::mp::svs->time;
if (!client_loading && !client.times.first_svr_time && client.times.cur_svr_time)
{
client.times.first_svr_time = client.times.cur_svr_time;
}
// if not already recording, start demo for client when auto recording is enabled
if (!client.is_recording() && sv_demo_autorecord && sv_demo_autorecord->current.enabled)
{
if (!sv_execute_demo_start_command_internal(client_num, std::string_view{}, false))
{
console::error("failed to start demo automatically for client num %d\n", client_num.value());
}
}
return true;
}
void sv_demo_recordings_t::write_data(sv_client_num_t client_num,
const game::mp::client_t& cl, std::span<const std::uint8_t> network_data, bool client_loading)
{
if (!client_num.valid() || !update_state(client_num, cl.lastConnectTime, client_loading))
{
return;
}
auto write_data_internal = [&cl, network_data, client_loading](auto& output)
{
const auto send_msg_count = static_cast<std::size_t>(cl.header.sendMessageCount);
const auto svr_msg_sequence = static_cast<std::size_t>(cl.header.netchan.outgoingSequence);
if (!client_loading)
{
sv_write_predicted_data(output, cl.gentity->client->ps, send_msg_count);
}
write_id_and_size(output, 4 + network_data.size(), demo_data_id::network_data);
output.write(reinterpret_cast<const char*>(&svr_msg_sequence), 4);
output.write(reinterpret_cast<const char*>(network_data.data()), network_data.size());
};
auto& client = get_client(client_num);
// keep buffering data even when recording a demo so subsequent demos for the same client
// can be recorded using the entire buffer; the entire buffer is required for demos to be valid
if (client.is_buffer_active())
{
write_data_internal(client.buffer);
}
if (client.is_recording())
{
write_data_internal(client.file);
if (static_cast<std::size_t>(cl.header.sendMessageCount) % 512 == 0)
{
client.file.flush();
}
}
}
}
namespace // hooks
{
utils::hook::detour SV_Netchan_Transmit_hook;
bool sv_capture_data(game::mp::client_t& cl, const char* data, std::int32_t length)
{
const auto valid_player = cl.gentity && cl.gentity->client && cl.testClient == game::TC_NONE
&& (cl.header.state == game::CS_CLIENTLOADING || cl.header.state == game::CS_ACTIVE);
if (sv_demos && sv_demos->current.enabled && valid_player)
{
const auto client_num = sv_client_num_t(static_cast<std::size_t>(cl.gentity->s.number));
if (!client_num.valid())
{
console::error("invalid client num %d\n", cl.gentity->s.number);
}
else
{
const auto client_loading = (cl.header.state == game::CS_CLIENTLOADING);
const auto size = static_cast<std::size_t>(length);
if ((!client_loading || cl.gamestateMessageNum == cl.header.netchan.outgoingSequence) && size < MAX_SIZE)
{
const std::span<const std::uint8_t> network_data(reinterpret_cast<const std::uint8_t*>(data), size);
demos.write_data(client_num, cl, network_data, client_loading);
}
else
{
console::warn("invalid data for client num %d, message count %d, message size %zu\n",
client_num.value(), cl.header.sendMessageCount, size);
}
}
}
return SV_Netchan_Transmit_hook.invoke<bool>(&cl, data, length);
}
}
namespace // command execution
{
bool sv_execute_demo_start_command_internal(sv_client_num_t client_num, std::string_view file_name, bool overwrite)
{
if (game::Live_SyncOnlineDataFlags(0))
{
console::error("server must be initialized to record a demo\n");
return false;
}
if (const auto* sv_running = game::Dvar_FindVar("sv_running"); !sv_running || !sv_running->current.enabled)
{
console::error("server must be online to record a demo\n");
return false;
}
if (!sv_demos || !sv_demos->current.enabled)
{
console::error("cannot record a demo with sv_demos disabled\n");
return false;
}
if (!client_num.valid())
{
console::error("invalid client num\n");
return false;
}
const auto state = game::mp::svs->clients[client_num.value()].header.state;
if (state != game::CS_CLIENTLOADING && state != game::CS_ACTIVE)
{
console::error("client needs to be fully connected\n");
return false;
}
const auto client_type = game::mp::svs->clients[client_num.value()].testClient;
if (client_type != game::TC_NONE)
{
console::error("can only record actual players\n");
return false;
}
if (demos.is_recording(client_num))
{
console::error("already recording client\n");
return false;
}
const auto opt_dir_path = sv_create_demo_directory();
if (!opt_dir_path)
{
console::error("could not create demo directory\n");
return false;
}
const auto& cl = game::mp::svs->clients[client_num.value()];
const auto empty_guid = (cl.playerGuid[0] == '\0');
if (empty_guid)
{
console::warn("player guid appears empty\n");
}
const auto opt_server_path = (file_name.empty())
? sv_create_path_server_demo(*opt_dir_path, (!empty_guid) ? cl.playerGuid : utils::string::strip(cl.name, true))
: sv_create_path_server_demo(*opt_dir_path, file_name, overwrite);
if (!opt_server_path)
{
console::error("could not create demo file\n");
return false;
}
if (!demos.start(client_num, *opt_server_path))
{
console::error("could not create demo file %s\n", opt_server_path->string().c_str());
return false;
}
return true;
}
void sv_execute_demo_start_command(const command::params& params)
{
if (params.size() < 2 || params.size() > 4 || (params.size() == 4 && std::string_view(params.get(3)) != "overwrite"))
{
console::info("usage: sv_demostart <client_num | player_name>, <file_name>(optional), overwrite(optional)\n");
return;
}
assert(params.size() != 4 || (params.size() == 4 && std::string_view(params.get(3)) == "overwrite"));
sv_execute_demo_start_command_internal(
sv_client_num_t(params.get(1)), (params.size() >= 3) ? params.get(2) : std::string_view{}, (params.size() == 4));
}
void sv_execute_demo_stop_command(const command::params& params)
{
if (params.size() != 2)
{
console::info("usage: sv_demostop <client_num | player_name>\n");
return;
}
const sv_client_num_t client_num(params.get(1));
if (!client_num.valid())
{
console::error("invalid client num %s\n", params.get(1));
}
else if (!demos.stop(client_num))
{
console::error("could not stop demo\n");
}
}
}
namespace demo_sv_recording
{
bool sv_recording(std::size_t client_num)
{
return demos.is_recording(sv_client_num_t(client_num));
}
bool sv_startrecord(std::size_t client_num, std::string_view file_name, bool overwrite)
{
return sv_execute_demo_start_command_internal(sv_client_num_t(client_num), file_name, overwrite);
}
bool sv_stoprecord(std::size_t client_num)
{
return demos.stop(sv_client_num_t(client_num));
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_dedi())
{
return;
}
// check run-time address assertions
check_address_assertions();
// capture outgoing packets to client
SV_Netchan_Transmit_hook.create(game::SV_Netchan_Transmit, sv_capture_data);
// execute server demo code based on this value
// if this was enabled after a player joined the server, then that match cannot be recorded for that player
sv_demos = game::Dvar_RegisterBool(
"sv_demos", false, game::DVAR_FLAG_NONE, "Enable server demos");
// add support to auto record all players
sv_demo_autorecord = game::Dvar_RegisterBool(
"sv_demoautorecord", false, game::DVAR_FLAG_NONE,
"Automatically start recording a demo for each connected client.");
// add console command support to record server demos
command::add("sv_demostart", sv_execute_demo_start_command);
command::add("sv_demostop", sv_execute_demo_stop_command);
scheduler::on_game_initialized([]()
{
demos.shutdown_watcher();
// check if demo directory exists / could be created
if (!sv_can_create_demo_directory())
{
console::error("could not create demo directory\n");
}
}, scheduler::main);
}
};
}
REGISTER_COMPONENT(demo_sv_recording::component)
@@ -1,8 +0,0 @@
#pragma once
namespace demo_sv_recording
{
[[nodiscard]] bool sv_recording(std::size_t client_num);
[[nodiscard]] bool sv_startrecord(std::size_t client_num, std::string_view file_name = {}, bool overwrite = {});
[[nodiscard]] bool sv_stoprecord(std::size_t client_num);
}
-178
View File
@@ -1,178 +0,0 @@
#include "std_include.hpp"
#include "loader/component_loader.hpp"
#include "demo_playback.hpp"
#include "fps.hpp"
#include "game/game.hpp"
#include "utils/hook.hpp"
#include "utils/string.hpp"
namespace demo_timescale
{
namespace
{
utils::hook::detour Com_TimeScaleMsec_hook;
utils::hook::detour LUI_Layout_hook;
utils::hook::detour Com_GetTimescaleForSnd_hook;
game::dvar_t* demo_timescale;
std::int32_t latest_delta_time;
void generate_pattern(std::array<std::uint8_t, 1000>& pattern, std::size_t ones_count, std::size_t zeros_count)
{
assert(ones_count + zeros_count == pattern.size());
for (std::size_t i = 0, zeros = 1, ones = 1; i < pattern.size(); ++i)
{
if (ones * zeros_count < zeros * ones_count)
{
++ones;
pattern[i] = 1;
}
else
{
++zeros;
pattern[i] = 0;
}
}
assert(std::accumulate(pattern.begin(), pattern.end(), 0) > 0);
}
void calculate_pattern(std::array<std::uint8_t, 1000>& pattern, float fps, float timescale)
{
// Com_TimeScaleMsec is called once per frame, so the number of calls it takes to advance 1000 ms
// can be calculated by using the following formula: fps / timescale
// example: 500 fps and 0.01 timescale -> 500 / 0.01 = 50'000
// a pattern needs to be generated where 1000 * 1ms and 49'000 * 0ms are interleaved,
// and fit in an array of size 1000
const auto call_count_per_sec = static_cast<std::size_t>(std::clamp(fps / timescale, 1000.0f, 1'000'000.0f));
const auto ones_count = static_cast<std::size_t>(pattern.size() / (call_count_per_sec / static_cast<float>(pattern.size())));
const auto zeros_count = pattern.size() - ones_count;
generate_pattern(pattern, ones_count, zeros_count);
}
std::int32_t get_delta_time_internal(std::int32_t msec)
{
if (!demo_playback::playing() || !demo_timescale || demo_timescale->current.value == 1.0f)
{
return Com_TimeScaleMsec_hook.invoke<std::int32_t>(msec);
}
if (demo_timescale->current.value == 0.0f)
{
return 0; // pause game
}
// the code below generates a pattern of interleaved 0s and 1s based on calculated avg fps
// a new pattern is generated every 1000 frames, or after timescale or maxfps changes
// the pattern determines the speed at which the game advances
const auto timescale = demo_timescale->current.value;
const auto avg_fps = fps::get_avg_fps();
const auto frame_time = timescale * 1000.0f / avg_fps;
const auto* com_maxfps = game::Dvar_FindVar("com_maxfps");
const auto maxfps = (com_maxfps) ? com_maxfps->current.integer : static_cast<std::int32_t>(avg_fps);
static auto last_timescale = timescale;
static auto last_maxfps = maxfps;
static std::size_t pattern_index;
static std::array<std::uint8_t, 1000> pattern;
if (last_timescale != timescale || last_maxfps != maxfps)
{
last_timescale = timescale;
last_maxfps = maxfps;
// update pattern using the maxfps instead of avg fps for now
calculate_pattern(pattern, static_cast<float>(maxfps), timescale);
// update the pattern again in the near future when the average fps is more accurate
pattern_index = 95 * pattern.size() / 100;
}
if (frame_time > 1.0f)
{
const auto i_frame_time = static_cast<std::int32_t>(frame_time);
const auto ones_count = static_cast<std::size_t>(frame_time * 1000 - i_frame_time * 1000);
if (ones_count <= 1)
{
return i_frame_time;
}
if (pattern_index % pattern.size() == 0)
{
const auto zeros_count = pattern.size() - ones_count;
generate_pattern(pattern, ones_count, zeros_count);
}
return i_frame_time + pattern[pattern_index++ % pattern.size()];
}
else if (pattern_index % pattern.size() == 0)
{
calculate_pattern(pattern, avg_fps, timescale);
}
// advance (1ms) or pause (0ms) based on the pattern
return pattern[pattern_index++ % pattern.size()];
}
std::int32_t get_delta_time(std::int32_t msec)
{
latest_delta_time = get_delta_time_internal(msec);
return latest_delta_time;
}
void set_delta_time_ui_elements(const std::int32_t client_num, const char* root_name, std::int32_t delta_time, void* lua_vm)
{
if (demo_playback::playing())
{
delta_time = latest_delta_time;
}
LUI_Layout_hook.invoke<void>(client_num, root_name, delta_time, lua_vm);
}
float get_timescale_for_sound()
{
if (!demo_playback::playing() || !demo_timescale || demo_timescale->current.value == 1.0f)
{
return Com_GetTimescaleForSnd_hook.invoke<float>();
}
return demo_timescale->current.value;
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
// add timescale support for demo playback
Com_TimeScaleMsec_hook.create(game::Com_TimeScaleMsec, get_delta_time);
// add timescale support for lua ui elements during demo playback
LUI_Layout_hook.create(game::LUI_Layout, set_delta_time_ui_elements);
// add timescale support for sounds during demo playback
Com_GetTimescaleForSnd_hook.create(game::Com_GetTimescaleForSnd, get_timescale_for_sound);
// allow the user to control the timescale during demo playback
demo_timescale = game::Dvar_RegisterFloat(
"demotimescale", 1.0f, 0.0f, 1000.0f, game::DVAR_FLAG_NONE, "Set playback speed for demos");
}
};
}
REGISTER_COMPONENT(demo_timescale::component)
File diff suppressed because it is too large Load Diff
-217
View File
@@ -1,217 +0,0 @@
#pragma once
#include "game/game.hpp"
namespace demo_utils
{
namespace globals
{
inline constexpr std::string_view DEMO_CODE_VERSION = "v0.0.1";
inline constexpr std::string_view DEMO_EXTENSION = ".dm_iw6";
inline constexpr std::size_t MAX_SIZE = 131'072;
}
using namespace globals;
namespace types
{
enum struct demo_data_id : std::uint8_t
{
mod_header = 0,
map_header = 1,
network_data = 2,
predicted_data = 3,
update_gamestate_data = 4,
gen_header = 5,
gen_footer = 6,
eof = 31,
unused_flag_1 = 0b0010'0000,
unused_flag_2 = 0b0100'0000,
one_byte_flag = 0b1000'0000,
flags = (unused_flag_1 | unused_flag_2 | one_byte_flag)
};
demo_data_id operator|(demo_data_id lhs, demo_data_id rhs);
demo_data_id operator&(demo_data_id lhs, demo_data_id rhs);
demo_data_id operator~(demo_data_id value);
enum struct predicted_data_id : std::uint8_t
{
player = 0,
vehicle_gryphon = 1,
vehicle_helo_pilot = 2,
vehicle_odin = 3,
vehicle_unknown = 4,
vehicle_helo_pilot_turret_fire = 5
};
struct persistent_data_t
{
// ksr -> killstreak reward
std::int32_t cur_kill_count{};
std::int32_t req_kill_count_next_ksr{};
std::int32_t unknown1{ 1 };
std::int32_t active_ksr_count{};
std::int32_t unknown2{ -1 };
std::int32_t active_ksr_flags{};
std::int32_t unknown3{};
std::int32_t first_ksr_icon{};
std::int32_t second_ksr_icon{};
std::int32_t third_ksr_icon{};
};
struct packed_gamestate_sizes
{
std::uint64_t string_data : 20{};
std::uint64_t string_offsets : 16{};
std::uint64_t unused : 27{};
std::uint64_t compressed : 1{};
};
static_assert(sizeof(packed_gamestate_sizes) == 8);
struct predicted_data_t
{
game::vec3_t viewangles{};
game::ClientArchiveData cad{};
};
struct gamestate_t
{
std::uint32_t svr_cmd_seq{};
game::gameState_t data{};
};
class buffer_t
{
public:
[[nodiscard]] std::size_t size() const;
[[nodiscard]] std::span<const std::uint8_t> get() const;
void reserve_memory(std::size_t size);
void write(const char* src, std::size_t size);
void clear();
private:
std::vector<std::uint8_t> buffer_;
};
}
using namespace types;
namespace gen_utilities
{
std::string_view get_dvar_string(std::string_view dvar_name, bool valid_string);
std::string_view get_mod_directory();
std::string_view get_base_path(bool default_string);
std::string_view get_mapname(bool default_string);
std::string get_shortened_mapname_lowercase(bool default_string);
std::string get_mapname_lowercase(bool default_string);
std::string_view get_gametype(bool default_string);
std::string get_gametype_lowercase(bool default_string);
//std::string get_datetime();
}
using namespace gen_utilities;
namespace file_directory
{
std::optional<std::filesystem::path> create_directory_user_demo();
std::optional<std::filesystem::path> create_directory_auto_demo();
bool can_create_demo_directories();
std::optional<std::filesystem::path> create_path_user_demo(const std::filesystem::path& dir_path);
std::optional<std::filesystem::path> create_path_user_demo(
const std::filesystem::path& dir_path, std::string_view demo_name, bool overwrite);
std::optional<std::filesystem::path> create_path_auto_demo(const std::filesystem::path& dir_path);
}
using namespace file_directory;
namespace file_directory_server
{
std::optional<std::filesystem::path> sv_create_demo_directory();
bool sv_can_create_demo_directory();
std::optional<std::filesystem::path> sv_create_path_server_demo(
const std::filesystem::path& dir_path, std::string_view client_id);
std::optional<std::filesystem::path> sv_create_path_server_demo(
const std::filesystem::path& dir_path, std::string_view demo_name, bool overwrite);
}
using namespace file_directory_server;
namespace serialization
{
void write_id_and_size(buffer_t& output, std::size_t size, demo_data_id id);
void write_id_and_size(std::ofstream& output, std::size_t size, demo_data_id id);
void write_network_data(buffer_t& output, std::span<const std::uint8_t> network_data);
void write_network_data(std::ofstream& output, std::span<const std::uint8_t> network_data);
//void write_predicted_player_data(buffer_t& output, const game::mp::playerState_t& ps,
//std::uint8_t cad_index, predicted_data_id id);
//void write_predicted_player_data(std::ofstream& output, const game::mp::playerState_t& ps,
//std::uint8_t cad_index, predicted_data_id id);
//void write_predicted_vehicle_data(buffer_t& output, const game::mp::playerState_t& ps,
//std::uint8_t cad_index, predicted_data_id id);
//void write_predicted_vehicle_data(std::ofstream& output, const game::mp::playerState_t& ps,
//std::uint8_t cad_index, predicted_data_id id);
void write_helo_pilot_turret_fire(buffer_t& output, std::uint8_t fire_count);
void write_helo_pilot_turret_fire(std::ofstream& output, std::uint8_t fire_count);
void write_mod_header(buffer_t& output);
void write_mod_header(std::ofstream& output);
bool write_map_header(buffer_t& output);
bool write_map_header(std::ofstream& output);
bool write_gamestate_data(buffer_t& output);
bool write_gamestate_data(std::ofstream& output);
void write_predicted_data(buffer_t& output);
void write_predicted_data(std::ofstream& output);
void write_general_header(buffer_t& output);
void write_general_header(std::ofstream& output);
//void write_general_footer(buffer_t& output, std::int32_t first_svr_time, std::int32_t last_svr_time);
void write_general_footer(std::ofstream& output, std::int32_t first_svr_time, std::int32_t last_svr_time);
//void write_end_of_file(buffer_t& output);
void write_end_of_file(std::ofstream& output);
}
using namespace serialization;
namespace serialization_server
{
void sv_write_predicted_data(buffer_t& output, const game::mp::playerState_t& ps, std::size_t send_msg_count);
void sv_write_predicted_data(std::ofstream& output, const game::mp::playerState_t& ps, std::size_t send_msg_count);
void sv_write_general_header(buffer_t& output, std::string_view player_name);
void sv_write_general_header(std::ofstream& output, std::string_view player_name);
}
using namespace serialization_server;
namespace deserialization
{
bool is_gamestate_valid(game::gameState_t& gs, bool sanitize);
bool parse_demo(std::ifstream& file, std::vector<std::uint8_t>& buffer,
std::size_t& post_map_header_file_offset, std::array<predicted_data_t, 256>& pred_data,
std::size_t& pred_data_index, std::optional<gamestate_t>& gs);
}
using namespace deserialization;
namespace misc
{
template <typename To, typename From>
requires (std::is_trivially_copyable_v<From>&& std::is_trivially_copyable_v<To>)
[[nodiscard]] To cpy_cast(const From* src)
{
To dst;
std::memcpy(&dst, src, sizeof(To));
return dst;
}
persistent_data_t& get_persistent_data();
void fast_forward_demo(std::uint32_t msec);
void check_address_assertions();
}
using namespace misc;
}
+2 -2
View File
@@ -222,7 +222,7 @@ namespace demonware
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap); vsnprintf(buffer, sizeof(buffer), msg, ap);
printf("%s: %s\n", function, buffer); printf("%s: %s\n", function, buffer);
va_end(ap); va_end(ap);
@@ -442,7 +442,7 @@ namespace demonware
void post_unpack() override void post_unpack() override
{ {
utils::hook::jump(SELECT_VALUE(0x140602230, 0x1406F54D0), bd_logger_stub); utils::hook::jump(0x1406F54D0, bd_logger_stub);
} }
void pre_destroy() override void pre_destroy() override
+5 -7
View File
@@ -37,7 +37,7 @@ namespace discord
if (!game::CL_IsCgameInitialized()) if (!game::CL_IsCgameInitialized())
{ {
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer"; discord_presence.details = "Multiplayer";
discord_presence.state = "Main Menu"; discord_presence.state = "Main Menu";
discord_presence.partySize = 0; discord_presence.partySize = 0;
@@ -47,15 +47,13 @@ namespace discord
} }
else else
{ {
if (game::environment::is_sp()) return;
const auto* gametype = game::UI_LocalizeGametype(game::Dvar_FindVar("ui_gametype")->current.string); 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); 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.details = utils::string::va("%s on %s", gametype, map);
discord_presence.partySize = game::mp::cg->snap != nullptr discord_presence.partySize = game::mp::cgArray->snap != nullptr
? game::mp::cg->snap->numClients ? game::mp::cgArray->snap->numClients
: 1; : 1;
if (game::Dvar_GetBool("xblive_privatematch")) if (game::Dvar_GetBool("xblive_privatematch"))
@@ -65,13 +63,13 @@ namespace discord
} }
else else
{ {
auto* host_name = game::mp::cgs->szHostName; auto* host_name = reinterpret_cast<char*>(0x14187EBC4);
utils::string::strip(host_name, host_name, std::strlen(host_name) + 1); utils::string::strip(host_name, host_name, std::strlen(host_name) + 1);
discord_presence.state = host_name; discord_presence.state = host_name;
discord_presence.partyMax = party::server_client_count(); discord_presence.partyMax = party::server_client_count();
std::hash<game::netadr_t> hash_fn; std::hash<game::netadr_s> hash_fn;
static const auto nonce = utils::cryptography::random::get_integer(); static const auto nonce = utils::cryptography::random::get_integer();
const auto& address = party::get_target(); const auto& address = party::get_target();
+17 -25
View File
@@ -1,25 +1,18 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "game/engine/sv_game.hpp"
#include "console.hpp" #include "console.hpp"
#include "demo_playback.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
namespace dvar_cheats namespace dvar_cheats
{ {
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value) void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::DvarValue* value)
{ {
// return early because sv_cheats is enabled when playing back demos
if (demo_playback::playing())
{
return;
}
if (dvar && dvar->name == "sv_cheats"s) 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 dedi, do not allow internal to change value so servers can allow cheats if they want to
@@ -117,25 +110,26 @@ namespace dvar_cheats
a.jmp(0x1404F0D74); a.jmp(0x1404F0D74);
}); });
void cg_set_client_dvar_from_server(const int local_client_num, game::mp::cg_t* cg, const char* dvar_id, const char* value) 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); const auto* dvar = game::Dvar_FindVar(dvar_id);
if (dvar)
{ 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 // 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); game::CG_SetClientDvarFromServer(local_client_num, cg, dvar_id, value);
return;
} }
if (dvar && ((dvar->flags & game::DVAR_FLAG_SAVED) != 0))
{
console::info("Not allowing server to override archive dvar '%s'\n", dvar->name);
return;
}
// 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::DVAR_SOURCE_EXTERNAL);
} }
void set_client_dvar_by_string(const int entity_num, const char* value) void set_client_dvar_by_string(const int entity_num, const char* value)
@@ -143,7 +137,7 @@ namespace dvar_cheats
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack 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); const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
game::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command); game::engine::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command);
} }
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a) const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
@@ -177,8 +171,6 @@ namespace dvar_cheats
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp()) return;
utils::hook::nop(0x1404F0D13, 4); // let our stub handle zero-source sets 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::jump(0x1404F0D1A, dvar_flag_checks_stub, true); // check extra dvar flags when setting values
+53 -14
View File
@@ -7,10 +7,10 @@
#include "console.hpp" #include "console.hpp"
#include "fastfiles.hpp" #include "fastfiles.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
#include <utils/memory.hpp>
#include <utils/string.hpp>
namespace fastfiles namespace fastfiles
{ {
@@ -32,6 +32,12 @@ namespace fastfiles
return; return;
} }
const auto out_name = std::format("gsc_dump/{}.gscbin", name);
if (utils::io::file_exists(out_name))
{
return;
}
std::string buffer; std::string buffer;
buffer.append(header.scriptfile->name, std::strlen(header.scriptfile->name) + 1); 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->compressedLen), 4);
@@ -40,12 +46,38 @@ namespace fastfiles
buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen); buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen);
buffer.append(reinterpret_cast<char*>(header.scriptfile->bytecode), header.scriptfile->bytecodeLen); 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); utils::io::write_file(out_name, buffer);
console::info("Dumped %s\n", out_name.data()); console::info("Dumped %s\n", out_name.c_str());
} }
void dump_csv_table(const std::string& name, game::XAssetHeader header)
{
if (!dvars::g_dump_string_tables->current.enabled)
{
return;
}
const auto out_name = std::format("csv_dump/{}.csv", name);
if (utils::io::file_exists(out_name))
{
return;
}
std::string buffer;
for (auto row = 0; row < header.stringTable->rowCount; row++)
{
for (auto column = 0; column < header.stringTable->columnCount; column++)
{
const auto* string = header.stringTable->values[(row * header.stringTable->columnCount) + column].string;
buffer.append(utils::string::va("%s%s", string ? string : "", (column == header.stringTable->columnCount - 1) ? "\n" : ","));
}
}
utils::io::write_file(out_name, buffer);
console::info("Dumped %s\n", out_name.c_str());
}
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default) game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{ {
@@ -58,6 +90,11 @@ namespace fastfiles
dump_gsc_script(name, result); dump_gsc_script(name, result);
} }
if (type == game::ASSET_TYPE_STRINGTABLE)
{
dump_csv_table(name, result);
}
if (diff > 100) if (diff > 100)
{ {
console::print( console::print(
@@ -106,18 +143,16 @@ namespace fastfiles
public: public:
void post_unpack() override void post_unpack() override
{ {
db_try_load_x_file_internal_hook.create(SELECT_VALUE(0x140275850, 0x1403237F0), &db_try_load_x_file_internal); db_try_load_x_file_internal_hook.create(0x1403237F0, &db_try_load_x_file_internal);
db_find_x_asset_header_hook.create(game::DB_FindXAssetHeader, db_find_x_asset_header_stub); 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"); dvars::g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts to binary format");
dvars::g_dump_string_tables = game::Dvar_RegisterBool("g_dumpStringTables", false, game::DVAR_FLAG_NONE, "Dump CSV files");
utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub); utils::hook::call(0x140156350, p_mem_free_stub);
utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub); utils::hook::call(0x140324259, p_mem_free_stub);
// Allow loading of unsigned fastfiles
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor
// who asked for this this ?
command::add("materiallist", [](const command::params& params) command::add("materiallist", [](const command::params& params)
{ {
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*) game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
@@ -129,10 +164,14 @@ namespace fastfiles
}, nullptr, false); }, nullptr, false);
}); });
if (!game::environment::is_sp())
{
reallocate_asset_pool(game::ASSET_TYPE_WEAPON, 320); reallocate_asset_pool(game::ASSET_TYPE_WEAPON, 320);
}
// Fixes ("Exceeded limit of 16 'font' assets.") new steam files use a 32 font pool (2026)
reallocate_asset_pool(game::ASSET_TYPE_FONT, 32);
// Allow loading of unsigned fastfiles
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor
} }
}; };
} }
+12 -21
View File
@@ -83,20 +83,22 @@ namespace filesystem
void startup() void startup()
{ {
register_path("iw6"); const auto base = std::filesystem::current_path();
register_path(base / "iw6");
register_path(get_binary_directory() + "\\data"); register_path(get_binary_directory() + "\\data");
if (get_binary_directory() != std::filesystem::current_path()) if (get_binary_directory() != base)
{ {
register_path(std::filesystem::current_path() / "data"); register_path(base / "data");
} }
// game's search paths // game's search paths
register_path("devraw"); register_path(base / "devraw");
register_path("devraw_shared"); register_path(base / "devraw_shared");
register_path("raw_shared"); register_path(base / "raw_shared");
register_path("raw"); register_path(base / "raw");
register_path("main"); register_path(base / "main");
} }
void check_for_startup() void check_for_startup()
@@ -273,22 +275,12 @@ namespace filesystem
void post_unpack() override void post_unpack() override
{ {
// Set fs_basegame // Set fs_basegame
utils::hook::inject(SELECT_VALUE(0x14041C053, 0x1404DDA13), "iw6"); utils::hook::inject(0x1404DDA13, "iw6");
#ifndef INJECT_HOST_AS_LIB #ifndef INJECT_HOST_AS_LIB
utils::hook::jump(SELECT_VALUE(0x1404351B0, 0x1404F96C0), sys_default_install_path_stub); utils::hook::jump(0x1404F96C0, sys_default_install_path_stub);
#endif #endif
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(0x1404DD704, fs_startup_stub);
utils::hook::call(0x1404DDB43, fs_startup_stub); utils::hook::call(0x1404DDB43, fs_startup_stub);
@@ -296,7 +288,6 @@ namespace filesystem
utils::hook::call(0x1404DE570, register_custom_path_stub); utils::hook::call(0x1404DE570, register_custom_path_stub);
utils::hook::call(0x1404DE5AF, register_custom_path_stub); utils::hook::call(0x1404DE5AF, register_custom_path_stub);
} }
}
}; };
} }
+3 -27
View File
@@ -1,6 +1,5 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "fps.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "game/game.hpp" #include "game/game.hpp"
@@ -75,23 +74,6 @@ namespace fps
++data->index; ++data->index;
} }
void enforce_fps_limit()
{
const auto* maxfps = game::Dvar_FindVar("com_maxfps");
const auto max = (maxfps) ? std::min(2 * maxfps->current.integer, maxfps->domain.integer.max + 250) : 1250;
const auto fps = static_cast<std::int32_t>(static_cast<float>(1000.0f /
static_cast<float>(cg_perf.average)) + 9.313225746154785e-10);
if (fps > max)
{
// workaround to limit fps
scheduler::once([]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}, scheduler::pipeline::renderer);
}
}
void perf_update() void perf_update()
{ {
cg_perf.count = 32; cg_perf.count = 32;
@@ -102,9 +84,8 @@ namespace fps
cg_perf.previous_ms = cg_perf.current_ms; cg_perf.previous_ms = cg_perf.current_ms;
perf_calc_fps(&cg_perf, cg_perf.frame_ms); perf_calc_fps(&cg_perf, cg_perf.frame_ms);
enforce_fps_limit();
utils::hook::invoke<void>(SELECT_VALUE(0x1405806E0, 0x140658E30)); utils::hook::invoke<void>(0x140658E30);
} }
void cg_draw_fps() void cg_draw_fps()
@@ -174,11 +155,6 @@ namespace fps
} }
} }
float get_avg_fps()
{
return 1000.0f / (cg_perf.average + std::numeric_limits<float>::epsilon());
}
class component final : public component_interface class component final : public component_interface
{ {
public: public:
@@ -191,10 +167,10 @@ namespace fps
// fps setup // fps setup
cg_perf.perf_start = std::chrono::high_resolution_clock::now(); cg_perf.perf_start = std::chrono::high_resolution_clock::now();
utils::hook::call(SELECT_VALUE(0x140242C11, 0x1402CF457), &perf_update); utils::hook::call(0x1402CF457, &perf_update);
// change cg_drawfps flags to saved // change cg_drawfps flags to saved
utils::hook::call(SELECT_VALUE(0x1401F400A, 0x140272B98), &cg_draw_fps_register_stub); utils::hook::call(0x140272B98, &cg_draw_fps_register_stub);
cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping"); cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping");
-6
View File
@@ -1,6 +0,0 @@
#pragma once
namespace fps
{
[[nodiscard]] float get_avg_fps();
}
+12 -12
View File
@@ -63,7 +63,7 @@ namespace game_console
void clear() void clear()
{ {
strncpy_s(con.buffer, "", sizeof(con.buffer)); game::I_strncpyz(con.buffer, "", sizeof(con.buffer));
con.cursor = 0; con.cursor = 0;
fixed_input = ""; fixed_input = "";
@@ -249,7 +249,7 @@ namespace game_console
dvars::con_inputDvarInactiveValueColor->current.vector, offset); dvars::con_inputDvarInactiveValueColor->current.vector, offset);
} }
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); game::I_strncpyz(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice));
con.globals.may_auto_complete = true; con.globals.may_auto_complete = true;
} }
else if (matches.size() > 1) else if (matches.size() > 1)
@@ -274,7 +274,7 @@ namespace game_console
} }
} }
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice)); game::I_strncpyz(con.globals.auto_complete_choice, matches[0].data(), sizeof(con.globals.auto_complete_choice));
con.globals.may_auto_complete = true; con.globals.may_auto_complete = true;
} }
} }
@@ -364,11 +364,11 @@ namespace game_console
void print_internal(const char* fmt, ...) void print_internal(const char* fmt, ...)
{ {
char va_buffer[0x200] = { 0 }; char va_buffer[1024]{};
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vsprintf_s(va_buffer, fmt, ap); vsnprintf(va_buffer, sizeof(va_buffer), fmt, ap);
va_end(ap); va_end(ap);
const auto formatted = std::string(va_buffer); const auto formatted = std::string(va_buffer);
@@ -424,7 +424,7 @@ namespace game_console
con.buffer[1] = '\0'; con.buffer[1] = '\0';
} }
strncat_s(con.buffer, con.globals.auto_complete_choice, 64); game::I_strncat(con.buffer, sizeof(con.buffer), con.globals.auto_complete_choice);
con.cursor = static_cast<int>(std::string(con.buffer).length()); con.cursor = static_cast<int>(std::string(con.buffer).length());
if (con.cursor != 254) if (con.cursor != 254)
@@ -505,7 +505,7 @@ namespace game_console
{ {
if (key == game::keyNum_t::K_F10) if (key == game::keyNum_t::K_F10)
{ {
if(game::mp::svs->clients[localClientNum].header.state > game::CS_FREE) if(game::mp::svs_clients[localClientNum].header.state > game::CS_FREE)
{ {
return false; return false;
} }
@@ -549,7 +549,7 @@ namespace game_console
if (history_index != -1) if (history_index != -1)
{ {
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); game::I_strncpyz(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
con.cursor = static_cast<int>(strlen(con.buffer)); con.cursor = static_cast<int>(strlen(con.buffer));
} }
} }
@@ -564,7 +564,7 @@ namespace game_console
if (history_index != -1) if (history_index != -1)
{ {
strncpy_s(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer)); game::I_strncpyz(con.buffer, history.at(history_index).c_str(), sizeof(con.buffer));
con.cursor = static_cast<int>(strlen(con.buffer)); con.cursor = static_cast<int>(strlen(con.buffer));
} }
} }
@@ -674,7 +674,7 @@ namespace game_console
} }
} }
game::cmd_function_t* cmd = (*game::cmd_functions); game::cmd_function_s* cmd = (*game::cmd_functions);
while (cmd) while (cmd)
{ {
if (cmd->name) if (cmd->name)
@@ -721,7 +721,7 @@ namespace game_console
con.output_visible = false; con.output_visible = false;
con.display_line_offset = 0; con.display_line_offset = 0;
con.line_count = 0; con.line_count = 0;
strncpy_s(con.buffer, "", sizeof(con.buffer)); game::I_strncpyz(con.buffer, "", sizeof(con.buffer));
con.globals.x = 0.0f; con.globals.x = 0.0f;
con.globals.y = 0.0f; con.globals.y = 0.0f;
@@ -729,7 +729,7 @@ namespace game_console
con.globals.font_height = 0.0f; con.globals.font_height = 0.0f;
con.globals.may_auto_complete = false; con.globals.may_auto_complete = false;
con.globals.info_line_count = 0; con.globals.info_line_count = 0;
strncpy_s(con.globals.auto_complete_choice, "", sizeof(con.globals.auto_complete_choice)); game::I_strncpyz(con.globals.auto_complete_choice, "", sizeof(con.globals.auto_complete_choice));
// add clear command // add clear command
command::add("clear", [&]() command::add("clear", [&]()
+7 -21
View File
@@ -3,12 +3,10 @@
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
#include "console.hpp" #include "console.hpp"
#include "game_log.hpp" #include "game_log.hpp"
#include "scheduler.hpp"
#include "gsc/script_extension.hpp" #include "scripting.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/io.hpp> #include <utils/io.hpp>
@@ -23,7 +21,7 @@ namespace game_log
char buf[1024]{}; char buf[1024]{};
std::size_t out_chars = 0; std::size_t out_chars = 0;
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i) for (std::uint32_t i = 0; i < game::Scr_GetNumParam(); ++i)
{ {
const auto* value = game::Scr_GetString(i); const auto* value = game::Scr_GetString(i);
const auto len = std::strlen(value); const auto len = std::strlen(value);
@@ -34,7 +32,7 @@ namespace game_log
break; break;
} }
strncat_s(buf, value, _TRUNCATE); game::I_strncat(buf, sizeof(buf), value);
} }
g_log_printf("%s", buf); g_log_printf("%s", buf);
@@ -49,22 +47,15 @@ namespace game_log
return; return;
} }
char buffer[0x400]{}; char buffer[1024]{};
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
vsprintf_s(buffer, fmt, ap);
va_end(ap); va_end(ap);
const auto time = *game::level_time / 1000; const auto time = *game::level_time / 1000;
utils::io::write_file(log, utils::string::va("%3i:%i%i %s", utils::io::write_file(log, utils::string::va("%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, buffer), true);
time / 60,
time % 60 / 10,
time % 60 % 10,
buffer
), true);
} }
class component final : public component_interface class component final : public component_interface
@@ -72,11 +63,6 @@ namespace game_log
public: public:
void post_load() override void post_load() override
{ {
if (game::environment::is_sp())
{
return;
}
utils::hook::set<game::BuiltinFunction>(0x1409E8A20, gscr_log_print); utils::hook::set<game::BuiltinFunction>(0x1409E8A20, gscr_log_print);
scheduler::once([] scheduler::once([]
+14 -46
View File
@@ -65,30 +65,6 @@ namespace gameplay
a.jmp(0x140383796); 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 pm_bouncing_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{ {
const auto no_bounce = a.newLabel(); const auto no_bounce = a.newLabel();
@@ -133,7 +109,7 @@ namespace gameplay
return jump_height; return jump_height;
} }
void jump_apply_slowdown_stub(game::mp::playerState_t* ps) void jump_apply_slowdown_stub(game::mp::playerState_s* ps)
{ {
assert(ps->pm_flags & game::PMF_JUMPING); assert(ps->pm_flags & game::PMF_JUMPING);
@@ -163,7 +139,7 @@ namespace gameplay
} }
} }
float jump_get_slowdown_friction(game::mp::playerState_t* ps) float jump_get_slowdown_friction(game::mp::playerState_s* ps)
{ {
assert(ps->pm_flags & game::PMF_JUMPING); assert(ps->pm_flags & game::PMF_JUMPING);
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME); assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
@@ -181,7 +157,7 @@ namespace gameplay
return 2.5f; return 2.5f;
} }
float jump_reduce_friction_stub(game::mp::playerState_t* ps) float jump_reduce_friction_stub(game::mp::playerState_s* ps)
{ {
float control; float control;
@@ -199,7 +175,7 @@ namespace gameplay
return control; return control;
} }
float jump_get_land_factor(game::mp::playerState_t* ps) float jump_get_land_factor(game::mp::playerState_s* ps)
{ {
assert(ps->pm_flags & game::PMF_JUMPING); assert(ps->pm_flags & game::PMF_JUMPING);
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME); assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
@@ -219,9 +195,9 @@ namespace gameplay
void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float height) void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float height)
{ {
static_assert(offsetof(game::mp::playerState_t, groundEntityNum) == 0x70); static_assert(offsetof(game::mp::playerState_s, groundEntityNum) == 0x70);
static_assert(offsetof(game::mp::playerState_t, pm_time) == 0x8); static_assert(offsetof(game::mp::playerState_s, pm_time) == 0x8);
static_assert(offsetof(game::mp::playerState_t, sprintState.sprintButtonUpRequired) == 0x240); static_assert(offsetof(game::mp::playerState_s, sprintState.sprintButtonUpRequired) == 0x240);
static_assert(offsetof(game::pml_t, frametime) == 0x24); static_assert(offsetof(game::pml_t, frametime) == 0x24);
static_assert(offsetof(game::pml_t, walking) == 0x2C); static_assert(offsetof(game::pml_t, walking) == 0x2C);
static_assert(offsetof(game::pml_t, groundPlane) == 0x30); static_assert(offsetof(game::pml_t, groundPlane) == 0x30);
@@ -229,9 +205,9 @@ namespace gameplay
float factor; float factor;
float velocity_sqrd; float velocity_sqrd;
game::mp::playerState_t* ps; game::mp::playerState_s* ps;
ps = static_cast<game::mp::playerState_t*>(pm->ps); ps = static_cast<game::mp::playerState_s*>(pm->ps);
assert(ps); assert(ps);
@@ -295,7 +271,7 @@ namespace gameplay
} }
} }
void pm_weapon_use_ammo_stub(game::playerState_t* ps, game::Weapon weapon, void pm_weapon_use_ammo_stub(game::playerState_s* ps, game::Weapon weapon,
bool is_alternate, int amount, game::PlayerHandIndex hand) bool is_alternate, int amount, game::PlayerHandIndex hand)
{ {
if (!dvars::player_sustainAmmo->current.enabled) if (!dvars::player_sustainAmmo->current.enabled)
@@ -304,10 +280,10 @@ namespace gameplay
} }
} }
game::mp::gentity_t* weapon_rocket_launcher_fire_stub(game::mp::gentity_t* ent, game::Weapon weapon, float spread, game::weaponParms* wp, 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) const float* gun_vel, game::mp::missileFireParms* fire_parms, bool magic_bullet)
{ {
auto* result = utils::hook::invoke<game::mp::gentity_t*>(0x1403DB8A0, ent, weapon, spread, wp, gun_vel, fire_parms, 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) if (ent->client != nullptr && wp->weapDef->inventoryType != game::WEAPINVENTORY_EXCLUSIVE)
{ {
@@ -327,21 +303,13 @@ namespace gameplay
void post_unpack() override void post_unpack() override
{ {
// Implement bouncing dvar // Implement bouncing dvar
if (game::environment::is_sp()) utils::hook::jump(0x140228FFF, pm_bouncing_stub_mp, true);
{
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, dvars::pm_bouncing = game::Dvar_RegisterBool("pm_bouncing", false,
game::DVAR_FLAG_REPLICATED, "Enable bouncing"); game::DVAR_FLAG_REPLICATED, "Enable bouncing");
dvars::player_sustainAmmo = game::Dvar_RegisterBool("player_sustainAmmo", false, dvars::player_sustainAmmo = game::Dvar_RegisterBool("player_sustainAmmo", false,
game::DVAR_FLAG_REPLICATED, "Firing weapon will not decrease clip ammo."); 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); pm_weapon_use_ammo_hook.create(0x140238A90, &pm_weapon_use_ammo_stub);
if (game::environment::is_sp()) return;
// Implement player ejection dvar // 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"); dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
+31 -7
View File
@@ -113,6 +113,26 @@ namespace gsc
return res; return res;
} }
bool player_cmd_set_client_dvar_is_valid_name(const char* dvar_name)
{
if (!game::Dvar_IsValidName(dvar_name))
{
scr_error(va("%s is an invalid dvar name", dvar_name));
}
return true;
}
void player_cmd_set_client_dvar_server_dvar()
{
scr_error("server dvar cannot be set as a client dvar");
}
void player_cmd_set_client_dvar_non_writable_dvar()
{
scr_error("non-writable dvar cannot be set as a client dvar");
}
} }
unsigned int scr_get_object(unsigned int index) unsigned int scr_get_object(unsigned int index)
@@ -283,11 +303,11 @@ namespace gsc
public: public:
void post_unpack() override void post_unpack() override
{ {
scr_emit_function_hook.create(SELECT_VALUE(0x1403D3350, 0x14042E150), &scr_emit_function_stub); scr_emit_function_hook.create(0x14042E150, &scr_emit_function_stub);
utils::hook::call(SELECT_VALUE(0x1403D32E4, 0x14042E0E4), compile_error_stub); // LinkFile utils::hook::call(0x14042E0E4, compile_error_stub); // LinkFile
utils::hook::call(SELECT_VALUE(0x1403D3338, 0x14042E138), compile_error_stub); // LinkFile utils::hook::call(0x14042E138, compile_error_stub); // LinkFile
utils::hook::call(SELECT_VALUE(0x1403D342A, 0x14042E22B), find_variable_stub); // Scr_EmitFunction utils::hook::call(0x14042E22B, find_variable_stub); // Scr_EmitFunction
// Restore basic error messages to scr functions // Restore basic error messages to scr functions
utils::hook::jump(game::Scr_GetObject, scr_get_object); utils::hook::jump(game::Scr_GetObject, scr_get_object);
@@ -297,9 +317,13 @@ namespace gsc
utils::hook::jump(game::Scr_GetInt, scr_get_int); utils::hook::jump(game::Scr_GetInt, scr_get_int);
utils::hook::jump(game::Scr_GetFloat, scr_get_float); utils::hook::jump(game::Scr_GetFloat, scr_get_float);
utils::hook::jump(SELECT_VALUE(0x1403DE150, 0x1404390B0), scr_get_pointer_type); utils::hook::jump(0x1404390B0, scr_get_pointer_type);
utils::hook::jump(SELECT_VALUE(0x1403DE320, 0x140439280), scr_get_type); utils::hook::jump(0x140439280, scr_get_type);
utils::hook::jump(SELECT_VALUE(0x1403DE390, 0x1404392F0), scr_get_type_name); utils::hook::jump(0x1404392F0, scr_get_type_name);
utils::hook::call(0x14038A4F7, player_cmd_set_client_dvar_is_valid_name);
utils::hook::call(0x14038A52F, player_cmd_set_client_dvar_server_dvar);
utils::hook::call(0x14038A53A, player_cmd_set_client_dvar_non_writable_dvar);
} }
void pre_destroy() override void pre_destroy() override
+25 -16
View File
@@ -200,7 +200,7 @@ namespace gsc
} }
console::warn("******* script runtime error ********\n"); console::warn("******* script runtime error ********\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(SELECT_VALUE(0x1455BE740, 0x144D57840)); 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(); const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
@@ -293,6 +293,17 @@ namespace gsc
game::Scr_AddInt(game::environment::is_dedi()); game::Scr_AddInt(game::environment::is_dedi());
} }
void scr_bot_auto_connect_enabled()
{
if (game::environment::is_dedi() && dvars::sv_botsAutoJoin->current.enabled)
{
game::Scr_AddInt(1); // 2 seems to be unused (incomplete GSC/game mode)
return;
}
game::Scr_AddInt(game::BG_BotsConnectType());
}
const char* get_code_pos(const int index) const char* get_code_pos(const int index)
{ {
if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount) if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount)
@@ -349,21 +360,21 @@ namespace gsc
public: public:
void post_unpack() override void post_unpack() override
{ {
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print); utils::hook::set<game::BuiltinFunction>(0x1409E6CE8, scr_print);
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln); utils::hook::set<game::BuiltinFunction>(0x1409E6D00, scr_print_ln);
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D353B + 1, 0x14042E33B + 1), 0x1000); // Scr_RegisterFunction utils::hook::set<std::uint32_t>(0x14042E33B + 1, 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>(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::set<std::uint32_t>(0x14043BBBE + 3, RVA(&func_table)); // VM_Execute_0
utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts utils::hook::inject(0x14042E734 + 3, &func_table); // Scr_BeginLoadScripts
utils::hook::nop(SELECT_VALUE(0x1403E0BDD + 5, 0x14043BBBE + 5), 2); utils::hook::nop(0x14043BBBE + 5, 2);
utils::hook::call(SELECT_VALUE(0x1403E0BDD, 0x14043BBBE), vm_call_builtin_function); utils::hook::call(0x14043BBBE, vm_call_builtin_function);
utils::hook::call(SELECT_VALUE(0x1403D391F, 0x14042E76F), scr_get_function_stub); utils::hook::call(0x14042E76F, scr_get_function_stub);
utils::hook::call(SELECT_VALUE(0x1403E1ED0, 0x14043CEB1), vm_error_stub); utils::hook::call(0x14043CEB1, vm_error_stub);
add_function("replacefunc", [] add_function("replacefunc", []
{ {
@@ -381,16 +392,14 @@ namespace gsc
command::execute(cmd); command::execute(cmd);
}); });
if (game::environment::is_sp())
{
return;
}
utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd); utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd); utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd); utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E94D0, scr_cmd_is_dedicated_server); utils::hook::set<game::BuiltinFunction>(0x1409E94D0, scr_cmd_is_dedicated_server);
dvars::sv_botsAutoJoin = game::Dvar_RegisterBool("sv_botsAutoJoin", false, game::DVAR_FLAG_NONE, "");
utils::hook::set<game::BuiltinFunction>(0x1409E92F0, scr_bot_auto_connect_enabled);
} }
}; };
} }
+10 -30
View File
@@ -225,16 +225,6 @@ namespace gsc
const auto* map_name = game::Dvar_FindVar("mapname"); const auto* map_name = game::Dvar_FindVar("mapname");
if (game::environment::is_sp())
{
const std::filesystem::path game_folder = "sp";
load(base_dir / game_folder);
load(base_dir / game_folder / map_name->current.string);
}
else
{
const std::filesystem::path game_folder = "mp"; const std::filesystem::path game_folder = "mp";
load(base_dir / game_folder); load(base_dir / game_folder);
@@ -244,7 +234,6 @@ namespace gsc
const auto* game_type = game::Dvar_FindVar("g_gametype"); const auto* game_type = game::Dvar_FindVar("g_gametype");
load(base_dir / game_folder / game_type->current.string); load(base_dir / game_folder / game_type->current.string);
} }
}
int db_is_x_asset_default(game::XAssetType type, const char* name) int db_is_x_asset_default(game::XAssetType type, const char* name)
{ {
@@ -347,7 +336,7 @@ namespace gsc
build = static_cast<xsk::gsc::build>(static_cast<unsigned int>(build) | static_cast<unsigned int>(xsk::gsc::build::dev_blocks)); build = static_cast<xsk::gsc::build>(static_cast<unsigned int>(build) | static_cast<unsigned int>(xsk::gsc::build::dev_blocks));
} }
gsc_ctx->init(build, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> gsc_ctx->init(build, []([[maybe_unused]] const auto* 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(); const auto script_name = std::filesystem::path(included_path).replace_extension().string();
@@ -369,14 +358,14 @@ namespace gsc
return {{}, script_data}; return {{}, script_data};
}); });
utils::hook::invoke<void>(SELECT_VALUE(0x1403D3860, 0x14042E6B0)); utils::hook::invoke<void>(0x14042E6B0);
} }
void scr_end_load_scripts_stub() void scr_end_load_scripts_stub()
{ {
gsc_ctx->cleanup(); gsc_ctx->cleanup();
utils::hook::invoke<void>(SELECT_VALUE(0x140603C00, 0x14042E7E0)); utils::hook::invoke<void>(0x14042E7E0);
} }
} }
@@ -403,22 +392,22 @@ namespace gsc
public: public:
loading() loading()
{ {
gsc_ctx = std::make_unique<xsk::gsc::iw6_pc::context>(); gsc_ctx = std::make_unique<xsk::gsc::iw6_pc::context>(xsk::gsc::instance::server);
} }
void post_unpack() override void post_unpack() override
{ {
dvars::com_developer = reinterpret_cast<game::dvar_t**>(SELECT_VALUE(0x141603850, 0x1419A9700)); dvars::com_developer = reinterpret_cast<game::dvar_t**>(0x1419A9700);
// Load our scripts with an uncompressed stack // Load our scripts with an uncompressed stack
utils::hook::call(SELECT_VALUE(0x1403DC8F0, 0x140437940), db_get_raw_buffer_stub); utils::hook::call(0x140437940, db_get_raw_buffer_stub);
utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts utils::hook::call(0x1403CCED9, scr_begin_load_scripts_stub); // GScr_LoadScripts
utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts utils::hook::call(0x1403CD08D, scr_end_load_scripts_stub); // GScr_LoadScripts
// ProcessScript // ProcessScript
utils::hook::call(SELECT_VALUE(0x1403DC887, 0x1404378D7), find_script); utils::hook::call(0x1404378D7, find_script);
utils::hook::call(SELECT_VALUE(0x1403DC897, 0x1404378E7), db_is_x_asset_default); utils::hook::call(0x1404378E7, db_is_x_asset_default);
dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments"); dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
@@ -430,14 +419,6 @@ namespace gsc
} }
}); });
if (game::environment::is_sp())
{
utils::hook::call(0x14034996F, g_scr_load_script_and_label_stub);
utils::hook::call(0x140316591, scr_load_level_singleplayer_stub);
}
else
{
// GScr_LoadScripts // GScr_LoadScripts
utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub); utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub);
@@ -445,7 +426,6 @@ namespace gsc
utils::hook::call(0x14039F64E, g_load_structs_stub); utils::hook::call(0x14039F64E, g_load_structs_stub);
utils::hook::call(0x14039F653, scr_load_level_multiplayer_stub); utils::hook::call(0x14039F653, scr_load_level_multiplayer_stub);
} }
}
}; };
} }
+2 -2
View File
@@ -50,8 +50,8 @@ namespace input
return; return;
} }
cl_char_event_hook.create(game::CL_CharEvent, cl_char_event_stub); cl_char_event_hook.create(0x1402C2AE0, cl_char_event_stub);
cl_key_event_hook.create(game::CL_KeyEvent, cl_key_event_stub); cl_key_event_hook.create(0x1402C2CE0, cl_key_event_stub);
} }
}; };
} }
+1 -1
View File
@@ -58,7 +58,7 @@ namespace localized_strings
void post_unpack() override void post_unpack() override
{ {
// Change some localized strings // Change some localized strings
seh_string_ed_get_string_hook.create(SELECT_VALUE(0x1403F42D0, 0x1404A5F60), &seh_string_ed_get_string); seh_string_ed_get_string_hook.create(0x1404A5F60, &seh_string_ed_get_string);
} }
}; };
} }
+5 -40
View File
@@ -10,43 +10,15 @@ namespace logger
{ {
namespace namespace
{ {
utils::hook::detour com_error_hook;
const game::dvar_t* logger_dev = nullptr; 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, ...) void print_warning(const char* msg, ...)
{ {
char buffer[2048]{}; char buffer[2048]{};
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap); vsnprintf(buffer, sizeof(buffer), msg, ap);
va_end(ap); va_end(ap);
console::warn("%s", buffer); console::warn("%s", buffer);
@@ -58,7 +30,7 @@ namespace logger
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap); vsnprintf(buffer, sizeof(buffer), msg, ap);
va_end(ap); va_end(ap);
console::info("%s", buffer); console::info("%s", buffer);
@@ -75,7 +47,7 @@ namespace logger
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap); vsnprintf(buffer, sizeof(buffer), msg, ap);
va_end(ap); va_end(ap);
console::info("%s", buffer); console::info("%s", buffer);
@@ -141,17 +113,10 @@ namespace logger
sub_1401DAA40(); 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 // Make havok script's print function actually print
utils::hook::jump(SELECT_VALUE(0x1406283A4, 0x140732184), print); utils::hook::jump(0x140732184, print);
logger_dev = game::Dvar_RegisterBool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff"); logger_dev = game::Dvar_RegisterBool("logger_dev", false, game::DVAR_FLAG_NONE, "Print dev stuff");
} }
}; };
} }
+1 -1
View File
@@ -99,7 +99,7 @@ namespace map_rotation
console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name); console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name);
} }
#ifdef _DEBUG #ifdef _DEBUG
console::info("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size()); console::info("dedicated_rotation size after parsing is '%llu'\n", dedicated_rotation.get_entries_size());
#endif #endif
} }
+86 -14
View File
@@ -3,9 +3,12 @@
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "mods.hpp" #include "mods.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp>
namespace mods namespace mods
{ {
@@ -13,24 +16,24 @@ namespace mods
{ {
utils::hook::detour sys_create_file_hook; utils::hook::detour sys_create_file_hook;
void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, unsigned int size, char* filename) void db_build_os_path_from_source(const char* zone_name, const game::FF_DIR source, const int size, char* filename)
{ {
char user_map[MAX_PATH]{}; char user_map[MAX_PATH]{};
switch (source) switch (source)
{ {
case game::FFD_DEFAULT: case game::FFD_DEFAULT:
(void)sprintf_s(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name); (void)game::Com_sprintf(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name);
break; break;
case game::FFD_MOD_DIR: case game::FFD_MOD_DIR:
assert(mods::is_using_mods()); assert(mods::is_using_mods());
(void)sprintf_s(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name); (void)game::Com_sprintf(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name);
break; break;
case game::FFD_USER_MAP: case game::FFD_USER_MAP:
strncpy_s(user_map, zone_name, _TRUNCATE); game::I_strncpyz(user_map, zone_name, sizeof(user_map));
(void)sprintf_s(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name); (void)game::Com_sprintf(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name);
break; break;
default: default:
assert(false && "inconceivable"); assert(false && "inconceivable");
@@ -38,9 +41,9 @@ namespace mods
} }
} }
game::Sys_File sys_create_file_stub(const char* dir, const char* filename) game::Sys_File sys_create_file_stub(game::Sys_Folder folder, const char* base_filename)
{ {
auto result = sys_create_file_hook.invoke<game::Sys_File>(dir, filename); auto result = sys_create_file_hook.invoke<game::Sys_File>(folder, base_filename);
if (result.handle != INVALID_HANDLE_VALUE) if (result.handle != INVALID_HANDLE_VALUE)
{ {
@@ -53,7 +56,7 @@ namespace mods
} }
// .ff extension was added previously // .ff extension was added previously
if (!std::strcmp(filename, "mod.ff") && mods::db_mod_file_exists()) if (!std::strcmp(base_filename, "mod.ff") && mods::db_mod_file_exists())
{ {
char file_path[MAX_PATH]{}; char file_path[MAX_PATH]{};
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path); db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path);
@@ -98,6 +101,40 @@ namespace mods
a.and_(ebp, r15d); a.and_(ebp, r15d);
a.jmp(0x1403217F6); a.jmp(0x1403217F6);
}); });
bool fs_game_dir_domain_func(game::dvar_t* dvar, game::DvarValue new_value)
{
if (*new_value.string == '\0')
{
return true;
}
if (game::I_strnicmp(new_value.string, "mods", 4) != 0)
{
game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0);
console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name);
return false;
}
if (5 < std::strlen(new_value.string) && (new_value.string[4] == '\\' || new_value.string[4] == '/'))
{
const auto* s1 = std::strstr(new_value.string, "..");
const auto* s2 = std::strstr(new_value.string, "::");
if (s1 == nullptr && s2 == nullptr)
{
return true;
}
game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0);
console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name);
return false;
}
// Invalid path specified
game::LiveStorage_StatsWriteNotNeeded(game::CONTROLLER_INDEX_0);
console::error("ERROR: Invalid server value '%s' for '%s'\n", new_value.string, dvar->name);
return false;
}
} }
bool is_using_mods() bool is_using_mods()
@@ -131,15 +168,12 @@ namespace mods
void post_unpack() override void post_unpack() override
{ {
dvars::fs_gameDirVar = reinterpret_cast<game::dvar_t**>(SELECT_VALUE(0x145856D38, 0x147876000)); dvars::fs_gameDirVar = reinterpret_cast<game::dvar_t**>(0x147876000);
// Remove DVAR_INIT from fs_game // Remove DVAR_INIT from fs_game
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO)); utils::hook::set<std::uint32_t>(0x1404DDA45 + 2, game::DVAR_FLAG_SERVERINFO);
if (game::environment::is_sp()) utils::hook::inject(0x1404DDA57 + 3, &fs_game_dir_domain_func);
{
return;
}
// Don't load eng_ + patch_ with loadzone // Don't load eng_ + patch_ with loadzone
utils::hook::nop(0x1403217B1, 15); utils::hook::nop(0x1403217B1, 15);
@@ -150,6 +184,44 @@ namespace mods
// Load mod.ff // Load mod.ff
utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from
command::add("loadmod", [](const command::params& params) -> void
{
if (params.size() != 2)
{
console::info("USAGE: %s \"mods/<mod name>\"", params.get(0));
return;
}
std::string mod_name = utils::string::to_lower(params.get(1));
if (!mod_name.empty() && !mod_name.starts_with("mods/"))
{
mod_name = "mods/" + mod_name;
}
// change fs_game if needed
if (mod_name != (*dvars::fs_gameDirVar)->current.string)
{
game::Dvar_SetString((*dvars::fs_gameDirVar), mod_name.c_str());
command::execute("vid_restart\n");
}
});
command::add("unloadmod", [](const command::params& params) -> void
{
if (*dvars::fs_gameDirVar == nullptr || *(*dvars::fs_gameDirVar)->current.string == '\0')
{
return;
}
game::Dvar_SetString(*dvars::fs_gameDirVar, "");
command::execute("vid_restart\n");
});
// TODO: without a way to monitor all the ways fs_game can be changed there is no way to detect when we
// should unregister the path from the internal filesystem we use
// HINT: It could be done in fs_game_dir_domain_func, but I haven't tested if that's the best place to monitor for changes and register/unregister the mods folder
} }
}; };
} }
+10 -15
View File
@@ -20,7 +20,7 @@ namespace network
return callbacks; return callbacks;
} }
bool handle_command(game::netadr_t* address, const char* command, game::msg_t* message) bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message)
{ {
const auto cmd_string = utils::string::to_lower(command); const auto cmd_string = utils::string::to_lower(command);
auto& callbacks = get_callbacks(); auto& callbacks = get_callbacks();
@@ -61,7 +61,7 @@ namespace network
a.jmp(0x1402C64EE); a.jmp(0x1402C64EE);
} }
int net_compare_base_address(const game::netadr_t* a1, const game::netadr_t* a2) int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
{ {
if (a1->type == a2->type) if (a1->type == a2->type)
{ {
@@ -83,12 +83,12 @@ namespace network
return false; return false;
} }
int net_compare_address(const game::netadr_t* a1, const game::netadr_t* a2) 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; return net_compare_base_address(a1, a2) && a1->port == a2->port;
} }
void reconnect_migrated_client(game::mp::client_t*, game::netadr_t* from, const int, const int, const char*, const char*, bool) void reconnect_migrated_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 // 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 // We don't want this to do anything. It decides to crash seemingly randomly
@@ -107,14 +107,14 @@ namespace network
get_callbacks()[utils::string::to_lower(command)] = callback; get_callbacks()[utils::string::to_lower(command)] = callback;
} }
int dw_send_to_stub(const int size, const char* src, game::netadr_t* addr) int dw_send_to_stub(const int size, const char* src, game::netadr_s* addr)
{ {
sockaddr s = {}; sockaddr s = {};
game::NetadrToSockadr(addr, &s); game::NetadrToSockadr(addr, &s);
return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0; return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0;
} }
void send(const game::netadr_t& address, const std::string& command, const std::string& data, const char separator) 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"; std::string packet = "\xFF\xFF\xFF\xFF";
packet.append(command); packet.append(command);
@@ -124,7 +124,7 @@ namespace network
send_data(address, packet); send_data(address, packet);
} }
void send_data(const game::netadr_t& address, const std::string& data) void send_data(const game::netadr_s& address, const std::string& data)
{ {
if (address.type == game::NA_LOOPBACK) if (address.type == game::NA_LOOPBACK)
{ {
@@ -136,12 +136,12 @@ namespace network
} }
} }
bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b) bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b)
{ {
return net_compare_address(&a, &b); return net_compare_address(&a, &b);
} }
const char* net_adr_to_string(const game::netadr_t& a) const char* net_adr_to_string(const game::netadr_s& a)
{ {
if (a.type == game::NA_LOOPBACK) if (a.type == game::NA_LOOPBACK)
{ {
@@ -210,11 +210,6 @@ namespace network
void post_unpack() override void post_unpack() override
{ {
{ {
if (game::environment::is_sp())
{
return;
}
// redirect dw_sendto to raw socket // redirect dw_sendto to raw socket
utils::hook::jump(0x140501A00, dw_send_to_stub); utils::hook::jump(0x140501A00, dw_send_to_stub);
@@ -286,7 +281,7 @@ namespace network
if (!game::environment::is_dedi()) if (!game::environment::is_dedi())
{ {
// we need this on the client for RCon // we need this on the client for RCon
on("print", [](const game::netadr_t& address, const std::string& message) on("print", [](const game::netadr_s& address, const std::string& message)
{ {
if (address != party::get_target()) if (address != party::get_target())
{ {
+11 -11
View File
@@ -2,23 +2,23 @@
namespace network namespace network
{ {
using callback = std::function<void(const game::netadr_t&, const std::string&)>; using callback = std::function<void(const game::netadr_s&, const std::string&)>;
void on(const std::string& command, const callback& callback); void on(const std::string& command, const callback& callback);
void send(const game::netadr_t& address, const std::string& command, const std::string& data = {}, char separator = ' '); void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' ');
void send_data(const game::netadr_t& address, const std::string& data); void send_data(const game::netadr_s& address, const std::string& data);
bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b); bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b);
const char* net_adr_to_string(const game::netadr_t& a); const char* net_adr_to_string(const game::netadr_s& a);
} }
inline bool operator==(const game::netadr_t& a, const game::netadr_t& b) inline bool operator==(const game::netadr_s& a, const game::netadr_s& b)
{ {
return network::are_addresses_equal(a, b); // return network::are_addresses_equal(a, b); //
} }
inline bool operator!=(const game::netadr_t& a, const game::netadr_t& b) inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b)
{ {
return !(a == b); // return !(a == b); //
} }
@@ -26,20 +26,20 @@ inline bool operator!=(const game::netadr_t& a, const game::netadr_t& b)
namespace std namespace std
{ {
template <> template <>
struct equal_to<game::netadr_t> struct equal_to<game::netadr_s>
{ {
using result_type = bool; using result_type = bool;
bool operator()(const game::netadr_t& lhs, const game::netadr_t& rhs) const bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const
{ {
return network::are_addresses_equal(lhs, rhs); return network::are_addresses_equal(lhs, rhs);
} }
}; };
template <> template <>
struct hash<game::netadr_t> struct hash<game::netadr_s>
{ {
size_t operator()(const game::netadr_t& x) const noexcept 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); return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&x.ip[0])) ^ hash<uint16_t>()(x.port);
} }
+5 -12
View File
@@ -61,14 +61,12 @@ namespace notifies
scheduler::once([params, message, client_num] scheduler::once([params, message, client_num]
{ {
const auto* guid = game::SV_GetGuid(client_num); const auto* guid = game::SV_GetGuid(client_num);
const auto* name = game::mp::svs->clients[client_num].name; const auto* name = game::mp::svs_clients[client_num].name;
game_log::g_log_printf("%s;%s;%i;%s;%s\n", game_log::g_log_printf("%s;%s;%i;%s;%s\n", params.get(0), guid,
params.get(0),
guid,
client_num, client_num,
name, name,
message.data() message.c_str()
); );
}, scheduler::pipeline::server); }, scheduler::pipeline::server);
@@ -126,7 +124,7 @@ namespace notifies
a.lea(eax, dword_ptr(r15, -0x17)); a.lea(eax, dword_ptr(r15, -0x17));
a.mov(dword_ptr(rbp, 0x60), r15d); a.mov(dword_ptr(rbp, 0x60), r15d);
a.jmp(SELECT_VALUE(0x1403DF5B3, 0x14043A593)); a.jmp(0x14043A593);
a.bind(replace); a.bind(replace);
@@ -174,7 +172,7 @@ namespace notifies
public: public:
void post_unpack() override void post_unpack() override
{ {
utils::hook::jump(SELECT_VALUE(0x1403DF5A4, 0x14043A584), utils::hook::assemble(vm_execute_stub), true); utils::hook::jump(0x14043A584, utils::hook::assemble(vm_execute_stub), true);
scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void
{ {
@@ -184,11 +182,6 @@ namespace notifies
} }
}); });
if (game::environment::is_sp())
{
return;
}
utils::hook::call(0x1404724DD, client_command_stub); utils::hook::call(0x1404724DD, client_command_stub);
} }
}; };
+34 -51
View File
@@ -2,6 +2,7 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "game/engine/sv_game.hpp"
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
@@ -26,7 +27,7 @@ namespace party
{ {
struct struct
{ {
game::netadr_t host{}; game::netadr_s host{};
std::string challenge{}; std::string challenge{};
bool hostDefined{false}; bool hostDefined{false};
} connect_state; } connect_state;
@@ -37,13 +38,8 @@ namespace party
int sv_maxclients; int sv_maxclients;
void connect_to_party(const game::netadr_t& target, const std::string& mapname, const std::string& gametype) 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)) if (game::Live_SyncOnlineDataFlags(0))
{ {
scheduler::once([=]() scheduler::once([=]()
@@ -58,7 +54,7 @@ namespace party
// CL_ConnectFromParty // CL_ConnectFromParty
char session_info[0x100] = {}; char session_info[0x100] = {};
reinterpret_cast<void(*)(int, char*, const game::netadr_t*, const char*, const char*)>(0x1402C5700)( reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x1402C5700)(
0, session_info, &target, mapname.data(), gametype.data()); 0, session_info, &target, mapname.data(), gametype.data());
} }
@@ -95,7 +91,7 @@ namespace party
info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1"); info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1");
info.set("clients", std::to_string(get_client_count())); info.set("clients", std::to_string(get_client_count()));
info.set("bots", std::to_string(get_bot_count())); info.set("bots", std::to_string(get_bot_count()));
info.set("sv_maxclients", std::to_string(game::mp::svs->clientCount)); info.set("sv_maxclients", std::to_string(*game::mp::svs_clientCount));
info.set("protocol", std::to_string(PROTOCOL)); info.set("protocol", std::to_string(PROTOCOL));
info.set("shortversion", SHORTVERSION); info.set("shortversion", SHORTVERSION);
@@ -103,12 +99,6 @@ namespace party
} }
} }
void connect_to_dummy_party(const std::string& mapname, const std::string& gametype)
{
const game::netadr_t dummy_target{};
connect_to_party(dummy_target, mapname, gametype);
}
void switch_gamemode_if_necessary(const std::string& gametype) void switch_gamemode_if_necessary(const std::string& gametype)
{ {
const auto target_mode = gametype == "aliens" ? game::CODPLAYMODE_ALIENS : game::CODPLAYMODE_CORE; const auto target_mode = gametype == "aliens" ? game::CODPLAYMODE_ALIENS : game::CODPLAYMODE_CORE;
@@ -146,9 +136,9 @@ namespace party
int get_client_count() int get_client_count()
{ {
auto count = 0; auto count = 0;
for (auto i = 0; i < game::mp::svs->clientCount; ++i) for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{ {
if (game::mp::svs->clients[i].header.state >= game::CS_CONNECTED) if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED)
{ {
++count; ++count;
} }
@@ -160,10 +150,10 @@ namespace party
int get_bot_count() int get_bot_count()
{ {
auto count = 0; auto count = 0;
for (auto i = 0; i < game::mp::svs->clientCount; ++i) for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{ {
if (game::mp::svs->clients[i].header.state >= game::CS_CONNECTED && if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED &&
game::mp::svs->clients[i].testClient != game::TC_NONE) game::mp::svs_clients[i].testClient != game::TC_NONE)
{ {
++count; ++count;
} }
@@ -172,7 +162,7 @@ namespace party
return count; return count;
} }
game::netadr_t& get_target() game::netadr_s& get_target()
{ {
return connect_state.host; return connect_state.host;
} }
@@ -184,12 +174,15 @@ namespace party
int get_client_num_from_name(const std::string& name) int get_client_num_from_name(const std::string& name)
{ {
for (auto i = 0; !name.empty() && i < game::mp::svs->clientCount; ++i) for (auto i = 0; !name.empty() && i < *game::mp::svs_clientCount; ++i)
{ {
if (game::mp::g_entities[i].client) if (!game::mp::g_entities[i].client)
{ {
char client_name[16] = {0}; continue;
strncpy_s(client_name, game::mp::g_entities[i].client->sess.cs.name, sizeof(client_name)); }
char client_name[16]{};
game::I_strncpyz(client_name, game::mp::g_entities[i].client->sess.cs.name, sizeof(client_name));
game::I_CleanStr(client_name); game::I_CleanStr(client_name);
if (client_name == name) if (client_name == name)
@@ -197,17 +190,12 @@ namespace party
return i; return i;
} }
} }
}
return -1; return -1;
} }
void connect(const game::netadr_t& target) void connect(const game::netadr_s& target)
{ {
if (game::environment::is_sp())
{
return;
}
command::execute("lui_open popup_acceptinginvite", false); command::execute("lui_open popup_acceptinginvite", false);
connect_state.host = target; connect_state.host = target;
@@ -232,11 +220,6 @@ namespace party
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp())
{
return;
}
// hook disconnect command function // hook disconnect command function
utils::hook::jump(0x1402C6370, disconnect_stub); utils::hook::jump(0x1402C6370, disconnect_stub);
@@ -278,7 +261,7 @@ namespace party
return; return;
} }
game::netadr_t target{}; game::netadr_s target{};
if (game::NET_StringToAdr(params[1], &target)) if (game::NET_StringToAdr(params[1], &target))
{ {
connect(target); connect(target);
@@ -309,7 +292,7 @@ namespace party
} }
const auto client_num = atoi(params.get(1)); const auto client_num = atoi(params.get(1));
if (client_num < 0 || client_num >= game::mp::svs->clientCount) if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
{ {
return; return;
} }
@@ -346,7 +329,7 @@ namespace party
const std::string name = params.get(1); const std::string name = params.get(1);
if (name == "all"s) if (name == "all"s)
{ {
for (auto i = 0; i < game::mp::svs->clientCount; ++i) for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
{ {
scheduler::once([i, reason] scheduler::once([i, reason]
{ {
@@ -357,7 +340,7 @@ namespace party
} }
const auto client_num = get_client_num_from_name(name); const auto client_num = get_client_num_from_name(name);
if (client_num < 0 || client_num >= game::mp::svs->clientCount) if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
{ {
return; return;
} }
@@ -386,7 +369,7 @@ namespace party
const auto message = params.join(2); const auto message = params.join(2);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
console::info("%s -> %i: %s\n", name, client_num, message.data()); console::info("%s -> %i: %s\n", name, client_num, message.data());
}); });
@@ -400,7 +383,7 @@ namespace party
const auto client_num = atoi(params.get(1)); const auto client_num = atoi(params.get(1));
const auto message = params.join(2); const auto message = params.join(2);
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); game::engine::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
console::info("%i: %s\n", client_num, message.data()); console::info("%i: %s\n", client_num, message.data());
}); });
@@ -414,7 +397,7 @@ namespace party
const auto message = params.join(1); const auto message = params.join(1);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string; const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data())); game::engine::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
console::info("%s: %s\n", name, message.data()); console::info("%s: %s\n", name, message.data());
}); });
@@ -427,11 +410,11 @@ namespace party
const auto message = params.join(1); const auto message = params.join(1);
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data())); game::engine::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
console::info("%s\n", message.data()); console::info("%s\n", message.data());
}); });
network::on("getInfo", [](const game::netadr_t& target, const std::string& data) network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
{ {
utils::info_string info = get_info(); utils::info_string info = get_info();
info.set("challenge", data); info.set("challenge", data);
@@ -439,7 +422,7 @@ namespace party
network::send(target, "infoResponse", info.build(), '\n'); network::send(target, "infoResponse", info.build(), '\n');
}); });
network::on("getStatus", [](const game::netadr_t& target, const std::string& data) network::on("getStatus", [](const game::netadr_s& target, const std::string& data)
{ {
std::string player_list; std::string player_list;
@@ -454,7 +437,7 @@ namespace party
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; ++i) for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; ++i)
{ {
auto* client = &game::mp::svs->clients[i]; auto* client = &game::mp::svs_clients[i];
auto* self = &game::mp::g_entities[i]; auto* self = &game::mp::g_entities[i];
if (client->header.state < game::CS_ACTIVE) if (client->header.state < game::CS_ACTIVE)
@@ -473,8 +456,8 @@ namespace party
} }
const auto score = game::G_GetClientScore(i); const auto score = game::G_GetClientScore(i);
const auto ping = game::mp::svs->clients[i].ping; const auto ping = game::mp::svs_clients[i].ping;
const std::string name = game::mp::svs->clients[i].name; const std::string name = game::mp::svs_clients[i].name;
player_list.append(std::format("{} {} \"{}\"\n", score, ping, name)); player_list.append(std::format("{} {} \"{}\"\n", score, ping, name));
} }
@@ -487,7 +470,7 @@ namespace party
return; return;
} }
network::on("infoResponse", [](const game::netadr_t& target, const std::string& data) network::on("infoResponse", [](const game::netadr_s& target, const std::string& data)
{ {
const utils::info_string info(data); const utils::info_string info(data);
server_list::handle_info_response(target, info); server_list::handle_info_response(target, info);
+2 -4
View File
@@ -2,14 +2,12 @@
namespace party namespace party
{ {
void connect_to_dummy_party(const std::string& mapname, const std::string& gametype);
void switch_gamemode_if_necessary(const std::string& gametype); void switch_gamemode_if_necessary(const std::string& gametype);
void perform_game_initialization(); void perform_game_initialization();
void reset_connect_state(); void reset_connect_state();
void connect(const game::netadr_t& target); void connect(const game::netadr_s& target);
void map_restart(); void map_restart();
[[nodiscard]] int server_client_count(); [[nodiscard]] int server_client_count();
@@ -17,5 +15,5 @@ namespace party
[[nodiscard]] int get_client_count(); [[nodiscard]] int get_client_count();
[[nodiscard]] int get_bot_count(); [[nodiscard]] int get_bot_count();
[[nodiscard]] game::netadr_t& get_target(); [[nodiscard]] game::netadr_s& get_target();
} }
+35 -52
View File
@@ -2,6 +2,7 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "game/dvars.hpp" #include "game/dvars.hpp"
#include "game/engine/sv_game.hpp"
#include "command.hpp" #include "command.hpp"
#include "console.hpp" #include "console.hpp"
@@ -32,12 +33,13 @@ namespace patches
{ {
return; return;
} }
return sv_kick_client_num_hook.invoke<void>(clientNum, reason); return sv_kick_client_num_hook.invoke<void>(clientNum, reason);
} }
std::string get_login_username() std::string get_login_username()
{ {
char username[UNLEN + 1]; char username[UNLEN + 1]{};
DWORD username_len = UNLEN + 1; DWORD username_len = UNLEN + 1;
if (!GetUserNameA(username, &username_len)) if (!GetUserNameA(username, &username_len))
{ {
@@ -54,7 +56,7 @@ namespace patches
if (game::environment::is_mp()) if (game::environment::is_mp())
{ {
// Make name save // Make name save
game::Dvar_RegisterString("name", get_login_username().data(), game::DVAR_FLAG_SAVED, "Player name."); game::Dvar_RegisterString("name", get_login_username().c_str(), game::DVAR_FLAG_SAVED, "Player name.");
} }
return com_register_dvars_hook.invoke<void>(); return com_register_dvars_hook.invoke<void>();
@@ -72,8 +74,10 @@ namespace patches
if (exec_params.size() == 2) if (exec_params.size() == 2)
{ {
std::string file_name = exec_params.get(1); std::string file_name = exec_params.get(1);
if (file_name.find(".cfg") == std::string::npos) if (!file_name.ends_with(".cfg"))
{
file_name.append(".cfg"); file_name.append(".cfg");
}
const auto file = filesystem::file(file_name); const auto file = filesystem::file(file_name);
if (file.exists()) if (file.exists())
@@ -103,23 +107,6 @@ namespace patches
a.jmp(0x1403F7574); 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 -_- int dvar_command_patch() // game makes this return an int and compares with eax instead of al -_-
{ {
const command::params args{}; const command::params args{};
@@ -150,7 +137,7 @@ namespace patches
return 0; return 0;
} }
game::Font_t* get_chat_font_handle() game::Font_s* get_chat_font_handle()
{ {
return game::R_RegisterFont("fonts/bigFont"); return game::R_RegisterFont("fonts/bigFont");
} }
@@ -163,11 +150,11 @@ namespace patches
game::AimAssist_AddToTargetList(a1, a2); game::AimAssist_AddToTargetList(a1, a2);
} }
game::dvar_t* register_cg_fov_stub(const char* name, float value, float min, float /*max*/, game::dvar_t* register_cg_fov_stub(const char* name, const float value, const float min, [[maybe_unused]] const float max,
const unsigned int flags, const unsigned int flags,
const char* description) const char* description)
{ {
return game::Dvar_RegisterFloat(name, value, min, 160, flags | 1, description); return game::Dvar_RegisterFloat(name, value, min, 160, flags, description);
} }
void bsp_sys_error_stub(const char* error, const char* arg1) void bsp_sys_error_stub(const char* error, const char* arg1)
@@ -187,11 +174,11 @@ namespace patches
} }
utils::hook::detour cmd_lui_notify_server_hook; utils::hook::detour cmd_lui_notify_server_hook;
void cmd_lui_notify_server_stub(game::mp::gentity_t* ent) void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
{ {
command::params_sv params{}; command::params_sv params{};
const auto menu_id = atoi(params.get(1)); const auto menu_id = atoi(params.get(1));
const auto client = &game::mp::svs->clients[ent->s.clientNum]; const auto client = &game::mp::svs_clients[ent->s.clientNum];
// 9 => "end_game" // 9 => "end_game"
if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK) if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK)
@@ -232,14 +219,10 @@ namespace patches
LoadLibraryA("PhysXUpdateLoader64.dll"); LoadLibraryA("PhysXUpdateLoader64.dll");
// Unlock fps in main menu // Unlock fps in main menu
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x140242DDB, 0x1402CF58B), 0xEB); utils::hook::set<std::uint8_t>(0x1402CF58B, 0xEB);
// Unlock cg_fov // Unlock cg_fov
utils::hook::call(SELECT_VALUE(0x1401F3E96, 0x14027273C), register_cg_fov_stub); utils::hook::call(0x14027273C, register_cg_fov_stub);
if (game::environment::is_sp())
{
utils::hook::call(0x1401F3EC7, register_cg_fov_stub);
}
// changed max value from 85 -> 1000 // changed max value from 85 -> 1000
if (!game::environment::is_dedi()) if (!game::environment::is_dedi())
@@ -247,11 +230,8 @@ namespace patches
dvars::override::register_int("com_maxfps", 85, 0, 1000, game::DVAR_FLAG_SAVED); dvars::override::register_int("com_maxfps", 85, 0, 1000, game::DVAR_FLAG_SAVED);
} }
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. // 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); utils::hook::call(0x140476F4F, register_network_fps_stub);
}
// register cg_gun_ dvars with new values and flags // 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 // maybe _x can stay usable within a reasonable range? it can make scoped weapons DRASTICALLY better on high FOVs
@@ -263,33 +243,33 @@ namespace patches
dvars::override::register_float("cg_fovScale", 1.0f, 0.1f, 5.0f, game::DVAR_FLAG_SAVED); dvars::override::register_float("cg_fovScale", 1.0f, 0.1f, 5.0f, game::DVAR_FLAG_SAVED);
// Patch Dvar_Command to print out values how CoD4 does it // Patch Dvar_Command to print out values how CoD4 does it
utils::hook::jump(SELECT_VALUE(0x1403BFCB0, 0x140416A60), dvar_command_patch); utils::hook::jump(0x140416A60, dvar_command_patch);
// Allow executing custom cfg files with the "exec" command // Allow executing custom cfg files with the "exec" command
utils::hook::jump(SELECT_VALUE(0x1403B39BB, 0x1403F752B), SELECT_VALUE(0x1403B3A12, 0x1403F7582)); utils::hook::jump(0x1403F752B, 0x1403F7582);
//Use a relative jump to empty memory first //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); utils::hook::jump(0x1403F7582, 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) // Use empty memory to go to our stub first (can't do close jump, so need space for 12 bytes)
// Fix mouse lag // nop call to SetThreadExecutionState for good luck (???)
utils::hook::nop(SELECT_VALUE(0x14043E6CB, 0x140504A2B), 6); utils::hook::nop(0x140504A2B, 6);
// They are lying, this cannot actually fix a black screen on startup :(
scheduler::loop([]() scheduler::loop([]()
{ {
SetThreadExecutionState(ES_DISPLAY_REQUIRED); SetThreadExecutionState(ES_DISPLAY_REQUIRED);
}, scheduler::pipeline::main); }, scheduler::pipeline::main);
if (game::environment::is_sp())
{
patch_sp();
}
else
{
patch_mp(); patch_mp();
} }
}
static void patch_mp() static void patch_mp()
{ {
// Bypass Arxan function
utils::hook::nop(0x1404758C0, 16);
utils::hook::jump(0x1404758C0, game::engine::SV_GameSendServerCommand, true);
utils::hook::call(0x140477399, game::engine::SV_SendServerCommand);
// Register dvars // Register dvars
com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub); com_register_dvars_hook.create(0x140413A90, &com_register_dvars_stub);
@@ -326,6 +306,13 @@ namespace patches
dvars::override::register_int("igs_s1", 1, 0, 1, 0); dvars::override::register_int("igs_s1", 1, 0, 1, 0);
dvars::override::register_int("igs_crossgame", 1, 0, 1, 0); dvars::override::register_int("igs_crossgame", 1, 0, 1, 0);
// Required by UI scripts. Missing when joining a dedi and causes crashes
game::Dvar_RegisterInt("scr_gun_winlimit", 1, 0, 10, game::DVAR_FLAG_REPLICATED, "Win limit for Gun Game");
game::Dvar_RegisterInt("scr_gun_scorelimit", 18, 1, 1000, game::DVAR_FLAG_REPLICATED, "Score limit for Gun Game");
// Remove cheat protection from r_hudOutlinePostMode
utils::hook::set<uint8_t>(0x1405DDF23 + 2, 0x40);
// Patch game chat on resolutions higher than 1080p to use the right font // Patch game chat on resolutions higher than 1080p to use the right font
utils::hook::call(0x14025C825, get_chat_font_handle); utils::hook::call(0x14025C825, get_chat_font_handle);
utils::hook::call(0x1402BC42F, get_chat_font_handle); utils::hook::call(0x1402BC42F, get_chat_font_handle);
@@ -357,13 +344,9 @@ namespace patches
utils::hook::nop(0x1403A1A0F, 1); utils::hook::nop(0x1403A1A0F, 1);
// ^^ // ^^
utils::hook::nop(0x1403A072F, 5); // LiveStorage_RecordMovementInMatchdata utils::hook::nop(0x1403A072F, 5); // LiveStorage_RecordMovementInMatchdata
}
static void patch_sp() // Disable Com_Error in NET_SendPacket
{ utils::hook::nop(0x140501AE3, 5);
// SP doesn't initialize WSA
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
} }
}; };
} }
-5
View File
@@ -74,11 +74,6 @@ namespace ranked
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp())
{
return;
}
if (game::environment::is_mp()) if (game::environment::is_mp())
{ {
// This must be registered as 'true' to avoid crash when starting a private match // This must be registered as 'true' to avoid crash when starting a private match
+6 -7
View File
@@ -181,16 +181,15 @@ namespace raw_mouse
return; return;
} }
utils::hook::call(SELECT_VALUE(0x14043845F, 0x1404FC3FB), &in_mouse_move); utils::hook::call(0x1404FC3FB, &in_mouse_move);
utils::hook::call(SELECT_VALUE(0x140519F71, 0x1405E69A1), &create_window_ex_a_stub); utils::hook::call(0x1405E69A1, &create_window_ex_a_stub);
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x140519F76, 0x1405E69A6), 0x90); utils::hook::set<std::uint8_t>(0x1405E69A6, 0x90);
utils::hook::call(SELECT_VALUE(0x14043BF04, 0x140500824), &register_class_ex_a_stub); utils::hook::call(0x140500824, &register_class_ex_a_stub);
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x14043BF09, 0x140500829), 0x90); utils::hook::set<std::uint8_t>(0x140500829, 0x90);
cl_rawInput = game::Dvar_RegisterBool("cl_rawInput", true, cl_rawInput = game::Dvar_RegisterBool("cl_rawInput", true, game::DVAR_FLAG_SAVED, "Use Raw Input for mouse. This fixes mouse acceleration issue");
game::DVAR_FLAG_SAVED, "Use Raw Input for mouse input. This fixes mouse acceleration issue");
} }
}; };
} }
+11 -11
View File
@@ -14,11 +14,11 @@ namespace rcon
namespace namespace
{ {
bool is_redirecting_ = false; bool is_redirecting_ = false;
game::netadr_t redirect_target_ = {}; game::netadr_s redirect_target_ = {};
std::string redirect_buffer = {}; std::string redirect_buffer = {};
std::recursive_mutex redirect_lock; std::recursive_mutex redirect_lock;
void setup_redirect(const game::netadr_t& target) void setup_redirect(const game::netadr_s& target)
{ {
std::lock_guard<std::recursive_mutex> $(redirect_lock); std::lock_guard<std::recursive_mutex> $(redirect_lock);
@@ -50,15 +50,18 @@ namespace rcon
for (int i = 0; i < sv_maxclients->current.integer; i++) for (int i = 0; i < sv_maxclients->current.integer; i++)
{ {
const auto client = &game::mp::svs->clients[i]; const auto client = &game::mp::svs_clients[i];
auto self = &game::mp::g_entities[i]; auto self = &game::mp::g_entities[i];
if (client->header.state == game::CS_FREE || !self || !self->client)
{
continue;
}
char clean_name[32]{}; char clean_name[32]{};
strncpy_s(clean_name, self->client->sess.cs.name, sizeof(clean_name)); game::I_strncpyz(clean_name, self->client->sess.cs.name, sizeof(clean_name));
game::I_CleanStr(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", buffer.append(utils::string::va("%3i %5i %3s %s %32s %16s %21s %5i\n",
i, i,
self->client->sess.scores.score, self->client->sess.scores.score,
@@ -70,7 +73,6 @@ namespace rcon
client->header.netchan.remoteAddress.port) client->header.netchan.remoteAddress.port)
); );
} }
}
return buffer; return buffer;
} }
@@ -92,7 +94,7 @@ namespace rcon
if (*reinterpret_cast<std::int32_t*>(0x1419E1AE0) >= 5) //clientUIActive.connectionState >= CA_CONNECTED if (*reinterpret_cast<std::int32_t*>(0x1419E1AE0) >= 5) //clientUIActive.connectionState >= CA_CONNECTED
{ {
const auto target = *reinterpret_cast<game::netadr_t*>(0x141CB535C); const auto target = *reinterpret_cast<game::netadr_s*>(0x141CB535C);
const auto buffer = password + " " + data; const auto buffer = password + " " + data;
network::send(target, "rcon", buffer); network::send(target, "rcon", buffer);
} }
@@ -121,8 +123,6 @@ namespace rcon
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp()) return;
scheduler::once([]() scheduler::once([]()
{ {
game::Dvar_RegisterString("rcon_password", "", game::DvarFlags::DVAR_FLAG_NONE, game::Dvar_RegisterString("rcon_password", "", game::DvarFlags::DVAR_FLAG_NONE,
@@ -170,7 +170,7 @@ namespace rcon
} }
else else
{ {
network::on("rcon", [](const game::netadr_t& addr, const std::string& data) network::on("rcon", [](const game::netadr_s& addr, const std::string& data)
{ {
const auto pos = data.find_first_of(" "); const auto pos = data.find_first_of(" ");
if (pos == std::string::npos) if (pos == std::string::npos)
+3 -4
View File
@@ -46,12 +46,11 @@ namespace renderer
return; return;
} }
dvars::r_fullbright = game::Dvar_RegisterBool("r_fullbright", false, game::DvarFlags::DVAR_FLAG_SAVED, dvars::r_fullbright = game::Dvar_RegisterBool("r_fullbright", false, game::DVAR_FLAG_CHEAT,
"Toggles rendering without lighting"); "Toggles rendering without lighting");
r_init_draw_method_hook.create(SELECT_VALUE(0x1404FF600, 0x1405CB470), &r_init_draw_method_stub); r_init_draw_method_hook.create(0x1405CB470, &r_init_draw_method_stub);
r_update_front_end_dvar_options_hook.create( r_update_front_end_dvar_options_hook.create(0x140603240, &r_update_front_end_dvar_options_stub);
SELECT_VALUE(0x140535FF0, 0x140603240), &r_update_front_end_dvar_options_stub);
} }
}; };
} }
+4 -4
View File
@@ -149,7 +149,7 @@ namespace scheduler
{ {
schedule([=]() schedule([=]()
{ {
const auto dw_init = game::environment::is_sp() || game::Live_SyncOnlineDataFlags(0) == 0; const auto dw_init = game::Live_SyncOnlineDataFlags(0) == 0;
if (dw_init && game::Sys_IsDatabaseReady2()) if (dw_init && game::Sys_IsDatabaseReady2())
{ {
once(callback, type, delay); once(callback, type, delay);
@@ -177,10 +177,10 @@ namespace scheduler
void post_unpack() override void post_unpack() override
{ {
r_end_frame_hook.create(SELECT_VALUE(0x140534860, 0x140601AA0), scheduler::r_end_frame_stub); r_end_frame_hook.create(0x140601AA0, scheduler::r_end_frame_stub);
utils::hook::call(SELECT_VALUE(0x1403BC922, 0x140413142), scheduler::main_frame_stub); utils::hook::call(0x140413142, scheduler::main_frame_stub);
utils::hook::call(SELECT_VALUE(0x1403185FD, 0x1403A0AF9), scheduler::server_frame_stub); utils::hook::call(0x1403A0AF9, scheduler::server_frame_stub);
} }
void pre_destroy() override void pre_destroy() override
+4 -8
View File
@@ -195,16 +195,13 @@ namespace scripting
public: public:
void post_unpack() override void post_unpack() override
{ {
// SP address is wrong, but should be ok scr_load_level_hook.create(0x1403C4E60, &scr_load_level_stub);
scr_load_level_hook.create(SELECT_VALUE(0x14013D5D0, 0x1403C4E60), &scr_load_level_stub); g_shutdown_game_hook.create(0x1403A0DF0, &g_shutdown_game_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); scr_set_thread_position_hook.create(0x14042E360, &scr_set_thread_position_stub);
process_script_hook.create(SELECT_VALUE(0x1403DC870, 0x1404378C0), &process_script_stub); process_script_hook.create(0x1404378C0, &process_script_stub);
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, &sl_get_canonical_string_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 // Make some room for pre_main hook
utils::hook::jump(0x1402084A0, has_config_string_index); utils::hook::jump(0x1402084A0, has_config_string_index);
@@ -213,7 +210,6 @@ namespace scripting
utils::hook::set<uint16_t>(0x1402084D0, 0xD3EB); // jump to 0x1402084A5 utils::hook::set<uint16_t>(0x1402084D0, 0xD3EB); // jump to 0x1402084A5
g_find_config_string_index.create(0x140161F90, &g_find_config_string_index_stub); g_find_config_string_index.create(0x140161F90, &g_find_config_string_index_stub);
} }
}
}; };
} }
+75 -1
View File
@@ -10,6 +10,8 @@ namespace security
{ {
namespace namespace
{ {
utils::hook::detour ui_replace_directive_hook;
void set_cached_playerdata_stub(const int localclient, const int index1, const int index2) void set_cached_playerdata_stub(const int localclient, const int index1, const int index2)
{ {
if (index1 >= 0 && index1 < 18 && index2 >= 0 && index2 < 42) if (index1 >= 0 && index1 < 18 && index2 >= 0 && index2 < 42)
@@ -30,6 +32,71 @@ namespace security
utils::hook::invoke<void>(0x140472500, client, msg); utils::hook::invoke<void>(0x140472500, client, msg);
} }
void ui_replace_directive_stub(const int local_client_num, const char* src_string, char* dst_string, const int dst_buffer_size)
{
assert(src_string);
if (!src_string)
{
return;
}
assert(dst_string);
if (!dst_string)
{
return;
}
assert(dst_buffer_size > 0);
if (dst_buffer_size <= 0)
{
return;
}
constexpr std::size_t MAX_HUDELEM_TEXT_LEN = 0x100;
if (std::strlen(src_string) > MAX_HUDELEM_TEXT_LEN)
{
return;
}
ui_replace_directive_hook.invoke<void>(local_client_num, src_string, dst_string, dst_buffer_size);
}
int hud_elem_set_enum_string_stub(char* string, const char* format, ...)
{
va_list ap;
va_start(ap, format);
const auto len = vsnprintf(string, 0x800, format, ap);
va_end(ap);
string[0x800 - 1] = '\0';
return len;
}
int sv_add_bot_stub(char* string, const char* format, ...)
{
va_list ap;
va_start(ap, format);
const auto len = vsnprintf(string, 0x400, format, ap);
va_end(ap);
string[0x400 - 1] = '\0';
return len;
}
int sv_add_test_client_stub(char* string, const char* format, ...)
{
va_list ap;
va_start(ap, format);
const auto len = vsnprintf(string, 0x400, format, ap);
va_end(ap);
string[0x400 - 1] = '\0';
return len;
}
} }
class component final : public component_interface class component final : public component_interface
@@ -37,13 +104,20 @@ namespace security
public: public:
void post_unpack() override void post_unpack() override
{ {
if (game::environment::is_sp()) return; // sprinf
utils::hook::call(0x140399B0F, hud_elem_set_enum_string_stub);
// Patch vulnerability in PlayerCards_SetCachedPlayerData // Patch vulnerability in PlayerCards_SetCachedPlayerData
utils::hook::call(0x140287C5C, set_cached_playerdata_stub); utils::hook::call(0x140287C5C, set_cached_playerdata_stub);
// sprinf
utils::hook::call(0x140470A88, sv_add_bot_stub);
utils::hook::call(0x140470F68, sv_add_test_client_stub);
// It is possible to make the server hang if left unchecked // It is possible to make the server hang if left unchecked
utils::hook::call(0x14047A29A, sv_execute_client_message_stub); utils::hook::call(0x14047A29A, sv_execute_client_message_stub);
ui_replace_directive_hook.create(0x1404D8A00, ui_replace_directive_stub);
} }
}; };
} }
+7 -7
View File
@@ -33,14 +33,14 @@ namespace server_list
std::string map_name; std::string map_name;
std::string game_type; std::string game_type;
char in_game; char in_game;
game::netadr_t address; game::netadr_s address;
}; };
struct struct
{ {
game::netadr_t address{}; game::netadr_s address{};
volatile bool requesting = false; volatile bool requesting = false;
std::unordered_map<game::netadr_t, int> queued_servers{}; std::unordered_map<game::netadr_s, int> queued_servers{};
} master_state; } master_state;
volatile bool update_server_list = false; volatile bool update_server_list = false;
@@ -307,12 +307,12 @@ namespace server_list
} }
} }
bool get_master_server(game::netadr_t& address) bool get_master_server(game::netadr_s& address)
{ {
return game::NET_StringToAdr("server.alterware.dev:20810", &address); return game::NET_StringToAdr("server.alterware.dev:20810", &address);
} }
void handle_info_response(const game::netadr_t& address, const utils::info_string& info) void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
{ {
int start_time{}; int start_time{};
const auto now = game::Sys_Milliseconds(); const auto now = game::Sys_Milliseconds();
@@ -436,7 +436,7 @@ namespace server_list
scheduler::loop(do_frame_work, scheduler::pipeline::main); scheduler::loop(do_frame_work, scheduler::pipeline::main);
network::on("getServersResponse", [](const game::netadr_t& target, const std::string& data) network::on("getServersResponse", [](const game::netadr_s& target, const std::string& data)
{ {
{ {
std::lock_guard<std::mutex> _(mutex); std::lock_guard<std::mutex> _(mutex);
@@ -469,7 +469,7 @@ namespace server_list
break; break;
} }
game::netadr_t address{}; game::netadr_s address{};
address.type = game::NA_IP; address.type = game::NA_IP;
address.localNetID = game::NS_CLIENT1; address.localNetID = game::NS_CLIENT1;
std::memcpy(&address.ip[0], data.data() + i + 0, 4); std::memcpy(&address.ip[0], data.data() + i + 0, 4);
+2 -2
View File
@@ -3,8 +3,8 @@
namespace server_list namespace server_list
{ {
bool get_master_server(game::netadr_t& address); bool get_master_server(game::netadr_s& address);
void handle_info_response(const game::netadr_t& address, const utils::info_string& info); void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
bool sl_key_event(int key, int down); bool sl_key_event(int key, int down);
} }
+31 -4
View File
@@ -2,6 +2,9 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "command.hpp"
#include "console.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@@ -50,10 +53,29 @@ namespace slowmotion
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; i++) for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; i++)
{ {
auto* client = &game::mp::svs->clients[i]; auto* client = &game::mp::svs_clients[i];
client->nextSnapshotTime = game::mp::svs->time - 1; client->nextSnapshotTime = *game::mp::serverTime - 1;
} }
} }
void set_code_time_scale(float timescale)
{
assert(timescale > 0.0f);
*game::com_codeTimeScale = timescale;
}
void com_timescale_f(const command::params& params)
{
if (params.size() != 2)
{
console::info("timescale <rate>\n");
return;
}
const auto timescale = static_cast<float>(std::atof(params.get(1)));
console::info("timescale set to %f\n", timescale);
set_code_time_scale(timescale);
}
} }
class component final : public component_interface class component final : public component_interface
@@ -61,12 +83,17 @@ namespace slowmotion
public: public:
void post_unpack() override void post_unpack() override
{ {
if (!game::environment::is_dedi()) return; command::add("timescale", com_timescale_f);
if (!game::environment::is_dedi())
{
return;
}
utils::hook::jump(0x1403B4A10, scr_cmd_set_slow_motion); 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 // Detour used here instead of call hook because Com_TimeScaleMsec is called from arxan encrypted function
com_timescale_msec_hook.create(game::Com_TimeScaleMsec, com_timescale_msec); com_timescale_msec_hook.create(0x140415D50, com_timescale_msec);
} }
}; };
} }
+3 -3
View File
@@ -30,9 +30,9 @@ namespace splash
void post_unpack() override void post_unpack() override
{ {
// Disable native splash screen // Disable native splash screen
utils::hook::nop(SELECT_VALUE(0x14043C15B, 0x140500ADB), 5); utils::hook::nop(0x140500ADB, 5);
utils::hook::jump(SELECT_VALUE(0x14043D5A0, 0x140502000), destroy_stub); utils::hook::jump(0x140502000, destroy_stub);
utils::hook::jump(SELECT_VALUE(0x14043D5E0, 0x140502040), destroy_stub); utils::hook::jump(0x140502040, destroy_stub);
} }
void pre_destroy() override void pre_destroy() override
+34 -4
View File
@@ -26,6 +26,8 @@ namespace steam_proxy
ownership_state state_; ownership_state state_;
utils::binary_resource runner_file(RUNNER, "runner.exe"); utils::binary_resource runner_file(RUNNER, "runner.exe");
bool is_disabled() { return true; }
} }
class component final : public component_interface class component final : public component_interface
@@ -33,7 +35,7 @@ namespace steam_proxy
public: public:
void post_load() override void post_load() override
{ {
if (game::environment::is_dedi()) if (game::environment::is_dedi() || is_disabled())
{ {
return; return;
} }
@@ -44,16 +46,27 @@ namespace steam_proxy
#ifndef DEV_BUILD #ifndef DEV_BUILD
try try
{ {
const std::string game_type = game::environment::is_sp() ? "singleplayer" : "multiplayer"; const std::string game_type = "multiplayer";
state_ = this->start_mod("\xF0\x9F\x92\x8E" " iw6-mod: " + (game_type), game::environment::is_sp() ? 209160 : 209170); state_ = this->start_mod("\xF0\x9F\x92\x8E" " iw6-mod: " + (game_type), 209170);
} }
catch (const std::exception& ex) catch (const std::exception& ex)
{ {
state_ = ownership_state::error; state_ = ownership_state::error;
printf("Steam: %s\n", ex.what()); printf("Steam: %s\n", ex.what());
} }
#ifdef STEAM_OWNERSHIP_CHECK
if (utils::nt::is_wine())
{
state_ = ownership_state::success;
return;
}
#endif
#ifdef STEAM_OWNERSHIP_CHECK
evaluate_ownership_state(state_);
#endif
#endif #endif
(void)state_;
} }
void pre_destroy() override void pre_destroy() override
@@ -210,6 +223,23 @@ namespace steam_proxy
return scheduler::cond_end; return scheduler::cond_end;
}); });
} }
#ifdef STEAM_OWNERSHIP_CHECK
void evaluate_ownership_state(const ownership_state state)
{
switch (state)
{
case ownership_state::nosteam:
throw std::runtime_error("Steam must be running to play this game!");
case ownership_state::unowned:
throw std::runtime_error("You must own the game on Steam to play this mod!");
case ownership_state::error:
throw std::runtime_error("Failed to verify ownership of the game!");
case ownership_state::success:
break;
}
}
#endif
}; };
} }
+2 -3
View File
@@ -51,13 +51,12 @@ namespace system_check
{ {
static std::unordered_map<std::string, std::string> mp_zone_hashes = static std::unordered_map<std::string, std::string> mp_zone_hashes =
{ {
{"patch_common_mp.ff", "F1F08BFD03D0496199FAFD49CAA3E5786B70084A4C3C8841ACC4A7B7616D226C"}, {"patch_common_mp.ff", "1731093802719CFD4F35FE1D16373D9F97BBB4E75E72D1AE4A84996CF51C6870"},
}; };
static std::unordered_map<std::string, std::string> sp_zone_hashes = static std::unordered_map<std::string, std::string> sp_zone_hashes =
{ {
{"patch_common.ff", "883DB33A1E386420EC6EF19F25C0D8081D01C1945BA2BCAD9FBD5460A201D6AA"}, {"patch_common_alien_mp.ff", "22F0CE99DD26376F44AE896FF8307CB38E14B65C805548E00A6251465CB6666A"},
{"patch_common_alien_mp.ff", "78B00BFF961F69F9A45446D40638A5A7F5C3462F1AF05A833066772C62FB5DB2"},
}; };
return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes)); return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes));
+41 -22
View File
@@ -32,15 +32,13 @@ namespace ui_scripting
std::string root; std::string root;
}; };
struct globals struct
{ {
std::string in_require_script; std::string in_require_script;
std::vector<script> loaded_scripts; std::vector<script> loaded_scripts;
bool load_raw_script{}; bool load_raw_script{};
std::string raw_script_name{}; std::string raw_script_name{};
}; } globals;
globals globals;
bool is_loaded_script(const std::string& name) bool is_loaded_script(const std::string& name)
{ {
@@ -156,7 +154,7 @@ namespace ui_scripting
game_type["issingleplayer"] = [](const game&) game_type["issingleplayer"] = [](const game&)
{ {
return ::game::environment::is_sp(); return false;
}; };
game_type["ismultiplayer"] = [](const game&) game_type["ismultiplayer"] = [](const game&)
@@ -195,6 +193,27 @@ namespace ui_scripting
setup_functions(); setup_functions();
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x14017B120)); // hks::base_print lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x14017B120)); // hks::base_print
lua["directoryexists"] = [](const std::string& string)
{
return utils::io::directory_exists(string);
};
lua["listfiles"] = [](const std::string& string)
{
return utils::io::list_files(string);
};
lua["directoryisempty"] = [](const std::string& string)
{
return utils::io::directory_is_empty(string);
};
lua["fileexists"] = [](const std::string& string)
{
return utils::io::file_exists(string);
};
lua["table"]["unpack"] = lua["unpack"]; lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua; lua["luiglobals"] = lua;
@@ -276,7 +295,7 @@ namespace ui_scripting
void* lua_atpanic_stub(void* l, [[maybe_unused]] void* panicf) void* lua_atpanic_stub(void* l, [[maybe_unused]] void* panicf)
{ {
return utils::hook::invoke<void*>(SELECT_VALUE(0x1401851F0, 0x1401A4730), l, SELECT_VALUE(0x1401789A0, 0x140197BC0)); return utils::hook::invoke<void*>(0x1401A4730, l, 0x140197BC0);
} }
int luaopen_stub([[maybe_unused]] void* l) int luaopen_stub([[maybe_unused]] void* l)
@@ -341,31 +360,31 @@ namespace ui_scripting
void post_unpack() override void post_unpack() override
{ {
// Disable debug breakpoints in the assembly of hksDefaultPanic // Disable debug breakpoints in the assembly of hksDefaultPanic
utils::hook::set<std::uint8_t>(SELECT_VALUE(0x140178C5E, 0x140197E7E), 0x90); utils::hook::set<std::uint8_t>(0x140197E7E, 0x90);
// Restore hksDefaultPanic for complete error messages // Restore hksDefaultPanic for complete error messages
utils::hook::call(SELECT_VALUE(0x1401B0028, 0x1401CE6C8), lua_atpanic_stub); utils::hook::call(0x1401CE6C8, lua_atpanic_stub);
// Do not allow the HKS vm to open LUA's libraries // Do not allow the HKS vm to open LUA's libraries
utils::hook::jump(SELECT_VALUE(0x14015BD10, 0x14017E040), luaopen_stub); // io utils::hook::jump(0x14017E040, luaopen_stub); // io
utils::hook::jump(SELECT_VALUE(0x14015C3D0, 0x14017E700), luaopen_stub); // os utils::hook::jump(0x14017E700, luaopen_stub); // os
utils::hook::jump(SELECT_VALUE(0x14015D400, 0x14017F730), luaopen_stub); // serialize utils::hook::jump(0x14017F730, luaopen_stub); // serialize
utils::hook::jump(SELECT_VALUE(0x14015D3D0, 0x14017F700), luaopen_stub); // havokscript utils::hook::jump(0x14017F700, luaopen_stub); // havokscript
utils::hook::jump(SELECT_VALUE(0x14015C930, 0x14017EC60), luaopen_stub); // debug utils::hook::jump(0x14017EC60, luaopen_stub); // debug
utils::hook::nop(SELECT_VALUE(0x14015B639, 0x14017D969), 5); // coroutine utils::hook::nop(0x14017D969, 5); // coroutine
// Disable unsafe functions // Disable unsafe functions
utils::hook::jump(SELECT_VALUE(0x140158C20, 0x14017AF50), hks_base_stub); // base_loadfile utils::hook::jump(0x14017AF50, hks_base_stub); // base_loadfile
utils::hook::jump(SELECT_VALUE(0x140158B10, 0x14017AE40), hks_base_stub); // base_dofile utils::hook::jump(0x14017AE40, hks_base_stub); // base_dofile
utils::hook::jump(SELECT_VALUE(0x14015D7E0, 0x14017FB10), hks_base_stub); // base_load utils::hook::jump(0x14017FB10, hks_base_stub); // base_load
utils::hook::jump(SELECT_VALUE(0x1401569D0, 0x140178CF0), hks_base_stub); // package_loadlib utils::hook::jump(0x140178CF0, hks_base_stub); // package_loadlib
utils::hook::jump(SELECT_VALUE(0x140163CD0, 0x140177E00), hks_base_stub); // package_c_loader utils::hook::jump(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(0x140177F10, hks_base_stub); // package_all_in_one_loader
utils::hook::jump(SELECT_VALUE(0x140157DA0, 0x14017A0D0), hks_base_stub); // string_dump utils::hook::jump(0x14017A0D0, hks_base_stub); // string_dump
utils::hook::jump(SELECT_VALUE(0x1401606A0, 0x1401747D0), hks_base_stub); // os_execute utils::hook::jump(0x1401747D0, hks_base_stub); // os_execute
if (!game::environment::is_mp()) if (!game::environment::is_mp())
{ {
+16 -90
View File
@@ -59,19 +59,6 @@ namespace ultrawide
} }
} }
// 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) auto ultrawide_spawn_window_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{ {
a.call_aligned(0x1405E5E70); // og - setup hwnd struct a.call_aligned(0x1405E5E70); // og - setup hwnd struct
@@ -84,27 +71,6 @@ namespace ultrawide
a.jmp(0x1405E6903); 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) auto ultrawide_menu_stub_01_mp = utils::hook::assemble([](utils::hook::assembler& a)
{ {
a.push(eax); a.push(eax);
@@ -124,22 +90,6 @@ namespace ultrawide
a.jmp(0x1402008D2); 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) auto ultrawide_menu_stub_02_mp = utils::hook::assemble([](utils::hook::assembler& a)
{ {
a.push(eax); a.push(eax);
@@ -154,22 +104,6 @@ namespace ultrawide
a.jmp(0x1402008FF); 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) auto ultrawide_hud_stub_mp = utils::hook::assemble([](utils::hook::assembler& a)
{ {
a.push(eax); a.push(eax);
@@ -191,45 +125,37 @@ namespace ultrawide
void post_unpack() override void post_unpack() override
{ {
// ultrawide patches // ultrawide patches
utils::hook::call(SELECT_VALUE(0x140510D0E, 0x1405DCF58), dvar_register_aspect_ratio); utils::hook::call(0x1405DCF58, dvar_register_aspect_ratio);
// register r_aspectRatioCustom // register r_aspectRatioCustom
utils::hook::jump( utils::hook::jump(0x1405E68F7, ultrawide_spawn_window_stub_mp, true);
SELECT_VALUE(0x140519EC7, 0x1405E68F7),
SELECT_VALUE(ultrawide_spawn_window_stub_sp, ultrawide_spawn_window_stub_mp), true);
// apply general aspect ratio // apply general aspect ratio
utils::hook::jump( utils::hook::jump(0x1402008BB, ultrawide_menu_stub_01_mp, true);
SELECT_VALUE(0x1401CD75B, 0x1402008BB),
SELECT_VALUE(ultrawide_menu_stub_01_sp, ultrawide_menu_stub_01_mp), true);
// fix menu aspect ratio and left offset // fix menu aspect ratio and left offset
utils::hook::jump( utils::hook::jump(0x1402008F3, ultrawide_menu_stub_02_mp, true);
SELECT_VALUE(0x1401CD793, 0x1402008F3),
SELECT_VALUE(ultrawide_menu_stub_02_sp, ultrawide_menu_stub_02_mp), true);
// fix menu aspect ratio right offset // fix menu aspect ratio right offset
utils::hook::jump( utils::hook::jump(0x1402F7043, ultrawide_hud_stub_mp, true); // fix aspect ratio for loadscreen and general in-game hud
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) // safeArea_adjusted dvars :: disable resets (in-game hud padding)
utils::hook::nop(SELECT_VALUE(0x14024AEF2, 0x1402D4FB2), 5); // safeArea_adjusted_horizontal utils::hook::nop(0x1402D4FB2, 5); // safeArea_adjusted_horizontal
utils::hook::nop(SELECT_VALUE(0x14024D655, 0x1402F7375), 5); // safeArea_adjusted_horizontal utils::hook::nop(0x1402F7375, 5); // safeArea_adjusted_horizontal
utils::hook::nop(SELECT_VALUE(0x14024AF19, 0x1402D4FD9), 5); utils::hook::nop(0x1402D4FD9, 5);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024AF19, 0x1402D4FD9), 0xC3); utils::hook::set<uint8_t>(0x1402D4FD9, 0xC3);
// safeArea_adjusted_vertical :: return instead of jumping to Dvar_SetFloat // safeArea_adjusted_vertical :: return instead of jumping to Dvar_SetFloat
utils::hook::nop(SELECT_VALUE(0x14024D664, 0x1402F7384), 5); // safeArea_adjusted_vertical utils::hook::nop(0x1402F7384, 5); // safeArea_adjusted_vertical
// safeArea_ dvars :: remove cheat protection + add saved flag // safeArea_ dvars :: remove cheat protection + add saved flag
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D19F + 4, 0x1402F6EBF + 4), 0x1); utils::hook::set<uint8_t>(0x1402F6EBF + 4, 0x1);
// register safeArea_horizontal (mov dword ptr [rsp+20h], 4) // register safeArea_horizontal (mov dword ptr [rsp+20h], 4)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D1D7 + 4, 0x1402F6EF7 + 4), 0x1); utils::hook::set<uint8_t>(0x1402F6EF7 + 4, 0x1);
// register safeArea_vertical (mov dword ptr [rsp+20h], 4) // register safeArea_vertical (mov dword ptr [rsp+20h], 4)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D207 + 5, 0x1402F6F27 + 5), 0x0); utils::hook::set<uint8_t>(0x1402F6F27 + 5, 0x0);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D207 + 4, 0x1402F6F27 + 4), 0x1); utils::hook::set<uint8_t>(0x1402F6F27 + 4, 0x1);
// register safeArea_adjusted_horizontal (mov dword ptr [rsp+20h], 2000h) // register safeArea_adjusted_horizontal (mov dword ptr [rsp+20h], 2000h)
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D237 + 5, 0x1402F6F57 + 5), 0x0); utils::hook::set<uint8_t>(0x1402F6F57 + 5, 0x0);
utils::hook::set<BYTE>(SELECT_VALUE(0x14024D237 + 4, 0x1402F6F57 + 4), 0x1); utils::hook::set<uint8_t>(0x1402F6F57 + 4, 0x1);
// register safeArea_adjusted_vertical (mov dword ptr [rsp+20h], 2000h) // register safeArea_adjusted_vertical (mov dword ptr [rsp+20h], 2000h)
} }
}; };
+3 -3
View File
@@ -30,7 +30,7 @@ namespace demonware
uint64_t file_id; uint64_t file_id;
uint32_t create_time; uint32_t create_time;
uint32_t modified_time; uint32_t modified_time;
bool priv; bool visibility;
uint64_t owner_id; uint64_t owner_id;
std::string filename; std::string filename;
uint32_t file_size; uint32_t file_size;
@@ -41,7 +41,7 @@ namespace demonware
buffer->write_uint64(this->file_id); buffer->write_uint64(this->file_id);
buffer->write_uint32(this->create_time); buffer->write_uint32(this->create_time);
buffer->write_uint32(this->modified_time); buffer->write_uint32(this->modified_time);
buffer->write_bool(this->priv); buffer->write_bool(this->visibility);
buffer->write_uint64(this->owner_id); buffer->write_uint64(this->owner_id);
buffer->write_string(this->filename); buffer->write_string(this->filename);
} }
@@ -52,7 +52,7 @@ namespace demonware
buffer->read_uint64(&this->file_id); buffer->read_uint64(&this->file_id);
buffer->read_uint32(&this->create_time); buffer->read_uint32(&this->create_time);
buffer->read_uint32(&this->modified_time); buffer->read_uint32(&this->modified_time);
buffer->read_bool(&this->priv); buffer->read_bool(&this->visibility);
buffer->read_uint64(&this->owner_id); buffer->read_uint64(&this->owner_id);
buffer->read_string(&this->filename); buffer->read_string(&this->filename);
} }
+20 -4
View File
@@ -10,19 +10,35 @@ namespace demonware
this->register_service(4, &bdGroup::get_groups); this->register_service(4, &bdGroup::get_groups);
} }
void bdGroup::set_groups(i_server* server, byte_buffer* /*buffer*/) const void bdGroup::set_groups(i_server* server, byte_buffer* buffer)
{ {
//uint32_t groupCount; uint32_t entries_count{};
// TODO: Implement array reading buffer->read_array_header(game::BD_BB_UNSIGNED_INTEGER32_TYPE, &entries_count);
auto reply = server->create_reply(this->get_sub_type()); auto reply = server->create_reply(this->get_sub_type());
buffer->set_use_data_types(false);
for (uint32_t i = 0; i < entries_count; ++i)
{
uint32_t group_id{};
buffer->read_uint32(&group_id);
if (group_id < ARRAYSIZE(this->groups))
{
this->groups[group_id] = 999;
}
}
buffer->set_use_data_types(true);
reply->send(); reply->send();
} }
void bdGroup::get_groups(i_server* server, byte_buffer* buffer) void bdGroup::get_groups(i_server* server, byte_buffer* buffer)
{ {
uint32_t group_count; uint32_t group_count;
buffer->read_array_header(8, &group_count); buffer->read_array_header(game::BD_BB_UNSIGNED_INTEGER32_TYPE, &group_count);
auto reply = server->create_reply(this->get_sub_type()); auto reply = server->create_reply(this->get_sub_type());
@@ -9,7 +9,7 @@ namespace demonware
bdGroup(); bdGroup();
private: private:
void set_groups(i_server* server, byte_buffer* buffer) const; void set_groups(i_server* server, byte_buffer* buffer);
void get_groups(i_server* server, byte_buffer* buffer); void get_groups(i_server* server, byte_buffer* buffer);
uint32_t groups[512]{}; uint32_t groups[512]{};
@@ -17,7 +17,7 @@ namespace demonware
{ {
std::string get_motd_text() std::string get_motd_text()
{ {
return "This is not a copy & pasted client"; return "Welcome to iw6-mod. Did you know aiming down sights increases accuracy? Online Interactions Not Rated by the ESRB.";
} }
} }
@@ -119,11 +119,11 @@ namespace demonware
void bdStorage::set_legacy_user_file(i_server* server, byte_buffer* buffer) const void bdStorage::set_legacy_user_file(i_server* server, byte_buffer* buffer) const
{ {
bool priv; bool visibility;
std::string filename, data; std::string filename, data;
buffer->read_string(&filename); buffer->read_string(&filename);
buffer->read_bool(&priv); buffer->read_bool(&visibility);
buffer->read_blob(&data); buffer->read_blob(&data);
const auto id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data()); const auto id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data());
@@ -139,11 +139,11 @@ namespace demonware
info->file_id = id; info->file_id = id;
info->filename = filename; info->filename = filename;
info->create_time = uint32_t(time(nullptr)); info->create_time = static_cast<uint32_t>(std::time(nullptr));
info->modified_time = info->create_time; info->modified_time = info->create_time;
info->file_size = uint32_t(data.size()); info->file_size = static_cast<uint32_t>(data.size());
info->owner_id = 0; info->owner_id = 0;
info->priv = priv; info->visibility = visibility;
auto reply = server->create_reply(this->get_sub_type()); auto reply = server->create_reply(this->get_sub_type());
reply->add(info); reply->add(info);
@@ -170,11 +170,11 @@ namespace demonware
info->file_id = id; info->file_id = id;
info->filename = "<>"; info->filename = "<>";
info->create_time = uint32_t(time(nullptr)); info->create_time = static_cast<uint32_t>(std::time(nullptr));
info->modified_time = info->create_time; info->modified_time = info->create_time;
info->file_size = uint32_t(data.size()); info->file_size = static_cast<uint32_t>(data.size());
info->owner_id = 0; info->owner_id = 0;
info->priv = false; info->visibility = false;
auto reply = server->create_reply(this->get_sub_type()); auto reply = server->create_reply(this->get_sub_type());
reply->add(info); reply->add(info);
@@ -207,12 +207,12 @@ namespace demonware
void bdStorage::list_legacy_user_files(i_server* server, byte_buffer* buffer) const void bdStorage::list_legacy_user_files(i_server* server, byte_buffer* buffer) const
{ {
uint64_t unk; uint64_t owner;
uint32_t date; uint32_t date;
uint16_t num_results, offset; uint16_t num_results, offset;
std::string filename, data; std::string filename, data;
buffer->read_uint64(&unk); buffer->read_uint64(&owner);
buffer->read_uint32(&date); buffer->read_uint32(&date);
buffer->read_uint16(&num_results); buffer->read_uint16(&num_results);
buffer->read_uint16(&offset); buffer->read_uint16(&offset);
@@ -229,9 +229,9 @@ namespace demonware
info->filename = filename; info->filename = filename;
info->create_time = 0; info->create_time = 0;
info->modified_time = info->create_time; info->modified_time = info->create_time;
info->file_size = uint32_t(data.size()); info->file_size = static_cast<uint32_t>(data.size());
info->owner_id = 0; info->owner_id = owner;
info->priv = false; info->visibility = false;
reply->add(info); reply->add(info);
} }
@@ -260,9 +260,9 @@ namespace demonware
info->filename = filename; info->filename = filename;
info->create_time = 0; info->create_time = 0;
info->modified_time = info->create_time; info->modified_time = info->create_time;
info->file_size = uint32_t(data.size()); info->file_size = static_cast<uint32_t>(data.size());
info->owner_id = 0; info->owner_id = 0;
info->priv = false; info->visibility = false;
reply->add(info); reply->add(info);
} }
@@ -312,13 +312,13 @@ namespace demonware
void bdStorage::set_user_file(i_server* server, byte_buffer* buffer) const void bdStorage::set_user_file(i_server* server, byte_buffer* buffer) const
{ {
bool priv; bool visibility;
uint64_t owner; uint64_t owner;
std::string game, filename, data; std::string game, filename, data;
buffer->read_string(&game); buffer->read_string(&game);
buffer->read_string(&filename); buffer->read_string(&filename);
buffer->read_bool(&priv); buffer->read_bool(&visibility);
buffer->read_blob(&data); buffer->read_blob(&data);
buffer->read_uint64(&owner); buffer->read_uint64(&owner);
@@ -329,11 +329,11 @@ namespace demonware
info->file_id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data()); info->file_id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data());
info->filename = filename; info->filename = filename;
info->create_time = uint32_t(time(nullptr)); info->create_time = static_cast<uint32_t>(std::time(nullptr));
info->modified_time = info->create_time; info->modified_time = info->create_time;
info->file_size = uint32_t(data.size()); info->file_size = static_cast<uint32_t>(data.size());
info->owner_id = owner; info->owner_id = owner;
info->priv = priv; info->visibility = visibility;
auto reply = server->create_reply(this->get_sub_type()); auto reply = server->create_reply(this->get_sub_type());
reply->add(info); reply->add(info);
+2
View File
@@ -16,6 +16,7 @@ namespace dvars
game::dvar_t* con_inputCmdMatchColor = nullptr; game::dvar_t* con_inputCmdMatchColor = nullptr;
game::dvar_t* sv_cheats = nullptr; game::dvar_t* sv_cheats = nullptr;
game::dvar_t* sv_botsAutoJoin = nullptr;
game::dvar_t* g_playerEjection = nullptr; game::dvar_t* g_playerEjection = nullptr;
game::dvar_t* g_playerCollision = nullptr; game::dvar_t* g_playerCollision = nullptr;
@@ -24,6 +25,7 @@ namespace dvars
game::dvar_t* g_rocketPushbackScale = nullptr; game::dvar_t* g_rocketPushbackScale = nullptr;
game::dvar_t* g_enableElevators = nullptr; game::dvar_t* g_enableElevators = nullptr;
game::dvar_t* g_dump_scripts = nullptr; game::dvar_t* g_dump_scripts = nullptr;
game::dvar_t* g_dump_string_tables = nullptr;
game::dvar_t* g_log = nullptr; game::dvar_t* g_log = nullptr;
game::dvar_t* bg_surfacePenetration = nullptr; game::dvar_t* bg_surfacePenetration = nullptr;
+2
View File
@@ -15,6 +15,7 @@ namespace dvars
extern game::dvar_t* con_inputCmdMatchColor; extern game::dvar_t* con_inputCmdMatchColor;
extern game::dvar_t* sv_cheats; extern game::dvar_t* sv_cheats;
extern game::dvar_t* sv_botsAutoJoin;
extern game::dvar_t* g_playerCollision; extern game::dvar_t* g_playerCollision;
extern game::dvar_t* g_playerEjection; extern game::dvar_t* g_playerEjection;
@@ -23,6 +24,7 @@ namespace dvars
extern game::dvar_t* g_rocketPushbackScale; extern game::dvar_t* g_rocketPushbackScale;
extern game::dvar_t* g_enableElevators; extern game::dvar_t* g_enableElevators;
extern game::dvar_t* g_dump_scripts; extern game::dvar_t* g_dump_scripts;
extern game::dvar_t* g_dump_string_tables;
extern game::dvar_t* g_log; extern game::dvar_t* g_log;
extern game::dvar_t* bg_surfacePenetration; extern game::dvar_t* bg_surfacePenetration;
+206
View File
@@ -0,0 +1,206 @@
#include <std_include.hpp>
#include <game/game.hpp>
#include "sv_game.hpp"
#include <component/console.hpp>
#include <utils/string.hpp>
namespace game::engine
{
char* SV_ExpandNewlines(char* in)
{
static char string[1024];
unsigned int l = 0;
while (*in && l < sizeof(string) - 3)
{
if (*in == '\n')
{
string[l++] = '\\';
string[l++] = 'n';
}
else
{
if (*in != '\x14' && *in != '\x15')
{
string[l++] = *in;
}
}
++in;
}
string[l] = '\0';
return string;
}
void SV_CullIgnorableServerCommands(mp::client_t* client)
{
int to = client->reliableSent + 1;
for (int from = to; from <= client->reliableSequence; ++from)
{
int from_index = from & 0x7F;
assert(client->netBuf.reliableCommandInfo[from_index].time >= 0);
if (client->netBuf.reliableCommandInfo[from_index].type)
{
int to_index = to & 0x7F;
if (to_index != from_index)
{
client->netBuf.reliableCommandInfo[to_index] = client->netBuf.reliableCommandInfo[from_index];
}
++to;
}
}
client->reliableSequence = to - 1;
}
void SV_DelayDropClient(mp::client_t* drop, const char* reason)
{
assert(drop);
assert(reason);
assert(drop->header.state != CS_FREE);
if (drop->header.state == CS_ZOMBIE)
{
#ifdef _DEBUG
console::info("(drop->dropReason) = %s", drop->dropReason);
#endif
}
else if (!drop->dropReason)
{
drop->dropReason = reason;
}
}
void SV_AddServerCommand(mp::client_t* client, svscmd_type type, const char* cmd)
{
static_assert(offsetof(mp::client_t, netBuf.reliableCommandInfo[0].cmd) == 0xC44);
if (client->testClient == TC_BOT)
{
return;
}
if (client->reliableSequence - client->reliableAcknowledge < 64 && client->header.state == CS_ACTIVE || (SV_CullIgnorableServerCommands(client), type))
{
int len = static_cast<int>(std::strlen(cmd)) + 1;
int to = SV_CanReplaceServerCommand(client, reinterpret_cast<const unsigned char*>(cmd), len);
if (to < 0)
{
++client->reliableSequence;
}
else
{
int from = to + 1;
while (from <= client->reliableSequence)
{
client->netBuf.reliableCommandInfo[to & 0x7F] = client->netBuf.reliableCommandInfo[from & 0x7F];
++from;
++to;
}
}
if (client->reliableSequence - client->reliableAcknowledge == 129)
{
#ifdef _DEBUG
console::info("===== pending server commands =====\n");
int i = 0;
for (i = client->reliableAcknowledge + 1; i <= client->reliableSequence; ++i)
{
console::info("cmd %5d: %8d: %s\n", i, client->netBuf.reliableCommandInfo[i & 0x7F].time, client->netBuf.reliableCommandInfo[i & 0x7F].cmd);
}
console::info("cmd %5d: %8d: %s\n", i, *game::mp::serverTime, cmd);
#endif
NET_OutOfBandPrint(NS_SERVER, &client->header.netchan.remoteAddress, "disconnect");
SV_DelayDropClient(client, "EXE_SERVERCOMMANDOVERFLOW");
type = SV_CMD_RELIABLE;
cmd = utils::string::va("%c \"EXE_SERVERCOMMANDOVERFLOW\"", 'r');
}
int index = client->reliableSequence & 0x7F;
MSG_WriteReliableCommandToBuffer(cmd, client->netBuf.reliableCommandInfo[index].cmd, sizeof(client->netBuf.reliableCommandInfo[index].cmd));
client->netBuf.reliableCommandInfo[index].time = *game::mp::serverTime;
client->netBuf.reliableCommandInfo[index].type = type;
}
}
void SV_SendServerCommand(mp::client_t* cl, svscmd_type type, const char* fmt, ...)
{
mp::client_t* client;
int j, len;
va_list va;
const auto server_command_buf_large = std::make_unique<char[]>(0x20000);
va_start(va, fmt);
len = vsnprintf(server_command_buf_large.get(), 0x20000, fmt, va);
va_end(va);
assert(len >= 0);
if (cl)
{
SV_AddServerCommand(cl, type, server_command_buf_large.get());
return;
}
if (environment::is_dedi() && !std::strncmp(server_command_buf_large.get(), "print", 5))
{
console::info("broadcast: %s\n", SV_ExpandNewlines(server_command_buf_large.get()));
}
const auto* sv_maxclients = Dvar_FindVar("sv_maxclients");
for (j = 0, client = mp::svs_clients; j < sv_maxclients->current.integer; j++, client++)
{
if (client->header.state < CS_CLIENTLOADING)
{
continue;
}
SV_AddServerCommand(client, type, server_command_buf_large.get());
}
}
void SV_GameSendServerCommand(int clientNum, svscmd_type type, const char* text)
{
[[maybe_unused]] const auto* sv_maxclients = Dvar_FindVar("sv_maxclients");
if (clientNum == -1)
{
SV_SendServerCommand(nullptr, type, "%s", text);
return;
}
assert(sv_maxclients->current.integer >= 1 && sv_maxclients->current.integer <= 18);
assert(static_cast<unsigned>(clientNum) < sv_maxclients->current.unsignedInt);
SV_SendServerCommand(&mp::svs_clients[clientNum], type, "%s", text);
}
void SV_ReconnectClients(int savepersist)
{
const auto* sv_maxclients = Dvar_FindVar("sv_maxclients");
for (int i = 0; i < sv_maxclients->current.integer; ++i)
{
mp::client_t* client = &mp::svs_clients[i];
if (client->header.state < CS_CONNECTED)
{
continue;
}
SV_AddServerCommand(client, SV_CMD_RELIABLE, utils::string::va("%c", savepersist != 0 ? 107 : 118));
const char* denied = ClientConnect(i, client->scriptId);
if (denied)
{
SV_DropClient(client, denied, true);
console::info("SV_MapRestart_f: dropped client %i - denied!\n", i);
}
else if (client->header.state == CS_ACTIVE)
{
SV_ClientEnterWorld(client, &client->lastUsercmd);
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
namespace game::engine
{
void SV_SendServerCommand(mp::client_t* cl, svscmd_type type, const char* fmt, ...);
void SV_GameSendServerCommand(int clientNum, svscmd_type type, const char* text);
void SV_ReconnectClients(int savepersist);
}
-8
View File
@@ -56,11 +56,6 @@ namespace game
return get_mode() == launcher::mode::multiplayer; return get_mode() == launcher::mode::multiplayer;
} }
bool is_sp()
{
return get_mode() == launcher::mode::singleplayer;
}
void set_mode(const launcher::mode _mode) void set_mode(const launcher::mode _mode)
{ {
mode = _mode; mode = _mode;
@@ -77,9 +72,6 @@ namespace game
case launcher::mode::multiplayer: case launcher::mode::multiplayer:
return "Multiplayer"; return "Multiplayer";
case launcher::mode::singleplayer:
return "Multiplayer";
case launcher::mode::none: case launcher::mode::none:
return "None"; return "None";
+9 -8
View File
@@ -1,9 +1,16 @@
#pragma once #pragma once
#include "structs.hpp" #include "structs.hpp"
#include "launcher/launcher.hpp"
#define SELECT_VALUE(sp, mp) (game::environment::is_sp() ? (sp) : (mp)) namespace launcher
{
enum class mode
{
none,
multiplayer,
server,
};
}
#define SERVER_CD_KEY "iw6-mod-CD-Key" #define SERVER_CD_KEY "iw6-mod-CD-Key"
@@ -14,7 +21,6 @@ namespace game
launcher::mode get_mode(); launcher::mode get_mode();
bool is_mp(); bool is_mp();
bool is_sp();
bool is_dedi(); bool is_dedi();
void set_mode(launcher::mode mode); void set_mode(launcher::mode mode);
@@ -34,11 +40,6 @@ namespace game
T* get() const T* get() const
{ {
if (environment::is_sp())
{
return sp_object_;
}
return mp_object_; return mp_object_;
} }
+1 -1
View File
@@ -70,7 +70,7 @@ namespace scripting
script_function get_function_by_index(const std::uint32_t index) script_function get_function_by_index(const std::uint32_t index)
{ {
static const auto function_table = &gsc::func_table; static const auto function_table = &gsc::func_table;
static const auto method_table = SELECT_VALUE(0x144E1F9E0, 0x1446B8A90); static const auto method_table = 0x1446B8A90;
if (index <= 0x1000) if (index <= 0x1000)
{ {
+162 -478
View File
@@ -10,6 +10,14 @@ namespace game
typedef vec_t vec3_t[3]; typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4]; typedef vec_t vec4_t[4];
enum ControllerIndex_t
{
INVALID_CONTROLLER_PORT = -1,
CONTROLLER_INDEX_0 = 0x0,
CONTROLLER_INDEX_FIRST = 0x0,
CONTROLLER_INDEX_COUNT = 0x1,
};
enum enum
{ {
FL_GODMODE = 0x1, FL_GODMODE = 0x1,
@@ -200,7 +208,7 @@ namespace game
static_assert(sizeof(db_internal_state) == 0x20); static_assert(sizeof(db_internal_state) == 0x20);
struct db_z_stream_t struct db_z_stream_s
{ {
unsigned char* next_in; unsigned char* next_in;
unsigned int avail_in; unsigned int avail_in;
@@ -235,7 +243,7 @@ namespace game
unsigned char* start_in; unsigned char* start_in;
_OVERLAPPED overlapped; _OVERLAPPED overlapped;
unsigned int readError; unsigned int readError;
db_z_stream_t stream; db_z_stream_s stream;
int readingResident; int readingResident;
}; };
@@ -641,6 +649,33 @@ namespace game
BD_MAX_ERROR_CODE = 0x27E2, BD_MAX_ERROR_CODE = 0x27E2,
}; };
enum bdBitBufferDataType
{
BD_BB_NO_TYPE = 0x0,
BD_BB_BOOL_TYPE = 0x1,
BD_BB_SIGNED_CHAR8_TYPE = 0x2,
BD_BB_UNSIGNED_CHAR8_TYPE = 0x3,
BD_BB_WCHAR16_TYPE = 0x4,
BD_BB_SIGNED_INTEGER16_TYPE = 0x5,
BD_BB_UNSIGNED_INTEGER16_TYPE = 0x6,
BD_BB_SIGNED_INTEGER32_TYPE = 0x7,
BD_BB_UNSIGNED_INTEGER32_TYPE = 0x8,
BD_BB_SIGNED_INTEGER64_TYPE = 0x9,
BD_BB_UNSIGNED_INTEGER64_TYPE = 0xA,
BD_BB_RANGED_SIGNED_INTEGER32_TYPE = 0xB,
BD_BB_RANGED_UNSIGNED_INTEGER32_TYPE = 0xC,
BD_BB_FLOAT32_TYPE = 0xD,
BD_BB_FLOAT64_TYPE = 0xE,
BD_BB_RANGED_FLOAT32_TYPE = 0xF,
BD_BB_SIGNED_CHAR8_STRING_TYPE = 0x10,
BD_BB_UNSIGNED_CHAR8_STRING_TYPE = 0x11,
BD_BB_MBSTRING_TYPE = 0x12,
BD_BB_BLOB_TYPE = 0x13,
BD_BB_NAN_TYPE = 0x14,
BD_BB_FULL_TYPE = 0x15,
BD_BB_MAX_TYPE = 0x20,
};
enum bdNATType : uint8_t enum bdNATType : uint8_t
{ {
BD_NAT_UNKNOWN = 0x0, BD_NAT_UNKNOWN = 0x0,
@@ -811,22 +846,6 @@ namespace game
enum ConfigString enum ConfigString
{ {
CS_TIMESCALE = 0xA,
CS_VISIONSET_NORMAL = 0x49C,
CS_VISIONSET_NIGHT = 0x49D,
CS_VISIONSET_MISSILECAM = 0x49E,
CS_VISIONSET_THERMAL = 0x49F,
CS_VISIONSET_PAIN = 0x4A0,
CS_VISIONSET_POST_APPLY = 0x4A1,
CS_VISIONSET_FIRST = CS_VISIONSET_NORMAL,
CS_VISIONSET_LAST = CS_VISIONSET_POST_APPLY,
CS_VISIONSET_COUNT = CS_VISIONSET_POST_APPLY - CS_VISIONSET_NORMAL + 1
// many of the indices below are incorrect
/*
CS_FIRST = 0x0, CS_FIRST = 0x0,
CS_SERVERINFO = 0x0, CS_SERVERINFO = 0x0,
CS_SYSTEMINFO = 0x1, CS_SYSTEMINFO = 0x1,
@@ -898,8 +917,7 @@ namespace game
CS_MP_ANIMS_LAST = 0xD21, CS_MP_ANIMS_LAST = 0xD21,
CS_ITEMS = 0xD22, CS_ITEMS = 0xD22,
CS_LEADERBOARDS = 0xD23, CS_LEADERBOARDS = 0xD23,
MAX_CONFIGSTRINGS = 0xD24 MAX_CONFIGSTRINGS = 0xD24,
*/
}; };
enum LiveClientDropType enum LiveClientDropType
@@ -972,6 +990,27 @@ namespace game
LOOKUP_ERROR_COUNT = 0x5, LOOKUP_ERROR_COUNT = 0x5,
}; };
struct ComStreamSyncModel
{
unsigned __int16 modelIndex;
unsigned __int8 alternateIndex;
};
static_assert(sizeof(ComStreamSyncModel) == 4);
struct ComStreamedSyncModelList
{
unsigned int modelCount;
ComStreamSyncModel models[18];
};
static_assert(sizeof(ComStreamedSyncModelList) == 0x4C);
struct ComStreamedSyncSwapBuffer
{
ComStreamedSyncModelList modelLists[2];
};
struct StructuredDataEnumEntry struct StructuredDataEnumEntry
{ {
unsigned int name; unsigned int name;
@@ -1100,7 +1139,7 @@ namespace game
float t1; float t1;
}; };
struct Font_t struct Font_s
{ {
const char* fontName; const char* fontName;
int pixelHeight; int pixelHeight;
@@ -1142,6 +1181,9 @@ namespace game
DVAR_FLAG_LATCHED = 0x2, DVAR_FLAG_LATCHED = 0x2,
DVAR_FLAG_CHEAT = 0x4, DVAR_FLAG_CHEAT = 0x4,
DVAR_FLAG_REPLICATED = 0x8, DVAR_FLAG_REPLICATED = 0x8,
DVAR_FLAG_SCRIPTINFO = 0x10,
DVAR_FLAG_INTERNAL = 0x80,
DVAR_FLAG_EXTERNAL = 0x100,
DVAR_FLAG_SERVERINFO = 0x400, DVAR_FLAG_SERVERINFO = 0x400,
DVAR_FLAG_WRITE = 0x800, DVAR_FLAG_WRITE = 0x800,
DVAR_FLAG_READ = 0x2000, DVAR_FLAG_READ = 0x2000,
@@ -1161,7 +1203,7 @@ namespace game
rgb = 9 // Color without alpha rgb = 9 // Color without alpha
}; };
union dvar_value union DvarValue
{ {
bool enabled; bool enabled;
int integer; int integer;
@@ -1204,9 +1246,9 @@ namespace game
unsigned int flags; //08 unsigned int flags; //08
dvar_type type; //0C dvar_type type; //0C
bool modified; //0D bool modified; //0D
dvar_value current; //10 DvarValue current; //10
dvar_value latched; DvarValue latched;
dvar_value reset; DvarValue reset;
dvar_limits domain; dvar_limits domain;
}; };
@@ -1219,9 +1261,9 @@ namespace game
const char** argv[8]; const char** argv[8];
}; };
struct cmd_function_t struct cmd_function_s
{ {
cmd_function_t* next; cmd_function_s* next;
const char* name; const char* name;
void (__cdecl* function)(); void (__cdecl* function)();
}; };
@@ -1272,7 +1314,7 @@ namespace game
WEAPON_HAND_DEFAULT = 0x0, WEAPON_HAND_DEFAULT = 0x0,
}; };
struct usercmd_t struct usercmd_s
{ {
int serverTime; int serverTime;
unsigned int buttons; unsigned int buttons;
@@ -1326,16 +1368,6 @@ namespace game
vec2_t corridorSpeeds; vec2_t corridorSpeeds;
}; };
struct ClientArchiveData
{
int serverTime;
vec3_t origin;
vec3_t velocity;
int bobCycle;
int movementDir;
PlayerVehicleState playerVehStateClientArchive;
};
struct PlayerActiveWeaponState struct PlayerActiveWeaponState
{ {
int weapAnim; int weapAnim;
@@ -1358,7 +1390,7 @@ namespace game
bool hybridScope; bool hybridScope;
}; };
struct compressedAnimData_t struct compressedAnimData_s
{ {
int flags; int flags;
int animRate; int animRate;
@@ -1377,7 +1409,7 @@ namespace game
int flags; int flags;
int startTime; int startTime;
float startPosition[3]; float startPosition[3];
compressedAnimData_t compressedAnimData; compressedAnimData_s compressedAnimData;
}; };
struct SlideState struct SlideState
@@ -1386,7 +1418,7 @@ namespace game
int noFricTime; int noFricTime;
}; };
struct SprintState_t struct SprintState_s
{ {
int sprintButtonUpRequired; int sprintButtonUpRequired;
int sprintDelay; int sprintDelay;
@@ -1445,7 +1477,7 @@ namespace game
NS_INVALID_NETSRC = 0x4, NS_INVALID_NETSRC = 0x4,
}; };
struct netadr_t struct netadr_s
{ {
netadrtype_t type; netadrtype_t type;
unsigned char ip[4]; unsigned char ip[4];
@@ -1458,26 +1490,18 @@ namespace game
{ {
int overflowed; int overflowed;
int readOnly; int readOnly;
const char* data; char* data;
const char* splitData; char* splitData;
int maxsize; int maxsize;
int cursize; int cursize;
int splitSize; int splitSize;
int readcount; int readcount;
int bit; int bit;
int lastEntityRef; int lastEntityRef;
netsrc_t targetLocalNetID;
int useZlib; int useZlib;
}; };
struct gameState_t
{
int stringOffsets[3524];
char stringData[131'072];
int dataCount;
};
static_assert(sizeof(game::gameState_t) == 145'172);
struct HsvColor struct HsvColor
{ {
unsigned char h; unsigned char h;
@@ -1518,7 +1542,7 @@ namespace game
unsigned int rgba; unsigned int rgba;
}; };
struct hudelem_t struct hudelem_s
{ {
he_type_t type; he_type_t type;
float x; float x;
@@ -1564,9 +1588,9 @@ namespace game
int flags; int flags;
}; };
struct game_hudelem_t struct game_hudelem_s
{ {
hudelem_t elem; hudelem_s elem;
int clientNum; int clientNum;
int team; int team;
int archived; int archived;
@@ -1971,6 +1995,12 @@ namespace game
const char* buffer; const char* buffer;
}; };
struct WeaponCompleteDef
{
const char* szInternalName;
WeaponDef* weapDef;
};
union XAssetHeader union XAssetHeader
{ {
void* data; void* data;
@@ -2001,14 +2031,14 @@ namespace game
FxWorld *fxWorld; FxWorld *fxWorld;
GfxWorld *gfxWorld; GfxWorld *gfxWorld;
GfxLightDef *lightDef;*/ GfxLightDef *lightDef;*/
Font_t* font; Font_s* font;
/*MenuList *menuList; /*MenuList *menuList;
menuDef_t *menu; menuDef_t *menu;
AnimationClass *animClass; AnimationClass *animClass;
LocalizeEntry *localize; LocalizeEntry *localize;
WeaponAttachment *attachment; WeaponAttachment *attachment;*/
WeaponCompleteDef* weapon; WeaponCompleteDef* weapon;
SndDriverGlobals *sndDriverGlobals; /*SndDriverGlobals *sndDriverGlobals;
FxEffectDef *fx; FxEffectDef *fx;
FxImpactTable *impactFx; FxImpactTable *impactFx;
SurfaceFxTable *surfaceFx;*/ SurfaceFxTable *surfaceFx;*/
@@ -2092,21 +2122,21 @@ namespace game
namespace sp namespace sp
{ {
// very shit structures for the moment cuz i cba mapping the whole thing out right now... // very shit structures for the moment cuz i cba mapping the whole thing out right now...
struct gclient_t struct gclient_s
{ {
char __0x00[0xB6DC]; char __0x00[0xB6DC];
int flags; int flags;
}; };
struct gentity_t struct gentity_s
{ {
char __0x00[0x110]; char __0x00[0x110];
gclient_t* client; gclient_s* client;
char __0x118[0x4C]; char __0x118[0x4C];
int flags; int flags;
}; };
struct playerState_t struct playerState_s
{ {
}; };
@@ -2123,7 +2153,7 @@ namespace game
namespace mp namespace mp
{ {
#pragma pack(push, 4) #pragma pack(push, 4)
struct playerState_t struct playerState_s
{ {
int commandTime; int commandTime;
int pm_type; int pm_type;
@@ -2173,11 +2203,9 @@ namespace game
int clientNum; int clientNum;
int viewmodelIndex; int viewmodelIndex;
vec3_t viewangles; vec3_t viewangles;
char _0x190[0x58]; char _0x190[0xAC];
int viewlocked_entNum;
char _0x1EC[0x50];
int locationSelectionInfo; int locationSelectionInfo;
SprintState_t sprintState; SprintState_s sprintState;
float holdBreathScale; float holdBreathScale;
int holdBreathTimer; int holdBreathTimer;
float moveSpeedScaleMultiplier; float moveSpeedScaleMultiplier;
@@ -2207,12 +2235,15 @@ namespace game
vec3_t weapLockedPos; vec3_t weapLockedPos;
int weaponIdleTime; int weaponIdleTime;
int lastStowedWeapon; int lastStowedWeapon;
char __0x638[0x2CEC]; char __0x638[0x2CE8];
}; };
static_assert(sizeof(playerState_t) == 0x3328);
#pragma pack(pop) #pragma pack(pop)
struct ClientCustomizationInfo
{
unsigned int modelIndex[3];
};
struct ScoreInfo struct ScoreInfo
{ {
unsigned short ping; unsigned short ping;
@@ -2227,140 +2258,6 @@ namespace game
unsigned short adrenaline; unsigned short adrenaline;
}; };
struct ScoreboardInfo
{
int teamScores[2];
ScoreInfo clientScores[18];
};
struct clSnapshot_t
{
mp::playerState_t ps;
ScoreboardInfo scores;
int valid;
int snapFlags;
int serverTime;
int messageNum;
int deltaNum;
int ping;
int cmdNum;
int numEntities;
int numClients;
int parseEntitiesIndex;
int parseClientsIndex;
int parseOmnvarsIdx;
int numAgents;
int parseAgentsIndex;
int scriptablesParseCount;
int scriptablesParseIndex;
int serverCommandNum;
};
static_assert(sizeof(clSnapshot_t) == 0x34B8);
struct clientActive_t
{
char usingAds;
char padding1[3];
int timeoutcount;
clSnapshot_t snap;
int oldSnapServerTime;
int oldServerTime;
int extrapolatedSnapshot;
char alwaysFalse;
char padding2[3];
int serverTime;
int oldFrameServerTime;
int serverTimeDelta;
int newSnapshots;
int serverId;
char mapname[64];
int mapcrc;
int parseEntitiesIndex;
int parseClientsIndex;
int parseOmnvarsIdx;
int mouseDx[2];
int mouseDy[2];
int mouseIndex;
char stanceHeld;
char padding3[3];
int stance;
int stancePosition;
int stanceTime;
char wasSprinting;
char padding4[3];
int cgameUserCmdWeapon;
int cgameUserCmdOffHand;
int cgameUserCmdAlternate;
float cgameFOVSensitivityScale;
float cgameMaxPitchSpeed;
float cgameMaxYawSpeed;
vec3_t cgameKickAngles;
vec3_t cgameOrigin;
vec3_t cgameVelocity;
PlayerVehicleState cgamePlayerVehState;
int cgameBobCycle;
int cgameMovementDir;
int cgameExtraButtons;
int cgamePredictedDataServerTime;
vec3_t clViewangles;
char _361C[12];
usercmd_t cmds[128];
int cmdNumber;
ClientArchiveData clientArchive[256];
int clientArchiveIndex;
int packetBackupCount;
int packetBackupMask;
int parseEntitiesCount;
int parseClientsCount;
int parseOmnvarsCount;
// missing fields
};
struct clientStatic_t
{
int quit;
int hunkUsersStarted;
char servername[256];
int rendererStarted;
int soundStarted;
int inUpdateSound;
int uiStarted;
int frametime;
int frametime_base;
int realtime;
char _0x124[0x80DBC];
gameState_t gameState;
// missing fields
};
struct clientConnection_t
{
int qport;
int clientNum;
netadr_t serverAddress;
int connectLastSendTime;
int connectPacketCount;
char serverMessage[256];
int challenge;
int checksumFeed;
int reliableSequence;
int reliableAcknowledge;
char reliableCommands[128][1024];
int serverMessageSequence;
int serverCommandSequence;
int lastExecutedServerCommand;
char serverCommands[128][1024];
char isServerRestarting;
char padding[3];
// missing fields
};
struct ClientCustomizationInfo
{
unsigned int modelIndex[3];
};
union OmnvarValue union OmnvarValue
{ {
bool enabled; bool enabled;
@@ -2386,58 +2283,51 @@ namespace game
TEAM_NUM_TEAMS = 0x4, TEAM_NUM_TEAMS = 0x4,
}; };
struct clientState_t struct clientState_s
{ {
int clientIndex; int clientIndex;
team_t team; team_t team;
int modelindex; int modelindex;
int dualWielding; int dualWielding;
char _0x10[0x30]; char _0x10[0x30];
char name[16]; char name[0x10];
int rank; int rank;
int prestige; int prestige;
char _0x00[0x10];
int nameplateFriendlyIndex; int nameplateFriendlyIndex;
int nameplateEnemyIndex; int nameplateEnemyIndex;
unsigned int perks[2]; unsigned int perks[2];
char _0x68[0x40]; char _0x78[0x80];
char clanAbbrev[8];
char _0xB0[0x28];
}; };
static_assert(sizeof(clientState_t) == 0xD8);
struct clientSession_t struct clientSession_t
{ {
int sessionState; int sessionState;
int forceSpectatorClient; int forceSpectatorClient;
int killCamEntity; int killCamEntity;
int isInKillcam; int isInKillcam;
char _0x10[0x10]; char __0x332C[0x14];
usercmd_t cmd; usercmd_s cmd;
usercmd_t oldcmd; usercmd_s oldcmd;
int localClient; int localClient;
char _0x9C[0x34]; char __0x33C4[0x24];
float moveSpeedScaleMultiplier; float moveSpeedScaleMultiplier;
int viewmodelIndex; int viewmodelIndex;
int noSpectate; int noSpectate;
clientState_t cs; clientState_s cs;
ScoreInfo scores; ScoreInfo scores;
char _0x1E6[0x1C0]; char __pad[0x1B0];
}; };
static_assert(sizeof(clientSession_t) == 0x388); struct gclient_s
struct gclient_t
{ {
playerState_t ps; playerState_s ps;
clientSession_t sess; clientSession_t sess;
int flags; int flags;
int spectatorClient; int spectatorClient;
char _0x36B8[0x3B0]; char __0x36B4[0x3B0];
}; };
static_assert(sizeof(gclient_t) == 0x3A68);
struct LerpEntityState struct LerpEntityState
{ {
int entFlags; int entFlags;
@@ -2472,8 +2362,6 @@ namespace game
char _0xD8[0x30]; char _0xD8[0x30];
}; };
static_assert(sizeof(EntityState) == 0x108);
struct EntityShared struct EntityShared
{ {
char _0x108[0x38]; char _0x108[0x38];
@@ -2482,18 +2370,11 @@ namespace game
char _0x14C[0xC]; char _0x14C[0xC];
}; };
struct centity_t struct gentity_s
{
char _0x0[0x238];
};
static_assert(sizeof(centity_t) == 0x238);
struct gentity_t
{ {
EntityState s; EntityState s;
EntityShared r; EntityShared r;
gclient_t* client; gclient_s* client;
char _0x170[0x50]; char _0x170[0x50];
int flags; int flags;
char _0x1C4[0x18]; char _0x1C4[0x18];
@@ -2502,232 +2383,21 @@ namespace game
char _0x1E4[0x10C]; char _0x1E4[0x10C];
}; };
struct snapshot_t struct snapshot_s
{ {
playerState_t ps; char _0x0[0x3C78];
ScoreboardInfo scores;
OmnvarData omnvars[256];
int snapFlags;
int ping; int ping;
int serverTime; int serverTime;
int numEntities; int numEntities;
int numClients; int numClients;
// missing fields
}; };
enum DemoType struct cg_s
{ {
DEMO_TYPE_NONE = 0x0, playerState_s ps;
DEMO_TYPE_CLIENT = 0x1, char _0x3324[0x64];
DEMO_TYPE_SERVER = 0x2 snapshot_s* snap;
}; snapshot_s* nextSnap; // theres alot after this cba rn
struct characterInfo_t
{
char _0x0[0x5D8];
};
static_assert(sizeof(characterInfo_t) == 0x5D8);
struct clientInfo_t
{
char _0x0[0x70];
};
static_assert(sizeof(clientInfo_t) == 0x70);
struct bgs_t
{
char _0x0[0x459F0];
characterInfo_t characterinfo[42];
clientInfo_t clientinfo[18];
};
static_assert(sizeof(bgs_t) == 0x55740);
struct visionSetVarsBase_t
{
int filmEnable;
float filmBrightness;
float filmContrast;
float filmDesaturation;
float filmDesaturationDark;
float filmInvert;
vec3_t filmLightTint;
vec3_t filmMediumTint;
vec3_t filmDarkTint;
int charPrimaryUseTweaks;
float charPrimaryDiffuseScale;
float charPrimarySpecularScale;
vec3_t charLightAmbient;
int viewModelPrimaryUseTweaks;
float viewModelPrimaryDiffuseScale;
float viewModelPrimarySpecularScale;
vec3_t viewModelLightAmbient;
float primaryLightOverride;
int materialBloomHQEnable;
float materialBloomHQHaziness;
float materialBloomHQGamma;
float materialBloomHQDesaturation;
float materialBloomIntensity;
float materialBloomRadius;
float materialBloomPinch;
float materialBloomLuminanceCutoff;
float materialBloomDesaturation;
int volumeLightScatter;
float volumeLightScatterLinearAtten;
float volumeLightScatterQuadraticAtten;
float volumeLightScatterAngularAtten;
float volumeLightScatterDepthAttenNear;
float volumeLightScatterDepthAttenFar;
float volumeLightScatterBackgroundDistance;
vec3_t volumeLightScatterColor;
float ssaoStrength;
float ssaoPower;
float ssaoBlurRadius;
int glareEnable;
float glareNormalStart;
float glareNormalEnd;
float glareNormalPower;
float glareNormalAmount;
float glareMirrorStart;
float glareMirrorEnd;
float glareMirrorPower;
float glareMirrorAmount;
float rimLightPitch;
float rimLightHeading;
float rimLightDiffuseIntensity;
float rimLightSpecIntensity;
float rimLightBias;
float rimLightPower;
vec3_t rimLightColor;
int overrideExistingMask;
float unlitSurfaceHDRScalar;
float litSurfaceHDRScalar;
float hdrSkyIntensity;
vec3_t hdrSkyColorTint;
};
static_assert(sizeof(visionSetVarsBase_t) == 0x12C);
struct visionSetVars_t
{
visionSetVarsBase_t baseclass_0;
void* _0x12C[3];
};
static_assert(sizeof(visionSetVars_t) == 0x148);
struct interpolationVisionSetVars_t
{
visionSetVarsBase_t baseclass_0;
void* _0x12C[0x2F];
};
static_assert(sizeof(interpolationVisionSetVars_t) == 0x2A8);
struct visionSetLerpData_t
{
int timeStart;
int timeDuration;
int style;
};
struct GfxFog
{
char _0x0[0x38];
int startTime;
int finishTime;
int color;
float density;
int sunColor;
};
struct ClientVisionSetData
{
visionSetVars_t visionSetPreLoaded[4];
char visionSetPreLoadedName[4][64];
int visionSetPreLoadedUsedTime[4];
interpolationVisionSetVars_t visionSetFrom[6];
interpolationVisionSetVars_t visionSetTo[6];
interpolationVisionSetVars_t visionSetCurrent[6];
interpolationVisionSetVars_t visionSetPrevious;
visionSetLerpData_t visionSetLerpData[6];
char visionName[6][64];
char visionBlendActive[6];
char thermalVisionActive;
char remoteMissileCam;
char painVisionActive;
float painVisionLerp;
int postApplyLerpTimeStart;
int postApplyLerpTimeDuration;
float postApplyLerpStart;
float postApplyLerpDest;
float postApplyLerpCurrent;
GfxFog lastClientFog;
int scriptFogActive;
int scriptFogLerpStartTime;
int scriptFogLerpEndTime;
};
static_assert(sizeof(ClientVisionSetData) == 0x3AF0);
struct cpose_t
{
char _0x0[0x90];
};
static_assert(sizeof(cpose_t) == 0x90);
struct cg_t
{
playerState_t ps;
char _0x3324[0x38];
int clientNum;
int localClientNum;
DemoType demoType;
char _0x336C[0x1C];
snapshot_t* snap;
snapshot_t* nextSnap;
char _0x3398[0xA66F8];
vec3_t refdefViewAngles;
char _0xA9A9C[0x8CC4];
int clientSideVisionSetsEnabled;
int voiceTime;
Weapon weaponSelect;
char _0xB276C[0x2C10];
int inKillCam;
char _0xB5380[0x148];
bgs_t bgs;
cpose_t viewModelPose;
cpose_t viewModelPoseLeftHand;
ClientVisionSetData cvsData;
int stagedVisionStateTo;
int stagedVisionStateFrom;
int stagedVisionStateEndTime;
int stagedVisionStateStartTime;
// missing fields
};
struct cgs_t
{
int viewX;
int viewY;
int viewWidth;
int viewHeight;
float viewAspect;
float _0x14;
int serverCommandSequence;
int processedSnapshotNum;
int localServer;
char gametype[32];
char szHostName[256];
int hardcore;
int maxclients;
int privateClients;
char mapname[64];
int gameEndTime;
// missing fields
}; };
struct netProfilePacket_t struct netProfilePacket_t
@@ -2762,7 +2432,7 @@ namespace game
netsrc_t sock; netsrc_t sock;
int dropped; int dropped;
int incomingSequence; int incomingSequence;
netadr_t remoteAddress; netadr_s remoteAddress;
int fragmentSequence; int fragmentSequence;
int fragmentLength; int fragmentLength;
char* fragmentBuffer; char* fragmentBuffer;
@@ -2777,7 +2447,7 @@ namespace game
struct __declspec(align(8)) clientHeader_t struct __declspec(align(8)) clientHeader_t
{ {
clientstate_t state; int state;
int sendAsActive; int sendAsActive;
int deltaMessage; int deltaMessage;
int rateDelayed; int rateDelayed;
@@ -2802,6 +2472,24 @@ namespace game
int isInKillcam; int isInKillcam;
}; };
struct svscmd_info_t
{
int time;
int type;
char cmd[1024];
};
static_assert(sizeof(svscmd_info_t) == 0x408);
struct client_net_buffers_t
{
svscmd_info_t reliableCommandInfo[128];
char netchanOutgoingBuffer[131072];
char netchanIncomingBuffer[2048];
};
static_assert(sizeof(client_net_buffers_t) == 0x40C00);
struct client_t struct client_t
{ {
clientHeader_t header; clientHeader_t header;
@@ -2814,8 +2502,15 @@ namespace game
int largeCommandSequence; int largeCommandSequence;
int gamestateMessageNum; int gamestateMessageNum;
int challenge; int challenge;
char _0xC3C[0x4122C]; client_net_buffers_t netBuf;
gentity_t* gentity; int cumulThinkTime;
int beginCmdIndex;
int currCmdIndex;
usercmd_s lastUsercmd;
usercmd_s cmds[8];
int lastClientCommand;
char lastClientCommandString[1024];
gentity_s* gentity;
char name[16]; char name[16];
int lastPacketTime; int lastPacketTime;
int lastConnectTime; int lastConnectTime;
@@ -2837,8 +2532,6 @@ namespace game
char _0x41E94[0x416A0]; char _0x41E94[0x416A0];
}; };
static_assert(sizeof(client_t) == 0x83570);
struct XZone struct XZone
{ {
char name[64]; char name[64];
@@ -2850,7 +2543,7 @@ namespace game
struct missileFireParms struct missileFireParms
{ {
gentity_t* target; gentity_s* target;
float targetPosOrOffset[3]; float targetPosOrOffset[3];
float autoDetonateTime; float autoDetonateTime;
bool lockon; bool lockon;
@@ -2858,23 +2551,14 @@ namespace game
}; };
static_assert(sizeof(missileFireParms) == 0x20); static_assert(sizeof(missileFireParms) == 0x20);
struct serverStatic_t
{
char _0x0[0x1681D00];
int time;
int loopbackProcessStopTime;
int snapFlagServerBit;
int clientCount;
client_t clients[18];
// missing fields
};
} }
union playerState_t static_assert(sizeof(mp::client_t) == 0x83570);
union playerState_s
{ {
sp::playerState_t* sp; sp::playerState_s* sp;
mp::playerState_t* mp; mp::playerState_s* mp;
}; };
enum GfxDrawSceneMethod enum GfxDrawSceneMethod
@@ -2905,7 +2589,7 @@ namespace game
TECHNIQUE_NONE = 0x137, TECHNIQUE_NONE = 0x137,
}; };
struct GfxDrawMethod_t struct GfxDrawMethod_s
{ {
int drawScene; int drawScene;
int baseTechType; int baseTechType;
@@ -2957,8 +2641,8 @@ namespace game
struct pmove_t struct pmove_t
{ {
void* ps; void* ps;
usercmd_t cmd; usercmd_s cmd;
usercmd_t oldcmd; usercmd_s oldcmd;
int tracemask; int tracemask;
int numtouch; int numtouch;
int touchents[32]; int touchents[32];
+74 -84
View File
@@ -19,6 +19,8 @@ namespace game
WEAK symbol<void(void*, void*)> AimAssist_AddToTargetList{0, 0x140139D80}; WEAK symbol<void(void*, void*)> AimAssist_AddToTargetList{0, 0x140139D80};
WEAK symbol<void(unsigned int weapon, bool isAlternate, char* output, unsigned int maxStringLen)> BG_GetWeaponNameComplete{0, 0x140239370}; WEAK symbol<void(unsigned int weapon, bool isAlternate, char* output, unsigned int maxStringLen)> BG_GetWeaponNameComplete{0, 0x140239370};
WEAK symbol<void()> BG_ClearWeaponDef{0x0, 0x140238D20};
WEAK symbol<bool()> BG_BotsConnectType{0x0, 0x140217080};
WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1403BC980, 0x1404131A0}; WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1403BC980, 0x1404131A0};
WEAK symbol<const char*(char const**)> Com_Parse{0x1404313E0, 0x1404F50E0}; WEAK symbol<const char*(char const**)> Com_Parse{0x1404313E0, 0x1404F50E0};
@@ -28,6 +30,8 @@ namespace game
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0, 0x1404158C0}; WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0, 0x1404158C0};
WEAK symbol<void(const char* text_in)> Com_TokenizeString{0x1403B4150, 0x1403F7CC0}; WEAK symbol<void(const char* text_in)> Com_TokenizeString{0x1403B4150, 0x1403F7CC0};
WEAK symbol<void()> Com_EndTokenizeString{0x1403B37C0, 0x1403F7330}; WEAK symbol<void()> Com_EndTokenizeString{0x1403B37C0, 0x1403F7330};
WEAK symbol<void()> Com_StreamSync_UpdateLaunchData{0x0, 0x140411B50};
WEAK symbol<int(char* dest, int size, const char* fmt, ...)> Com_sprintf{0x140432310, 0x1404F6260};
WEAK symbol<void(const char* message)> Conbuf_AppendText{0x14043DDE0, 0x1405028C0}; WEAK symbol<void(const char* message)> Conbuf_AppendText{0x14043DDE0, 0x1405028C0};
@@ -36,30 +40,13 @@ namespace game
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1403B3050, 0x1403F6B50}; WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1403B3050, 0x1403F6B50};
WEAK symbol<void(int localClientNum, int controllerIndex, const char* buffer, void (int, int, const char*))> Cbuf_ExecuteBufferInternal{0x1403B3160, 0x1403F6C60}; WEAK symbol<void(int localClientNum, int controllerIndex, const char* buffer, void (int, int, const char*))> Cbuf_ExecuteBufferInternal{0x1403B3160, 0x1403F6C60};
WEAK symbol<void(int localClientNum, int latestSequence)> CG_ExecuteNewServerCommands{0, 0x140288500};
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401F2E20, 0x140271320};
WEAK symbol<void*(const mp::playerState_t* ps)> CG_GetVehicleDef{0, 0x140229760};
WEAK symbol<void(int localClientNum, const mp::centity_t* cent, unsigned int event, unsigned int eventParm, bool isPlayerView)> CG_HandleTurretFire{0, 0x140269DF0};
WEAK symbol<void(int localClientNum, mp::cg_t* cg, const char* dvar, const char* value)> CG_SetClientDvarFromServer{0, 0x14028A2C0};
WEAK symbol<void(int localClientNum, const mp::snapshot_t* oldSnap, const mp::snapshot_t* newSnap)> CG_UpdateOmnvars{0, 0x14028E770};
WEAK symbol<void(int localClientNum, int key)> CL_CharEvent{0x14023CE50, 0x1402C2AE0};
WEAK symbol<bool(int localClientNum, int clientNum, char* buffer, int length)> CL_GetClientNameColorize{0, 0x1402CFA60};
WEAK symbol<bool(const mp::clientActive_t* cl, const int serverTime,
mp::playerState_t* to)> CL_GetPredictedPlayerInformationForServerTime{0, 0x1402CC710};
WEAK symbol<bool(const mp::clientActive_t* cl, const int serverTime,
PlayerVehicleState* predictedPlayerVehState)> CL_GetPredictedVehicleForServerTime{0, 0x1402CC7F0};
WEAK symbol<bool()> CL_IsCgameInitialized{0x140234DA0, 0x1402B9A70}; WEAK symbol<bool()> CL_IsCgameInitialized{0x140234DA0, 0x1402B9A70};
WEAK symbol<void(int localClientNum, int key, int down, unsigned int time)> CL_KeyEvent{0x14023D070, 0x1402C2CE0};
WEAK symbol<void(int localClientNum, msg_t*)> CL_ParseServerMessage{0, 0x1402CDFC0};
WEAK symbol<bool(int localClientNum, int weapon, int useAltMode)> CG_SelectWeapon{0, 0x1402AB310};
WEAK symbol<void(int localClientNum)> CL_SetCGameTime{0, 0x1402B9D80};
WEAK symbol<void(int localClientNum)> CL_WritePacket{0, 0x1402C1E70};
WEAK symbol<void(const char* cmdName, void (), cmd_function_t* allocedCmd)> Cmd_AddCommandInternal{0x1403B3570, 0x1403F7070}; WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401F2E20, 0x140271320};
WEAK symbol<void(int localClientNum, mp::cg_s* cg, const char* dvar, const char* value)> CG_SetClientDvarFromServer{0x0, 0x14028A2C0};
WEAK symbol<void(const char* cmdName, void (), cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{0x1403B3570, 0x1403F7070};
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x1403B3B10, 0x1403F7680}; WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x1403B3B10, 0x1403F7680};
WEAK symbol<float()> Com_GetTimescaleForSnd{0, 0x140413860};
WEAK symbol<int(int msec)> Com_TimeScaleMsec{0, 0x140415D50};
WEAK symbol<void (XAssetType type, void (__cdecl *func)(XAssetHeader, void*), void* inData, bool includeOverride)> DB_EnumXAssets_FastFile{0x140271F50, 0x14031EF90}; WEAK symbol<void (XAssetType type, void (__cdecl *func)(XAssetHeader, void*), void* inData, bool includeOverride)> DB_EnumXAssets_FastFile{0x140271F50, 0x14031EF90};
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)> DB_EnumXAssets_Internal{0x140271FC0, 0x14031F000}; WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)> DB_EnumXAssets_Internal{0x140271FC0, 0x14031F000};
@@ -74,6 +61,7 @@ namespace game
WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{0x140272E80, 0x14031FF80}; WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{0x140272E80, 0x14031FF80};
WEAK symbol<void(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x140272D50, 0x14031FE50}; WEAK symbol<void(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x140272D50, 0x14031FE50};
WEAK symbol<int(const char* zoneName)> DB_IsLocalized{0x140273210, 0x140320360}; WEAK symbol<int(const char* zoneName)> DB_IsLocalized{0x140273210, 0x140320360};
WEAK symbol<int(XAssetType type, void** assets, int maxCount)> DB_GetAllXAssetOfType_FastFile{0x0, 0x14031FC00};
WEAK symbol<void*(unsigned int size, unsigned int alignment, unsigned int type, PMem_Source source)> PMem_AllocFromSource_NoDebug{0x140430B80, 0x1404F46C0}; WEAK symbol<void*(unsigned int size, unsigned int alignment, unsigned int type, PMem_Source source)> PMem_AllocFromSource_NoDebug{0x140430B80, 0x1404F46C0};
WEAK symbol<void(const char* name, PMem_Direction allocDir)> PMem_Free{0x140430EC0 , 0x1404F4A30}; WEAK symbol<void(const char* name, PMem_Direction allocDir)> PMem_Free{0x140430EC0 , 0x1404F4A30};
@@ -102,7 +90,8 @@ namespace game
WEAK symbol<void(const dvar_t* dvar, const char* string)> Dvar_SetString{0x14042D6E0, 0x1404F08E0}; WEAK symbol<void(const dvar_t* dvar, const char* string)> Dvar_SetString{0x14042D6E0, 0x1404F08E0};
WEAK symbol<void(const char*, const char*, DvarSetSource)> Dvar_SetFromStringByNameFromSource{0x14042D000, 0x1404F00B0}; WEAK symbol<void(const char*, const char*, DvarSetSource)> Dvar_SetFromStringByNameFromSource{0x14042D000, 0x1404F00B0};
WEAK symbol<void()> Dvar_Sort{0x14042DEF0, 0x1404F1210}; WEAK symbol<void()> Dvar_Sort{0x14042DEF0, 0x1404F1210};
WEAK symbol<const char*(dvar_t* dvar, dvar_value value)> Dvar_ValueToString{0x14042E710, 0x1404F1A30}; WEAK symbol<const char*(dvar_t* dvar, DvarValue value)> Dvar_ValueToString{0x14042E710, 0x1404F1A30};
WEAK symbol<bool(const char* dvarName)> Dvar_IsValidName{0x0, 0x1404ED0E0};
WEAK symbol<long long (const char* qpath, char** buffer)> FS_ReadFile{0x14041D0B0, 0x1404DE900}; WEAK symbol<long long (const char* qpath, char** buffer)> FS_ReadFile{0x14041D0B0, 0x1404DE900};
WEAK symbol<void(void* buffer)> FS_FreeFile{0x14041D0A0, 0x1404DE8F0}; WEAK symbol<void(void* buffer)> FS_FreeFile{0x14041D0A0, 0x1404DE8F0};
@@ -110,33 +99,33 @@ namespace game
WEAK symbol<void(const char *path, const char *dir, int bLanguageDirectory, int iLanguage)> FS_AddGameDirectory{0x14041A120, 0x1404DC570}; WEAK symbol<void(const char *path, const char *dir, int bLanguageDirectory, int iLanguage)> FS_AddGameDirectory{0x14041A120, 0x1404DC570};
WEAK symbol<void(const char *path, const char *dir)> FS_AddLocalizedGameDirectory{0x14041A2F0, 0x1404DC760}; WEAK symbol<void(const char *path, const char *dir)> FS_AddLocalizedGameDirectory{0x14041A2F0, 0x1404DC760};
WEAK symbol<bool(int localClientNum)> GetRemoteEyeValues{0, 0x1402A0190};
WEAK symbol<Weapon(const char* pickupName, int model)> G_FindItem{0x140462490, 0x14021B7E0}; WEAK symbol<Weapon(const char* pickupName, int model)> G_FindItem{0x140462490, 0x14021B7E0};
WEAK symbol<int(playerState_t* ps, Weapon weapon, int dualWield, int startInAltMode, int usedBefore)> G_GivePlayerWeapon{0x140359E20, 0x1403DA5E0}; WEAK symbol<int(playerState_s* ps, Weapon weapon, int dualWield, int startInAltMode, int usedBefore)> G_GivePlayerWeapon{0x140359E20, 0x1403DA5E0};
WEAK symbol<Weapon(const char* name)> G_GetWeaponForName{0x140359890, 0x1403DA060}; WEAK symbol<Weapon(const char* name)> G_GetWeaponForName{0x140359890, 0x1403DA060};
WEAK symbol<void()> G_SetupLevelWeaponDef{0x0, 0x1403DA910};
WEAK symbol<void()> G_Glass_Update{0x14030E680, 0x140397450}; WEAK symbol<void()> G_Glass_Update{0x14030E680, 0x140397450};
WEAK symbol<void (playerState_t* ps, Weapon weapon, int hadWeapon)> G_InitializeAmmo{0x140311F00, 0x14039AEA0}; WEAK symbol<void (playerState_s* ps, Weapon weapon, int hadWeapon)> G_InitializeAmmo{0x140311F00, 0x14039AEA0};
WEAK symbol<void(int clientNum, Weapon weapon)> G_SelectWeapon{0x14035A200, 0x1403DA880}; WEAK symbol<void(int clientNum, Weapon weapon)> G_SelectWeapon{0x14035A200, 0x1403DA880};
WEAK symbol<int(playerState_t* ps, Weapon weapon)> G_TakePlayerWeapon{0x14035A350, 0x1403DA9C0}; WEAK symbol<int(playerState_s* ps, Weapon weapon)> G_TakePlayerWeapon{0x14035A350, 0x1403DA9C0};
WEAK symbol<unsigned int (const char* name, /*ConfigString*/ unsigned int start, unsigned int max, int create, WEAK symbol<unsigned int (const char* name, /*ConfigString*/ unsigned int start, unsigned int max, int create,
const char* errormsg)> G_FindConfigstringIndex{0, 0x140161F90}; const char* errormsg)> G_FindConfigstringIndex{0x0, 0x140161F90};
WEAK symbol<int(int server_time)> G_RunFrame{0, 0x1403A05E0}; WEAK symbol<int(int server_time)> G_RunFrame{0x0, 0x1403A05E0};
WEAK symbol<int(int clientNun)> G_GetClientScore{0, 0x14039EF60}; WEAK symbol<int(int clientNun)> G_GetClientScore{0x0, 0x14039EF60};
WEAK symbol<game_hudelem_t*(int clientNum, int teamNum)> HudElem_Alloc{0, 0x1403997E0}; WEAK symbol<game_hudelem_s*(int clientNum, int teamNum)> HudElem_Alloc{0x0, 0x1403997E0};
WEAK symbol<char*(char* string)> I_CleanStr{0x140432460, 0x1404F63C0}; WEAK symbol<char*(char* string)> I_CleanStr{0x140432460, 0x1404F63C0};
WEAK symbol<void(char* dest, const char* src, int destsize)> I_strncpyz{0x140432810, 0x1404F67A0};
WEAK symbol<void(char* dest, int size, const char* src)> I_strncat{0x140432740, 0x1404F66D0};
WEAK symbol<char*(GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount, WEAK symbol<char*(GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount,
uint32_t imageFlags, DXGI_FORMAT imageFormat, const char* name, const void* initData)> uint32_t imageFlags, DXGI_FORMAT imageFormat, const char* name, const void* initData)> Image_Setup{0x140517910, 0x1405E4380};
Image_Setup{0x140517910, 0x1405E4380};
WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x14023D9A0, 0x1402C40E0}; WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x14023D9A0, 0x1402C40E0};
WEAK symbol<unsigned int (int)> Live_SyncOnlineDataFlags{0, 0x1405ABF70}; WEAK symbol<unsigned int (int)> Live_SyncOnlineDataFlags{0, 0x1405ABF70};
WEAK symbol<void(int localControllerIndex)> LiveStorage_StatsWriteNotNeeded{0x1403BA420, 0x140409120};
WEAK symbol<const char*()> LiveStorage_FetchFFotD{0x140415FD0, 0x1404D7C00}; // get build number
WEAK symbol<bool(int controllerIndex, unsigned int name, int value, StatsGroup statsGroup)> LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730}; WEAK symbol<bool(int controllerIndex, unsigned int name, int value, StatsGroup statsGroup)> LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730};
WEAK symbol<bool(std::uint8_t* persistentData, const char* lookupString, int value, std::uint8_t* modifiedFlags, StatsGroup statsGroup)> LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820}; WEAK symbol<bool(std::uint8_t* persistentData, const char* lookupString, int value, std::uint8_t* modifiedFlags, StatsGroup statsGroup)> LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820};
WEAK symbol<std::int64_t(std::uint8_t* persistentData, const char* lookupString, const StatsGroup statsGroup)> LiveStorage_PlayerDataGetReservedInt{0x1403B84F0, 0x140403CF0}; WEAK symbol<std::int64_t(std::uint8_t* persistentData, const char* lookupString, const StatsGroup statsGroup)> LiveStorage_PlayerDataGetReservedInt{0x1403B84F0, 0x140403CF0};
@@ -157,34 +146,27 @@ namespace game
WEAK symbol<int(const StringTable* table, int comparisonColumn, const char* value)> StringTable_LookupRowNumForValue{0, 0x1404E6260}; WEAK symbol<int(const StringTable* table, int comparisonColumn, const char* value)> StringTable_LookupRowNumForValue{0, 0x1404E6260};
WEAK symbol<int(const char* string)> StringTable_HashString{0x1404259A0, 0x1404E6320}; WEAK symbol<int(const char* string)> StringTable_HashString{0x1404259A0, 0x1404E6320};
WEAK symbol<bool(int localClientNum, void* luaVM)> LUI_IntermissionBegan{0, 0x1401CEB40};
WEAK symbol<void(int localClientNum, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)> LUI_OpenMenu{0x1403FD460, 0x1404B3610}; WEAK symbol<void(int localClientNum, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)> LUI_OpenMenu{0x1403FD460, 0x1404B3610};
WEAK symbol<void(int localClientNum, const char* rootName, int deltaTime, void* luaVM)> LUI_Layout{0, 0x1401CF060};
// Made up name, replaced by ScopedCriticalSection on Black Ops 3 // Made up name, replaced by ScopedCriticalSection on Black Ops 3
WEAK symbol<void()> LUI_EnterCriticalSection{0x1401AE940, 0x1401CD040}; WEAK symbol<void()> LUI_EnterCriticalSection{0x1401AE940, 0x1401CD040};
WEAK symbol<void()> LUI_LeaveCriticalSection{0x1401B0AA0, 0x1401CF1A0}; WEAK symbol<void()> LUI_LeaveCriticalSection{0x1401B0AA0, 0x1401CF1A0};
WEAK symbol<bool(int localClientNum, const char* menuName)> Menu_IsMenuOpenAndVisible{0, 0x1404B38A0}; WEAK symbol<bool(int localClientNum, const char* menuName)> Menu_IsMenuOpenAndVisible{0x0, 0x1404B38A0};
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x140523D90, 0x1405F0E20}; WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x140523D90, 0x1405F0E20};
WEAK symbol<int(msg_t*)> MSG_ReadLong{0, 0x1404181C0}; WEAK symbol<void(netsrc_t, netadr_s*, const char*)> NET_OutOfBandPrint{0, 0x14041D5C0};
WEAK symbol<void(msg_t*, int, unsigned int)> MSG_WriteBits{0, 0x140418740}; WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_s* to)> NET_SendLoopPacket{0, 0x14041D780};
WEAK symbol<bool(const char* s, netadr_s* a)> NET_StringToAdr{0, 0x14041D870};
WEAK symbol<void(netsrc_t, netadr_t*, const char*)> NET_OutOfBandPrint{0, 0x14041D5C0}; WEAK symbol<void(netadr_s*, sockaddr*)> NetadrToSockadr{0, 0x1404E53D0};
WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendLoopPacket{0, 0x14041D780};
WEAK symbol<bool(const char* s, netadr_t* a)> NET_StringToAdr{0, 0x14041D870};
WEAK symbol<void(netadr_t*, sockaddr*)> NetadrToSockadr{0, 0x1404E53D0};
WEAK symbol<void*(int)> Omnvar_GetDef{0, 0x1404F3E80};
WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1, WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1,
float* color, Material* material)> R_AddCmdDrawStretchPic{0x140234460, 0x140600BE0}; float* color, Material* material)> R_AddCmdDrawStretchPic{0x140234460, 0x140600BE0};
WEAK symbol<void(const char*, int, const Font_t*, float, float, float, float, float, const float*, int)> R_AddCmdDrawText{0x140533E40, 0x140601070}; WEAK symbol<void(const char*, int, Font_s*, float, float, float, float, float, float*, int)> R_AddCmdDrawText{0x140533E40, 0x140601070};
WEAK symbol<void(const char*, int, const Font_t*, float, float, float, float, float, const float*, int, int, char)> R_AddCmdDrawTextWithCursor{0x140534170, 0x1406013A0}; WEAK symbol<void(const char*, int, Font_s*, float, float, float, float, float, const float*, int, int, char)> R_AddCmdDrawTextWithCursor{0x140534170, 0x1406013A0};
WEAK symbol<Font_t*(const char* font)> R_RegisterFont{0x1405130B0, 0x1405DFAC0}; WEAK symbol<Font_s*(const char* font)> R_RegisterFont{0x1405130B0, 0x1405DFAC0};
WEAK symbol<void()> R_SyncRenderThread{0x140535AF0, 0x140602D30}; WEAK symbol<void()> R_SyncRenderThread{0x140535AF0, 0x140602D30};
WEAK symbol<int(const char* text, int maxChars, Font_t* font)> R_TextWidth{0x140513390, 0x1405DFDB0}; WEAK symbol<int(const char* text, int maxChars, Font_s* font)> R_TextWidth{0x140513390, 0x1405DFDB0};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x14024D150, 0x1402F6D40}; WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x14024D150, 0x1402F6D40};
@@ -197,8 +179,8 @@ namespace game
WEAK symbol<const char*(unsigned int index)> Scr_GetString{0x1403DE200, 0x140439160}; WEAK symbol<const char*(unsigned int index)> Scr_GetString{0x1403DE200, 0x140439160};
WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstString{0x1403DDAC0, 0x140438AD0}; WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstString{0x1403DDAC0, 0x140438AD0};
WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstIString{0x1403DD8A0, 0x1404388B0}; WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstIString{0x1403DD8A0, 0x1404388B0};
WEAK symbol<void(int value)> Scr_AddInt{0, 0x140437E70}; WEAK symbol<void(int value)> Scr_AddInt{0x0, 0x140437E70};
WEAK symbol<void(const char* value)> Scr_AddString{0, 0x1404381D0}; WEAK symbol<void(const char* value)> Scr_AddString{0x0, 0x1404381D0};
WEAK symbol<int(unsigned int index)> Scr_GetInt{0x1403DDEB0, 0x140438E10}; WEAK symbol<int(unsigned int index)> Scr_GetInt{0x1403DDEB0, 0x140438E10};
WEAK symbol<float(unsigned int index)> Scr_GetFloat{0x1403DDD50, 0x140438D60}; WEAK symbol<float(unsigned int index)> Scr_GetFloat{0x1403DDD50, 0x140438D60};
WEAK symbol<void(unsigned int index, float* vectorValue)> Scr_GetVector{0x1403DE470, 0x1404393D0}; WEAK symbol<void(unsigned int index, float* vectorValue)> Scr_GetVector{0x1403DE470, 0x1404393D0};
@@ -206,11 +188,11 @@ namespace game
WEAK symbol<unsigned int()> Scr_GetNumParam{0x1403DDF60, 0x140438EC0}; WEAK symbol<unsigned int()> Scr_GetNumParam{0x1403DDF60, 0x140438EC0};
WEAK symbol<void()> Scr_ClearOutParams{0x1403DD500, 0x140438600}; WEAK symbol<void()> Scr_ClearOutParams{0x1403DD500, 0x140438600};
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x1403DBDC0, 0x140436E10}; WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x1403DBDC0, 0x140436E10};
WEAK symbol<void(int entnum, unsigned int classnum)> Scr_AddEntityNum{0, 0x140437F60}; WEAK symbol<void(int entnum, unsigned int classnum)> Scr_AddEntityNum{0x0, 0x140437F60};
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x140350E70, 0x1403D3FE0}; WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x140350E70, 0x1403D3FE0};
WEAK symbol<void(unsigned int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x1403DE730, 0x140439700}; WEAK symbol<void(unsigned int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x1403DE730, 0x140439700};
WEAK symbol<void(unsigned int stringValue, unsigned int paramcount)> Scr_NotifyLevel{0, 0x1404397D0}; WEAK symbol<void(unsigned int stringValue, unsigned int paramcount)> Scr_NotifyLevel{0x0, 0x1404397D0};
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0, 0x140436D60}; WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0x0, 0x140436D60};
WEAK symbol<bool(VariableValue* value)> Scr_CastString{0x1403D9BA0, 0x140434AC0}; WEAK symbol<bool(VariableValue* value)> Scr_CastString{0x1403D9BA0, 0x140434AC0};
WEAK symbol<unsigned __int16(int handle, unsigned int paramcount)> Scr_ExecThread{0x1403DD600, 0x1404386E0}; WEAK symbol<unsigned __int16(int handle, unsigned int paramcount)> Scr_ExecThread{0x1403DD600, 0x1404386E0};
@@ -235,32 +217,35 @@ namespace game
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403B4560, 0x1403F80D0}; WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403B4560, 0x1403F80D0};
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0, 0x1403F8150}; WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0, 0x1403F8150};
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0, 0x1403F8110}; WEAK symbol<void()> SV_Cmd_EndTokenizedString{0, 0x1403F8110};
WEAK symbol<void()> SV_MatchEnd{0, 0x14047A090}; WEAK symbol<void()> SV_MatchEnd{0x0, 0x14047A090};
WEAK symbol<void(netadr_t* from)> SV_DirectConnect{0, 0x140471390}; WEAK symbol<void(netadr_s* from)> SV_DirectConnect{0, 0x140471390};
WEAK symbol<bool()> SV_Loaded{0x140491820, 0x1404770C0}; WEAK symbol<bool()> SV_Loaded{0x140491820, 0x1404770C0};
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded)> SV_StartMap{0, 0x140470170}; WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded)> SV_StartMap{0, 0x140470170};
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded, bool migrate)> SV_StartMapForParty{0, 0x1404702F0}; WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded, bool migrate)> SV_StartMapForParty{0, 0x1404702F0};
WEAK symbol<void(mp::client_t* cl, int isReconnectingClient)> SV_StreamSync_ClientConnect{0x0, 0x140488080};
WEAK symbol<const char*(int clientNum, unsigned __int16 scriptPersId)> ClientConnect{0x0, 0x140387630};
WEAK symbol<void(mp::client_t* client, usercmd_s* cmd)> SV_ClientEnterWorld{0x0, 0x1404710F0};
WEAK symbol<void(mp::client_t* drop, const char* reason, bool tellThem)> SV_DropClient{0x0, 0x140472110};
WEAK symbol<mp::gentity_t*(const char*, unsigned int, unsigned int, unsigned int)> SV_AddBot{0, 0x140470920}; WEAK symbol<mp::gentity_s*(const char*, unsigned int, unsigned int, unsigned int)> SV_AddBot{0, 0x140470920};
WEAK symbol<bool(int clientNum)> SV_BotIsBot{0, 0x140461340}; WEAK symbol<bool(int clientNum)> SV_BotIsBot{0, 0x140461340};
WEAK symbol<const char*()> SV_BotGetRandomName{0, 0x140460B80}; WEAK symbol<const char*()> SV_BotGetRandomName{0, 0x140460B80};
WEAK symbol<void(mp::gentity_t*)> SV_SpawnTestClient{0, 0x1404740A0}; WEAK symbol<void(mp::gentity_s*)> SV_SpawnTestClient{0, 0x1404740A0};
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0, 0x140472430}; WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0, 0x140472430};
WEAK symbol<void()> SV_FastRestart{0x14048B890, 0x14046F440}; WEAK symbol<void()> SV_FastRestart{0x14048B890, 0x14046F440};
WEAK symbol<playerState_t*(int num)> SV_GetPlayerstateForClientNum{0x140490F80, 0x140475A10}; WEAK symbol<playerState_s*(int num)> SV_GetPlayerstateForClientNum{0x140490F80, 0x140475A10};
WEAK symbol<const char*(int clientNum)> SV_GetGuid{0, 0x140475990}; WEAK symbol<const char*(int clientNum)> SV_GetGuid{0, 0x140475990};
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0, 0x14046F730}; WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0, 0x14046F730};
WEAK symbol<bool(mp::client_t*, const char*, int)> SV_Netchan_Transmit{0, 0x14047CC60};
WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0, 0x140477450}; WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0, 0x140477450};
WEAK symbol<void(int clientNum, svscmd_type type, const char* text)> SV_GameSendServerCommand{0x140490F40, 0x1404758C0}; WEAK symbol<int(mp::client_t* client, const unsigned char* cmd, int cmdSize)> SV_CanReplaceServerCommand{0x0, 0x140478F00};
WEAK symbol<void(const char* error, ...)> Sys_Error{0x14043AC20, 0x1404FF510}; WEAK symbol<void(const char* error, ...)> Sys_Error{0x14043AC20, 0x1404FF510};
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920}; WEAK symbol<bool()> Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920};
WEAK symbol<int()> Sys_Milliseconds{0x14043D2A0, 0x140501CA0}; WEAK symbol<int()> Sys_Milliseconds{0x14043D2A0, 0x140501CA0};
WEAK symbol<void()> Sys_ShowConsole{0x14043E650, 0x140503130}; WEAK symbol<void()> Sys_ShowConsole{0x14043E650, 0x140503130};
WEAK symbol<bool(int, void const*, const netadr_t*)> Sys_SendPacket{0x14043D000, 0x140501A00}; WEAK symbol<bool(int, void const*, const netadr_s*)> Sys_SendPacket{0x14043D000, 0x140501A00};
WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x1403C2C30, 0x1404237D0}; WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x1403C2C30, 0x1404237D0};
WEAK symbol<bool()> Sys_IsMainThread{0x1478FC470, 0x140423950}; WEAK symbol<bool()> Sys_IsMainThread{0x1478FC470, 0x140423950};
WEAK symbol<HANDLE(Sys_Folder folder, const char* baseFilename)> Sys_CreateFile{0x140434B10, 0x1404F8FD0}; WEAK symbol<HANDLE(Sys_Folder folder, const char* baseFilename)> Sys_CreateFile{0x140434B10, 0x1404F8FD0};
@@ -271,9 +256,7 @@ namespace game
WEAK symbol<const char*(const char*)> UI_LocalizeMapname{0, 0x1404B96D0}; WEAK symbol<const char*(const char*)> UI_LocalizeMapname{0, 0x1404B96D0};
WEAK symbol<const char*(const char*)> UI_LocalizeGametype{0, 0x1404B90F0}; WEAK symbol<const char*(const char*)> UI_LocalizeGametype{0, 0x1404B90F0};
WEAK symbol<void(int localClientNum, const char* srcString, char* dstString, int dstBufferSize)> UI_ReplaceDirective{0x0, 0x1404D8A00};
WEAK symbol<void(int localClientNum, mp::cg_t* cg, vec3_t* outViewOrigin,
vec3_t* outViewAngles)> VehicleCam_UpdatePlayerControlCam{0, 0x140568CA0};
WEAK symbol<DWOnlineStatus(int)> dwGetLogOnStatus{0, 0x140589490}; WEAK symbol<DWOnlineStatus(int)> dwGetLogOnStatus{0, 0x140589490};
@@ -282,10 +265,14 @@ namespace game
WEAK symbol<void(const pmove_t* move, trace_t* trace, const float*, WEAK symbol<void(const pmove_t* move, trace_t* trace, const float*,
const float*, const Bounds*, int, int)> PM_trace{0, 0x140225DB0}; const float*, const Bounds*, int, int)> PM_trace{0, 0x140225DB0};
WEAK symbol<void(void* ps)> Jump_ClearState{0, 0x140213120}; WEAK symbol<void(void* ps)> Jump_ClearState{0x0, 0x140213120};
WEAK symbol<void(const char* pszCommand, char* pszBuffer, int iBufferSize)> MSG_WriteReliableCommandToBuffer{0x0, 0x1404232B0};
WEAK symbol<void(unsigned __int64 markPos)> LargeLocalResetToMark{0x140423B50, 0x1404E4D00}; WEAK symbol<void(unsigned __int64 markPos)> LargeLocalResetToMark{0x140423B50, 0x1404E4D00};
WEAK symbol<int(const char* s0, const char* s1, int n)> I_strnicmp{0x140432840, 0x1404F67D0};
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14062E030, 0x140738060}; WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14062E030, 0x140738060};
WEAK symbol<int (jmp_buf* Buf)> _setjmp{0x14062F030, 0x140739060}; WEAK symbol<int (jmp_buf* Buf)> _setjmp{0x14062F030, 0x140739060};
@@ -297,7 +284,7 @@ namespace game
WEAK symbol<CmdArgs> cmd_args{0x144CE7F70, 0x144518480}; WEAK symbol<CmdArgs> cmd_args{0x144CE7F70, 0x144518480};
WEAK symbol<CmdArgs> sv_cmd_args{0x144CE8020, 0x144518530}; WEAK symbol<CmdArgs> sv_cmd_args{0x144CE8020, 0x144518530};
WEAK symbol<cmd_function_t*> cmd_functions{0x144CE80C8, 0x1445185D8}; WEAK symbol<cmd_function_s*> cmd_functions{0x144CE80C8, 0x1445185D8};
WEAK symbol<int> dvarCount{0x1458CBA3C, 0x1478EADF4}; WEAK symbol<int> dvarCount{0x1458CBA3C, 0x1478EADF4};
WEAK symbol<dvar_t*> sortedDvars{0x1458CBA60, 0x1478EAE10}; WEAK symbol<dvar_t*> sortedDvars{0x1458CBA60, 0x1478EAE10};
@@ -321,6 +308,8 @@ namespace game
WEAK symbol<DB_LoadData> g_load{0x1418CF4A0, 0x141EEFBA0}; WEAK symbol<DB_LoadData> g_load{0x1418CF4A0, 0x141EEFBA0};
WEAK symbol<float> com_codeTimeScale{0x0, 0x1445CEF84};
WEAK symbol<WinVars_t> g_wv{0x145A7BAD0, 0x147AD2630}; WEAK symbol<WinVars_t> g_wv{0x145A7BAD0, 0x147AD2630};
WEAK symbol<WinMouseVars_t> s_wmv{0x145A73750, 0x147AC9D78}; WEAK symbol<WinMouseVars_t> s_wmv{0x145A73750, 0x147AC9D78};
@@ -334,37 +323,38 @@ namespace game
WEAK symbol<function_stack_t> scr_function_stack{0x1455BE708, 0x144D57808}; WEAK symbol<function_stack_t> scr_function_stack{0x1455BE708, 0x144D57808};
WEAK symbol<unsigned int> scr_levelEntityId{0x1452A9F30, 0x144A43020}; WEAK symbol<unsigned int> scr_levelEntityId{0x1452A9F30, 0x144A43020};
WEAK symbol<int> level_time{0, 0x1443F4B6C}; WEAK symbol<int> level_time{0x0, 0x1443F4B6C};
WEAK symbol<int> level_finished{0, 0x1443F6FAC}; WEAK symbol<int> level_finished{0x0, 0x1443F6FAC};
WEAK symbol<int> level_savepersist{0, 0x1443F5710}; WEAK symbol<int> level_savepersist{0x0, 0x1443F5710};
WEAK symbol<DWORD> threadIds{0x144DE6640, 0x1446B4960}; WEAK symbol<DWORD> threadIds{0x144DE6640, 0x1446B4960};
WEAK symbol<GfxDrawMethod_t> gfxDrawMethod{0x145F525A8, 0x1480350D8}; WEAK symbol<GfxDrawMethod_s> gfxDrawMethod{0x145F525A8, 0x1480350D8};
namespace sp namespace sp
{ {
WEAK symbol<gentity_t> g_entities{0x143C91600, 0}; WEAK symbol<gentity_s> g_entities{0x143C91600, 0};
WEAK symbol<XZone> g_zones_0{0x1434892D8, 0}; WEAK symbol<XZone> g_zones_0{0x1434892D8, 0};
} }
namespace mp namespace mp
{ {
WEAK symbol<cg_t> cg{0, 0x14176EC00}; WEAK symbol<cg_s> cgArray{0, 0x14176EC00};
WEAK symbol<cgs_t> cgs{0, 0x14187EB80};
WEAK symbol<clientActive_t> cl{0, 0x1419E1C70};
WEAK symbol<clientConnection_t> clc{0, 0x141CB5350};
WEAK symbol<clientStatic_t> cls{0, 0x141D1AB60};
WEAK symbol<serverStatic_t> svs{0, 0x144DF9580};
WEAK symbol<gentity_t> g_entities{0, 0x14427A0E0}; WEAK symbol<gentity_s> g_entities{0, 0x14427A0E0};
WEAK symbol<centity_t> centities{0, 0x141887350};
WEAK symbol<int> svs_clientCount{0, 0x14647B28C};
WEAK symbol<client_t> svs_clients{0, 0x14647B290};
WEAK symbol<std::uint32_t> sv_serverId_value{0, 0x144DF9478};
WEAK symbol<int> gameTime{0, 0x1443F4B6C};
WEAK symbol<int> serverTime{0, 0x14647B280};
WEAK symbol<XZone> g_zones_0{0, 0x143A46498}; WEAK symbol<XZone> g_zones_0{0, 0x143A46498};
WEAK symbol<connstate_t> connstate{0, 0x1419E1AE0};
WEAK symbol<int> gameTime{0, 0x1443F4B6C}; WEAK symbol<int> s_launchDataAvailable{0x0, 0x1445CE354};
WEAK symbol<std::uint32_t> sv_serverId_value{0, 0x144DF9478};
} }
namespace hks namespace hks
@@ -1,115 +0,0 @@
#include <std_include.hpp>
#include "html_frame.hpp"
doc_host_ui_handler::doc_host_ui_handler(html_frame* frame): frame_(frame)
{
}
HRESULT doc_host_ui_handler::QueryInterface(REFIID riid, LPVOID* ppvObj)
{
auto client_site = this->frame_->get_client_site();
if (client_site)
{
return client_site->QueryInterface(riid, ppvObj);
}
return E_NOINTERFACE;
}
ULONG doc_host_ui_handler::AddRef()
{
return 1;
}
ULONG doc_host_ui_handler::Release()
{
return 1;
}
HRESULT doc_host_ui_handler::ShowContextMenu(DWORD /*dwID*/, POINT* /*ppt*/, IUnknown* /*pcmdtReserved*/,
IDispatch* /*pdispReserved*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::ShowUI(DWORD /*dwID*/, IOleInPlaceActiveObject* /*pActiveObject*/,
IOleCommandTarget* /*pCommandTarget*/,
IOleInPlaceFrame* /*pFrame*/, IOleInPlaceUIWindow* /*pDoc*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::HideUI()
{
return S_OK;
}
HRESULT doc_host_ui_handler::UpdateUI()
{
return S_OK;
}
HRESULT doc_host_ui_handler::EnableModeless(BOOL /*fEnable*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::OnDocWindowActivate(BOOL /*fActivate*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::OnFrameWindowActivate(BOOL /*fActivate*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::ResizeBorder(LPCRECT /*prcBorder*/, IOleInPlaceUIWindow* /*pUIWindow*/,
BOOL /*fRameWindow*/)
{
return S_OK;
}
HRESULT doc_host_ui_handler::TranslateAcceleratorA(LPMSG /*lpMsg*/, const GUID* pguidCmdGroup, DWORD /*nCmdID*/)
{
pguidCmdGroup = nullptr;
return S_FALSE;
}
HRESULT doc_host_ui_handler::GetOptionKeyPath(LPOLESTR* /*pchKey*/, DWORD /*dw*/)
{
return S_FALSE;
}
HRESULT doc_host_ui_handler::GetDropTarget(IDropTarget* /*pDropTarget*/, IDropTarget** /*ppDropTarget*/)
{
return S_FALSE;
}
HRESULT doc_host_ui_handler::GetExternal(IDispatch** ppDispatch)
{
*ppDispatch = this->frame_->get_html_dispatch();
return (*ppDispatch) ? S_OK : S_FALSE;
}
HRESULT doc_host_ui_handler::FilterDataObject(IDataObject* /*pDO*/, IDataObject** ppDORet)
{
*ppDORet = nullptr;
return S_FALSE;
}
HRESULT STDMETHODCALLTYPE doc_host_ui_handler::TranslateUrl(DWORD /*dwTranslate*/, OLECHAR __RPC_FAR* /*pchURLIn*/,
OLECHAR __RPC_FAR* __RPC_FAR* ppchURLOut)
{
*ppchURLOut = nullptr;
return S_FALSE;
}
HRESULT doc_host_ui_handler::GetHostInfo(DOCHOSTUIINFO __RPC_FAR * pInfo)
{
pInfo->cbSize = sizeof(DOCHOSTUIINFO);
pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_DPI_AWARE /*| DOCHOSTUIFLAG_SCROLL_NO*/;
pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
return S_OK;
}
@@ -1,47 +0,0 @@
#pragma once
class html_frame;
class doc_host_ui_handler final : public IDocHostUIHandler
{
public:
doc_host_ui_handler(html_frame* frame);
virtual ~doc_host_ui_handler() = default;
private:
html_frame* frame_;
public: // IDocHostUIHandler interface
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObj) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE ShowContextMenu(
DWORD dwID,
POINT __RPC_FAR * ppt,
IUnknown __RPC_FAR * pcmdtReserved,
IDispatch __RPC_FAR * pdispReserved) override;
HRESULT STDMETHODCALLTYPE ShowUI(
DWORD dwID,
IOleInPlaceActiveObject __RPC_FAR * pActiveObject,
IOleCommandTarget __RPC_FAR * pCommandTarget,
IOleInPlaceFrame __RPC_FAR * pFrame,
IOleInPlaceUIWindow __RPC_FAR * pDoc) override;
HRESULT STDMETHODCALLTYPE GetHostInfo(DOCHOSTUIINFO __RPC_FAR * pInfo) override;
HRESULT STDMETHODCALLTYPE HideUI() override;
HRESULT STDMETHODCALLTYPE UpdateUI() override;
HRESULT STDMETHODCALLTYPE EnableModeless(BOOL fEnable) override;
HRESULT STDMETHODCALLTYPE OnDocWindowActivate(BOOL fActivate) override;
HRESULT STDMETHODCALLTYPE OnFrameWindowActivate(BOOL fActivate) override;
HRESULT STDMETHODCALLTYPE ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow __RPC_FAR * pUIWindow,
BOOL fRameWindow) override;
HRESULT STDMETHODCALLTYPE TranslateAccelerator(LPMSG lpMsg, const GUID __RPC_FAR * pguidCmdGroup, DWORD nCmdID)
override;
HRESULT STDMETHODCALLTYPE GetOptionKeyPath(LPOLESTR __RPC_FAR * pchKey, DWORD dw) override;
HRESULT STDMETHODCALLTYPE GetDropTarget(IDropTarget __RPC_FAR * pDropTarget,
IDropTarget __RPC_FAR *__RPC_FAR * ppDropTarget) override;
HRESULT STDMETHODCALLTYPE GetExternal(IDispatch __RPC_FAR *__RPC_FAR * ppDispatch) override;
HRESULT STDMETHODCALLTYPE TranslateUrl(DWORD dwTranslate, OLECHAR __RPC_FAR * pchURLIn,
OLECHAR __RPC_FAR *__RPC_FAR * ppchURLOut) override;
HRESULT STDMETHODCALLTYPE FilterDataObject(IDataObject __RPC_FAR * pDO, IDataObject __RPC_FAR *__RPC_FAR * ppDORet)
override;
};
@@ -1,48 +0,0 @@
#include <std_include.hpp>
#include "html_argument.hpp"
html_argument::html_argument(VARIANT* val) : value_(val)
{
}
bool html_argument::is_empty() const
{
return this->value_ == nullptr || this->value_->vt == VT_EMPTY;
}
bool html_argument::is_string() const
{
if (this->is_empty()) return false;
return this->value_->vt == VT_BSTR;
}
bool html_argument::is_number() const
{
if (this->is_empty()) return false;
return this->value_->vt == VT_I4;
}
bool html_argument::is_bool() const
{
if (this->is_empty()) return false;
return this->value_->vt == VT_BOOL;
}
std::string html_argument::get_string() const
{
if (!this->is_string()) return {};
std::wstring wide_string(this->value_->bstrVal);
return std::string(wide_string.begin(), wide_string.end());
}
int html_argument::get_number() const
{
if (!this->is_number()) return 0;
return this->value_->intVal;
}
bool html_argument::get_bool() const
{
if (!this->is_bool()) return false;
return this->value_->boolVal != FALSE;
}
@@ -1,20 +0,0 @@
#pragma once
class html_argument final
{
public:
html_argument(VARIANT* val);
bool is_empty() const;
bool is_string() const;
bool is_number() const;
bool is_bool() const;
std::string get_string() const;
int get_number() const;
bool get_bool() const;
private:
VARIANT* value_;
};
@@ -1,61 +0,0 @@
#include <std_include.hpp>
#include "html_frame.hpp"
html_dispatch::html_dispatch(html_frame* frame) : frame_(frame)
{
}
HRESULT html_dispatch::QueryInterface(const IID& riid, LPVOID* ppvObj)
{
if (!memcmp(&riid, &IID_IUnknown, sizeof(GUID)) ||
!memcmp(&riid, &IID_IDispatch, sizeof(GUID)))
{
*ppvObj = this;
this->AddRef();
return S_OK;
}
*ppvObj = nullptr;
return E_NOINTERFACE;
}
ULONG html_dispatch::AddRef()
{
return 1;
}
ULONG html_dispatch::Release()
{
return 1;
}
HRESULT html_dispatch::GetTypeInfoCount(UINT* pctinfo)
{
return S_FALSE;
}
HRESULT html_dispatch::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
{
return S_FALSE;
}
HRESULT html_dispatch::GetIDsOfNames(const IID& riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
{
for (unsigned int i = 0; i < cNames; ++i)
{
std::wstring wide_name(rgszNames[i]);
std::string name(wide_name.begin(), wide_name.end());
rgDispId[i] = this->frame_->get_callback_id(name);
}
return S_OK;
}
HRESULT html_dispatch::Invoke(DISPID dispIdMember, const IID& riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
html_frame::callback_params params(pDispParams, pVarResult);
this->frame_->invoke_callback(dispIdMember, &params);
return S_OK;
}
@@ -1,24 +0,0 @@
#pragma once
class html_frame;
class html_dispatch final : public IDispatch
{
public:
html_dispatch(html_frame* frame);
virtual ~html_dispatch() = default;
private:
html_frame* frame_;
public: // IDispatch interface
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID FAR* ppvObj) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override;
HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override;
HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
override;
HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) override;
};
-320
View File
@@ -1,320 +0,0 @@
#include <std_include.hpp>
#include "html_frame.hpp"
#include <utils/nt.hpp>
#include <utils/hook.hpp>
std::atomic<int> html_frame::frame_count_ = 0;
namespace
{
void* original_func{};
GUID browser_emulation_guid{0xac969931, 0x3566, 0x4b50, {0xae, 0x48, 0x71, 0xb9, 0x6a, 0x75, 0xc8, 0x79}};
int WINAPI co_internet_feature_value_internal_stub(const GUID* guid, uint32_t* result)
{
const auto res = static_cast<decltype(co_internet_feature_value_internal_stub)*>(original_func)(guid, result);
if (IsEqualGUID(*guid, browser_emulation_guid))
{
*result = 11000;
return 0;
}
return res;
}
void setup_ie_hook()
{
static const auto _ = []
{
const auto urlmon = utils::nt::library::load("urlmon.dll"s);
const auto target = urlmon.get_iat_entry("iertutil.dll", MAKEINTRESOURCEA(700));
original_func = *target;
utils::hook::set(target, co_internet_feature_value_internal_stub);
return 0;
}();
(void)_;
}
}
html_frame::callback_params::callback_params(DISPPARAMS* params, VARIANT* res) : result(res)
{
for (auto i = params->cArgs; i > 0; --i)
{
auto param = &params->rgvarg[i - 1];
this->arguments.emplace_back(param);
}
}
html_frame::html_frame() : in_place_frame_(this), in_place_site_(this), ui_handler_(this), client_site_(this),
html_dispatch_(this)
{
setup_ie_hook();
if (frame_count_++ == 0 && OleInitialize(nullptr) != S_OK)
{
throw std::runtime_error("Unable to initialize the OLE library");
}
set_browser_feature("FEATURE_BROWSER_EMULATION", 11000);
set_browser_feature("FEATURE_GPU_RENDERING", 1);
}
html_frame::~html_frame()
{
if (--frame_count_ <= 0)
{
frame_count_ = 0;
OleUninitialize();
}
}
void html_frame::object_deleter(IUnknown* object)
{
if (object)
{
object->Release();
}
}
HWND html_frame::get_window() const
{
return this->window_;
}
std::shared_ptr<IOleObject> html_frame::get_browser_object() const
{
return this->browser_object_;
}
ole_in_place_frame* html_frame::get_in_place_frame()
{
return &this->in_place_frame_;
}
ole_in_place_site* html_frame::get_in_place_site()
{
return &this->in_place_site_;
}
doc_host_ui_handler* html_frame::get_ui_handler()
{
return &this->ui_handler_;
}
ole_client_site* html_frame::get_client_site()
{
return &this->client_site_;
}
html_dispatch* html_frame::get_html_dispatch()
{
return &this->html_dispatch_;
}
std::shared_ptr<IWebBrowser2> html_frame::get_web_browser() const
{
if (!this->browser_object_) return {};
IWebBrowser2* web_browser = nullptr;
if (FAILED(this->browser_object_->QueryInterface(IID_IWebBrowser2, reinterpret_cast<void**>(&web_browser)))
|| !web_browser)
return {};
return std::shared_ptr<IWebBrowser2>(web_browser, object_deleter);
}
std::shared_ptr<IDispatch> html_frame::get_dispatch() const
{
const auto web_browser = this->get_web_browser();
if (!web_browser) return {};
IDispatch* dispatch = nullptr;
if (FAILED(web_browser->get_Document(&dispatch)) || !dispatch) return {};
return std::shared_ptr<IDispatch>(dispatch, object_deleter);
}
std::shared_ptr<IHTMLDocument2> html_frame::get_document() const
{
const auto dispatch = this->get_dispatch();
if (!dispatch) return {};
IHTMLDocument2* document = nullptr;
if (FAILED(dispatch->QueryInterface(IID_IHTMLDocument2, reinterpret_cast<void**>(&document)))
|| !document)
return {};
return std::shared_ptr<IHTMLDocument2>(document, object_deleter);
}
void html_frame::initialize(const HWND window)
{
if (this->window_) return;
this->window_ = window;
this->create_browser();
this->initialize_browser();
}
void html_frame::create_browser()
{
LPCLASSFACTORY class_factory = nullptr;
if (FAILED(
CoGetClassObject(CLSID_WebBrowser, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, nullptr, IID_IClassFactory,
reinterpret_cast<void **>(&class_factory))) || !class_factory)
{
throw std::runtime_error("Unable to get the class factory");
}
IOleObject* browser_object = nullptr;
class_factory->CreateInstance(nullptr, IID_IOleObject, reinterpret_cast<void**>(&browser_object));
class_factory->Release();
if (!browser_object)
{
throw std::runtime_error("Unable to create browser object");
}
this->browser_object_ = std::shared_ptr<IOleObject>(browser_object, [](IOleObject* browser_object)
{
if (browser_object)
{
browser_object->Close(OLECLOSE_NOSAVE);
object_deleter(browser_object);
}
});
}
void html_frame::initialize_browser()
{
this->browser_object_->SetClientSite(this->get_client_site());
this->browser_object_->SetHostNames(L"Hostname", nullptr);
RECT rect;
GetClientRect(this->get_window(), &rect);
OleSetContainedObject(this->browser_object_.get(), TRUE);
this->browser_object_->DoVerb(OLEIVERB_SHOW, nullptr, this->get_client_site(), -1, this->get_window(), &rect);
this->resize(rect.right, rect.bottom);
}
bool html_frame::set_browser_feature(const std::string& feature, DWORD value)
{
const auto registry_path = R"(SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\)" + feature;
HKEY key = nullptr;
if (RegCreateKeyA(HKEY_CURRENT_USER, registry_path.data(), &key) == ERROR_SUCCESS)
{
RegCloseKey(key);
}
key = nullptr;
if (RegOpenKeyExA(
HKEY_CURRENT_USER, registry_path.data(), 0,
KEY_ALL_ACCESS, &key) != ERROR_SUCCESS)
{
return false; // Error :(
}
const utils::nt::library self;
const auto name = self.get_name();
DWORD type{};
auto is_new = true;
if (RegQueryValueExA(key, name.data(), nullptr, &type, nullptr, nullptr) == ERROR_SUCCESS)
{
is_new = false;
}
RegSetValueExA(key, name.data(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(key);
return is_new;
}
void html_frame::resize(const DWORD width, const DWORD height) const
{
auto web_browser = this->get_web_browser();
if (web_browser)
{
web_browser->put_Left(0);
web_browser->put_Top(0);
web_browser->put_Width(width);
web_browser->put_Height(height);
}
}
bool html_frame::load_url(const std::string& url) const
{
auto web_browser = this->get_web_browser();
if (!web_browser) return false;
std::wstring wide_url(url.begin(), url.end());
VARIANT my_url;
VariantInit(&my_url);
my_url.vt = VT_BSTR;
my_url.bstrVal = SysAllocString(wide_url.data());
const auto _ = gsl::finally([&my_url]() { VariantClear(&my_url); });
if (!my_url.bstrVal) return false;
return SUCCEEDED(web_browser->Navigate2(&my_url, nullptr, nullptr, nullptr, nullptr));
}
bool html_frame::load_html(const std::string& html) const
{
if (!this->load_url("about:blank")) return false;
const auto document = this->get_document();
if (!document) return false;
SAFEARRAYBOUND safe_array_bound = {1, 0};
auto safe_array = SafeArrayCreate(VT_VARIANT, 1, &safe_array_bound);
if (!safe_array) return false;
const auto _ = gsl::finally([safe_array]() { SafeArrayDestroy(safe_array); });
VARIANT* variant = nullptr;
if (FAILED(SafeArrayAccessData(safe_array, reinterpret_cast<void**>(&variant))) || !variant) return false;
std::wstring wide_html(html.begin(), html.end());
variant->vt = VT_BSTR;
variant->bstrVal = SysAllocString(wide_html.data());
if (!variant->bstrVal) return false;
document->write(safe_array);
document->close();
return true;
}
int html_frame::get_callback_id(const std::string& name)
{
for (auto i = 0u; i < this->callbacks_.size(); ++i)
{
if (this->callbacks_[i].first == name)
{
return i;
}
}
return -1;
}
void html_frame::invoke_callback(const int id, callback_params* params)
{
if (id >= 0 && static_cast<unsigned int>(id) < this->callbacks_.size())
{
this->callbacks_[id].second(params);
}
}
void html_frame::register_callback(const std::string& name, const std::function<void(callback_params*)>& callback)
{
this->callbacks_.emplace_back(name, callback);
}
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include "ole_in_place_frame.hpp"
#include "ole_in_place_site.hpp"
#include "doc_host_ui_handler.hpp"
#include "ole_client_site.hpp"
#include "html_dispatch.hpp"
#include "html_argument.hpp"
class html_frame
{
public:
class callback_params final
{
public:
callback_params(DISPPARAMS* params, VARIANT* res);
std::vector<html_argument> arguments;
html_argument result;
};
html_frame();
virtual ~html_frame();
void initialize(HWND window);
void resize(DWORD width, DWORD height) const;
bool load_url(const std::string& url) const;
bool load_html(const std::string& html) const;
HWND get_window() const;
std::shared_ptr<IOleObject> get_browser_object() const;
std::shared_ptr<IWebBrowser2> get_web_browser() const;
std::shared_ptr<IDispatch> get_dispatch() const;
std::shared_ptr<IHTMLDocument2> get_document() const;
ole_in_place_frame* get_in_place_frame();
ole_in_place_site* get_in_place_site();
doc_host_ui_handler* get_ui_handler();
ole_client_site* get_client_site();
html_dispatch* get_html_dispatch();
int get_callback_id(const std::string& name);
void invoke_callback(int id, callback_params* params);
void register_callback(const std::string& name, const std::function<void(callback_params*)>& callback);
private:
HWND window_ = nullptr;
std::shared_ptr<IOleObject> browser_object_;
ole_in_place_frame in_place_frame_;
ole_in_place_site in_place_site_;
doc_host_ui_handler ui_handler_;
ole_client_site client_site_;
html_dispatch html_dispatch_;
std::vector<std::pair<std::string, std::function<void(callback_params*)>>> callbacks_;
void create_browser();
void initialize_browser();
static bool set_browser_feature(const std::string& feature, DWORD value);
static void object_deleter(IUnknown* object);
static std::atomic<int> frame_count_;
};
-29
View File
@@ -1,29 +0,0 @@
#include <std_include.hpp>
#include "html_window.hpp"
window* html_window::get_window()
{
return this;
}
html_frame* html_window::get_html_frame()
{
return this;
}
LRESULT html_window::processor(const UINT message, const WPARAM w_param, const LPARAM l_param)
{
if (message == WM_SIZE)
{
this->resize(LOWORD(l_param), HIWORD(l_param));
return 0;
}
if (message == WM_CREATE)
{
this->initialize(*this);
return 0;
}
return window::processor(message, w_param, l_param);
}
-15
View File
@@ -1,15 +0,0 @@
#pragma once
#include "../window.hpp"
#include "html_frame.hpp"
class html_window final : public window, public html_frame
{
public:
~html_window() = default;
window* get_window();
html_frame* get_html_frame();
private:
LRESULT processor(UINT message, WPARAM w_param, LPARAM l_param) override;
};
@@ -1,77 +0,0 @@
#include <std_include.hpp>
#include "html_frame.hpp"
ole_client_site::ole_client_site(html_frame* frame): frame_(frame)
{
}
HRESULT ole_client_site::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
if (!memcmp(&riid, &IID_IUnknown, sizeof(GUID)) ||
!memcmp(&riid, &IID_IOleClientSite, sizeof(GUID)))
{
*ppvObject = this;
this->AddRef();
return S_OK;
}
if (!memcmp(&riid, &IID_IOleInPlaceSite, sizeof(GUID)))
{
auto in_place_site = this->frame_->get_in_place_site();
in_place_site->AddRef();
*ppvObject = in_place_site;
return S_OK;
}
if (!memcmp(&riid, &IID_IDocHostUIHandler, sizeof(GUID)))
{
auto ui_handler = this->frame_->get_ui_handler();
ui_handler->AddRef();
*ppvObject = ui_handler;
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}
ULONG ole_client_site::AddRef()
{
return 1;
}
ULONG ole_client_site::Release()
{
return 1;
}
HRESULT ole_client_site::SaveObject()
{
return E_NOTIMPL;
}
HRESULT ole_client_site::GetMoniker(DWORD /*dwAssign*/, DWORD /*dwWhichMoniker*/, IMoniker** /*ppmk*/)
{
return E_NOTIMPL;
}
HRESULT ole_client_site::GetContainer(LPOLECONTAINER* ppContainer)
{
*ppContainer = nullptr;
return E_NOINTERFACE;
}
HRESULT ole_client_site::ShowObject()
{
return NOERROR;
}
HRESULT ole_client_site::OnShowWindow(BOOL /*fShow*/)
{
return E_NOTIMPL;
}
HRESULT ole_client_site::RequestNewObjectLayout()
{
return E_NOTIMPL;
}
@@ -1,24 +0,0 @@
#pragma once
class html_frame;
class ole_client_site final : public IOleClientSite
{
public:
ole_client_site(html_frame* frame);
virtual ~ole_client_site() = default;
private:
html_frame* frame_;
public:
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObject) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE SaveObject() override;
HRESULT STDMETHODCALLTYPE GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk) override;
HRESULT STDMETHODCALLTYPE GetContainer(LPOLECONTAINER FAR* ppContainer) override;
HRESULT STDMETHODCALLTYPE ShowObject() override;
HRESULT STDMETHODCALLTYPE OnShowWindow(BOOL fShow) override;
HRESULT STDMETHODCALLTYPE RequestNewObjectLayout() override;
};
@@ -1,82 +0,0 @@
#include <std_include.hpp>
#include "html_frame.hpp"
ole_in_place_frame::ole_in_place_frame(html_frame* frame): frame_(frame)
{
}
HRESULT ole_in_place_frame::QueryInterface(REFIID /*riid*/, LPVOID* /*ppvObj*/)
{
return E_NOTIMPL;
}
ULONG ole_in_place_frame::AddRef()
{
return 1;
}
ULONG ole_in_place_frame::Release()
{
return 1;
}
HRESULT ole_in_place_frame::GetWindow(HWND* lphwnd)
{
*lphwnd = this->frame_->get_window();
return S_OK;
}
HRESULT ole_in_place_frame::ContextSensitiveHelp(BOOL /*fEnterMode*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::GetBorder(LPRECT /*lprectBorder*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::RequestBorderSpace(LPCBORDERWIDTHS /*pborderwidths*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::SetBorderSpace(LPCBORDERWIDTHS /*pborderwidths*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::SetActiveObject(IOleInPlaceActiveObject* /*pActiveObject*/, LPCOLESTR /*pszObjName*/)
{
return S_OK;
}
HRESULT ole_in_place_frame::InsertMenus(HMENU /*hmenuShared*/, LPOLEMENUGROUPWIDTHS /*lpMenuWidths*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::SetMenu(HMENU /*hmenuShared*/, HOLEMENU /*holemenu*/, HWND /*hwndActiveObject*/)
{
return S_OK;
}
HRESULT ole_in_place_frame::RemoveMenus(HMENU /*hmenuShared*/)
{
return E_NOTIMPL;
}
HRESULT ole_in_place_frame::SetStatusText(LPCOLESTR /*pszStatusText*/)
{
return S_OK;
}
HRESULT ole_in_place_frame::EnableModeless(BOOL /*fEnable*/)
{
return S_OK;
}
HRESULT ole_in_place_frame::TranslateAcceleratorA(LPMSG /*lpmsg*/, WORD /*wID*/)
{
return E_NOTIMPL;
}
@@ -1,30 +0,0 @@
#pragma once
class html_frame;
class ole_in_place_frame final : public IOleInPlaceFrame
{
public:
ole_in_place_frame(html_frame* frame);
virtual ~ole_in_place_frame() = default;
private:
html_frame* frame_;
public: // IOleInPlaceFrame interface
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID FAR* ppvObj) override;
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE GetWindow(HWND FAR* lphwnd) override;
HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode) override;
HRESULT STDMETHODCALLTYPE GetBorder(LPRECT lprectBorder) override;
HRESULT STDMETHODCALLTYPE RequestBorderSpace(LPCBORDERWIDTHS pborderwidths) override;
HRESULT STDMETHODCALLTYPE SetBorderSpace(LPCBORDERWIDTHS pborderwidths) override;
HRESULT STDMETHODCALLTYPE SetActiveObject(IOleInPlaceActiveObject* pActiveObject, LPCOLESTR pszObjName) override;
HRESULT STDMETHODCALLTYPE InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths) override;
HRESULT STDMETHODCALLTYPE SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject) override;
HRESULT STDMETHODCALLTYPE RemoveMenus(HMENU hmenuShared) override;
HRESULT STDMETHODCALLTYPE SetStatusText(LPCOLESTR pszStatusText) override;
HRESULT STDMETHODCALLTYPE EnableModeless(BOOL fEnable) override;
HRESULT STDMETHODCALLTYPE TranslateAccelerator(LPMSG lpmsg, WORD wID) override;
};

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