Compare commits

..

5 Commits

Author SHA1 Message Date
m
6b06a25629 allow loading sp maps on mp 2025-05-17 22:48:36 -05:00
m
31301fe203 tiny fixes 2025-05-17 14:34:45 -05:00
m
ce047e96da fixes 2025-05-17 14:31:18 -05:00
m
1d98df42b3 map ents patch 2025-05-17 14:30:52 -05:00
m
6266241293 basic custom fastfile support 2025-05-17 14:30:52 -05:00
25 changed files with 1139 additions and 433 deletions

2
deps/GSL vendored

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

2
deps/asmjit vendored

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

2
deps/gsc-tool vendored

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

2
deps/libtomcrypt vendored

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

2
deps/minhook vendored

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

View File

@ -1,61 +0,0 @@
#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::DvarValue* value)
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
{
if (dvar && dvar->name == "sv_cheats"s)
{

View File

@ -2,38 +2,65 @@
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include "fastfiles.hpp"
#include "command.hpp"
#include "console.hpp"
#include "fastfiles.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/concurrency.hpp>
#include <utils/string.hpp>
namespace fastfiles
{
static utils::concurrency::container<std::string> current_fastfile;
namespace
{
template <typename T>
inline void merge(std::vector<T>* target, T* source, size_t length)
{
if (source)
{
for (size_t i = 0; i < length; ++i)
{
target->push_back(source[i]);
}
}
}
template <typename T>
inline void merge(std::vector<T>* target, std::vector<T> source)
{
for (auto& entry : source)
{
target->push_back(entry);
}
}
}
namespace
{
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)
{
static std::unordered_map<std::string, std::string> zone_overrides = {};
auto override_zone_name = zone_overrides.find(zone_name);
if (override_zone_name != zone_overrides.end())
{
console::info("Overriding fastfile %s with %s\n", zone_name, override_zone_name->second.c_str());
zone_name = override_zone_name->second.c_str();
}
console::info("Loading fastfile %s\n", zone_name);
current_fastfile.access([&](std::string& fastfile)
{
fastfile = zone_name;
});
return db_try_load_x_file_internal_hook.invoke<void>(zone_name, flags);
}
@ -81,6 +108,243 @@ namespace fastfiles
return result;
}
namespace mp
{
void skip_extra_zones_stub(utils::hook::assembler& a)
{
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);
}
}
namespace sp
{
void skip_extra_zones_stub(utils::hook::assembler& a)
{
const auto skip = a.newLabel();
const auto original = a.newLabel();
a.pushad64();
a.test(edi, game::DB_ZONE_CUSTOM); // allocFlags
a.jnz(skip);
a.bind(original);
a.popad64();
a.call(0x140379360);
a.xor_(ecx, ecx);
a.test(eax, eax);
a.setz(cl);
a.jmp(0x1401802D6);
a.bind(skip);
a.popad64();
a.mov(r13d, game::DB_ZONE_CUSTOM);
a.not_(r13d);
a.and_(edi, r13d);
a.jmp(0x1401803EF);
}
}
utils::hook::detour db_read_stream_file_hook;
void db_read_stream_file_stub(int a1, int a2)
{
// always use lz4 compressor type when reading stream files
*game::g_compressor = 4;
return db_read_stream_file_hook.invoke<void>(a1, a2);
}
utils::hook::detour sys_createfile_hook;
HANDLE sys_create_file(game::Sys_Folder folder, const char* base_filename)
{
const auto* fs_basepath = game::Dvar_FindVar("fs_basepath");
const auto* fs_game = game::Dvar_FindVar("fs_game");
const std::string dir = fs_basepath ? fs_basepath->current.string : "";
const std::string mod_dir = fs_game ? fs_game->current.string : "";
const std::string name = base_filename;
if (name == "mod.ff")
{
if (!mod_dir.empty())
{
const auto path = utils::string::va("%s\\%s\\%s",
dir.data(), mod_dir.data(), base_filename);
if (utils::io::file_exists(path))
{
return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr);
}
}
return INVALID_HANDLE_VALUE;
}
return sys_createfile_hook.invoke<HANDLE>(folder, base_filename);
}
HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename)
{
return sys_create_file(folder, base_filename);
}
bool try_load_zone(std::string name, bool localized, bool game = false)
{
if (localized)
{
const auto language = game::SEH_GetCurrentLanguageCode();
try_load_zone(language + "_"s + name, false);
if (game::environment::is_mp())
{
try_load_zone(language + "_"s + name + "_mp"s, false);
}
}
if (!fastfiles::exists(name))
{
return false;
}
game::XZoneInfo info{};
info.name = name.data();
info.allocFlags = (game ? game::DB_ZONE_GAME : game::DB_ZONE_COMMON) | game::DB_ZONE_CUSTOM;
info.freeFlags = 0;
game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_ASYNC);
return true;
}
void load_pre_gfx_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
{
//imagefiles::close_custom_handles();
std::vector<game::XZoneInfo> data;
merge(&data, zoneInfo, zoneCount);
// code_pre_gfx
//weapon::clear_modifed_enums();
try_load_zone("mod_pre_gfx", true);
try_load_zone("s1_mod_pre_gfx", true);
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
}
void load_post_gfx_and_ui_and_common_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
{
std::vector<game::XZoneInfo> data;
merge(&data, zoneInfo, zoneCount);
// code_post_gfx
// ui
// common
try_load_zone("s1_mod_common", true);
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
try_load_zone("mod_common", true);
}
void load_ui_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
{
std::vector<game::XZoneInfo> data;
merge(&data, zoneInfo, zoneCount);
// ui
game::DB_LoadXAssets(data.data(), static_cast<std::uint32_t>(data.size()), syncMode);
}
void load_lua_file_asset_stub(void* a1)
{
const auto fastfile = fastfiles::get_current_fastfile();
if (fastfile == "mod")
{
console::error("Mod tried to load a lua file!\n");
return;
}
// TODO: add usermap LUI loading checks
/*
const auto usermap = fastfiles::get_current_usermap();
if (usermap.has_value())
{
const auto& usermap_value = usermap.value();
const auto usermap_load = usermap_value + "_load";
if (fastfile == usermap_value || fastfile == usermap_load)
{
console::error("Usermap tried to load a lua file!\n");
return;
}
}
*/
utils::hook::invoke<void>(0x140276480, a1);
}
utils::hook::detour db_level_load_add_zone_hook;
void db_level_load_add_zone_stub(void* load, const char* name, const unsigned int alloc_flags,
const size_t size_est)
{
if (!strcmp(name, "mp_terminal_cls"))
{
db_level_load_add_zone_hook.invoke<void>(load, name, alloc_flags | game::DB_ZONE_CUSTOM, size_est);
return;
}
db_level_load_add_zone_hook.invoke<void>(load, name, alloc_flags, size_est);
; }
void db_find_aipaths_stub(game::XAssetType type, const char* name, int allow_create_default)
{
if (game::DB_XAssetExists(type, name))
{
game::DB_FindXAssetHeader(type, name, allow_create_default);
}
else
{
console::warn("No aipaths found for this map\n");
}
}
int format_bsp_name(char* filename, int size, const char* mapname)
{
std::string name = mapname;
auto fmt = "maps/%s.d3dbsp";
if (name.starts_with("mp_"))
{
fmt = "maps/mp/%s.d3dbsp";
}
return game::Com_sprintf(filename, size, fmt, mapname);
}
void get_bsp_filename_stub(char* filename, int size, const char* mapname)
{
auto base_mapname = mapname;
game::Com_IsAddonMap(mapname, &base_mapname);
format_bsp_name(filename, size, base_mapname);
}
}
std::string get_current_fastfile()
@ -93,37 +357,35 @@ namespace fastfiles
return fastfile_copy;
}
constexpr int get_asset_type_size(const game::XAssetType type)
bool exists(const std::string& zone)
{
constexpr int asset_type_sizes[] =
{
96, 88, 128, 56, 40, 216, 56, 680,
480, 32, 32, 32, 32, 32, 352, 1456,
104, 32, 24, 152, 152, 152, 16, 64,
640, 40, 16, 408, 24, 288, 176, 2800,
48, -1, 40, 24, 200, 88, 16, 120,
3560, 32, 64, 16, 16, -1, -1, -1,
-1, 24, 40, 24, 40, 24, 128, 2256,
136, 32, 72, 24, 64, 88, 48, 32,
96, 152, 64, 32,
};
const auto is_localized = game::DB_IsLocalized(zone.data());
const auto handle = sys_create_file((is_localized ? game::SF_ZONE_LOC : game::SF_ZONE),
utils::string::va("%s.ff", zone.data()));
return asset_type_sizes[type];
if (handle != INVALID_HANDLE_VALUE)
{
CloseHandle(handle);
return true;
}
return false;
}
template <game::XAssetType Type, size_t Size>
char* reallocate_asset_pool()
void reallocate_asset_pool(game::XAssetType Type, size_t Size)
{
constexpr auto element_size = get_asset_type_size(Type);
static char new_pool[element_size * Size] = {0};
assert(get_asset_type_size(Type) == game::DB_GetXAssetTypeSize(Type));
const size_t element_size = game::DB_GetXAssetTypeSize(Type);
auto* new_pool = utils::memory::get_allocator()->allocate(Size * element_size);
std::memmove(new_pool, game::DB_XAssetPool[Type], game::g_poolSize[Type] * element_size);
game::DB_XAssetPool[Type] = new_pool;
game::g_poolSize[Type] = Size;
game::g_poolSize[Type] = static_cast<int>(Size);
}
return new_pool;
template <game::XAssetType Type, size_t Multiplier>
void reallocate_asset_pool_multiplier()
{
reallocate_asset_pool(Type, Multiplier * game::g_poolSize[Type]);
}
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool include_override)
@ -135,6 +397,75 @@ namespace fastfiles
}), &callback, include_override);
}
const char* get_zone_name(const unsigned int index)
{
if (game::environment::is_sp())
{
return "";
}
else
{
return game::g_zones[index].name;
}
}
void reallocate_asset_pools()
{
reallocate_asset_pool(game::ASSET_TYPE_FONT, 48);
if (!game::environment::is_sp())
{
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LUA_FILE, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_WEAPON, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LOCALIZE_ENTRY, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_XANIMPARTS, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_ATTACHMENT, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_FONT, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_SNDDRIVER_GLOBALS, 4>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_EQUIPMENT_SND_TABLE, 4>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_SOUND, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LOADED_SOUND, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LEADERBOARD, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_VERTEXDECL, 6>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_COMPUTESHADER, 4>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_REVERB_PRESET, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_IMPACT_FX, 10>();
}
}
void enum_asset_entries(const game::XAssetType type, const std::function<void(game::XAssetEntry*)>& callback, bool include_override)
{
constexpr auto max_asset_count = 0x1D8A8;
auto hash = &game::db_hashTable[0];
for (auto c = 0; c < max_asset_count; c++)
{
for (auto i = *hash; i; )
{
const auto entry = &game::g_assetEntryPool[i];
if (entry->asset.type == type)
{
callback(entry);
if (include_override && entry->nextOverride)
{
auto next_ovveride = entry->nextOverride;
while (next_ovveride)
{
const auto override = &game::g_assetEntryPool[next_ovveride];
callback(override);
next_ovveride = override->nextOverride;
}
}
}
i = entry->nextHash;
}
++hash;
}
}
class component final : public component_interface
{
public:
@ -145,6 +476,21 @@ 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 = game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM;
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++)
@ -153,22 +499,66 @@ namespace fastfiles
}
});
#ifdef DEBUG
//reallocate_asset_pools();
#endif
// Allow loading of unsigned fastfiles
if (!game::environment::is_sp())
{
utils::hook::nop(0x1402427A5, 2); // DB_InflateInit
}
// Don't load extra zones with loadzone
if (game::environment::is_sp())
{
utils::hook::nop(0x1401802CA, 12);
utils::hook::jump(0x1401802CA, utils::hook::assemble(sp::skip_extra_zones_stub), true);
}
else
{
utils::hook::nop(0x140271B54, 15);
utils::hook::jump(0x140271B54, utils::hook::assemble(mp::skip_extra_zones_stub), true);
// dont load localized zone for custom maps (proper patch for this is here: https://github.com/auroramod/h1-mod/blob/develop/src/client/component/fastfiles.cpp#L442)
db_level_load_add_zone_hook.create(0x1402705C0, db_level_load_add_zone_stub);
}
if (game::environment::is_mp())
{
// Allow loading sp maps on mp
utils::hook::jump(0x1404ACE70, get_bsp_filename_stub);
}
else if (game::environment::is_sp())
{
// TODO: needs S1 singleplayer addresses
/*
// Allow loading mp maps
utils::hook::set(0x40AF90_b, 0xC300B0);
// Don't sys_error if aipaths are missing
utils::hook::call(0x2F8EE9_b, db_find_aipaths_stub);
*/
}
// Allow loading of mixed compressor types
utils::hook::nop(SELECT_VALUE(0x1401536D7, 0x140242DF7), 2);
reallocate_asset_pool<game::ASSET_TYPE_FONT, 48>();
// Fix compressor type on streamed file load
db_read_stream_file_hook.create(SELECT_VALUE(0x140187450, 0x14027AA70), db_read_stream_file_stub);
if (!game::environment::is_sp())
// Add custom zone paths
sys_createfile_hook.create(game::Sys_CreateFile, sys_create_file_stub);
// load our custom ui and common zones
utils::hook::call(SELECT_VALUE(0x140487CF8, 0x1405A562A), load_post_gfx_and_ui_and_common_zones);
// load our custom ui zones
utils::hook::call(SELECT_VALUE(0x1402F91D4, 0x1403D06FC), load_ui_zones);
// prevent mod.ff from loading lua files
if (game::environment::is_mp())
{
const auto* xmodel_pool = reallocate_asset_pool<game::ASSET_TYPE_XMODEL, 8832>();
utils::hook::inject(0x14026FD63, xmodel_pool + 8);
utils::hook::inject(0x14026FDB3, xmodel_pool + 8);
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);
utils::hook::call(0x14024F1B4, load_lua_file_asset_stub);
}
}
};

View File

@ -6,5 +6,11 @@ namespace fastfiles
{
std::string get_current_fastfile();
bool exists(const std::string& zone);
void enum_assets(game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, bool include_override);
const char* get_zone_name(const unsigned int index);
void enum_asset_entries(const game::XAssetType type, const std::function<void(game::XAssetEntry*)>& callback, bool include_override);
}

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.integer > 0)
if (dvars::com_developer_script && dvars::com_developer_script->current.enabled)
{
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]] const auto* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
gsc_ctx->init(build, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
{
const auto script_name = std::filesystem::path(included_path).replace_extension().string();
@ -434,11 +434,8 @@ 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);
// 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);
dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE);
if (game::environment::is_sp())
{

View File

@ -0,0 +1,239 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "fastfiles.hpp"
#include "scheduler.hpp"
#include "imagefiles.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
#include <utils/concurrency.hpp>
#define CUSTOM_IMAGE_FILE_INDEX 96
namespace imagefiles
{
namespace
{
utils::memory::allocator image_file_allocator;
std::unordered_map<std::string, HANDLE> image_file_handles;
std::string get_image_file_name()
{
return fastfiles::get_current_fastfile();
}
namespace mp
{
struct image_file_unk
{
char __pad0[112]; // 120 in H1
};
std::unordered_map<std::string, image_file_unk*> image_file_unk_map;
void* get_image_file_unk_mp(unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return &reinterpret_cast<image_file_unk*>(
SELECT_VALUE(0x0, 0x143DC4E90))[index];
}
const auto name = get_image_file_name();
if (image_file_unk_map.find(name) == image_file_unk_map.end())
{
const auto unk = image_file_allocator.allocate<image_file_unk>();
image_file_unk_map[name] = unk;
return unk;
}
return image_file_unk_map[name];
}
}
/*
namespace sp
{
struct image_file_unk
{
char __pad0[96];
};
std::unordered_map<std::string, image_file_unk*> image_file_unk_map;
void* get_image_file_unk_mp(unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return &reinterpret_cast<image_file_unk*>(
SELECT_VALUE(0x0, 0x143DC4E90))[index];
}
const auto name = get_image_file_name();
if (image_file_unk_map.find(name) == image_file_unk_map.end())
{
const auto unk = image_file_allocator.allocate<image_file_unk>();
image_file_unk_map[name] = unk;
return unk;
}
return image_file_unk_map[name];
}
}
*/
HANDLE get_image_file_handle(unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return reinterpret_cast<HANDLE*>(
SELECT_VALUE(0x0, 0x143B74E80))[index];
}
const auto name = get_image_file_name();
return image_file_handles[name];
}
void db_create_gfx_image_stream_stub(utils::hook::assembler& a)
{
const auto handle_is_open = a.newLabel();
a.movzx(eax, cx);
a.mov(esi, eax);
a.imul(rsi, 0x70);
a.add(rsi, r15);
a.push(rax);
a.push(rax);
a.pushad64();
a.mov(rcx, rax);
a.call_aligned(mp::get_image_file_unk_mp);
a.mov(qword_ptr(rsp, 0x80), rax);
a.popad64();
a.pop(rax);
a.mov(rsi, rax);
a.pop(rax);
a.push(rax);
a.push(rax);
a.pushad64();
a.mov(rcx, rax);
a.call_aligned(get_image_file_handle);
a.mov(qword_ptr(rsp, 0x80), rax);
a.popad64();
a.pop(rax);
a.mov(r12, rax);
a.pop(rax);
a.cmp(r12, r13);
a.jnz(handle_is_open);
a.jmp(SELECT_VALUE(0x0, 0x140279C8B));
a.bind(handle_is_open);
a.jmp(SELECT_VALUE(0x0, 0x140279CDB));
}
void* pakfile_open_stub(void* /*handles*/, unsigned int count, int is_imagefile,
unsigned int index, short is_localized)
{
#ifdef DEBUG
console::info("Opening %s%d.pak (localized:%d)\n", is_imagefile ? "imagefile" : "soundfile", index, is_localized);
#endif
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return utils::hook::invoke<void*>(
SELECT_VALUE(0x0, 0x1404CC180),
SELECT_VALUE(0x0, 0x143B74E80),
count, is_imagefile, index, is_localized
);
}
const auto name = get_image_file_name();
const auto handle = game::Sys_CreateFile(is_localized ? game::SF_PAKFILE_LOC : game::SF_PAKFILE, utils::string::va("%s.pak", name.data()));
if (handle != nullptr)
{
image_file_handles[name] = handle;
}
return handle;
}
int com_sprintf_stub(char* buffer, const int len, const char* fmt, unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return game::Com_sprintf(buffer, len, fmt, index);
}
const auto name = get_image_file_name();
return game::Com_sprintf(buffer, len, "%s.pak", name.data());
}
}
void close_custom_handles()
{
for (const auto& handle : image_file_handles)
{
if (handle.second != nullptr)
{
utils::hook::invoke<void>(0x140608A20, handle.second); // Sys_CloseFile
}
}
image_file_handles.clear();
//sp::image_file_unk_map.clear();
mp::image_file_unk_map.clear();
image_file_allocator.clear();
}
void close_handle(const std::string& fastfile)
{
if (!image_file_handles.contains(fastfile))
{
return;
}
const auto handle = image_file_handles[fastfile];
if (handle != nullptr)
{
utils::hook::invoke<void>(0x140608A20, handle);
}
image_file_handles.erase(fastfile);
if (game::environment::is_sp())
{
//image_file_allocator.free(sp::image_file_unk_map[fastfile]);
//sp::image_file_unk_map.erase(fastfile);
}
else
{
image_file_allocator.free(mp::image_file_unk_map[fastfile]);
mp::image_file_unk_map.erase(fastfile);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
utils::hook::jump(SELECT_VALUE(0x0, 0x140279C79),
utils::hook::assemble(db_create_gfx_image_stream_stub), true);
utils::hook::call(SELECT_VALUE(0x0, 0x140279CBD), pakfile_open_stub);
utils::hook::call(SELECT_VALUE(0x0, 0x140279C9F), com_sprintf_stub);
}
};
}
REGISTER_COMPONENT(imagefiles::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace imagefiles
{
void close_custom_handles();
void close_handle(const std::string& fastfile);
}

View File

@ -0,0 +1,212 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "gsc/script_loading.hpp"
namespace mapents
{
namespace
{
std::optional<std::string> parse_mapents(const std::string& source)
{
std::string out_buffer{};
const auto lines = utils::string::split(source, '\n');
auto in_map_ent = false;
auto empty = false;
auto in_comment = false;
for (auto i = 0; i < lines.size(); i++)
{
auto line_num = i+1;
auto line = lines[i];
if (line.ends_with('\r'))
{
line.pop_back();
}
if (line.starts_with("/*"))
{
in_comment = true;
continue;
}
if (line.ends_with("*/"))
{
in_comment = false;
continue;
}
if (in_comment)
{
continue;
}
if (line.starts_with("//"))
{
continue;
}
if (line[0] == '{' && !in_map_ent)
{
in_map_ent = true;
out_buffer.append("{\n");
continue;
}
if (line[0] == '{' && in_map_ent)
{
console::error("[map_ents parser] Unexpected '{' on line %i\n", line_num);
return {};
}
if (line[0] == '}' && in_map_ent)
{
if (empty)
{
out_buffer.append("\n}\n");
}
else if (i < static_cast<int>(lines.size()) - 1)
{
out_buffer.append("}\n");
}
else
{
out_buffer.append("}\0");
}
in_map_ent = false;
continue;
}
if (line[0] == '}' && !in_map_ent)
{
console::error("[map_ents parser] Unexpected '}' on line %i\n", line_num);
return {};
}
std::regex expr(R"~((.+) "(.*)")~");
std::smatch match{};
if (!std::regex_search(line, match, expr) && !line.empty())
{
console::warn("[map_ents parser] Failed to parse line %i (%s)\n", line_num, line.data());
continue;
}
auto key = utils::string::to_lower(match[1].str());
const auto value = match[2].str();
if (key.size() <= 0)
{
console::warn("[map_ents parser] Invalid key ('%s') on line %i (%s)\n", key.data(), line_num, line.data());
continue;
}
if (value.size() <= 0)
{
continue;
}
empty = false;
if (utils::string::is_numeric(key) || key.size() < 3 || !key.starts_with("\"") || !key.ends_with("\""))
{
out_buffer.append(line);
out_buffer.append("\n");
continue;
}
const auto key_ = key.substr(1, key.size() - 2);
const auto id = gsc::gsc_ctx->token_id(key_);
if (id == 0)
{
console::warn("[map_ents parser] Key '%s' not found, on line %i (%s)\n", key_.data(), line_num, line.data());
continue;
}
out_buffer.append(utils::string::va("%i \"%s\"\n", id, value.data()));
}
return {out_buffer};
}
std::string raw_ents;
bool load_raw_mapents()
{
auto mapents_name = utils::string::va("%s.ents", **reinterpret_cast<const char***>(SELECT_VALUE(0x0, 0x1479E6940)));
if (filesystem::exists(mapents_name))
{
try
{
console::info("Reading raw ents file \"%s\"\n", mapents_name);
raw_ents = filesystem::read_file(mapents_name);
if (!raw_ents.empty())
{
return true;
}
}
catch (const std::exception& ex)
{
console::error("Failed to read raw ents file \"%s\"\n%s\n", mapents_name, ex.what());
}
}
return false;
}
std::string entity_string;
const char* cm_entity_string_stub()
{
const char* ents = nullptr;
if (load_raw_mapents())
{
ents = raw_ents.data();
}
else
{
if (!entity_string.empty())
{
return entity_string.data();
}
ents = utils::hook::invoke<const char*>(SELECT_VALUE(0x0, 0x1403A1F30));
}
const auto parsed = parse_mapents(ents);
if (parsed.has_value())
{
entity_string = parsed.value();
return entity_string.data();
}
else
{
return ents;
}
}
void cm_unload_stub(void* clip_map)
{
entity_string.clear();
raw_ents.clear();
utils::hook::invoke<void>(SELECT_VALUE(0x0, 0x1403A1ED0), clip_map);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::call(SELECT_VALUE(0x0, 0x1402F5A54), cm_entity_string_stub);
utils::hook::call(SELECT_VALUE(0x0, 0x14026BF54), cm_unload_stub);
}
};
}
REGISTER_COMPONENT(mapents::component)

View File

@ -1,233 +0,0 @@
#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

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

View File

@ -163,6 +163,17 @@ 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)
{
@ -287,8 +298,9 @@ 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); // Dvar_SetFloat inside LUI_CoD_LuaCall_StopFollow (function doesn't exist on dev builds)
utils::hook::nop(0x1400FAE36, 5);
utils::hook::set<uint8_t>(0x14019B9B9, 0xEB);
// some anti tamper thing that kills performance

View File

@ -0,0 +1,113 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "console.hpp"
#include "fastfiles.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
namespace weapon
{
namespace
{
utils::hook::detour g_setup_level_weapon_def_hook;
void g_setup_level_weapon_def_stub()
{
// precache level weapons first
g_setup_level_weapon_def_hook.invoke<void>();
std::vector<game::WeaponDef*> weapons;
// find all weapons in asset pools
fastfiles::enum_assets(game::ASSET_TYPE_WEAPON, [&weapons](game::XAssetHeader header)
{
weapons.push_back(reinterpret_cast<game::WeaponDef*>(header.data));
}, false);
// sort weapons
std::sort(weapons.begin(), weapons.end(), [](game::WeaponDef* weapon1, game::WeaponDef* weapon2)
{
return std::string_view(weapon1->name) <
std::string_view(weapon2->name);
});
// precache items
for (std::size_t i = 0; i < weapons.size(); i++)
{
//console::info("precaching weapon \"%s\"\n", weapons[i]->name);
game::G_GetWeaponForName(weapons[i]->name);
}
}
utils::hook::detour xmodel_get_bone_index_hook;
int xmodel_get_bone_index_stub(game::XModel* model, game::scr_string_t name, unsigned int offset, char* index)
{
auto result = xmodel_get_bone_index_hook.invoke<int>(model, name, offset, index);
if (result)
{
return result;
}
const auto original_index = *index;
const auto original_result = result;
if (name == game::SL_FindString("tag_weapon_right") ||
name == game::SL_FindString("tag_knife_attach"))
{
const auto tag_weapon = game::SL_FindString("tag_weapon");
result = xmodel_get_bone_index_hook.invoke<int>(model, tag_weapon, offset, index);
if (result)
{
console::info("using tag_weapon instead of %s (%s, %d, %d)\n", game::SL_ConvertToString(name), model->name, offset, *index);
return result;
}
}
*index = original_index;
result = original_result;
return result;
}
void cw_mismatch_error_stub(int, const char* msg, ...)
{
char buffer[0x100];
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
va_end(ap);
console::error(buffer);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
// precache all weapons that are loaded in zones
g_setup_level_weapon_def_hook.create(0x140340DE0, g_setup_level_weapon_def_stub);
// use tag_weapon if tag_weapon_right or tag_knife_attach are not found on model
xmodel_get_bone_index_hook.create(0x1404E2A50, xmodel_get_bone_index_stub);
// make custom weapon index mismatch not drop in CG_SetupCustomWeapon
utils::hook::call(0x1401E973D, cw_mismatch_error_stub);
}
};
}
REGISTER_COMPONENT(weapon::component)

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,13 +30,6 @@ 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,8 +66,6 @@ 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,14 +25,6 @@ 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,
@ -913,8 +905,6 @@ 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,
};
@ -933,7 +923,7 @@ namespace game
rgb = 9 // Color without alpha
};
union DvarValue
union dvar_value
{
bool enabled;
int integer;
@ -976,9 +966,9 @@ namespace game
unsigned int flags;
dvar_type type;
bool modified;
DvarValue current;
DvarValue latched;
DvarValue reset;
dvar_value current;
dvar_value latched;
dvar_value reset;
dvar_limits domain;
};
@ -1329,15 +1319,6 @@ namespace game
const char *name;
};
struct WeaponDef
{};
struct WeaponCompleteDef
{
const char* szInternalName;
WeaponDef* weapDef;
}; // Incomplete
union XAssetHeader
{
void* data;
@ -1462,40 +1443,6 @@ 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
@ -2151,4 +2098,39 @@ namespace game
HksError m_error;
};
}
enum DBAllocFlags : std::int32_t
{
DB_ZONE_NONE = 0x0,
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 // added for custom zone loading
};
struct XZone
{
char name[64];
char pad_0040[432];
};
static_assert(sizeof(XZone) == 496);
struct WeaponDef
{
union
{
const char* szInternalName;
const char* name;
};
};
struct XModel
{
const char* name; // 0
};
}

View File

@ -21,6 +21,7 @@ namespace game
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1402F7570, 0x1403CE480};
WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1402F7E10, 0x1403CEF30};
WEAK symbol<CodPlayMode()> Com_GetCurrentCoDPlayMode{0, 0x1404C9690};
WEAK symbol<bool(const char* mapname, const char** base_mapname)> Com_IsAddonMap{0, 0x1404ACEC0};
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0, 0x1403D19B0};
WEAK symbol<void()> Com_Quit_f{0x1402F9390, 0x1403D08C0};
WEAK symbol<void(const char* finalmsg)> Com_Shutdown{0x0, 0x1403D1BF0};
@ -60,7 +61,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<bool(const char* filename)> DB_IsLocalized{0x14017EC80, 0x140270190};
WEAK symbol<dvar_t*(const char* name)> Dvar_FindVar{0x140370860, 0x1404BF8B0};
WEAK symbol<void(const dvar_t* dvar)> Dvar_ClearModified{0x140370700, 0x1404BF690};
@ -71,7 +72,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, DvarValue value)> Dvar_ValueToString{0x140374E10, 0x1404C47B0};
WEAK symbol<const char*(dvar_t* dvar, dvar_value 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};
@ -99,13 +100,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};
@ -114,7 +115,6 @@ 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};
@ -216,6 +216,7 @@ namespace game
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0, 0x140490060};
WEAK symbol<int(const char* text, int maxChars, Font_s* font, float scale)> UI_TextWidth{0, 0x140492380};
WEAK symbol<const char* ()> SEH_GetCurrentLanguageCode{0x140339280, 0x140474560};
WEAK symbol<const char*()> SEH_GetCurrentLanguageName{0x140339300, 0x1404745C0};
WEAK symbol<void*(unsigned int size, unsigned int alignment, unsigned int type, int source)> PMem_AllocFromSource_NoDebug{0x1403775F0, 0x1404C7BA0};
@ -228,8 +229,6 @@ 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};
@ -277,6 +276,9 @@ namespace game
WEAK symbol<unsigned int> tls_index{0x14F65DAF0, 0x150085C44};
WEAK symbol<unsigned int> g_zoneCount{0x0, 0x14379DBCC};
WEAK symbol<XZone> g_zones{0x0, 0x143B50618};
namespace mp
{
WEAK symbol<gentity_s> g_entities{0, 0x144758C70};

View File

@ -176,4 +176,52 @@ namespace utils::string
return str;
}
bool is_numeric(const std::string& text)
{
return std::to_string(atoi(text.data())) == text;
}
bool find_lower(const std::string& a, const std::string& b)
{
return to_lower(a).find(to_lower(b)) != std::string::npos;
}
bool strstr_lower(const char* a, const char* b)
{
const char* a_ = a;
const char* b_ = b;
while (*a_ != '\0' && *b_ != '\0')
{
if (*b_ == '*' || std::tolower(*a_) == std::tolower(*b_))
{
b_++;
}
else
{
b_ = b;
}
a_++;
}
return *b_ == '\0';
}
void set_clipboard_data(const std::string& text)
{
const auto len = text.size() + 1;
const auto mem = GlobalAlloc(GMEM_MOVEABLE, len);
memcpy(GlobalLock(mem), text.data(), len);
GlobalUnlock(mem);
if (OpenClipboard(nullptr))
{
EmptyClipboard();
SetClipboardData(CF_TEXT, mem);
CloseClipboard();
}
}
}

View File

@ -95,4 +95,12 @@ namespace utils::string
std::wstring convert(const std::string& str);
std::string replace(std::string str, const std::string& from, const std::string& to);
bool is_numeric(const std::string& text);
bool find_lower(const std::string& a, const std::string& b);
bool strstr_lower(const char* a, const char* b);
void set_clipboard_data(const std::string& text);
}