Compare commits

...

3 Commits

Author SHA1 Message Date
d6c93e6966
chore: update 2025-05-21 18:51:02 +02:00
84d7702ba2 chore: update deps 2025-05-19 10:31:38 +02:00
b25aeb38a5 mods: untested code (YOLO) 2025-05-18 16:48:11 +02:00
18 changed files with 405 additions and 47 deletions

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 3325bbd33d24d1f8f5a0f69e782c92ad5a39a68e
Subproject commit 466e4ebaa54dcf4257698d707d76d69a90614450

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit e8c8e2e48a1a38154c8e8864eb3bc61db80a1e31
Subproject commit cecc73f2979e9704c81a2c2ec79a7475b31c56ac

2
deps/gsc-tool vendored

@ -1 +1 @@
Subproject commit 2d9781ce0ce9e8551eaf040d7761fd986f33cfdc
Subproject commit c508e5b10f5ecef9313ef49b0ec6b8ebf52e182f

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit a6b9aff7aab857fe1b491710a5c5b9e2be49cb08
Subproject commit d448df1938e8988bcdb0eed6591387e82b26874b

2
deps/minhook vendored

@ -1 +1 @@
Subproject commit c3fcafdc10146beb5919319d0683e44e3c30d537
Subproject commit 565968b28583221751cc2810e09ea621745fc3a3

View File

@ -0,0 +1,61 @@
#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 this game seems pretty high
std::array<game::WeaponCompleteDef*, 2048> 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
{
if (game::environment::is_sp()) return;
// Kill Scr_PrecacheItem (We are going to do this from code)
utils::hook::nop(0x1403101D0, 4);
utils::hook::set<std::uint8_t>(0x1403101D0, 0xC3);
// Load weapons from the DB
utils::hook::call(0x1402F6EF4, g_setup_level_weapon_def_stub);
utils::hook::call(0x140307401, g_setup_level_weapon_def_stub);
}
};
}
REGISTER_COMPONENT(weapons::component)

View File

@ -11,7 +11,7 @@
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)
{
if (dvar && dvar->name == "sv_cheats"s)
{

View File

@ -18,6 +18,14 @@ namespace fastfiles
{
utils::hook::detour db_try_load_x_file_internal_hook;
utils::hook::detour db_find_x_asset_header_hook;
utils::hook::detour db_read_stream_file_hook;
int db_read_stream_file_stub(int allow_abort, int finish)
{
// always use lz4 compressor type when reading stream files
*game::g_compressor = game::DB_COMPRESSOR_LZX;
return db_read_stream_file_hook.invoke<int>(allow_abort, finish);
}
void db_try_load_x_file_internal(const char* zone_name, const int flags)
{
@ -137,21 +145,6 @@ namespace fastfiles
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);
command::add("loadzone", [](const command::params& params)
{
if (params.size() < 2)
{
console::info("usage: loadzone <zone>\n");
return;
}
game::XZoneInfo info{};
info.name = params.get(1);
info.allocFlags = 1;
info.freeFlags = 0;
game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_SYNC);
});
command::add("g_poolSizes", []()
{
for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++)
@ -160,6 +153,9 @@ namespace fastfiles
}
});
// Allow loading of mixed compressor types
utils::hook::nop(SELECT_VALUE(0x1401536D7, 0x140242DF7), 2);
reallocate_asset_pool<game::ASSET_TYPE_FONT, 48>();
if (!game::environment::is_sp())
@ -170,6 +166,9 @@ namespace fastfiles
utils::hook::inject(0x14026FFAC, xmodel_pool + 8);
utils::hook::inject(0x14027463C, xmodel_pool + 8);
utils::hook::inject(0x140274689, xmodel_pool + 8);
// Fix compressor type on streamed file load
db_read_stream_file_hook.create(0x14027AA70, db_read_stream_file_stub);
}
}
};

View File

@ -357,12 +357,12 @@ namespace gsc
build = static_cast<xsk::gsc::build>(static_cast<unsigned int>(build) | static_cast<unsigned int>(xsk::gsc::build::dev_maps));
}
if (dvars::com_developer_script && dvars::com_developer_script->current.enabled)
if (dvars::com_developer_script && dvars::com_developer_script->current.integer > 0)
{
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();
@ -434,8 +434,11 @@ namespace gsc
utils::hook::call(SELECT_VALUE(0x14031AB47, 0x1403F7317), find_script);
utils::hook::call(SELECT_VALUE(0x14031AB57, 0x1403F7327), db_is_x_asset_default);
// Enable development options
dvars::com_developer = game::Dvar_RegisterInt("developer", 0, 0, 2, game::DVAR_FLAG_NONE);
dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE);
// Enable developer script comments: 0 disabled, 1 full developer script, 2 only dev scripts required by art/lighting tweaks.
// gsc-tool will only have one mode which supports both 1 and 2 dev blocks in the code simultaneously.
dvars::com_developer_script = game::Dvar_RegisterInt("developer_script", 0, 0, 2, game::DVAR_FLAG_NONE);
if (game::environment::is_sp())
{

View File

@ -0,0 +1,233 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "command.hpp"
#include "console.hpp"
#include "mods.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace mods
{
namespace
{
utils::hook::detour sys_create_file_hook;
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]{};
switch (source)
{
case game::FFD_DEFAULT:
(void)game::Com_sprintf(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name);
break;
case game::FFD_MOD_DIR:
assert(mods::is_using_mods());
(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;
case game::FFD_USER_MAP:
game::I_strncpyz(user_map, zone_name, sizeof(user_map));
(void)game::Com_sprintf(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name);
break;
default:
assert(false && "inconceivable");
break;
}
}
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;
}
const auto skip_extra_zones_stub = utils::hook::assemble([](utils::hook::assembler& a) -> void
{
const auto skip = a.newLabel();
const auto original = a.newLabel();
a.pushad64();
a.test(esi, game::DB_ZONE_CUSTOM); // allocFlags
a.jnz(skip);
a.bind(original);
a.popad64();
a.mov(rdx, 0x140809D40);
a.mov(rcx, rbp);
a.call(0x1406FE120);
a.jmp(0x140271B63);
a.bind(skip);
a.popad64();
a.mov(r13d, game::DB_ZONE_CUSTOM);
a.not_(r13d);
a.and_(esi, r13d);
a.jmp(0x140271D02);
});
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>(folder, base_filename);
if (result.handle != INVALID_HANDLE_VALUE)
{
return result;
}
if (!is_using_mods())
{
return result;
}
// .ff extension was added previously
if (!std::strcmp(base_filename, "mod.ff") && mods::db_mod_file_exists())
{
char file_path[MAX_PATH]{};
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path);
result.handle = game::Sys_OpenFileReliable(file_path);
}
return result;
}
void db_load_x_assets_stub(game::XZoneInfo* zone_info, unsigned int zone_count, game::DBSyncMode sync_mode)
{
std::vector<game::XZoneInfo> zones(zone_info, zone_info + zone_count);
if (db_mod_file_exists())
{
zones.emplace_back("mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0);
}
game::DB_LoadXAssets(zones.data(), static_cast<unsigned int>(zones.size()), sync_mode);
}
}
bool is_using_mods()
{
return (*dvars::fs_gameDirVar) && *(*dvars::fs_gameDirVar)->current.string;
}
bool db_mod_file_exists()
{
if (!*(*dvars::fs_gameDirVar)->current.string)
{
return false;
}
char filename[MAX_PATH]{};
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(filename), filename);
if (auto zone_file = game::Sys_OpenFileReliable(filename); zone_file != INVALID_HANDLE_VALUE)
{
::CloseHandle(zone_file);
return true;
}
return false;
}
class component final : public component_interface
{
public:
static_assert(sizeof(game::Sys_File) == 8);
void post_unpack() override
{
dvars::fs_gameDirVar = reinterpret_cast<game::dvar_t**>(SELECT_VALUE(0x14A6A7D98, 0x14B20EB48));
// Remove DVAR_INIT from fs_game
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x14036137F + 2, 0x1404AE4CB + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO));
utils::hook::inject(SELECT_VALUE(0x140361391 + 3, 0x1404AE4D6 + 3), &fs_game_dir_domain_func);
if (game::environment::is_sp())
{
return;
}
utils::hook::nop(0x140271B54, 15);
utils::hook::jump(0x140271B54, skip_extra_zones_stub, true);
// Add custom zone paths
sys_create_file_hook.create(game::Sys_CreateFile, sys_create_file_stub);
// Load mod.ff
utils::hook::call(0x1405A562A, 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.insert(0, "mods/");
}
// 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", []([[maybe_unused]] 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
}
};
}
REGISTER_COMPONENT(mods::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace mods
{
bool is_using_mods();
bool db_mod_file_exists();
}

View File

@ -163,17 +163,6 @@ namespace patches
}
}
void set_client_dvar_from_server_stub(void* a1, void* a2, const char* dvar, const char* value)
{
if (utils::string::to_lower(dvar) == "cg_fov")
{
return;
}
// CG_SetClientDvarFromServer
reinterpret_cast<void(*)(void*, void*, const char*, const char*)>(0x1401BF0A0)(a1, a2, dvar, value);
}
utils::hook::detour cmd_lui_notify_server_hook;
void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
{
@ -298,9 +287,8 @@ namespace patches
utils::hook::inject(0x1404398B2, VERSION);
// prevent servers overriding our fov
utils::hook::call(0x1401BB782, set_client_dvar_from_server_stub);
utils::hook::nop(0x1403D1195, 5);
utils::hook::nop(0x1400FAE36, 5);
utils::hook::nop(0x1400FAE36, 5); // Dvar_SetFloat inside LUI_CoD_LuaCall_StopFollow (function doesn't exist on dev builds)
utils::hook::set<uint8_t>(0x14019B9B9, 0xEB);
// some anti tamper thing that kills performance

View File

@ -36,13 +36,13 @@ namespace dvars
game::dvar_t* r_fullbright = nullptr;
game::dvar_t* cg_legacyCrashHandling = nullptr;
game::dvar_t* sv_cheats = nullptr;
game::dvar_t* com_developer = nullptr;
game::dvar_t* com_developer_script = nullptr;
game::dvar_t** fs_gameDirVar = nullptr;
std::string get_dvar_string(const std::string& dvar)
{
const auto* dvar_value = game::Dvar_FindVar(dvar.data());

View File

@ -35,13 +35,13 @@ namespace dvars
extern game::dvar_t* r_fullbright;
extern game::dvar_t* cg_legacyCrashHandling;
extern game::dvar_t* sv_cheats;
extern game::dvar_t* com_developer;
extern game::dvar_t* com_developer_script;
extern game::dvar_t** fs_gameDirVar;
std::string get_dvar_string(const std::string& dvar);
bool get_dvar_bool(const std::string& dvar);

View File

@ -30,6 +30,13 @@ namespace game
return !game::environment::is_sp() && *mp::virtualLobby_loaded == 1;
}
HANDLE Sys_OpenFileReliable(const char* filename)
{
return ::CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr);
}
namespace environment
{
launcher::mode mode = launcher::mode::none;

View File

@ -66,6 +66,8 @@ namespace game
[[nodiscard]] bool VirtualLobby_Loaded();
[[nodiscard]] HANDLE Sys_OpenFileReliable(const char* filename);
[[nodiscard]] bool is_headless();
void show_error(const std::string& text, const std::string& title = "Error");
}

View File

@ -25,6 +25,14 @@ namespace game
typedef void(*BuiltinMethod)(scr_entref_t);
typedef void(*BuiltinFunction)();
enum ControllerIndex_t
{
INVALID_CONTROLLER_PORT = -1,
CONTROLLER_INDEX_0 = 0x0,
CONTROLLER_INDEX_FIRST = 0x0,
CONTROLLER_INDEX_COUNT = 0x1,
};
enum
{
VAR_UNDEFINED = 0x0,
@ -905,6 +913,8 @@ namespace game
DVAR_FLAG_LATCHED = 0x2,
DVAR_FLAG_CHEAT = 0x4,
DVAR_FLAG_REPLICATED = 0x8,
DVAR_FLAG_SCRIPTINFO = 0x10,
DVAR_FLAG_SERVERINFO = 0x400,
DVAR_FLAG_WRITE = 0x800,
DVAR_FLAG_READ = 0x2000,
};
@ -923,7 +933,7 @@ namespace game
rgb = 9 // Color without alpha
};
union dvar_value
union DvarValue
{
bool enabled;
int integer;
@ -966,9 +976,9 @@ namespace game
unsigned int flags;
dvar_type type;
bool modified;
dvar_value current;
dvar_value latched;
dvar_value reset;
DvarValue current;
DvarValue latched;
DvarValue reset;
dvar_limits domain;
};
@ -1319,6 +1329,15 @@ namespace game
const char *name;
};
struct WeaponDef
{};
struct WeaponCompleteDef
{
const char* szInternalName;
WeaponDef* weapDef;
}; // Incomplete
union XAssetHeader
{
void* data;
@ -1443,6 +1462,40 @@ namespace game
netProfileStream_t recieve;
};
enum
{
DB_ZONE_COMMON = 0x1,
DB_ZONE_UI = 0x2,
DB_ZONE_GAME = 0x4,
DB_ZONE_LOAD = 0x8,
DB_ZONE_DEV = 0x10,
DB_ZONE_BASEMAP = 0x20,
DB_ZONE_TRANSIENT_POOL = 0x40,
DB_ZONE_TRANSIENT_MASK = 0x40,
DB_ZONE_CUSTOM = 0x1000,
};
enum FF_DIR
{
FFD_DEFAULT = 0x0,
FFD_MOD_DIR = 0x1,
FFD_USER_MAP = 0x2,
};
struct Sys_File
{
HANDLE handle;
};
enum DB_CompressorType
{
DB_COMPRESSOR_INVALID = 0x0,
DB_COMPRESSOR_ZLIB = 0x1,
DB_COMPRESSOR_UNK2 = 0x2,
DB_COMPRESSOR_PASSTHROUGH = 0x3,
DB_COMPRESSOR_LZX = 0x4,
};
namespace mp
{
enum

View File

@ -60,6 +60,7 @@ namespace game
WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{0x14017E890, 0x14026FCC0};
WEAK symbol<void(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x14017E750, 0x14026FB90};
WEAK symbol<char*(const char* filename, char* buf, int size)> DB_ReadRawFile{0x140180E30, 0x140273080};
WEAK symbol<int(XAssetType type, void** assets, int maxCount)> DB_GetAllXAssetOfType_FastFile{0x0, 0x14026F970};
WEAK symbol<dvar_t*(const char* name)> Dvar_FindVar{0x140370860, 0x1404BF8B0};
WEAK symbol<void(const dvar_t* dvar)> Dvar_ClearModified{0x140370700, 0x1404BF690};
@ -70,7 +71,7 @@ namespace game
WEAK symbol<void(const dvar_t* dvar, const char* string)> Dvar_SetString{0x140373DE0, 0x1404C3610};
WEAK symbol<void(const dvar_t* dvar, bool value)> Dvar_SetBool{0x0, 0x1404C1F30};
WEAK symbol<void(const char*, const char*, DvarSetSource)> Dvar_SetFromStringByNameFromSource{0x1403737D0, 0x1404C2E40};
WEAK symbol<const char*(dvar_t* dvar, dvar_value value)> Dvar_ValueToString{0x140374E10, 0x1404C47B0};
WEAK symbol<const char*(dvar_t* dvar, DvarValue value)> Dvar_ValueToString{0x140374E10, 0x1404C47B0};
WEAK symbol<dvar_t*(const char* dvarName, bool value, unsigned int flags)> Dvar_RegisterBool{0x140371850, 0x1404C0BE0};
WEAK symbol<dvar_t*(const char* dvarName, const char** valueList, int defaultIndex, unsigned int flags)> Dvar_RegisterEnum{0x140371B30, 0x1404C0EC0};
@ -98,13 +99,13 @@ namespace game
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{0x0, 0x1403F3730};
WEAK symbol<void()> G_Glass_Update{0x14021D540, 0x1402EDEE0};
WEAK symbol<int(int clientNum)> G_GetClientScore{0, 0x1402F6AB0};
WEAK symbol<unsigned int(const char* name)> G_GetWeaponForName{0x140274590, 0x14033FF60};
WEAK symbol<int(playerState_s* ps, unsigned int weapon, int dualWield, int startInAltMode, int, int, int, char)> G_GivePlayerWeapon{0x1402749B0, 0x140340470};
WEAK symbol<void(playerState_s* ps, unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x1402217F0, 0x1402F22B0};
WEAK symbol<void(int clientNum, unsigned int weapon)> G_SelectWeapon{0x140275380, 0x140340D50};
WEAK symbol<int(playerState_s* ps, unsigned int weapon)> G_TakePlayerWeapon{0x1402754E0, 0x1403411D0};
WEAK symbol<void()> G_SetupLevelWeaponDef{0x0, 0x140340DE0};
WEAK symbol<char*(char* string)> I_CleanStr{0x140379010, 0x1404C99A0};
@ -113,6 +114,7 @@ namespace game
WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x14013F380, 0x140207C50};
WEAK symbol<unsigned int(int)> Live_SyncOnlineDataFlags{0x1404459A0, 0x140562830};
WEAK symbol<void(int localControllerIndex)> LiveStorage_StatsWriteNotNeeded{0x1402F51F0, 0x1403C3CD0};
WEAK symbol<void(int localClientNum, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)> LUI_OpenMenu{0, 0x14048E450};
WEAK symbol<void()> LUI_EnterCriticalSection{0, 0x1400D2B10};
@ -226,6 +228,8 @@ namespace game
WEAK symbol<void(const char* pszCommand, char* pszBuffer, int iBufferSize)> MSG_WriteReliableCommandToBuffer{0x0, 0x1403E1090};
WEAK symbol<int(const char* s0, const char* s1, int n)> I_strnicmp{0x1403793E0, 0x1404C9E90};
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14059C5C0, 0x1406FD930};
WEAK symbol<int(jmp_buf* Buf)> _setjmp{0x14059CD00, 0x1406FE070};
@ -265,6 +269,7 @@ namespace game
WEAK symbol<XAssetEntry> g_assetEntryPool{0x142CC2400, 0x14379F100};
WEAK symbol<int> g_poolSize{0x140804140, 0x1409B4B90};
WEAK symbol<const char*> g_assetNames{0x140803C90, 0x1409B3180};
WEAK symbol<int> g_compressor{0x141598580, 0x141E0B080};
WEAK symbol<DWORD> threadIds{0x149632EC0, 0x147DCEA30};