This commit is contained in:
Ahrimdon 2024-03-06 15:10:30 -05:00
parent 6125b85f72
commit 2b6ab07460
41 changed files with 3127 additions and 139 deletions

2
.gitignore vendored
View File

@ -53,7 +53,7 @@ Temporary Items
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
# Ignore everything in the build directory # Build results
build build
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory

24
.gitmodules vendored Normal file
View File

@ -0,0 +1,24 @@
[submodule "deps/discord-rpc"]
path = deps/discord-rpc
url = https://github.com/discord/discord-rpc.git
[submodule "deps/GSL"]
path = deps/GSL
url = https://github.com/microsoft/GSL.git
[submodule "deps/libtomcrypt"]
path = deps/libtomcrypt
url = https://github.com/libtom/libtomcrypt.git
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
[submodule "deps/minhook"]
path = deps/minhook
url = https://github.com/TsudaKageyu/minhook.git
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/Tencent/rapidjson
[submodule "deps/udis86"]
path = deps/udis86
url = https://github.com/vmt/udis86.git
[submodule "deps/zlib"]
path = deps/zlib
url = https://github.com/madler/zlib.git

View File

@ -1,5 +1,3 @@
[![website](https://img.shields.io/badge/Repackers-_Website-blue)](https://rimmyscorner.com/)
# IW4: SP Client # IW4: SP Client
This is a client modification for IW4 (Singleplayer) 159 This is a client modification for IW4 (Singleplayer) 159
@ -7,8 +5,10 @@ This is a client modification for IW4 (Singleplayer) 159
## How to compile ## How to compile
This project requires [Git](https://git-scm.com), [Premake5](https://premake.github.io), and [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus) to build.
- Run `premake5 vs2022` or use the delivered `generate.bat`. - Run `premake5 vs2022` or use the delivered `generate.bat`.
- Build via solution file in `build\iw4-sp.sln`. - Build via solution file found in `build\iw4-sp.sln`.
## Premake arguments ## Premake arguments
@ -16,11 +16,12 @@ This is a client modification for IW4 (Singleplayer) 159
|:----------------------------|:-----------------------------------------------| |:----------------------------|:-----------------------------------------------|
| `--copy-to=PATH` | Optional, copy the exe to a custom folder after build. | | `--copy-to=PATH` | Optional, copy the exe to a custom folder after build. |
## Command line arguments ## Contributing
| Argument | Description | Contributions are welcome! Please follow the guidelines below:
|:------------------------|:-----------------------------------------------|
| `-nosteam` | Disable Steam integration. | - Sign [AlterWare CLA](https://alterware.dev/cla) and send a pull request or email your patch at patches@alterware.dev
- Make sure that PRs have only one commit, and deal with one issue only
## Disclaimer ## Disclaimer

View File

@ -1,4 +1,4 @@
gitVersioningCommand = "git describe --tags --always" gitVersioningCommand = "git describe --tags --dirty --always"
gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD" gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD"
-- Quote the given string input as a C string -- Quote the given string input as a C string

View File

@ -4,6 +4,7 @@
#include "localize_entry.hpp" #include "localize_entry.hpp"
#include "map_ents.hpp" #include "map_ents.hpp"
#include "raw_file.hpp" #include "raw_file.hpp"
#include "string_table.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
@ -13,13 +14,16 @@ void load_asset(game::XAssetType type, game::XAssetHeader* header) {
if (header) { if (header) {
switch (type) { switch (type) {
case game::ASSET_TYPE_LOCALIZE_ENTRY: case game::ASSET_TYPE_LOCALIZE_ENTRY:
process_localize_entry(*header); process_localize_entry(header);
break; break;
case game::ASSET_TYPE_MAP_ENTS: case game::ASSET_TYPE_MAP_ENTS:
process_map_ents(*header); process_map_ents(header);
break; break;
case game::ASSET_TYPE_RAWFILE: case game::ASSET_TYPE_RAWFILE:
process_raw_file(*header); process_raw_file(header);
break;
case game::ASSET_TYPE_STRINGTABLE:
process_string_table(header);
break; break;
default: default:
break; break;

View File

@ -11,12 +11,12 @@ namespace {
bool is_enabled() { IS_FLAG_ENABLED(dump_localize_entry); } bool is_enabled() { IS_FLAG_ENABLED(dump_localize_entry); }
} // namespace } // namespace
void process_localize_entry(game::XAssetHeader header) { void process_localize_entry(game::XAssetHeader* header) {
if (!is_enabled()) { if (!is_enabled()) {
return; return;
} }
auto* localize = header.localize; auto* localize = header->localize;
const auto filename = const auto filename =
utils::string::va("raw/localizedstrings/{0}", localize->name); utils::string::va("raw/localizedstrings/{0}", localize->name);

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
namespace assets { namespace assets {
void process_localize_entry(game::XAssetHeader header); void process_localize_entry(game::XAssetHeader* header);
} }

View File

@ -23,8 +23,8 @@ void load_map_entities(game::MapEnts* entry) {
} }
} // namespace } // namespace
void process_map_ents(game::XAssetHeader header) { void process_map_ents(game::XAssetHeader* header) {
auto* map_ents = header.mapEnts; auto* map_ents = header->mapEnts;
load_map_entities(map_ents); load_map_entities(map_ents);
} }
} // namespace assets } // namespace assets

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
namespace assets { namespace assets {
void process_map_ents(game::XAssetHeader header); void process_map_ents(game::XAssetHeader* header);
} }

View File

@ -19,7 +19,7 @@ char* db_read_raw_file_stub(const char* filename, char* buf, int size) {
auto file_handle = 0; auto file_handle = 0;
const auto file_size = game::FS_FOpenFileRead(filename, &file_handle); const auto file_size = game::FS_FOpenFileRead(filename, &file_handle);
if (file_handle != 0) { if (file_handle) {
if ((file_size + 1) <= size) { if ((file_size + 1) <= size) {
game::FS_Read(buf, file_size, file_handle); game::FS_Read(buf, file_size, file_handle);
buf[file_size] = '\0'; buf[file_size] = '\0';
@ -78,7 +78,7 @@ const char* com_load_info_string_load_obj(const char* file_name,
game::FS_FOpenFileByMode(file_name, &file_handle, game::FS_READ); game::FS_FOpenFileByMode(file_name, &file_handle, game::FS_READ);
if (file_len < 0) { if (file_len < 0) {
game::Com_DPrintf(game::CON_CHANNEL_SYSTEM, game::Com_DPrintf(game::CON_CHANNEL_SYSTEM,
"Could not load %s [%s] as rawfile", file_desc, "Could not load %s [%s] as rawfile\n", file_desc,
file_name); file_name);
return nullptr; return nullptr;
} }
@ -134,12 +134,12 @@ const char* com_load_info_string_stub(const char* file_name,
bool is_enabled() { IS_FLAG_ENABLED(dump_raw_file); } bool is_enabled() { IS_FLAG_ENABLED(dump_raw_file); }
} // namespace } // namespace
void process_raw_file(game::XAssetHeader header) { void process_raw_file(game::XAssetHeader* header) {
if (!is_enabled()) { if (!is_enabled()) {
return; return;
} }
const auto* raw_file = header.rawfile; const auto* raw_file = header->rawfile;
const auto filename = utils::string::va("raw/{0}", raw_file->name); const auto filename = utils::string::va("raw/{0}", raw_file->name);
if (raw_file->compressedLen > 0) { if (raw_file->compressedLen > 0) {
@ -147,7 +147,7 @@ void process_raw_file(game::XAssetHeader header) {
uncompressed.resize(raw_file->len); uncompressed.resize(raw_file->len);
if (uncompress(uncompressed.data(), (uLongf*)&raw_file->len, if (uncompress(uncompressed.data(), (uLongf*)&raw_file->len,
(const Bytef*)raw_file->buffer, reinterpret_cast<const Bytef*>(raw_file->buffer),
raw_file->compressedLen) == Z_OK) { raw_file->compressedLen) == Z_OK) {
std::string data; std::string data;
data.assign(uncompressed.begin(), uncompressed.end()); data.assign(uncompressed.begin(), uncompressed.end());

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
namespace assets { namespace assets {
void process_raw_file(game::XAssetHeader header); void process_raw_file(game::XAssetHeader* header);
} }

View File

@ -0,0 +1,46 @@
#include <std_include.hpp>
#include "string_table.hpp"
#include <utils/flags.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace assets {
namespace {
void dump_string_table(game::XAssetHeader* header) {
const auto* string_table = header->stringTable;
const auto filename = utils::string::va("raw/{0}", string_table->name);
std::string csv;
const auto rows = string_table->rowCount;
const auto columns = string_table->columnCount;
for (auto x = 0; x < rows; ++x) {
for (auto y = 0; y < columns; ++y) {
const char* cell = string_table->values[(x * columns) + y].string;
csv += cell;
if (y + 1 < columns) {
csv += ",";
}
}
if (x + 1 < rows) {
csv += "\n";
}
}
utils::io::write_file(filename, csv);
}
bool is_enabled() { IS_FLAG_ENABLED(dump_string_table); }
} // namespace
void process_string_table(game::XAssetHeader* header) {
if (is_enabled()) {
dump_string_table(header);
}
}
} // namespace assets

View File

@ -0,0 +1,5 @@
#pragma once
namespace assets {
void process_string_table(game::XAssetHeader* header);
}

View File

@ -94,7 +94,7 @@ game::define_s* copy_define([[maybe_unused]] game::source_s* source,
game::token_s *token, *newtoken, *lasttoken; game::token_s *token, *newtoken, *lasttoken;
auto* newdefine = static_cast<game::define_s*>( auto* newdefine = static_cast<game::define_s*>(
game::GetMemory(sizeof(game::define_s) + strlen(define->name) + 1)); game::GetMemory(sizeof(game::define_s) + std::strlen(define->name) + 1));
// copy the define name // copy the define name
newdefine->name = (char*)newdefine + sizeof(game::define_s); newdefine->name = (char*)newdefine + sizeof(game::define_s);

View File

@ -153,7 +153,7 @@ game::script_s* load_script_file(const char* filename) {
// pointer to end of script buffer // pointer to end of script buffer
script->end_p = &script->buffer[length]; script->end_p = &script->buffer[length];
// set if there's a token available in script->token // set if there's a token available in script->token
script->tokenavailable = 0; script->tokenavailable = false;
script->line = 1; script->line = 1;
script->lastline = 1; script->lastline = 1;
@ -183,7 +183,7 @@ game::script_s* load_script_memory(const char* ptr, int length,
// pointer to end of script buffer // pointer to end of script buffer
script->end_p = &script->buffer[length]; script->end_p = &script->buffer[length];
// set if there's a token available in script->token // set if there's a token available in script->token
script->tokenavailable = 0; script->tokenavailable = false;
script->line = 1; script->line = 1;
script->lastline = 1; script->lastline = 1;

View File

@ -98,7 +98,7 @@ public:
register_branding_dvars(); register_branding_dvars();
utils::hook(0x57DAFF, cg_draw_full_screen_debug_overlays_stub, HOOK_CALL) utils::hook(0x57DAFF, cg_draw_full_screen_debug_overlays_stub, HOOK_CALL)
.install() .install() // hook*
->quick(); ->quick();
} }

View File

@ -19,6 +19,7 @@ public:
utils::hook::set<std::uint32_t>(0x47C2E0, 0xC301B0); utils::hook::set<std::uint32_t>(0x47C2E0, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4EEA90, 0xC301B0); utils::hook::set<std::uint32_t>(0x4EEA90, 0xC301B0);
utils::hook::set<std::uint32_t>(0x40E380, 0xC301B0); utils::hook::set<std::uint32_t>(0x40E380, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4E45C0, 0xC301B0);
// Killer caller // Killer caller
utils::hook::set<std::uint8_t>(0x43F320, 0xC3); utils::hook::set<std::uint8_t>(0x43F320, 0xC3);

View File

@ -119,7 +119,7 @@ public:
#ifdef _DEBUG #ifdef _DEBUG
utils::hook(0x4C79DF, g_init_game_stub, HOOK_CALL) utils::hook(0x4C79DF, g_init_game_stub, HOOK_CALL)
.install() .install() // hook*
->quick(); // Scr_FreeEntityList ->quick(); // Scr_FreeEntityList
#endif #endif
} }

View File

@ -61,7 +61,6 @@ private:
static void ready(const DiscordUser* request) { static void ready(const DiscordUser* request) {
ZeroMemory(&discord_presence, sizeof(discord_presence)); ZeroMemory(&discord_presence, sizeof(discord_presence));
discord_presence.state = "Singleplayer";
discord_presence.instance = 1; discord_presence.instance = 1;
discord_presence.startTimestamp = 0; discord_presence.startTimestamp = 0;
printf("Discord: Ready\n"); printf("Discord: Ready\n");
@ -69,7 +68,7 @@ private:
} }
static void errored(const int error_code, const char* message) { static void errored(const int error_code, const char* message) {
printf("Discord: (%i) %s", error_code, message); printf("Discord: (%i) %s\n", error_code, message);
} }
}; };
} // namespace discord } // namespace discord

View File

@ -19,9 +19,9 @@ const game::dvar_t* dvar_register_name(const char* dvar_name, const char* value,
class component final : public component_interface { class component final : public component_interface {
public: public:
void post_start() override { void post_start() override {
dvar::override::register_bool("intro", true, game::DVAR_NONE); dvar::override::register_bool("intro", false, game::DVAR_ARCHIVE);
dvar::override::register_float("cg_fov", 65.0f, 65.0f, 160.0f, dvar::override::register_float("cg_fov", 65.0f, 65.0f, 160.0f,
game::DVAR_ARCHIVE); game::DVAR_ARCHIVE | game::DVAR_SAVED);
dvar::override::register_string("fs_basegame", BASEGAME, game::DVAR_INIT); dvar::override::register_string("fs_basegame", BASEGAME, game::DVAR_INIT);
#ifdef _DEBUG #ifdef _DEBUG

File diff suppressed because it is too large Load Diff

View File

@ -69,9 +69,6 @@ private:
// Build os path stuff // Build os path stuff
utils::hook::set<std::uint8_t>(0x6300BF, 0xEB); utils::hook::set<std::uint8_t>(0x6300BF, 0xEB);
// Show intro (or not)
utils::hook::set<std::uint8_t>(0x6035BD, 0x0);
// raw -> main // raw -> main
utils::hook::set<std::uint32_t>(0x50A0B2, 0x723390); utils::hook::set<std::uint32_t>(0x50A0B2, 0x723390);

View File

@ -109,6 +109,59 @@ void pm_player_trace_stub(game::pmove_t* pm, game::trace_t* results,
results->startsolid = false; results->startsolid = false;
} }
} }
void pm_crash_land_stub(const float* v, float scale, const float* result) {
if (!dvars::pm_disableLandingSlowdown->current.enabled) {
game::Vec3Scale(v, scale, result);
}
}
void __declspec(naked) jump_check_stub() {
__asm {
push eax;
mov eax, dvars::pm_bunnyHop;
cmp byte ptr [eax + 0x10], 1;
pop eax;
je auto_hop;
// Game's code
test dword ptr [ebx + 0x48], 0x400;
push 0x4D25EF;
ret;
auto_hop:
push 0x4D25FE;
ret;
}
}
void __declspec(naked) p_move_single_stub() {
__asm {
push eax;
mov eax, dvars::pm_snapVector;
cmp byte ptr [eax + 0x10], 1;
pop eax;
jnz skip;
lea ebp, [ebp + 0x28]; // velocity
push ebp;
call game::Sys_SnapVector;
add esp, 0x4;
skip:
// Game's code
pop edi;
pop esi;
pop ebp;
pop ebx;
add esp, 0x90;
ret;
}
}
void g_scr_is_sprinting(const game::scr_entref_t entref) { void g_scr_is_sprinting(const game::scr_entref_t entref) {
const auto* client = game::GetEntity(entref)->client; const auto* client = game::GetEntity(entref)->client;
if (!client) { if (!client) {
@ -154,6 +207,13 @@ public:
.install() .install()
->quick(); // PM_CorrectAllSolid ->quick(); // PM_CorrectAllSolid
utils::hook(0x64E571, pm_crash_land_stub, HOOK_CALL)
.install()
->quick(); // Vec3Scale
utils::hook(0x4D25E8, jump_check_stub, HOOK_JUMP).install()->quick();
utils::hook(0x6530C3, p_move_single_stub, HOOK_JUMP).install()->quick();
gsc::add_method("IsSprinting", g_scr_is_sprinting); gsc::add_method("IsSprinting", g_scr_is_sprinting);
register_dvars(); register_dvars();
} }
@ -172,6 +232,11 @@ public:
"pm_playerCollision", true, game::DVAR_NONE, "Push intersecting players away from each other"); "pm_playerCollision", true, game::DVAR_NONE, "Push intersecting players away from each other");
dvars::pm_elevators = game::Dvar_RegisterBool( dvars::pm_elevators = game::Dvar_RegisterBool(
"pm_elevators", false, game::DVAR_NONE, "CoD4 elevators"); "pm_elevators", false, game::DVAR_NONE, "CoD4 elevators");
dvars::pm_disableLandingSlowdown = game::Dvar_RegisterBool("pm_disableLandingSlowdown",
false, game::DVAR_NONE, "Toggle landing slowdown");
dvars::pm_bunnyHop = game::Dvar_RegisterBool("pm_bunnyHop",
false, game::DVAR_NONE, "Constantly jump when holding space");
dvars::pm_snapVector = game::Dvar_RegisterBool("pm_snapVector", false, game::DVAR_NONE, "Snap velocity");
// clang-format on // clang-format on
} }
}; };

View File

@ -2,7 +2,6 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include <utils/binary_resource.hpp> #include <utils/binary_resource.hpp>
#include <utils/flags.hpp>
#include <utils/nt.hpp> #include <utils/nt.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
@ -14,28 +13,12 @@
#include "scheduler.hpp" #include "scheduler.hpp"
namespace { namespace {
enum class ownership_state {
success,
unowned,
nosteam,
error,
};
utils::binary_resource runner_file(RUNNER, "iw4sp-runner.exe"); utils::binary_resource runner_file(RUNNER, "iw4sp-runner.exe");
bool is_disabled() {
static const auto disabled = utils::flags::has_flag("nosteam");
return disabled;
}
} // namespace } // namespace
class steam_proxy final : public component_interface { class steam_proxy final : public component_interface {
public: public:
void post_load() override { void post_load() override {
if (is_disabled()) {
return;
}
this->load_client(); this->load_client();
this->clean_up_on_error(); this->clean_up_on_error();
@ -63,9 +46,9 @@ public:
private: private:
utils::nt::library steam_client_module_{}; utils::nt::library steam_client_module_{};
steam::interface client_engine_ {}; steam::interface client_engine_{};
steam::interface client_user_ {}; steam::interface client_user_{};
steam::interface client_utils_ {}; steam::interface client_utils_{};
void* steam_pipe_ = nullptr; void* steam_pipe_ = nullptr;
void* global_user_ = nullptr; void* global_user_ = nullptr;
@ -113,36 +96,25 @@ private:
14, this->steam_pipe_); // GetIClientUtils 14, this->steam_pipe_); // GetIClientUtils
} }
ownership_state start_mod(const std::string& title, void start_mod(const std::string& title, const std::size_t app_id) {
const std::size_t app_id) {
__try { __try {
return this->start_mod_unsafe(title, app_id); this->start_mod_unsafe(title, app_id);
} __except (EXCEPTION_EXECUTE_HANDLER) { } __except (EXCEPTION_EXECUTE_HANDLER) {
this->do_cleanup(); this->do_cleanup();
return ownership_state::error;
} }
} }
ownership_state start_mod_unsafe(const std::string& title, void start_mod_unsafe(const std::string& title, std::size_t app_id) {
std::size_t app_id) {
if (!this->client_utils_ || !this->client_user_) if (!this->client_utils_ || !this->client_user_)
return ownership_state::nosteam; return;
if (!this->client_user_.invoke<bool>("BIsSubscribedApp", app_id)) { if (!this->client_user_.invoke<bool>("BIsSubscribedApp", app_id)) {
#ifdef _DEBUG
app_id = 480; // Spacewar app_id = 480; // Spacewar
#else
return ownership_state::unowned;
#endif
}
if (is_disabled()) {
return ownership_state::success;
} }
this->client_utils_.invoke<void>("SetAppIDForCurrentPipe", app_id, false); this->client_utils_.invoke<void>("SetAppIDForCurrentPipe", app_id, false);
char our_directory[MAX_PATH] = {0}; char our_directory[MAX_PATH]{};
GetCurrentDirectoryA(sizeof(our_directory), our_directory); GetCurrentDirectoryA(sizeof(our_directory), our_directory);
const auto path = runner_file.get_extracted_file(); const auto path = runner_file.get_extracted_file();
@ -160,8 +132,6 @@ private:
this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmd_line, this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmd_line,
our_directory, game_id.bits, title.data(), our_directory, game_id.bits, title.data(),
app_id, 0, 0, 0); app_id, 0, 0, 0);
return ownership_state::success;
} }
void do_cleanup() { void do_cleanup() {

629
src/client/component/ui.cpp Normal file
View File

@ -0,0 +1,629 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "botlib/l_precomp.hpp"
#include <utils/hook.hpp>
namespace ui {
namespace {
#define ITEM_TYPE_LISTBOX 6 // scrollable list
#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated
#define PARSE_FLOAT_TOKEN(name) \
if (!game::I_stricmp(token.string, #name)) { \
if (!game::PC_Float_Parse(handle, &g_load.loadAssets.##name##)) { \
return false; \
} \
continue; \
}
#define PARSE_INT_TOKEN(name) \
if (!game::I_stricmp(token.string, #name)) { \
if (!game::PC_Int_Parse(handle, &g_load.loadAssets.##name##)) { \
return false; \
} \
continue; \
}
#define FREE_STATEMENT(statement) \
{ \
if ((statement)) { \
menu_free_expression_supporting_data((statement)->supportingData); \
} \
game::free_expression((statement)); \
}
template <typename T, int N, int M> struct KeywordHashEntry {
const char* keyword;
int (*func)(T*, int);
};
KeywordHashEntry<game::menuDef_t, 128, 3523>** menu_parse_keyword_hash;
std::vector<game::menuDef_t*> loaded_menus_list;
struct {
game::loadAssets_t loadAssets;
game::MenuList menuList;
game::itemDef_s* items[512];
game::menuDef_t* menus[512];
} g_load;
char menu_buf[0x8000];
int asset_parse(int handle) {
game::pc_token_s token{};
if (!game::PC_ReadTokenHandle(handle, &token)) {
return false;
}
if (game::I_stricmp(token.string, "{") != 0) {
return false;
}
while (true) {
if (!game::PC_ReadTokenHandle(handle, &token)) {
return false;
}
if (!game::I_stricmp(token.string, ";")) {
continue;
}
if (!game::I_stricmp(token.string, "}")) {
return true;
}
PARSE_FLOAT_TOKEN(fadeClamp);
PARSE_INT_TOKEN(fadeCycle);
PARSE_FLOAT_TOKEN(fadeAmount);
PARSE_FLOAT_TOKEN(fadeInAmount);
game::PC_SourceError(
handle,
"Unknown token %s in assetGlobalDef. Valid commands are 'fadeClamp', "
"'fadeCycle', 'fadeAmount', and 'fadeInAmount'\n",
token.string);
}
}
void* alloc(int size, int alignment) {
return game::Hunk_AllocAlignInternal(size, alignment);
}
template <int HASH_COUNT, int HASH_SEED>
int keyword_hash_key(const char* keyword) {
auto hash = 0;
for (auto i = 0; keyword[i]; ++i) {
hash +=
(i + HASH_SEED) * std::tolower(static_cast<unsigned char>(keyword[i]));
}
return (hash + (hash >> 8)) & (128 - 1);
}
template <typename T, int N, int M>
KeywordHashEntry<T, N, M>* keyword_hash_find(KeywordHashEntry<T, N, M>** table,
const char* keyword) {
auto hash = keyword_hash_key<N, M>(keyword);
KeywordHashEntry<T, N, M>* key = table[hash];
if (key && !game::I_stricmp(key->keyword, keyword)) {
return key;
}
return nullptr;
}
int menu_parse(int handle, game::menuDef_t* menu) {
game::pc_token_s token{};
if (!game::PC_ReadTokenHandle(handle, &token))
return false;
if (*token.string != '{')
return false;
while (true) {
std::memset(&token, 0, sizeof(game::pc_token_s));
if (!game::PC_ReadTokenHandle(handle, &token)) {
game::PC_SourceError(handle, "end of file inside menu\n");
return false;
}
if (*token.string == '}') {
return true;
}
auto* key = keyword_hash_find(menu_parse_keyword_hash, token.string);
if (!key) {
game::PC_SourceError(handle, "unknown menu keyword %s", token.string);
continue;
}
if (!key->func(menu, handle)) {
game::PC_SourceError(handle, "couldn't parse menu keyword %s",
token.string);
return false;
}
}
}
void item_set_screen_coords([[maybe_unused]] int context_index,
game::itemDef_s* item, float x, float y,
int horz_align, int vert_align) {
assert(item);
if (!item) {
return;
}
if (item->window.border) {
x += item->window.borderSize;
y += item->window.borderSize;
}
item->window.rect = item->window.rectClient;
item->window.rect.x = item->window.rect.x + x;
item->window.rect.y = item->window.rect.y + y;
if (!item->window.rect.horzAlign && !item->window.rect.vertAlign) {
item->window.rect.horzAlign = static_cast<char>(horz_align);
item->window.rect.vertAlign = static_cast<char>(vert_align);
}
}
game::rectDef_s* window_get_rect(game::windowDef_t* w) {
assert(w);
return &w->rect;
}
void menu_update_position(int context_index, game::menuDef_t* menu) {
if (menu == nullptr) {
return;
}
const auto* rect = window_get_rect(&menu->window);
float x = rect->x;
float y = rect->y;
if (menu->window.border) {
x += menu->window.borderSize;
y += menu->window.borderSize;
}
for (int i = 0; i < menu->itemCount; ++i) {
item_set_screen_coords(context_index, menu->items[i], x, y, rect->horzAlign,
rect->vertAlign);
}
}
void menu_post_parse(game::menuDef_t* menu) {
assert(menu);
const auto item_count = 4 * menu->itemCount;
menu->items = static_cast<game::itemDef_s**>(alloc(item_count, 4));
std::memcpy(menu->items, g_load.items, item_count);
if (menu->fullScreen) {
menu->window.rect.x = 0.0f;
menu->window.rect.y = 0.0f;
menu->window.rect.w = 640.0f;
menu->window.rect.h = 480.0f;
}
menu_update_position(0, menu);
}
void menu_set_cursor_item(int context_index, game::menuDef_t* menu,
int cursor_item) {
assert(context_index < game::MAX_POSSIBLE_LOCAL_CLIENTS);
assert(menu);
menu->cursorItem[context_index] = cursor_item;
}
void window_init(game::windowDef_t* w) {
std::memset(w, 0, sizeof(game::windowDef_t));
w->borderSize = 1.0f;
w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0f;
}
void menu_init(game::menuDef_t* menu) {
std::memset(menu, 0, sizeof(game::menuDef_t));
menu_set_cursor_item(0, menu, -1);
menu->fadeAmount = g_load.loadAssets.fadeAmount;
menu->fadeInAmount = g_load.loadAssets.fadeInAmount;
menu->fadeClamp = g_load.loadAssets.fadeClamp;
menu->fadeCycle = g_load.loadAssets.fadeCycle;
menu->items = g_load.items;
window_init(&menu->window);
}
void menu_free_expression_supporting_data(
game::ExpressionSupportingData* data) {
if (!data) {
return;
}
for (auto i = 0; i < data->uifunctions.totalFunctions; ++i) {
auto* function = data->uifunctions.functions[i];
FREE_STATEMENT(function);
}
data->uifunctions.totalFunctions = 0;
}
void menu_free_event_handler(game::MenuEventHandlerSet* event_handler) {
if (!event_handler) {
return;
}
for (auto i = 0; i < event_handler->eventHandlerCount; ++i) {
auto* event = event_handler->eventHandlers[i];
game::ConditionalScript* conditional_script;
game::MenuEventHandlerSet* else_script;
game::SetLocalVarData* local_var;
switch (event->eventType) {
case game::EVENT_IF:
conditional_script = event->eventData.conditionalScript;
menu_free_event_handler(conditional_script->eventHandlerSet);
FREE_STATEMENT(conditional_script->eventExpression);
break;
case game::EVENT_ELSE:
else_script = event->eventData.elseScript;
menu_free_event_handler(else_script);
break;
case game::EVENT_SET_LOCAL_VAR_BOOL:
case game::EVENT_SET_LOCAL_VAR_INT:
case game::EVENT_SET_LOCAL_VAR_FLOAT:
case game::EVENT_SET_LOCAL_VAR_STRING:
local_var = event->eventData.setLocalVarData;
FREE_STATEMENT(local_var->expression);
break;
default:
break;
}
}
event_handler->eventHandlerCount = 0;
}
void menu_free_item_key_handler(const game::ItemKeyHandler* key_handler) {
while (key_handler) {
menu_free_event_handler(key_handler->action);
key_handler = key_handler->next;
}
}
void menu_free_memory(game::menuDef_t* menu) {
if (!menu) {
return;
}
for (auto i = 0; i < menu->itemCount; ++i) {
auto* item = menu->items[i];
FREE_STATEMENT(item->visibleExp);
FREE_STATEMENT(item->disabledExp);
FREE_STATEMENT(item->textExp);
FREE_STATEMENT(item->materialExp);
menu_free_event_handler(item->mouseEnterText);
menu_free_event_handler(item->mouseExitText);
menu_free_event_handler(item->mouseEnter);
menu_free_event_handler(item->mouseExit);
menu_free_event_handler(item->action);
menu_free_event_handler(item->accept);
menu_free_event_handler(item->onFocus);
menu_free_event_handler(item->leaveFocus);
menu_free_item_key_handler(item->onKey);
// free ItemFloatExpression*
game::Menu_FreeItemMemory(item);
if (item->dataType == ITEM_TYPE_LISTBOX) {
menu_free_event_handler(item->typeData.listBox->onDoubleClick);
}
}
menu->itemCount = 0;
menu_free_event_handler(menu->onOpen);
menu_free_event_handler(menu->onCloseRequest);
menu_free_event_handler(menu->onClose);
menu_free_event_handler(menu->onESC);
menu_free_item_key_handler(menu->onKey);
FREE_STATEMENT(menu->visibleExp);
FREE_STATEMENT(menu->rectXExp);
FREE_STATEMENT(menu->rectYExp);
FREE_STATEMENT(menu->rectWExp);
FREE_STATEMENT(menu->rectHExp);
FREE_STATEMENT(menu->openSoundExp);
FREE_STATEMENT(menu->closeSoundExp);
// Our menu compiler code does not support parsing 'ui functions'
if (!menu->expressionData) {
return;
}
for (auto i = 0; i < menu->expressionData->uifunctions.totalFunctions; ++i) {
auto* function_statement = menu->expressionData->uifunctions.functions[i];
FREE_STATEMENT(function_statement);
}
menu->expressionData->uifunctions.totalFunctions = 0;
}
void menus_free_all_memory(game::UiContext* dc) {
for (auto menu = 0; menu < dc->menuCount; ++menu) {
menu_free_memory(dc->Menus[menu]);
}
}
bool menu_new(int handle) {
auto* menu = static_cast<game::menuDef_t*>(alloc(sizeof(game::menuDef_t), 4));
menu_init(menu);
if (!menu_parse(handle, menu)) {
menu_free_memory(menu);
return false;
}
if (!menu->window.name) {
game::PC_SourceError(handle, "menu has no name");
menu_free_memory(menu);
return false;
}
menu_post_parse(menu);
if (static_cast<std::size_t>(g_load.menuList.menuCount) >=
std::extent_v<decltype(g_load.menus)>) {
game::Com_Error(game::ERR_DROP, "\x15"
"Menu_New: "
"\x14"
"EXE_ERR_OUT_OF_MEMORY");
}
g_load.menuList.menus[g_load.menuList.menuCount++] = menu;
loaded_menus_list.emplace_back(menu);
return true;
}
bool parse_menu_internal(const char* menu_file) {
const char* builtin_defines[2];
game::pc_token_s token;
builtin_defines[0] = "PC";
builtin_defines[1] = nullptr;
game::Com_Printf(game::CON_CHANNEL_UI, "\tLoading '%s'...\n", menu_file);
const auto handle = pc::load_source_handle(menu_file, builtin_defines);
if (!handle) {
game::Com_PrintError(game::CON_CHANNEL_UI, "Couldn't find menu file '%s'\n",
menu_file);
return false;
}
while (game::PC_ReadTokenHandle(handle, &token)) {
if (!game::I_stricmp(token.string, "}") ||
!game::I_stricmp(token.string, "{")) {
continue;
}
if (!game::I_stricmp(token.string, "assetGlobalDef")) {
asset_parse(handle);
continue;
}
if (!game::I_stricmp(token.string, "menudef")) {
menu_new(handle);
continue;
}
game::PC_SourceError(handle,
"Unknown token %s in menu file. Expected \"menudef\" "
"or \"assetglobaldef\".\n",
token.string);
}
pc::free_source_handle(handle);
return true;
}
int load_menu(const char** p) {
if (*game::Com_Parse(p) != '{') {
return false;
}
for (;;) {
const auto* token = game::Com_Parse(p);
if (!game::I_stricmp(token, "}")) {
return true;
}
if (!token || !*token) {
break;
}
parse_menu_internal(token);
}
return false;
}
game::MenuList* load_menus_load_obj(const char* menu_file) {
std::memset(&g_load, 0, sizeof(g_load));
g_load.menuList.menus = g_load.menus;
auto f = 0;
auto len = game::FS_FOpenFileRead(menu_file, &f);
if (!f) {
game::Com_PrintWarning(game::CON_CHANNEL_UI,
"WARNING: menu file not found: %s\n", menu_file);
#ifdef UI_LOAD_DEFAULT_MENU
len = game::FS_FOpenFileByMode("ui/default.menu", &f, game::FS_READ);
if (!f) {
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15"
"default.menu file not found. This is a default menu "
"that you should have.\n");
return nullptr;
}
#else
return nullptr;
#endif
}
if (static_cast<std::size_t>(len) >= sizeof(menu_buf)) {
game::FS_FCloseFile(f);
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15^1menu file too large: %s is %i, max allowed is %i",
menu_file, len, sizeof(menu_buf));
}
game::FS_Read(menu_buf, len, f);
menu_buf[len] = '\0';
game::FS_FCloseFile(f);
game::Com_Compress(menu_buf);
const auto* p = menu_buf;
game::Com_BeginParseSession(menu_file);
for (auto* token = game::Com_Parse(&p); token; token = game::Com_Parse(&p)) {
if (!*token || *token == '}' || !game::I_stricmp(token, "}") ||
!game::I_stricmp(token, "loadmenu") && !load_menu(&p)) {
break;
}
}
game::Com_EndParseSession();
return &g_load.menuList;
}
game::MenuList* load_menu_load_obj(const char* menu_file) {
std::memset(&g_load, 0, sizeof(g_load));
g_load.menuList.menus = g_load.menus;
if (!parse_menu_internal(menu_file)) {
game::Com_PrintWarning(game::CON_CHANNEL_UI,
"WARNING: menu file not found: %s\n", menu_file);
#ifdef UI_LOAD_DEFAULT_MENU
if (!parse_menu_internal("ui/default.menu")) {
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15"
"default.menu file not found. This is a default menu "
"that you should have.\n");
}
#else
return nullptr;
#endif
}
return &g_load.menuList;
}
game::MenuList* load_menus_fast_file(const char* menu_file) {
return game::DB_FindXAssetHeader(game::ASSET_TYPE_MENULIST, menu_file)
.menuList;
}
game::MenuList* load_menus_stub(const char* menu_file) {
auto* menu_list = load_menus_load_obj(menu_file);
if (!menu_list) {
menu_list = load_menus_fast_file(menu_file);
}
return menu_list;
}
game::MenuList* load_menu_fast_file(const char* menu_file) {
return game::DB_FindXAssetHeader(game::ASSET_TYPE_MENULIST, menu_file)
.menuList;
}
game::MenuList* load_menu_stub(const char* menu_file) {
auto* menu_list = load_menu_load_obj(menu_file);
if (!menu_list) {
menu_list = load_menu_fast_file(menu_file);
}
return menu_list;
}
void snd_fade_all_sounds_stub(float volume, int fade_time) {
utils::hook::invoke<void>(0x4206A0, volume, fade_time);
for (auto& menu : loaded_menus_list) {
menu_free_memory(menu);
}
loaded_menus_list.clear();
}
void ui_shutdown_stub() {
for (auto& menu : loaded_menus_list) {
menu_free_memory(menu);
}
loaded_menus_list.clear();
utils::hook::invoke<void>(0x4CBD70);
}
} // namespace
class component final : public component_interface {
public:
static_assert(offsetof(game::UiContext, menuCount) == 0xA38);
static_assert(offsetof(game::UiContext, Menus) == 0x38);
static_assert(offsetof(game::menuDef_t, itemCount) == 0xAC);
static_assert(offsetof(game::itemDef_s, dataType) == 0xBC);
static_assert(offsetof(game::itemDef_s, typeData) == 0x134);
static_assert(offsetof(game::itemDef_s, floatExpressionCount) == 0x13C);
static_assert(offsetof(game::itemDef_s, floatExpressions) == 0x140);
void post_load() override {
menu_parse_keyword_hash =
reinterpret_cast<KeywordHashEntry<game::menuDef_t, 128, 3523>**>(
0x1933DB0);
patch_sp();
}
void pre_destroy() override { assert(loaded_menus_list.empty()); }
private:
static void patch_sp() {
utils::hook(0x62DDD0, load_menus_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(0x62DDD0 + 5, 3);
utils::hook(0x621194, load_menu_stub, HOOK_CALL).install()->quick();
// Remove the menus from memory (CG_Shutdown)
utils::hook(0x447905, snd_fade_all_sounds_stub, HOOK_CALL)
.install() // hook*
->quick();
// Remove the menus from memory (UI_Shutdown)
utils::hook(0x4F4C4F, ui_shutdown_stub, HOOK_CALL)
.install() // hook*
->quick();
}
};
} // namespace ui
REGISTER_COMPONENT(ui::component)

View File

@ -25,32 +25,32 @@ void set_aspect_ratio() {
__declspec(naked) void set_aspect_ratio_stub() { __declspec(naked) void set_aspect_ratio_stub() {
__asm { __asm {
mov eax, [eax + 0x10]; mov eax, [eax + 0x10];
cmp eax, 4; cmp eax, 4;
mov dword ptr ds:0x1C91A68, edx; mov dword ptr ds:0x1C91A68, edx;
mov dword ptr ds:0x1C91A6C, esi; mov dword ptr ds:0x1C91A6C, esi;
mov dword ptr ds:0x1C91A74, ecx; mov dword ptr ds:0x1C91A74, ecx;
ja default_case; ja default_case;
je custom_ratio; je custom_ratio;
push 0x50AE6C; push 0x50AE6C;
ret; ret;
default_case: default_case:
push 0x50AF6C; push 0x50AF6C;
ret; ret;
custom_ratio: custom_ratio:
pushad; pushad;
call set_aspect_ratio; call set_aspect_ratio;
popad; popad;
mov eax, 1; // set widescreen to 1 mov eax, 1; // set widescreen to 1
push 0x50AF05; push 0x50AF05;
ret; ret;
} }
} }

View File

@ -9,6 +9,9 @@ const game::dvar_t* pm_rocketJump = nullptr;
const game::dvar_t* pm_rocketJumpScale = nullptr; const game::dvar_t* pm_rocketJumpScale = nullptr;
const game::dvar_t* pm_playerCollision = nullptr; const game::dvar_t* pm_playerCollision = nullptr;
const game::dvar_t* pm_elevators = nullptr; const game::dvar_t* pm_elevators = nullptr;
const game::dvar_t* pm_disableLandingSlowdown = nullptr;
const game::dvar_t* pm_bunnyHop = nullptr;
const game::dvar_t* pm_snapVector = nullptr;
const game::dvar_t* cg_drawVersion = nullptr; const game::dvar_t* cg_drawVersion = nullptr;
const game::dvar_t* cg_drawVersionX = nullptr; const game::dvar_t* cg_drawVersionX = nullptr;
@ -27,4 +30,9 @@ const game::dvar_t** sv_mapname =
const game::dvar_t** version = const game::dvar_t** version =
reinterpret_cast<const game::dvar_t**>(0x145D690); reinterpret_cast<const game::dvar_t**>(0x145D690);
const game::dvar_t** com_developer =
reinterpret_cast<const game::dvar_t**>(0x145D648);
const game::dvar_t** com_developer_script =
reinterpret_cast<const game::dvar_t**>(0x145EC58);
} // namespace dvars } // namespace dvars

View File

@ -9,6 +9,9 @@ extern const game::dvar_t* pm_rocketJump;
extern const game::dvar_t* pm_rocketJumpScale; extern const game::dvar_t* pm_rocketJumpScale;
extern const game::dvar_t* pm_playerCollision; extern const game::dvar_t* pm_playerCollision;
extern const game::dvar_t* pm_elevators; extern const game::dvar_t* pm_elevators;
extern const game::dvar_t* pm_disableLandingSlowdown;
extern const game::dvar_t* pm_bunnyHop;
extern const game::dvar_t* pm_snapVector;
extern const game::dvar_t* cg_drawVersion; extern const game::dvar_t* cg_drawVersion;
extern const game::dvar_t* cg_drawVersionX; extern const game::dvar_t* cg_drawVersionX;
@ -24,4 +27,7 @@ extern const game::dvar_t** g_specialops;
extern const game::dvar_t** sv_mapname; extern const game::dvar_t** sv_mapname;
extern const game::dvar_t** version; extern const game::dvar_t** version;
extern const game::dvar_t** com_developer;
extern const game::dvar_t** com_developer_script;
} // namespace dvars } // namespace dvars

View File

@ -5,28 +5,29 @@
namespace game::engine { namespace game::engine {
fast_critical_section_scope_read::fast_critical_section_scope_read( fast_critical_section_scope_read::fast_critical_section_scope_read(
FastCriticalSection* cs) FastCriticalSection* cs) noexcept
: cs_(cs) { : cs_(cs) {
if (this->cs_) { if (this->cs_) {
Sys_LockRead(this->cs_); Sys_LockRead(this->cs_);
} }
} }
fast_critical_section_scope_read::~fast_critical_section_scope_read() { fast_critical_section_scope_read::~fast_critical_section_scope_read() noexcept {
if (this->cs_) { if (this->cs_) {
Sys_UnlockRead(this->cs_); Sys_UnlockRead(this->cs_);
} }
} }
fast_critical_section_scope_write::fast_critical_section_scope_write( fast_critical_section_scope_write::fast_critical_section_scope_write(
FastCriticalSection* cs) FastCriticalSection* cs) noexcept
: cs_(cs) { : cs_(cs) {
if (this->cs_) { if (this->cs_) {
Sys_LockWrite(this->cs_); Sys_LockWrite(this->cs_);
} }
} }
fast_critical_section_scope_write::~fast_critical_section_scope_write() { fast_critical_section_scope_write::
~fast_critical_section_scope_write() noexcept {
if (this->cs_) { if (this->cs_) {
Sys_UnlockWrite(this->cs_); Sys_UnlockWrite(this->cs_);
} }

View File

@ -3,8 +3,8 @@
namespace game::engine { namespace game::engine {
class fast_critical_section_scope_read { class fast_critical_section_scope_read {
public: public:
fast_critical_section_scope_read(FastCriticalSection* cs); fast_critical_section_scope_read(FastCriticalSection* cs) noexcept;
~fast_critical_section_scope_read(); ~fast_critical_section_scope_read() noexcept;
private: private:
FastCriticalSection* cs_; FastCriticalSection* cs_;
@ -12,8 +12,8 @@ private:
class fast_critical_section_scope_write { class fast_critical_section_scope_write {
public: public:
fast_critical_section_scope_write(FastCriticalSection* cs); fast_critical_section_scope_write(FastCriticalSection* cs) noexcept;
~fast_critical_section_scope_write(); ~fast_critical_section_scope_write() noexcept;
private: private:
FastCriticalSection* cs_; FastCriticalSection* cs_;

View File

@ -4,7 +4,7 @@
namespace game::engine { namespace game::engine {
scoped_critical_section::scoped_critical_section( scoped_critical_section::scoped_critical_section(
const CriticalSection s, const ScopedCriticalSectionType type) const CriticalSection s, const ScopedCriticalSectionType type) noexcept
: s_(s), is_scoped_release_(false) { : s_(s), is_scoped_release_(false) {
if (type == SCOPED_CRITSECT_NORMAL) { if (type == SCOPED_CRITSECT_NORMAL) {
Sys_EnterCriticalSection(this->s_); Sys_EnterCriticalSection(this->s_);
@ -17,35 +17,37 @@ scoped_critical_section::scoped_critical_section(
Sys_LeaveCriticalSection(this->s_); Sys_LeaveCriticalSection(this->s_);
this->is_scoped_release_ = true; this->is_scoped_release_ = true;
} }
this->has_ownership_ = false; this->has_ownership_ = false;
} }
} }
} }
scoped_critical_section::~scoped_critical_section() { scoped_critical_section::~scoped_critical_section() noexcept {
if (!this->has_ownership_ || this->is_scoped_release_) { if (!this->has_ownership_ || this->is_scoped_release_) {
if (!this->has_ownership_ && this->is_scoped_release_) if (!this->has_ownership_ && this->is_scoped_release_) {
Sys_EnterCriticalSection(this->s_); Sys_EnterCriticalSection(this->s_);
}
} else { } else {
Sys_LeaveCriticalSection(this->s_); Sys_LeaveCriticalSection(this->s_);
} }
} }
void scoped_critical_section::enter_crit_sect() { void scoped_critical_section::enter_crit_sect() noexcept {
assert(!this->has_ownership_); assert(!this->has_ownership_);
this->has_ownership_ = true; this->has_ownership_ = true;
Sys_EnterCriticalSection(this->s_); Sys_EnterCriticalSection(this->s_);
} }
void scoped_critical_section::leave_crit_sect() { void scoped_critical_section::leave_crit_sect() noexcept {
assert(this->has_ownership_); assert(this->has_ownership_);
this->has_ownership_ = false; this->has_ownership_ = false;
Sys_LeaveCriticalSection(this->s_); Sys_LeaveCriticalSection(this->s_);
} }
bool scoped_critical_section::try_enter_crit_sect() { bool scoped_critical_section::try_enter_crit_sect() noexcept {
assert(!this->has_ownership_); assert(!this->has_ownership_);
const auto result = Sys_TryEnterCriticalSection(this->s_); const auto result = Sys_TryEnterCriticalSection(this->s_);
@ -53,11 +55,11 @@ bool scoped_critical_section::try_enter_crit_sect() {
return result; return result;
} }
bool scoped_critical_section::has_ownership() const { bool scoped_critical_section::has_ownership() const noexcept {
return this->has_ownership_; return this->has_ownership_;
} }
bool scoped_critical_section::is_scoped_release() const { bool scoped_critical_section::is_scoped_release() const noexcept {
return this->is_scoped_release_; return this->is_scoped_release_;
} }
} // namespace game::engine } // namespace game::engine

View File

@ -3,15 +3,16 @@
namespace game::engine { namespace game::engine {
class scoped_critical_section { class scoped_critical_section {
public: public:
scoped_critical_section(CriticalSection s, ScopedCriticalSectionType type); scoped_critical_section(CriticalSection s,
~scoped_critical_section(); ScopedCriticalSectionType type) noexcept;
~scoped_critical_section() noexcept;
void enter_crit_sect(); void enter_crit_sect() noexcept;
void leave_crit_sect(); void leave_crit_sect() noexcept;
bool try_enter_crit_sect(); bool try_enter_crit_sect() noexcept;
[[nodiscard]] bool has_ownership() const; [[nodiscard]] bool has_ownership() const noexcept;
[[nodiscard]] bool is_scoped_release() const; [[nodiscard]] bool is_scoped_release() const noexcept;
private: private:
CriticalSection s_; CriticalSection s_;

View File

@ -81,6 +81,12 @@ void Sys_UnlockWrite(FastCriticalSection* critSect) {
Sys_TempPriorityEnd(&critSect->tempPriority); Sys_TempPriorityEnd(&critSect->tempPriority);
} }
void Sys_SnapVector(float* v) {
v[0] = std::floorf(v[0] + 0.5f);
v[1] = std::floorf(v[1] + 0.5f);
v[2] = std::floorf(v[2] + 0.5f);
}
int PC_Int_Parse(int handle, int* i) { int PC_Int_Parse(int handle, int* i) {
const static DWORD PC_Int_Parse_t = 0x62DF10; const static DWORD PC_Int_Parse_t = 0x62DF10;
int result{}; int result{};
@ -116,4 +122,18 @@ int PC_Float_Parse(int handle, float* f) {
return result; return result;
} }
void Menu_FreeItemMemory(itemDef_s* item) {
const static DWORD Menu_FreeItemMemory_t = 0x62B7E0;
__asm {
pushad;
mov edi, item;
call Menu_FreeItemMemory_t;
popad;
}
}
} // namespace game } // namespace game

View File

@ -39,16 +39,25 @@ bool Sys_TryEnterCriticalSection(CriticalSection critSect);
void Sys_LockRead(FastCriticalSection* critSect); void Sys_LockRead(FastCriticalSection* critSect);
void Sys_UnlockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect);
void Sys_UnlockWrite(FastCriticalSection* critSect); void Sys_UnlockWrite(FastCriticalSection* critSect);
void Sys_SnapVector(float* v);
int PC_Int_Parse(int handle, int* i); int PC_Int_Parse(int handle, int* i);
int PC_Float_Parse(int handle, float* f); int PC_Float_Parse(int handle, float* f);
void Menu_FreeItemMemory(itemDef_s* item);
// Global definitions // Global definitions
constexpr auto CMD_MAX_NESTING = 8; constexpr auto CMD_MAX_NESTING = 8;
constexpr auto MAX_POSSIBLE_LOCAL_CLIENTS = 1;
constexpr std::size_t MAX_LOCAL_CLIENTS = 1; constexpr std::size_t MAX_LOCAL_CLIENTS = 1;
constexpr auto MAX_QPATH = 64; constexpr auto MAX_QPATH = 64;
constexpr auto MAX_OPCODE_LOOKUP_SIZE = 0x1000000;
constexpr auto MAX_SOURCEPOS_LOOKUP_SIZE = 0x800000;
constexpr auto MAX_SOURCEBUF_LOOKUP_SIZE = 0x40000;
} // namespace game } // namespace game
#include "symbols.hpp" #include "symbols.hpp"

View File

@ -14,22 +14,342 @@ struct scr_entref_t {
unsigned __int16 classnum; unsigned __int16 classnum;
}; };
typedef void(__cdecl* BuiltinMethod)(scr_entref_t); typedef void (*BuiltinMethod)(scr_entref_t);
typedef void(__cdecl* BuiltinFunction)(); typedef void (*BuiltinFunction)();
struct BuiltinMethodDef { struct BuiltinMethodDef {
const char* actionString; const char* actionString;
void(__cdecl* actionFunc)(scr_entref_t); void (*actionFunc)(scr_entref_t);
int type; int type;
}; };
struct BuiltinFunctionDef { struct BuiltinFunctionDef {
const char* actionString; const char* actionString;
void(__cdecl* actionFunc)(); void (*actionFunc)();
int type; int type;
}; };
struct OpcodeLookup {
const char* codePos;
unsigned int sourcePosIndex;
unsigned __int16 sourcePosCount;
unsigned __int16 cumulOffset;
unsigned __int16* localVars;
int profileTime;
int profileBuiltInTime;
int profileUsage;
};
static_assert(sizeof(OpcodeLookup) == 0x1C);
struct SourceLookup {
unsigned int sourcePos;
int type;
};
struct SaveSourceBufferInfo {
char* buf;
char* sourceBuf;
int len;
};
struct scrParserGlob_t {
OpcodeLookup* opcodeLookup;
unsigned int opcodeLookupMaxSize;
unsigned int opcodeLookupLen;
SourceLookup* sourcePosLookup;
unsigned int sourcePosLookupMaxSize;
unsigned int sourcePosLookupLen;
unsigned int sourceBufferLookupMaxSize;
const char* currentCodePos;
unsigned int currentSourcePosCount;
SaveSourceBufferInfo* saveSourceBufferLookup;
unsigned int saveSourceBufferLookupLen;
int delayedSourceIndex;
int threadStartSourceIndex;
};
static_assert(sizeof(scrParserGlob_t) == 0x34);
struct SourceBufferInfo {
const char* codePos;
char* buf;
const char* sourceBuf;
int len;
int sortedIndex;
bool archive;
int time;
int avgTime;
int maxTime;
float totalTime;
float totalBuiltIn;
};
struct CodeOffsetMap {
int type;
unsigned int cumulOffset;
int codeOffset;
int sourcePos;
int newCodeOffest;
};
struct scrParserPub_t {
SourceBufferInfo* sourceBufferLookup;
unsigned int sourceBufferLookupLen;
const char* scriptfilename;
const char* sourceBuf;
CodeOffsetMap* codeOffsetMap;
unsigned int codeOffsetMapLen;
int useCodeOffsetMap;
};
static_assert(sizeof(scrParserPub_t) == 0x1C);
struct VariableStackBuffer {
const char* pos;
unsigned __int16 size;
unsigned __int16 bufLen;
unsigned __int16 localId;
unsigned __int8 time;
char buf[1];
};
union VariableUnion {
int intValue;
float floatValue;
unsigned int stringValue;
const float* vectorValue;
const char* codePosValue;
unsigned int pointerValue;
VariableStackBuffer* stackValue;
unsigned int entityOffset;
};
struct VariableValue {
VariableUnion u;
int type;
};
struct function_stack_t {
const char* pos;
unsigned int localId;
unsigned int localVarCount;
VariableValue* top;
VariableValue* startTop;
};
struct function_frame_t {
function_stack_t fs;
int topType;
};
struct scrVmPub_t {
unsigned int* localVars;
VariableValue* maxstack;
int function_count;
function_frame_t* function_frame;
VariableValue* top;
bool debugCode;
bool abort_on_error;
bool terminal_error;
unsigned int inparamcount;
unsigned int outparamcount;
function_frame_t function_frame_start[32];
VariableValue stack[2048];
};
struct HunkUser {
HunkUser* current;
HunkUser* next;
int maxSize;
int end;
int pos;
const char* name;
bool fixed;
int type;
unsigned __int8 buf[1];
};
static_assert(sizeof(HunkUser) == 0x24);
struct scrVarPub_t {
const char* fieldBuffer;
unsigned __int16 canonicalStrCount;
bool developer_script;
bool evaluate;
const char* error_message;
int error_index;
unsigned int time;
unsigned int timeArrayId;
unsigned int pauseArrayId;
unsigned int notifyArrayId;
unsigned int objectStackId;
unsigned int levelId;
unsigned int gameId;
unsigned int animId;
unsigned int freeEntList;
unsigned int tempVariable;
unsigned int numScriptValues[2];
bool bInited;
unsigned __int16 savecount;
unsigned __int16 savecountMark;
unsigned int checksum;
unsigned int entId;
unsigned int entFieldName;
HunkUser* programHunkUser;
const char* programBuffer;
const char* endScriptBuffer;
unsigned __int16 saveIdMap[36864];
unsigned __int16 saveIdMapRev[36864];
bool bScriptProfile;
float scriptProfileMinTime;
bool bScriptProfileBuiltin;
float scriptProfileBuiltinMinTime;
unsigned int numScriptThreads;
unsigned int numScriptObjects;
const char* varUsagePos;
int ext_threadcount;
int totalObjectRefCount;
volatile int totalVectorRefCount;
unsigned int removeId;
};
static_assert(sizeof(scrVarPub_t) == 0x2408C);
struct scr_localVar_t {
unsigned int name;
unsigned int sourcePos;
};
struct scr_block_t {
int abortLevel;
int localVarsCreateCount;
int localVarsPublicCount;
int localVarsCount;
unsigned __int8 localVarsInitBits[8];
scr_localVar_t localVars[64];
};
struct scrCompilePub_t {
int value_count;
int far_function_count;
unsigned int loadedscripts;
unsigned int scriptsPos;
unsigned int scriptsCount;
unsigned int scriptsDefine;
unsigned int builtinFunc;
unsigned int builtinMeth;
unsigned __int16 canonicalStrings[65536];
const char* in_ptr;
bool in_ptr_valid;
const char* parseBuf;
bool script_loading;
bool allowedBreakpoint;
int developer_statement;
char* opcodePos;
unsigned int programLen;
int func_table_size;
int func_table[1024];
scr_block_t* pauseBlock;
};
struct CaseStatementInfo {
unsigned int name;
const char* codePos;
unsigned int sourcePos;
CaseStatementInfo* next;
};
struct BreakStatementInfo {
const char* codePos;
const char* nextCodePos;
BreakStatementInfo* next;
};
struct ContinueStatementInfo {
const char* codePos;
const char* nextCodePos;
ContinueStatementInfo* next;
};
struct PrecacheEntry {
unsigned __int16 filename;
bool include;
unsigned int sourcePos;
};
union sval_u {
int type;
unsigned int stringValue;
unsigned int idValue;
float floatValue;
int intValue;
sval_u* node;
unsigned int sourcePosValue;
const char* codePosValue;
const char* debugString;
scr_block_t* block;
};
struct VariableCompileValue {
VariableValue value;
sval_u sourcePos;
};
struct scrCompileGlob_t {
unsigned char* codePos;
unsigned char* prevOpcodePos;
unsigned int filePosId;
unsigned int fileCountId;
int cumulOffset;
int prevCumulOffset;
int maxOffset;
int maxCallOffset;
bool bConstRefCount;
bool in_developer_thread;
unsigned int developer_thread_sourcePos;
bool firstThread[2];
CaseStatementInfo* currentCaseStatement;
bool bCanBreak;
BreakStatementInfo* currentBreakStatement;
bool bCanContinue;
ContinueStatementInfo* currentContinueStatement;
scr_block_t** breakChildBlocks;
int* breakChildCount;
scr_block_t* breakBlock;
scr_block_t** continueChildBlocks;
int* continueChildCount;
bool forceNotCreate;
PrecacheEntry* precachescriptList;
VariableCompileValue value_start[32];
};
struct scr_animtree_t {
void* anims;
};
struct scrAnimPub_t {
unsigned int animtrees;
unsigned int animtree_node;
unsigned int animTreeNames;
scr_animtree_t xanim_lookup[2][128];
unsigned int xanim_num[2];
unsigned int animTreeIndex;
bool animtree_loading;
};
enum {
SOURCE_TYPE_BREAKPOINT = 0x1,
SOURCE_TYPE_CALL = 0x2,
SOURCE_TYPE_CALL_POINTER = 0x4,
SOURCE_TYPE_THREAD_START = 0x8,
SOURCE_TYPE_BUILTIN_CALL = 0x10,
SOURCE_TYPE_NOTIFY = 0x20,
SOURCE_TYPE_GETFUNCTION = 0x40,
SOURCE_TYPE_WAIT = 0x80,
};
enum { enum {
VAR_STRING = 0x2, VAR_STRING = 0x2,
VAR_FLOAT = 0x5, VAR_FLOAT = 0x5,
@ -490,12 +810,455 @@ struct GameWorldSp {
static_assert(sizeof(GameWorldSp) == 0x38); static_assert(sizeof(GameWorldSp) == 0x38);
struct StringTableCell {
const char* string;
int hash;
};
struct StringTable {
const char* name;
int columnCount;
int rowCount;
StringTableCell* values;
};
#define MAX_UISTRINGS 0x800
struct rectDef_s {
float x;
float y;
float w;
float h;
char horzAlign;
char vertAlign;
};
struct windowDef_t {
const char* name;
rectDef_s rect;
rectDef_s rectClient;
const char* group;
int style;
int border;
int ownerDraw;
int ownerDrawFlags;
float borderSize;
int staticFlags;
int dynamicFlags[1];
int nextTime;
float foreColor[4];
float backColor[4];
float borderColor[4];
float outlineColor[4];
float disableColor[4];
void* background;
};
static_assert(sizeof(windowDef_t) == 0xA4);
enum expDataType {
VAL_INT = 0x0,
VAL_FLOAT = 0x1,
VAL_STRING = 0x2,
NUM_INTERNAL_DATATYPES = 0x3,
VAL_FUNCTION = 0x3,
NUM_DATATYPES = 0x4,
};
struct Operand {
expDataType dataType;
char internals[4];
};
union entryInternalData {
int op;
Operand operand;
};
struct expressionEntry {
int type;
entryInternalData data;
};
struct menuTransition {
int transitionType;
int targetField;
int startTime;
float startVal;
float endVal;
float time;
int endTriggerType;
};
struct StaticDvar {
dvar_t* dvar;
char* dvarName;
};
struct StaticDvarList {
int numStaticDvars;
StaticDvar** staticDvars;
};
struct StringList {
int totalStrings;
const char** strings;
};
struct ExpressionSupportingData; // required against my will
struct Statement_s {
int numEntries;
expressionEntry* entries;
ExpressionSupportingData* supportingData;
int lastExecuteTime;
Operand lastResult;
};
struct UIFunctionList {
int totalFunctions;
Statement_s** functions;
};
struct ExpressionSupportingData {
UIFunctionList uifunctions;
StaticDvarList staticDvarList;
StringList uiStrings;
};
static_assert(sizeof(Statement_s) == 0x18);
struct ItemFloatExpression {
int target;
Statement_s* expression;
};
struct MenuEventHandlerSet; // required against my will
struct ConditionalScript {
MenuEventHandlerSet* eventHandlerSet;
Statement_s* eventExpression;
};
static_assert(sizeof(ConditionalScript) == 0x8);
struct SetLocalVarData {
const char* localVarName;
Statement_s* expression;
};
union EventData {
const char* unconditionalScript;
ConditionalScript* conditionalScript;
MenuEventHandlerSet* elseScript;
SetLocalVarData* setLocalVarData;
};
#define MAX_EVENT_HANDLERS_PER_EVENT 200
enum EventType {
EVENT_UNCONDITIONAL = 0x0,
EVENT_IF = 0x1,
EVENT_ELSE = 0x2,
EVENT_SET_LOCAL_VAR_BOOL = 0x3,
EVENT_SET_LOCAL_VAR_INT = 0x4,
EVENT_SET_LOCAL_VAR_FLOAT = 0x5,
EVENT_SET_LOCAL_VAR_STRING = 0x6,
EVENT_COUNT = 0x7,
};
struct MenuEventHandler {
EventData eventData;
char eventType;
};
static_assert(sizeof(MenuEventHandler) == 0x8);
struct MenuEventHandlerSet {
int eventHandlerCount;
MenuEventHandler** eventHandlers;
};
struct ItemKeyHandler {
int key;
MenuEventHandlerSet* action;
ItemKeyHandler* next;
};
struct columnInfo_s {
int pos;
int width;
int maxChars;
int alignment;
};
struct listBoxDef_s {
int mousePos;
int startPos[1];
int endPos[1];
int drawPadding;
float elementWidth;
float elementHeight;
int elementStyle;
int numColumns;
columnInfo_s columnInfo[16];
MenuEventHandlerSet* onDoubleClick;
int notselectable;
int noScrollBars;
int usePaging;
float selectBorder[4];
void* selectIcon;
};
static_assert(sizeof(listBoxDef_s) == 0x144);
struct editFieldDef_s {
float minVal;
float maxVal;
float defVal;
float range;
int maxChars;
int maxCharsGotoNext;
int maxPaintChars;
int paintOffset;
};
static_assert(sizeof(editFieldDef_s) == 0x20);
#define MAX_MULTI_DVARS 32
struct multiDef_s {
const char* dvarList[MAX_MULTI_DVARS];
const char* dvarStr[MAX_MULTI_DVARS];
float dvarValue[MAX_MULTI_DVARS];
int count;
int strDef;
};
static_assert(sizeof(multiDef_s) == 0x188);
struct newsTickerDef_s {
int feedId;
int speed;
int spacing;
int lastTime;
int start;
int end;
float x;
};
static_assert(sizeof(newsTickerDef_s) == 0x1C);
struct textScrollDef_s {
int startTime;
};
union itemDefData_t {
listBoxDef_s* listBox;
editFieldDef_s* editField;
multiDef_s* multi;
const char* enumDvarName;
newsTickerDef_s* ticker;
textScrollDef_s* scroll;
void* data;
};
struct menuDef_t; // required against my will
struct itemDef_s {
windowDef_t window;
rectDef_s textRect[1];
int type;
int dataType;
int alignment;
int fontEnum;
int textAlignMode;
float textalignx;
float textaligny;
float textscale;
int textStyle;
int gameMsgWindowIndex;
int gameMsgWindowMode;
const char* text;
int itemFlags;
menuDef_t* parent;
MenuEventHandlerSet* mouseEnterText;
MenuEventHandlerSet* mouseExitText;
MenuEventHandlerSet* mouseEnter;
MenuEventHandlerSet* mouseExit;
MenuEventHandlerSet* action;
MenuEventHandlerSet* accept;
MenuEventHandlerSet* onFocus;
MenuEventHandlerSet* leaveFocus;
const char* dvar;
const char* dvarTest;
ItemKeyHandler* onKey;
const char* enableDvar;
const char* localVar;
int dvarFlags;
void* focusSound;
float special;
int cursorPos[1];
itemDefData_t typeData;
int imageTrack;
int floatExpressionCount;
ItemFloatExpression* floatExpressions;
Statement_s* visibleExp;
Statement_s* disabledExp;
Statement_s* textExp;
Statement_s* materialExp;
float glowColor[4];
bool decayActive;
int fxBirthTime;
int fxLetterTime;
int fxDecayStartTime;
int fxDecayDuration;
int lastSoundPlayedTime;
};
static_assert(sizeof(itemDef_s) == 0x17C);
struct menuDef_t {
windowDef_t window;
const char* font;
int fullScreen;
int itemCount;
int fontIndex;
int cursorItem[1];
int fadeCycle;
float fadeClamp;
float fadeAmount;
float fadeInAmount;
float blurRadius;
MenuEventHandlerSet* onOpen;
MenuEventHandlerSet* onCloseRequest;
MenuEventHandlerSet* onClose;
MenuEventHandlerSet* onESC;
ItemKeyHandler* onKey;
Statement_s* visibleExp;
const char* allowedBinding;
const char* soundName;
int imageTrack;
float focusColor[4];
Statement_s* rectXExp;
Statement_s* rectYExp;
Statement_s* rectWExp;
Statement_s* rectHExp;
Statement_s* openSoundExp;
Statement_s* closeSoundExp;
itemDef_s** items;
menuTransition scaleTransition[1];
menuTransition alphaTransition[1];
menuTransition xTransition[1];
menuTransition yTransition[1];
ExpressionSupportingData* expressionData;
};
static_assert(sizeof(menuDef_t) == 0x190);
struct loadAssets_t {
float fadeClamp;
int fadeCycle;
float fadeAmount;
float fadeInAmount;
};
enum UILocalVarType {
UILOCALVAR_INT = 0x0,
UILOCALVAR_FLOAT = 0x1,
UILOCALVAR_STRING = 0x2,
};
struct UILocalVar {
UILocalVarType type;
const char* name;
union {
int integer;
float value;
const char* string;
} u;
};
struct UILocalVarContext {
UILocalVar table[256];
};
struct UiContext {
int localClientNum;
float bias;
int realTime;
int frameTime;
struct {
float x;
float y;
int lastMoveTime;
} cursor;
int isCursorVisible;
int paintFull;
int screenWidth;
int screenHeight;
float screenAspect;
float FPS;
float blurRadiusOut;
menuDef_t* Menus[640];
int menuCount;
menuDef_t* menuStack[16];
int openMenuCount;
UILocalVarContext localVars;
const StringTable* cinematicSubtitles;
};
struct MenuList { struct MenuList {
const char* name; const char* name;
int menuCount; int menuCount;
void** menus; menuDef_t** menus;
}; };
struct savegameStatus_s {
int sortKey;
int sortDir;
int displaySavegames[256];
};
struct qtime_s {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
struct SavegameInfo {
const char* savegameFile;
const char* savegameName;
const char* imageName;
const char* mapName;
const char* savegameInfoText;
const char* time;
const char* date;
qtime_s tm;
};
struct uiInfo_s {
UiContext uiDC;
SavegameInfo savegameList[512];
int savegameCount;
savegameStatus_s savegameStatus;
int timeIndex;
int previousTimes[4];
bool allowScriptMenuResponse;
char savegameName[64];
char savegameInfo[256];
void* sshotImage;
char sshotImageName[64];
};
static_assert(sizeof(uiInfo_s) == 0x9C2C);
struct LocalizeEntry { struct LocalizeEntry {
const char* value; const char* value;
const char* name; const char* name;
@ -510,6 +1273,7 @@ union XAssetHeader {
LocalizeEntry* localize; LocalizeEntry* localize;
WeaponCompleteDef* weapon; WeaponCompleteDef* weapon;
RawFile* rawfile; RawFile* rawfile;
StringTable* stringTable;
void* data; void* data;
}; };

View File

@ -8,11 +8,16 @@ WEAK symbol<void(int channel, const char* fmt, ...)> Com_Printf{0x41BD20};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintWarning{0x406320}; WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintWarning{0x406320};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintError{0x4C6980}; WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintError{0x4C6980};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_DPrintf{0x42B1F0}; WEAK symbol<void(int channel, const char* fmt, ...)> Com_DPrintf{0x42B1F0};
WEAK symbol<void(int channel, const char* msg, int error)> Com_PrintMessage{
0x456B70};
WEAK symbol<void(errorParm_t code, const char* fmt, ...)> Com_Error{0x43DD90}; WEAK symbol<void(errorParm_t code, const char* fmt, ...)> Com_Error{0x43DD90};
WEAK symbol<void()> Com_OpenLogFile{0x603030}; WEAK symbol<void()> Com_OpenLogFile{0x603030};
WEAK symbol<int(char* data_p)> Com_Compress{0x4316A0}; WEAK symbol<int(char* data_p)> Com_Compress{0x4316A0};
WEAK symbol<void()> Com_EventLoop{0x4987C0}; WEAK symbol<void()> Com_EventLoop{0x4987C0};
WEAK symbol<void()> Com_ServerPacketEvent{0x47FD30}; WEAK symbol<void()> Com_ServerPacketEvent{0x47FD30};
WEAK symbol<void(const char* filename)> Com_BeginParseSession{0x4A5C90};
WEAK symbol<void()> Com_EndParseSession{0x4D12C0};
WEAK symbol<const char*(const char** data_p)> Com_Parse{0x486600};
WEAK symbol<const char*(const char* fmt, ...)> va{0x4869F0}; WEAK symbol<const char*(const char* fmt, ...)> va{0x4869F0};
@ -31,6 +36,7 @@ WEAK symbol<bool()> Sys_IsServerThread{0x4590E0};
WEAK symbol<bool()> Sys_IsDatabaseThread{0x4C9380}; WEAK symbol<bool()> Sys_IsDatabaseThread{0x4C9380};
WEAK symbol<void(int valueIndex, void* data)> Sys_SetValue{0x483310}; WEAK symbol<void(int valueIndex, void* data)> Sys_SetValue{0x483310};
WEAK symbol<void(int msec)> Sys_Sleep{0x4CFBE0}; WEAK symbol<void(int msec)> Sys_Sleep{0x4CFBE0};
WEAK symbol<void(const char* error, ...)> Sys_Error{0x40BFF0};
WEAK symbol<short(short l)> BigShort{0x40E7E0}; WEAK symbol<short(short l)> BigShort{0x40E7E0};
WEAK symbol<short(short l)> ShortNoSwap{0x4261A0}; WEAK symbol<short(short l)> ShortNoSwap{0x4261A0};
@ -94,7 +100,23 @@ WEAK symbol<void(const char* error)> Scr_Error{0x4E9C50};
WEAK symbol<void(const char* error)> Scr_ObjectError{0x470600}; WEAK symbol<void(const char* error)> Scr_ObjectError{0x470600};
WEAK symbol<void(unsigned int paramIndex, const char* error)> Scr_ParamError{ WEAK symbol<void(unsigned int paramIndex, const char* error)> Scr_ParamError{
0x42C880}; 0x42C880};
WEAK symbol<void()> Scr_ShutdownAllocNode{0x486BC0};
WEAK symbol<unsigned int(const char* filename)> Scr_CreateCanonicalFilename{
0x43A5E0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
FindVariable{0x4B78B0};
WEAK symbol<void(unsigned int parentId, unsigned int unsignedValue)>
RemoveVariable{0x4C2DD0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> FindObject{
0x49A980};
WEAK symbol<gentity_s*(scr_entref_t entref)> GetEntity{0x4678C0}; WEAK symbol<gentity_s*(scr_entref_t entref)> GetEntity{0x4678C0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
GetVariable{0x482290};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
GetNewVariable{0x4F1990};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> GetObject{
0x4370B0};
WEAK symbol<unsigned int()> Scr_GetNumParam{0x4443F0}; WEAK symbol<unsigned int()> Scr_GetNumParam{0x4443F0};
WEAK symbol<void()> Scr_ClearOutParams{0x4A3A00}; WEAK symbol<void()> Scr_ClearOutParams{0x4A3A00};
@ -110,6 +132,7 @@ WEAK symbol<void(float value)> Scr_AddFloat{0x4986E0};
WEAK symbol<int(unsigned int index)> Scr_GetType{0x464EE0}; WEAK symbol<int(unsigned int index)> Scr_GetType{0x464EE0};
WEAK symbol<void(int func, const char* name)> Scr_RegisterFunction{0x4F59C0}; WEAK symbol<void(int func, const char* name)> Scr_RegisterFunction{0x4F59C0};
WEAK symbol<unsigned int(unsigned int index)> Scr_GetFunc{0x438E10}; WEAK symbol<unsigned int(unsigned int index)> Scr_GetFunc{0x438E10};
WEAK symbol<int(const char* pos)> Scr_IsInOpcodeMemory{0x47D1D0};
WEAK symbol<char*(const char* filename, const char* extFilename, WEAK symbol<char*(const char* filename, const char* extFilename,
const char* codePos, bool archive)> const char* codePos, bool archive)>
@ -120,17 +143,35 @@ WEAK symbol<int(const char* filename, const char* name)> Scr_GetFunctionHandle{
WEAK symbol<int(int handle, unsigned int paramcount)> Scr_ExecThread{0x41A2C0}; WEAK symbol<int(int handle, unsigned int paramcount)> Scr_ExecThread{0x41A2C0};
WEAK symbol<void(unsigned __int16 handle)> Scr_FreeThread{0x4C44A0}; WEAK symbol<void(unsigned __int16 handle)> Scr_FreeThread{0x4C44A0};
WEAK symbol<void(sval_u* parseData, unsigned char user)> ScriptParse{0x4956B0};
WEAK symbol<void(sval_u* val, unsigned int filePosId, unsigned int fileCountId,
unsigned int scriptId, PrecacheEntry* entries,
int entriesCount)>
ScriptCompile{0x4FFDA0};
WEAK symbol<char*(int len)> TempMalloc{0x4EA7C0};
// SL // SL
WEAK symbol<const char*(unsigned int stringValue)> SL_ConvertToString{0x40E990}; WEAK symbol<const char*(unsigned int stringValue)> SL_ConvertToString{0x40E990};
WEAK symbol<void(unsigned int stringValue)> SL_AddRefToString{0x4C4BD0}; WEAK symbol<void(unsigned int stringValue)> SL_AddRefToString{0x4C4BD0};
WEAK symbol<void(unsigned int stringValue)> SL_RemoveRefToString{0x4698E0}; WEAK symbol<void(unsigned int stringValue)> SL_RemoveRefToString{0x4698E0};
// NET
WEAK symbol<const char*(netadr_t a)> NET_AdrToString{0x4BF490}; WEAK symbol<const char*(netadr_t a)> NET_AdrToString{0x4BF490};
WEAK symbol<const char*()> NET_ErrorString{0x430390}; WEAK symbol<const char*()> NET_ErrorString{0x430390};
// Memory // Memory
WEAK symbol<void*(int size)> Hunk_AllocateTempMemory{0x492DF0}; WEAK symbol<void*(int size)> Hunk_AllocateTempMemory{0x492DF0};
WEAK symbol<void*(int size, int alignment)> Hunk_AllocAlignInternal{0x486C40}; WEAK symbol<void*(int size, int alignment)> Hunk_AllocAlignInternal{0x486C40};
WEAK symbol<void*(int size)> Hunk_AllocateTempMemoryHigh{0x403B40};
WEAK symbol<HunkUser*(int maxSize, const char* name, bool fixed, int type)>
Hunk_UserCreate{0x4F1A10};
WEAK symbol<void*(HunkUser* user, int size, int alignment)> Hunk_UserAlloc{
0x469410};
WEAK symbol<void(Statement_s* statement)> free_expression{0x436260};
WEAK symbol<void(void*)> _free{0x674BC5};
// Zone // Zone
WEAK symbol<void*(int size)> Z_VirtualAllocInternal{0x4D9CF0}; WEAK symbol<void*(int size)> Z_VirtualAllocInternal{0x4D9CF0};
@ -150,6 +191,7 @@ WEAK symbol<void(XZoneInfo* zoneInfo, unsigned int zoneCount,
DB_LoadXAssets{0x4CFC90}; DB_LoadXAssets{0x4CFC90};
WEAK symbol<char*(const char* filename, char* buf, int size)> DB_ReadRawFile{ WEAK symbol<char*(const char* filename, char* buf, int size)> DB_ReadRawFile{
0x46DA60}; 0x46DA60};
WEAK symbol<int(RawFile* rawfile)> DB_GetRawFileLen{0x4D2E60};
// FS // FS
WEAK symbol<int(const char* qpath, void** buffer)> _FS_ReadFile{0x4A5480}; WEAK symbol<int(const char* qpath, void** buffer)> _FS_ReadFile{0x4A5480};
@ -215,16 +257,44 @@ WEAK symbol<int(const char* s0, const char* s1, int n)> I_strnicmp{0x491E60};
WEAK symbol<void(field_t* edit)> Field_Clear{0x45C350}; WEAK symbol<void(field_t* edit)> Field_Clear{0x45C350};
// String
WEAK symbol<int(const char* string)> StringTable_HashString{0x498080};
// Vec3
WEAK symbol<void(const float* v, float scale, const float* result)> Vec3Scale{
0x429220};
// Variables // Variables
WEAK symbol<CmdArgs> cmd_args{0x144FED0}; WEAK symbol<CmdArgs> cmd_args{0x144FED0};
WEAK symbol<CmdArgs> sv_cmd_args{0x145ABA0}; WEAK symbol<CmdArgs> sv_cmd_args{0x145ABA0};
WEAK symbol<gentity_s> g_entities{0xEAAC38}; WEAK symbol<gentity_s> g_entities{0xEAAC38};
WEAK symbol<gclient_s> g_clients{0x10911E8}; WEAK symbol<gclient_s> g_clients{0x10911E8};
WEAK symbol<scrVmPub_t> scrVmPub{0x190DDF0};
WEAK symbol<scrVarPub_t> scrVarPub{0x18E7508};
WEAK symbol<scrCompilePub_t> scrCompilePub{0x156BF88};
WEAK symbol<scrCompileGlob_t> scrCompileGlob{0x158CFC8};
WEAK symbol<scrAnimPub_t> scrAnimPub{0x156BB68};
WEAK symbol<bool> g_loadedImpureScript{0x168F308};
WEAK symbol<char> g_EndPos{0x1912598};
WEAK symbol<unsigned char*> g_largeLocalBuf{0x195AAF8};
WEAK symbol<int> g_largeLocalPos{0x1963998};
WEAK symbol<int> g_maxLargeLocalPos{0x195AAFC};
WEAK symbol<int> g_largeLocalRightPos{0x195AAE8};
WEAK symbol<int> g_minLargeLocalRightPos{0x195AB00};
WEAK symbol<unsigned long> g_dwTlsIndex{0x1BFC750};
WEAK symbol<int> com_frameTime{0x145EC7C}; WEAK symbol<int> com_frameTime{0x145EC7C};
WEAK symbol<bool> cin_skippable{0x73264C}; WEAK symbol<bool> cin_skippable{0x73264C};
WEAK symbol<int> com_fixedConsolePosition{0x145EC10};
WEAK symbol<field_t> g_consoleField{0x88C700}; WEAK symbol<field_t> g_consoleField{0x88C700};
WEAK symbol<ConDrawInputGlob> conDrawInputGlob{0x86E788}; WEAK symbol<ConDrawInputGlob> conDrawInputGlob{0x86E788};
WEAK symbol<Console> con{0x86ED88}; WEAK symbol<Console> con{0x86ED88};
@ -243,17 +313,9 @@ WEAK symbol<SOCKET> ip_socket{0x1A040C8};
WEAK symbol<source_s*> sourceFiles{0x7440E8}; WEAK symbol<source_s*> sourceFiles{0x7440E8};
WEAK symbol<int> numtokens{0x7441F0}; WEAK symbol<int> numtokens{0x7441F0};
WEAK symbol<uiInfo_s> uiInfoArray{0x1920470};
WEAK symbol<void*> DB_GetXAssetSizeHandlers{0x733408}; WEAK symbol<void*> DB_GetXAssetSizeHandlers{0x733408};
WEAK symbol<void*> DB_XAssetPool{0x7337F8}; WEAK symbol<void*> DB_XAssetPool{0x7337F8};
WEAK symbol<unsigned int> g_poolSize{0x733510}; WEAK symbol<unsigned int> g_poolSize{0x733510};
WEAK symbol<unsigned char*> g_largeLocalBuf{0x195AAF8};
WEAK symbol<int> g_largeLocalPos{0x1963998};
WEAK symbol<int> g_maxLargeLocalPos{0x195AAFC};
WEAK symbol<int> g_largeLocalRightPos{0x195AAE8};
WEAK symbol<int> g_minLargeLocalRightPos{0x195AB00};
WEAK symbol<unsigned long> g_dwTlsIndex{0x1BFC750};
} // namespace game } // namespace game

View File

@ -21,15 +21,25 @@
#undef min #undef min
#endif #endif
#include <algorithm> #undef GetObject
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
#include <cstring> #include <cstring>
#include <algorithm>
#include <filesystem>
#include <format>
#include <fstream>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <map>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <map> #include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "Crypt32.lib") #pragma comment(lib, "Crypt32.lib")

118
src/common/utils/csv.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "csv.hpp"
#include "io.hpp"
#include "string.hpp"
namespace utils {
csv::csv(const std::string& file, const bool is_file, const bool allow_comments)
: valid_(false) {
this->parse(file, is_file, allow_comments);
}
std::size_t csv::get_rows() const noexcept { return this->data_map_.size(); }
std::size_t csv::get_columns() const {
std::size_t count = 0;
for (std::size_t i = 0; i < this->get_rows(); ++i) {
count = std::max(this->get_columns(i), count);
}
return count;
}
std::size_t csv::get_columns(const std::size_t row) const {
if (this->data_map_.size() > row) {
return this->data_map_[row].size();
}
return 0;
}
std::string csv::get_element_at(const std::size_t row,
const std::size_t column) const {
if (this->data_map_.size() > row) {
const auto& data = this->data_map_[row];
if (data.size() > column) {
return data[column];
}
}
return {};
}
bool csv::is_valid() const noexcept { return this->valid_; }
void csv::parse(const std::string& file, const bool is_file,
const bool allow_comments) {
std::string buffer;
if (is_file) {
if (io::read_file(file, &buffer) && !buffer.empty()) {
this->valid_ = true;
}
} else {
buffer = file;
}
if (!buffer.empty()) {
const auto rows = string::split(buffer, '\n');
for (auto& row : rows) {
this->parse_row(row, allow_comments);
}
}
}
void csv::parse_row(const std::string& row, const bool allow_comments) {
bool is_string = false;
std::string element;
std::vector<std::string> data;
char temp_char = '\0';
for (std::size_t i = 0; i < row.size(); ++i) {
if (row[i] == ',' && !is_string) // Flush entry
{
data.push_back(element);
element.clear();
continue;
}
if (row[i] == '"') // Start / Terminate string
{
is_string = !is_string;
continue;
}
if (i < (row.size() - 1) && row[i] == '\\' && row[i + 1] == '"' &&
is_string) // Handle quotes in strings as \"
{
temp_char = '"';
++i;
} else if (!is_string && (row[i] == '\n' || row[i] == '\x0D' ||
row[i] == '\x0A' || row[i] == '\t')) {
continue;
} else if (!is_string &&
(row[i] == '#' ||
(row[i] == '/' && (i + 1) < row.size() && row[i + 1] == '/')) &&
allow_comments) {
// Skip comments. I know CSVs usually don't have comments, but in this
// case it's useful
return;
} else {
temp_char = row[i];
}
element.push_back(temp_char);
}
// Push last element
data.push_back(element);
if (data.empty() || (data.size() == 1 && data[0].empty())) // Skip empty rows
{
return;
}
this->data_map_.push_back(data);
}
} // namespace utils

27
src/common/utils/csv.hpp Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
namespace utils {
class csv {
public:
csv(const std::string& file, bool is_file = true, bool allow_comments = true);
[[nodiscard]] std::size_t get_rows() const noexcept;
[[nodiscard]] std::size_t get_columns() const;
[[nodiscard]] std::size_t get_columns(std::size_t row) const;
[[nodiscard]] std::string get_element_at(std::size_t row,
std::size_t column) const;
[[nodiscard]] bool is_valid() const noexcept;
private:
bool valid_;
std::vector<std::vector<std::string>> data_map_;
void parse(const std::string& file, bool is_file = true,
bool allow_comments = true);
void parse_row(const std::string& row, bool allow_comments = true);
};
} // namespace utils

View File

@ -57,10 +57,9 @@ std::vector<PIMAGE_SECTION_HEADER> library::get_section_headers() const {
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections;
++i, ++section) { ++i, ++section) {
if (section) if (section) {
headers.push_back(section); headers.push_back(section);
else }
OutputDebugStringA("There was an invalid section :O");
} }
return headers; return headers;
@ -97,8 +96,9 @@ bool library::is_valid() const {
} }
std::string library::get_name() const { std::string library::get_name() const {
if (!this->is_valid()) if (!this->is_valid()) {
return {}; return {};
}
auto path = this->get_path(); auto path = this->get_path();
const auto pos = path.find_last_of("/\\"); const auto pos = path.find_last_of("/\\");
@ -109,8 +109,9 @@ std::string library::get_name() const {
} }
std::string library::get_path() const { std::string library::get_path() const {
if (!this->is_valid()) if (!this->is_valid()) {
return {}; return {};
}
char name[MAX_PATH] = {0}; char name[MAX_PATH] = {0};
GetModuleFileNameA(this->module_, name, sizeof name); GetModuleFileNameA(this->module_, name, sizeof name);
@ -119,8 +120,9 @@ std::string library::get_path() const {
} }
std::string library::get_folder() const { std::string library::get_folder() const {
if (!this->is_valid()) if (!this->is_valid()) {
return {}; return {};
}
const auto path = std::filesystem::path(this->get_path()); const auto path = std::filesystem::path(this->get_path());
return path.parent_path().generic_string(); return path.parent_path().generic_string();
@ -202,6 +204,15 @@ std::string library::get_dll_directory() {
return directory; return directory;
} }
bool is_wine() {
static const auto has_wine_export = []() -> bool {
const library ntdll("ntdll.dll");
return ntdll.get_proc<void*>("wine_get_version");
}();
return has_wine_export;
}
void raise_hard_exception() { void raise_hard_exception() {
int data = false; int data = false;
const library ntdll("ntdll.dll"); const library ntdll("ntdll.dll");

View File

@ -101,6 +101,8 @@ private:
HMODULE module_; HMODULE module_;
}; };
bool is_wine();
__declspec(noreturn) void raise_hard_exception(); __declspec(noreturn) void raise_hard_exception();
std::string load_resource(int id); std::string load_resource(int id);