Compare commits
5 Commits
master
...
custom-fas
Author | SHA1 | Date | |
---|---|---|---|
6b06a25629 | |||
31301fe203 | |||
ce047e96da | |||
1d98df42b3 | |||
6266241293 |
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit 466e4ebaa54dcf4257698d707d76d69a90614450
|
||||
Subproject commit 3325bbd33d24d1f8f5a0f69e782c92ad5a39a68e
|
2
deps/asmjit
vendored
2
deps/asmjit
vendored
@ -1 +1 @@
|
||||
Subproject commit cecc73f2979e9704c81a2c2ec79a7475b31c56ac
|
||||
Subproject commit e8c8e2e48a1a38154c8e8864eb3bc61db80a1e31
|
2
deps/gsc-tool
vendored
2
deps/gsc-tool
vendored
@ -1 +1 @@
|
||||
Subproject commit c508e5b10f5ecef9313ef49b0ec6b8ebf52e182f
|
||||
Subproject commit 2d9781ce0ce9e8551eaf040d7761fd986f33cfdc
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit d448df1938e8988bcdb0eed6591387e82b26874b
|
||||
Subproject commit a6b9aff7aab857fe1b491710a5c5b9e2be49cb08
|
2
deps/minhook
vendored
2
deps/minhook
vendored
@ -1 +1 @@
|
||||
Subproject commit 565968b28583221751cc2810e09ea621745fc3a3
|
||||
Subproject commit c3fcafdc10146beb5919319d0683e44e3c30d537
|
@ -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)
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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())
|
||||
{
|
||||
|
239
src/client/component/imagefiles.cpp
Normal file
239
src/client/component/imagefiles.cpp
Normal 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)
|
7
src/client/component/imagefiles.hpp
Normal file
7
src/client/component/imagefiles.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace imagefiles
|
||||
{
|
||||
void close_custom_handles();
|
||||
void close_handle(const std::string& fastfile);
|
||||
}
|
212
src/client/component/mapents.cpp
Normal file
212
src/client/component/mapents.cpp
Normal 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)
|
@ -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)
|
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace mods
|
||||
{
|
||||
bool is_using_mods();
|
||||
bool db_mod_file_exists();
|
||||
}
|
@ -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
|
||||
|
113
src/client/component/weapon.cpp
Normal file
113
src/client/component/weapon.cpp
Normal 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)
|
@ -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());
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user