diff --git a/.gitignore b/.gitignore index d78a2e0..5be63f0 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,7 @@ Temporary Items # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -# Ignore everything in the build directory +# Build results build # Visual Studio 2015 cache/options directory diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..77f7c24 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/README.md b/README.md index a3f8374..91c98f3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![website](https://img.shields.io/badge/Repackers-_Website-blue)](https://rimmyscorner.com/) - # IW4: SP Client 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 +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`. -- Build via solution file in `build\iw4-sp.sln`. +- Build via solution file found in `build\iw4-sp.sln`. ## 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. | -## Command line arguments +## Contributing -| Argument | Description | -|:------------------------|:-----------------------------------------------| -| `-nosteam` | Disable Steam integration. | +Contributions are welcome! Please follow the guidelines below: + +- 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 diff --git a/premake5.lua b/premake5.lua index ba5cfcd..7212366 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,4 +1,4 @@ -gitVersioningCommand = "git describe --tags --always" +gitVersioningCommand = "git describe --tags --dirty --always" gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD" -- Quote the given string input as a C string diff --git a/src/client/component/assets/assets.cpp b/src/client/component/assets/assets.cpp index d7073e3..7dc08e8 100644 --- a/src/client/component/assets/assets.cpp +++ b/src/client/component/assets/assets.cpp @@ -4,6 +4,7 @@ #include "localize_entry.hpp" #include "map_ents.hpp" #include "raw_file.hpp" +#include "string_table.hpp" #include @@ -13,13 +14,16 @@ void load_asset(game::XAssetType type, game::XAssetHeader* header) { if (header) { switch (type) { case game::ASSET_TYPE_LOCALIZE_ENTRY: - process_localize_entry(*header); + process_localize_entry(header); break; case game::ASSET_TYPE_MAP_ENTS: - process_map_ents(*header); + process_map_ents(header); break; case game::ASSET_TYPE_RAWFILE: - process_raw_file(*header); + process_raw_file(header); + break; + case game::ASSET_TYPE_STRINGTABLE: + process_string_table(header); break; default: break; diff --git a/src/client/component/assets/localize_entry.cpp b/src/client/component/assets/localize_entry.cpp index a8f9d5d..0df761e 100644 --- a/src/client/component/assets/localize_entry.cpp +++ b/src/client/component/assets/localize_entry.cpp @@ -11,12 +11,12 @@ namespace { bool is_enabled() { IS_FLAG_ENABLED(dump_localize_entry); } } // namespace -void process_localize_entry(game::XAssetHeader header) { +void process_localize_entry(game::XAssetHeader* header) { if (!is_enabled()) { return; } - auto* localize = header.localize; + auto* localize = header->localize; const auto filename = utils::string::va("raw/localizedstrings/{0}", localize->name); diff --git a/src/client/component/assets/localize_entry.hpp b/src/client/component/assets/localize_entry.hpp index 171a8f5..e25682e 100644 --- a/src/client/component/assets/localize_entry.hpp +++ b/src/client/component/assets/localize_entry.hpp @@ -1,5 +1,5 @@ #pragma once namespace assets { -void process_localize_entry(game::XAssetHeader header); +void process_localize_entry(game::XAssetHeader* header); } diff --git a/src/client/component/assets/map_ents.cpp b/src/client/component/assets/map_ents.cpp index 9df841d..ad3405a 100644 --- a/src/client/component/assets/map_ents.cpp +++ b/src/client/component/assets/map_ents.cpp @@ -23,8 +23,8 @@ void load_map_entities(game::MapEnts* entry) { } } // namespace -void process_map_ents(game::XAssetHeader header) { - auto* map_ents = header.mapEnts; +void process_map_ents(game::XAssetHeader* header) { + auto* map_ents = header->mapEnts; load_map_entities(map_ents); } } // namespace assets diff --git a/src/client/component/assets/map_ents.hpp b/src/client/component/assets/map_ents.hpp index ccfa5a6..d2b802d 100644 --- a/src/client/component/assets/map_ents.hpp +++ b/src/client/component/assets/map_ents.hpp @@ -1,5 +1,5 @@ #pragma once namespace assets { -void process_map_ents(game::XAssetHeader header); +void process_map_ents(game::XAssetHeader* header); } diff --git a/src/client/component/assets/raw_file.cpp b/src/client/component/assets/raw_file.cpp index 3586b5f..7cc3eca 100644 --- a/src/client/component/assets/raw_file.cpp +++ b/src/client/component/assets/raw_file.cpp @@ -19,7 +19,7 @@ char* db_read_raw_file_stub(const char* filename, char* buf, int size) { auto file_handle = 0; const auto file_size = game::FS_FOpenFileRead(filename, &file_handle); - if (file_handle != 0) { + if (file_handle) { if ((file_size + 1) <= size) { game::FS_Read(buf, file_size, file_handle); 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); if (file_len < 0) { 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); 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); } } // namespace -void process_raw_file(game::XAssetHeader header) { +void process_raw_file(game::XAssetHeader* header) { if (!is_enabled()) { return; } - const auto* raw_file = header.rawfile; + const auto* raw_file = header->rawfile; const auto filename = utils::string::va("raw/{0}", raw_file->name); if (raw_file->compressedLen > 0) { @@ -147,7 +147,7 @@ void process_raw_file(game::XAssetHeader header) { uncompressed.resize(raw_file->len); if (uncompress(uncompressed.data(), (uLongf*)&raw_file->len, - (const Bytef*)raw_file->buffer, + reinterpret_cast(raw_file->buffer), raw_file->compressedLen) == Z_OK) { std::string data; data.assign(uncompressed.begin(), uncompressed.end()); diff --git a/src/client/component/assets/raw_file.hpp b/src/client/component/assets/raw_file.hpp index 0d634f1..11309c8 100644 --- a/src/client/component/assets/raw_file.hpp +++ b/src/client/component/assets/raw_file.hpp @@ -1,5 +1,5 @@ #pragma once namespace assets { -void process_raw_file(game::XAssetHeader header); +void process_raw_file(game::XAssetHeader* header); } diff --git a/src/client/component/assets/string_table.cpp b/src/client/component/assets/string_table.cpp new file mode 100644 index 0000000..d03df73 --- /dev/null +++ b/src/client/component/assets/string_table.cpp @@ -0,0 +1,46 @@ +#include + +#include "string_table.hpp" + +#include +#include +#include + +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 diff --git a/src/client/component/assets/string_table.hpp b/src/client/component/assets/string_table.hpp new file mode 100644 index 0000000..8a16394 --- /dev/null +++ b/src/client/component/assets/string_table.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace assets { +void process_string_table(game::XAssetHeader* header); +} diff --git a/src/client/component/botlib/l_precomp.cpp b/src/client/component/botlib/l_precomp.cpp index a9f798e..d61bd0d 100644 --- a/src/client/component/botlib/l_precomp.cpp +++ b/src/client/component/botlib/l_precomp.cpp @@ -94,7 +94,7 @@ game::define_s* copy_define([[maybe_unused]] game::source_s* source, game::token_s *token, *newtoken, *lasttoken; auto* newdefine = static_cast( - 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 newdefine->name = (char*)newdefine + sizeof(game::define_s); diff --git a/src/client/component/botlib/l_script.cpp b/src/client/component/botlib/l_script.cpp index 93df92f..9e942f1 100644 --- a/src/client/component/botlib/l_script.cpp +++ b/src/client/component/botlib/l_script.cpp @@ -153,7 +153,7 @@ game::script_s* load_script_file(const char* filename) { // pointer to end of script buffer script->end_p = &script->buffer[length]; // set if there's a token available in script->token - script->tokenavailable = 0; + script->tokenavailable = false; script->line = 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 script->end_p = &script->buffer[length]; // set if there's a token available in script->token - script->tokenavailable = 0; + script->tokenavailable = false; script->line = 1; script->lastline = 1; diff --git a/src/client/component/branding.cpp b/src/client/component/branding.cpp index bb5d44c..2560def 100644 --- a/src/client/component/branding.cpp +++ b/src/client/component/branding.cpp @@ -98,7 +98,7 @@ public: register_branding_dvars(); utils::hook(0x57DAFF, cg_draw_full_screen_debug_overlays_stub, HOOK_CALL) - .install() + .install() // hook* ->quick(); } diff --git a/src/client/component/ceg.cpp b/src/client/component/ceg.cpp index 6a85c33..d779e39 100644 --- a/src/client/component/ceg.cpp +++ b/src/client/component/ceg.cpp @@ -19,6 +19,7 @@ public: utils::hook::set(0x47C2E0, 0xC301B0); utils::hook::set(0x4EEA90, 0xC301B0); utils::hook::set(0x40E380, 0xC301B0); + utils::hook::set(0x4E45C0, 0xC301B0); // Killer caller utils::hook::set(0x43F320, 0xC3); diff --git a/src/client/component/debug.cpp b/src/client/component/debug.cpp index e3de982..c479921 100644 --- a/src/client/component/debug.cpp +++ b/src/client/component/debug.cpp @@ -119,7 +119,7 @@ public: #ifdef _DEBUG utils::hook(0x4C79DF, g_init_game_stub, HOOK_CALL) - .install() + .install() // hook* ->quick(); // Scr_FreeEntityList #endif } diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index 1c90a26..936dbb2 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -61,7 +61,6 @@ private: static void ready(const DiscordUser* request) { ZeroMemory(&discord_presence, sizeof(discord_presence)); - discord_presence.state = "Singleplayer"; discord_presence.instance = 1; discord_presence.startTimestamp = 0; printf("Discord: Ready\n"); @@ -69,7 +68,7 @@ private: } 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 diff --git a/src/client/component/dvar_patches.cpp b/src/client/component/dvar_patches.cpp index f709941..4c2b6d5 100644 --- a/src/client/component/dvar_patches.cpp +++ b/src/client/component/dvar_patches.cpp @@ -19,9 +19,9 @@ const game::dvar_t* dvar_register_name(const char* dvar_name, const char* value, class component final : public component_interface { public: 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, - game::DVAR_ARCHIVE); + game::DVAR_ARCHIVE | game::DVAR_SAVED); dvar::override::register_string("fs_basegame", BASEGAME, game::DVAR_INIT); #ifdef _DEBUG diff --git a/src/client/component/gsc/error.cpp b/src/client/component/gsc/error.cpp new file mode 100644 index 0000000..73e375f --- /dev/null +++ b/src/client/component/gsc/error.cpp @@ -0,0 +1,1206 @@ +#include +#include "loader/component_loader.hpp" +#include "game/dvars.hpp" + +#include + +namespace gsc { +namespace hunk { +game::HunkUser* g_debug_user_; + +void user_destroy(game::HunkUser* user) { + auto* current = user->next; + while (current) { + auto* next = current->next; + ::VirtualFree(current, 0, MEM_RELEASE); + current = next; + } + + ::VirtualFree(user, 0, MEM_RELEASE); +} + +void init_debug_memory() { + assert(game::Sys_IsMainThread()); + assert(!g_debug_user_); + + g_debug_user_ = + game::Hunk_UserCreate(0x1000000, "Hunk_InitDebugMemory", false, 0); +} + +void shutdown_debug_memory() { + assert(game::Sys_IsMainThread()); + assert(g_debug_user_); + + user_destroy(g_debug_user_); + g_debug_user_ = nullptr; +} + +void* alloc_debug_mem(int size) { + assert(game::Sys_IsMainThread()); + assert(g_debug_user_); + + return game::Hunk_UserAlloc(g_debug_user_, size, 4); +} + +void free_debug_mem([[maybe_unused]] void* ptr) { + assert(game::Sys_IsMainThread()); + assert(g_debug_user_); + + // Let's hope it gets cleared by Hunk_ShutdownDebugMemory +} +} // namespace hunk + +namespace scr { +int developer_; + +game::scrParserGlob_t parser_glob_; +game::scrParserPub_t parser_pub_; + +int get_cumul_offset() { + assert(game::scrCompileGlob->cumulOffset >= 0); + return game::scrCompileGlob->cumulOffset; +} + +int get_line_num_internal(const char* buf, unsigned int source_pos, + const char** start_line, int* col) { + assert(buf); + + *start_line = buf; + unsigned int line_num = 0; + + while (source_pos) { + if (!*buf) { + *start_line = buf + 1; + ++line_num; + } + + ++buf; + --source_pos; + } + + *col = buf - *start_line; + return static_cast(line_num); +} + +int get_line_num(const unsigned int buffer_index, + const unsigned int source_pos) { + const char* start_line; + int col; + + assert(developer_); + + return get_line_num_internal( + parser_pub_.sourceBufferLookup[buffer_index].sourceBuf, source_pos, + &start_line, &col); +} + +game::OpcodeLookup* get_prev_source_pos_opcode_lookup(const char* code_pos) { + assert(game::Scr_IsInOpcodeMemory(code_pos)); + assert(parser_glob_.opcodeLookup); + + unsigned int low = 0; + unsigned int high = parser_glob_.opcodeLookupLen - 1; + + while (low <= high) { + const auto middle = (high + low) >> 1; + + if (code_pos < parser_glob_.opcodeLookup[middle].codePos) { + high = middle - 1; + } else { + low = middle + 1; + if (low == parser_glob_.opcodeLookupLen || + code_pos < parser_glob_.opcodeLookup[low].codePos) { + return &parser_glob_.opcodeLookup[middle]; + } + } + } + + return nullptr; +} + +unsigned int get_prev_source_pos(const char* code_pos, + const unsigned int index) { + return parser_glob_ + .sourcePosLookup[index + get_prev_source_pos_opcode_lookup(code_pos) + ->sourcePosIndex] + .sourcePos; +} + +void copy_formatted_line(char* line, const char* raw_line) { + auto len = std::strlen(raw_line); + if (len >= 1024) { + len = 1024 - 1; + } + + for (std::size_t i = 0; i < len; ++i) { + if (raw_line[i] == '\t') { + line[i] = ' '; + } else { + line[i] = raw_line[i]; + } + } + + if (line[len - 1] == '\r') { + line[len - 1] = '\0'; + } + + line[len] = '\0'; +} + +unsigned int get_source_buffer(const char* code_pos) { + unsigned int buffer_index; + + assert(game::Scr_IsInOpcodeMemory(code_pos)); + assert(parser_pub_.sourceBufferLookupLen > 0); + + for (buffer_index = parser_pub_.sourceBufferLookupLen - 1; buffer_index; + --buffer_index) { + if (!parser_pub_.sourceBufferLookup[buffer_index].codePos) { + continue; + } + + if (parser_pub_.sourceBufferLookup[buffer_index].codePos > code_pos) { + continue; + } + + break; + } + + return buffer_index; +} + +int get_line_info(const char* buf, const unsigned int source_pos, int* col, + char* line) { + const char* start_line; + unsigned int line_num; + + if (buf) { + line_num = get_line_num_internal(buf, source_pos, &start_line, col); + } else { + line_num = 0; + start_line = ""; + *col = 0; + } + + copy_formatted_line(line, start_line); + return static_cast(line_num); +} + +void print_source_pos(const int channel, const char* filename, const char* buf, + const unsigned int source_pos) { + char line[1024]{}; + int col; + + assert(filename); + const auto line_num = get_line_info(buf, source_pos, &col, line); + + game::Com_PrintMessage( + channel, + game::va("(file '%s'%s, line %d)\n", filename, + parser_glob_.saveSourceBufferLookup ? " (savegame)" : "", + line_num + 1), + 0); + game::Com_PrintMessage(channel, game::va("%s\n", line), 0); + + for (auto i = 0; i < col; ++i) { + game::Com_PrintMessage(channel, " ", 0); + } + + game::Com_PrintMessage(channel, "*\n", 0); +} + +void print_prev_code_pos(const int channel, const char* code_pos, + const unsigned int index) { + if (!code_pos) { + game::Com_PrintMessage(channel, "\n", 0); + return; + } + + if (code_pos == game::g_EndPos) { + game::Com_PrintMessage(channel, "\n", 0); + return; + } + + if (!developer_) { + if (game::Scr_IsInOpcodeMemory(code_pos - 1)) { + game::Com_PrintMessage( + channel, + game::va("@ %d\n", code_pos - game::scrVarPub->programBuffer), 0); + return; + } + } else { + if (game::scrVarPub->programBuffer && + game::Scr_IsInOpcodeMemory(code_pos)) { + const auto buffer_index = get_source_buffer(code_pos - 1); + + print_source_pos(channel, + parser_pub_.sourceBufferLookup[buffer_index].buf, + parser_pub_.sourceBufferLookup[buffer_index].sourceBuf, + get_prev_source_pos(code_pos - 1, index)); + return; + } + } + + game::Com_PrintMessage(channel, game::va("%s\n\n", code_pos), 0); +} + +void get_text_source_pos([[maybe_unused]] const char* buf, const char* code_pos, + char* line) { + int col; + + if (developer_ && code_pos && code_pos != game::g_EndPos && + game::scrVarPub->programBuffer && game::Scr_IsInOpcodeMemory(code_pos)) { + const auto buffer_index = get_source_buffer(code_pos - 1); + + get_line_info(parser_pub_.sourceBufferLookup[buffer_index].sourceBuf, + get_prev_source_pos(code_pos - 1, 0), &col, line); + } else { + *line = '\0'; + } +} + +void init_opcode_lookup() { + assert(!parser_glob_.opcodeLookup); + assert(!parser_glob_.sourcePosLookup); + assert(!parser_pub_.sourceBufferLookup); + assert(!parser_pub_.codeOffsetMap); + assert(!parser_pub_.useCodeOffsetMap); + + if (!developer_) { + return; + } + + parser_glob_.delayedSourceIndex = -1; + parser_glob_.opcodeLookupMaxSize = 0; + parser_glob_.opcodeLookupLen = 0; + parser_glob_.opcodeLookup = static_cast(::VirtualAlloc( + nullptr, game::MAX_OPCODE_LOOKUP_SIZE, MEM_RESERVE, PAGE_READWRITE)); + + parser_glob_.sourcePosLookupMaxSize = 0; + parser_glob_.sourcePosLookupLen = 0; + parser_glob_.sourcePosLookup = static_cast( + ::VirtualAlloc(nullptr, game::MAX_SOURCEPOS_LOOKUP_SIZE, MEM_RESERVE, + PAGE_READWRITE)); + + parser_glob_.currentCodePos = nullptr; + parser_glob_.currentSourcePosCount = 0; + + parser_glob_.sourceBufferLookupMaxSize = 0; + parser_pub_.sourceBufferLookupLen = 0; + parser_pub_.sourceBufferLookup = static_cast( + ::VirtualAlloc(nullptr, game::MAX_SOURCEBUF_LOOKUP_SIZE, MEM_RESERVE, + PAGE_READWRITE)); +} + +void shutdown_opcode_lookup() { + if (parser_glob_.opcodeLookup) { + ::VirtualFree(parser_glob_.opcodeLookup, 0, MEM_RELEASE); + parser_glob_.opcodeLookup = nullptr; + } + + if (parser_glob_.sourcePosLookup) { + ::VirtualFree(parser_glob_.sourcePosLookup, 0, MEM_RELEASE); + parser_glob_.sourcePosLookup = nullptr; + } + + if (parser_pub_.sourceBufferLookup) { + for (unsigned int i = 0; i < parser_pub_.sourceBufferLookupLen; ++i) { + hunk::free_debug_mem(parser_pub_.sourceBufferLookup[i].buf); + } + + ::VirtualFree(parser_pub_.sourceBufferLookup, 0, MEM_RELEASE); + parser_pub_.sourceBufferLookup = nullptr; + } + + if (parser_glob_.saveSourceBufferLookup) { + for (unsigned int i = 0; i < parser_glob_.saveSourceBufferLookupLen; ++i) { + if (parser_glob_.saveSourceBufferLookup[i].sourceBuf) { + hunk::free_debug_mem(parser_glob_.saveSourceBufferLookup[i].buf); + } + } + + hunk::free_debug_mem(parser_glob_.saveSourceBufferLookup); + parser_glob_.saveSourceBufferLookup = nullptr; + } +} + +game::SourceBufferInfo* get_new_source_buffer() { + assert(parser_pub_.sourceBufferLookup); + + auto size = + sizeof(game::SourceBufferInfo) * (parser_pub_.sourceBufferLookupLen + 1); + if (size > parser_glob_.sourceBufferLookupMaxSize) { + if (parser_glob_.sourceBufferLookupMaxSize >= + game::MAX_SOURCEBUF_LOOKUP_SIZE) { + game::Sys_Error("MAX_SOURCEBUF_LOOKUP_SIZE exceeded"); + } + + ::VirtualAlloc((char*)parser_pub_.sourceBufferLookup + + parser_glob_.sourceBufferLookupMaxSize, + 0x20000, MEM_COMMIT, PAGE_READWRITE); + parser_glob_.sourceBufferLookupMaxSize += 0x20000; + + assert(size <= parser_glob_.sourceBufferLookupMaxSize); + } + + return &parser_pub_.sourceBufferLookup[parser_pub_.sourceBufferLookupLen++]; +} + +void add_source_buffer_internal(const char* ext_filename, const char* code_pos, + char* source_buf, int len, bool do_eol_fixup, + bool archive) { + int i; + + if (!parser_pub_.sourceBufferLookup) { + parser_pub_.sourceBuf = nullptr; + return; + } + + assert((len >= -1)); + assert((len >= 0) || !source_buf); + + const auto str_len = std::strlen(ext_filename) + 1; + const auto new_len = str_len + len + 2; + auto* buf = static_cast(hunk::alloc_debug_mem( + static_cast(new_len))); // Scr_AddSourceBufferInternal + + strncpy_s(buf, new_len, ext_filename, _TRUNCATE); + auto* source_buf2 = source_buf ? buf + str_len : nullptr; + auto* source = source_buf; + auto* dest = source_buf2; + + if (do_eol_fixup) { + for (i = 0; i <= len; ++i) { + const auto c = *source++; + if (c == '\n' || c == '\r' && *source != '\n') { + *dest = 0; + } else { + *dest = c; + } + + ++dest; + } + } else { + for (i = 0; i <= len; ++i) { + const auto c = *source; + ++source; + *dest = c; + ++dest; + } + } + + auto* buffer_info = get_new_source_buffer(); + buffer_info->codePos = code_pos; + buffer_info->buf = buf; + buffer_info->sourceBuf = source_buf2; + buffer_info->len = len; + buffer_info->sortedIndex = -1; + buffer_info->archive = archive; + + if (source_buf2) { + parser_pub_.sourceBuf = source_buf2; + } +} + +char* read_file_fast_file([[maybe_unused]] const char* filename, + const char* ext_filename, const char* code_pos, + [[maybe_unused]] const bool archive) { + auto* rawfile = + game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, ext_filename).rawfile; + if (game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, ext_filename)) { + add_source_buffer_internal(ext_filename, code_pos, nullptr, -1, true, true); + return nullptr; + } + + const auto len = game::DB_GetRawFileLen(rawfile); + auto* source_buf = static_cast( + game::Hunk_AllocateTempMemoryHigh(len)); // Scr_ReadFile_FastFile + game::DB_GetRawBuffer(rawfile, source_buf, len); + add_source_buffer_internal(ext_filename, code_pos, source_buf, len - 1, true, + true); + return source_buf; +} + +char* read_file_load_obj([[maybe_unused]] const char* filename, + const char* ext_filename, const char* code_pos, + const bool archive) { + int f; + + auto len = game::FS_FOpenFileByMode(ext_filename, &f, game::FS_READ); + if (len < 0) { + add_source_buffer_internal(ext_filename, code_pos, nullptr, -1, true, + archive); + return nullptr; + } + + *game::g_loadedImpureScript = true; + + auto* source_buf = + static_cast(game::Hunk_AllocateTempMemoryHigh(len + 1)); + game::FS_Read(source_buf, len, f); + source_buf[len] = '\0'; + + game::FS_FCloseFile(f); + add_source_buffer_internal(ext_filename, code_pos, source_buf, len, true, + archive); + + return source_buf; +} + +char* read_file(const char* filename, const char* ext_filename, + const char* code_pos, const bool archive) { + int file; + + if (game::FS_FOpenFileRead(ext_filename, &file) < 0) { + return read_file_fast_file(filename, ext_filename, code_pos, archive); + } + + game::FS_FCloseFile(file); + return read_file_load_obj(filename, ext_filename, code_pos, archive); +} + +char* add_source_buffer(const char* filename, const char* ext_filename, + const char* code_pos, const bool archive) { + char* source_buf; + + if (archive && parser_glob_.saveSourceBufferLookup) { + assert(parser_glob_.saveSourceBufferLookupLen > 0); + + --parser_glob_.saveSourceBufferLookupLen; + + const auto* save_source_buffer = parser_glob_.saveSourceBufferLookup + + parser_glob_.saveSourceBufferLookupLen; + const auto len = save_source_buffer->len; + + assert(len >= -1); + + if (len < 0) { + add_source_buffer_internal(ext_filename, code_pos, nullptr, -1, true, + archive); + source_buf = nullptr; + } else { + source_buf = + static_cast(game::Hunk_AllocateTempMemoryHigh(len + 1)); + + const char* source = save_source_buffer->sourceBuf; + auto* dest = source_buf; + for (int i = 0; i < len; ++i) { + const auto c = *source++; + *dest = c ? c : '\n'; + dest++; + } + + *dest = '\0'; + add_source_buffer_internal(ext_filename, code_pos, source_buf, len, false, + archive); + } + + return source_buf; + } + + return read_file(filename, ext_filename, code_pos, archive); +} + +unsigned int load_script_internal_stub(const char* filename, + game::PrecacheEntry* entries, + int entries_count) { + char ext_filename[game::MAX_QPATH]; + game::sval_u parse_data; + + assert(game::scrCompilePub->script_loading); + assert(std::strlen(filename) < game::MAX_QPATH); + + const auto name = game::Scr_CreateCanonicalFilename(filename); + + if (game::FindVariable(game::scrCompilePub->loadedscripts, name)) { + game::SL_RemoveRefToString(name); + const auto file_pos_ptr = + game::FindVariable(game::scrCompilePub->scriptsPos, name); + return file_pos_ptr + ? game::FindObject(game::scrCompilePub->scriptsPos, file_pos_ptr) + : 0; + } + + const auto script_id = + game::GetNewVariable(game::scrCompilePub->loadedscripts, name); + game::SL_RemoveRefToString(name); + + sprintf_s(ext_filename, "%s.gsc", + game::SL_ConvertToString(static_cast(name))); + + const auto* old_source_buf = parser_pub_.sourceBuf; + const auto* source_buffer = add_source_buffer( + game::SL_ConvertToString(static_cast(name)), ext_filename, + game::TempMalloc(0), true); + + if (!source_buffer) { + return 0; + } + + const auto old_anim_tree_names = game::scrAnimPub->animTreeNames; + game::scrAnimPub->animTreeNames = 0; + game::scrCompilePub->far_function_count = 0; + + const auto* old_filename = parser_pub_.scriptfilename; + parser_pub_.scriptfilename = ext_filename; + + game::scrCompilePub->in_ptr = "+"; + game::scrCompilePub->in_ptr_valid = false; + game::scrCompilePub->parseBuf = source_buffer; + + game::ScriptParse(&parse_data, 0); + game::scrCompilePub->parseBuf = nullptr; + + const auto file_pos_id = + game::GetObject(game::scrCompilePub->scriptsPos, + game::GetVariable(game::scrCompilePub->scriptsPos, name)); + const auto file_count_id = game::GetObject( + game::scrCompilePub->scriptsCount, + game::GetVariable(game::scrCompilePub->scriptsCount, name)); + + game::ScriptCompile(parse_data.node, file_pos_id, file_count_id, script_id, + entries, entries_count); + + game::RemoveVariable(game::scrCompilePub->scriptsCount, name); + + parser_pub_.scriptfilename = old_filename; + parser_pub_.sourceBuf = old_source_buf; + + game::scrAnimPub->animTreeNames = old_anim_tree_names; + + return file_pos_id; +} + +void settings_stub([[maybe_unused]] int developer, int developer_script, + int abort_on_error) { + static_assert(offsetof(game::scrVarPub_t, developer_script) == 0x6); + static_assert(offsetof(game::scrVmPub_t, abort_on_error) == 0x15); + + assert(!abort_on_error || developer); + + developer_ = (*dvars::com_developer)->current.enabled; + game::scrVarPub->developer_script = developer_script != 0; + game::scrVmPub->abort_on_error = abort_on_error != 0; +} +} // namespace scr + +namespace { +void add_opcode_pos(const unsigned int source_pos, int type) { + game::OpcodeLookup* opcode_lookup; + game::SourceLookup* source_pos_lookup; + + if (!scr::developer_) { + return; + } + + if (game::scrCompilePub->developer_statement == 2) { + assert(!game::scrVarPub->developer_script); + return; + } + + if (game::scrCompilePub->developer_statement == 3) { + return; + } + + if (!game::scrCompilePub->allowedBreakpoint) { + type &= ~game::SOURCE_TYPE_BREAKPOINT; + } + + assert(scr::parser_glob_.opcodeLookup); + assert(scr::parser_glob_.sourcePosLookup); + assert(game::scrCompilePub->opcodePos); + + auto size = + sizeof(game::OpcodeLookup) * (scr::parser_glob_.opcodeLookupLen + 1); + if (size > scr::parser_glob_.opcodeLookupMaxSize) { + if (scr::parser_glob_.opcodeLookupMaxSize >= game::MAX_OPCODE_LOOKUP_SIZE) { + game::Sys_Error("MAX_OPCODE_LOOKUP_SIZE exceeded"); + } + + ::VirtualAlloc((char*)scr::parser_glob_.opcodeLookup + + scr::parser_glob_.opcodeLookupMaxSize, + 0x20000, MEM_COMMIT, PAGE_READWRITE); + scr::parser_glob_.opcodeLookupMaxSize += 0x20000; + + assert(size <= scr::parser_glob_.opcodeLookupMaxSize); + } + + size = + sizeof(game::SourceLookup) * (scr::parser_glob_.sourcePosLookupLen + 1); + if (size > scr::parser_glob_.sourcePosLookupMaxSize) { + if (scr::parser_glob_.sourcePosLookupMaxSize >= + game::MAX_SOURCEPOS_LOOKUP_SIZE) { + game::Sys_Error("MAX_SOURCEPOS_LOOKUP_SIZE exceeded"); + } + + ::VirtualAlloc((char*)scr::parser_glob_.sourcePosLookup + + scr::parser_glob_.sourcePosLookupMaxSize, + 0x20000, MEM_COMMIT, PAGE_READWRITE); + scr::parser_glob_.sourcePosLookupMaxSize += 0x20000; + + assert(size <= scr::parser_glob_.sourcePosLookupMaxSize); + } + + if (scr::parser_glob_.currentCodePos == game::scrCompilePub->opcodePos) { + assert(scr::parser_glob_.currentSourcePosCount); + + --scr::parser_glob_.opcodeLookupLen; + opcode_lookup = + &scr::parser_glob_.opcodeLookup[scr::parser_glob_.opcodeLookupLen]; + + assert(opcode_lookup->sourcePosIndex + + scr::parser_glob_.currentSourcePosCount == + scr::parser_glob_.sourcePosLookupLen); + assert(opcode_lookup->codePos == (char*)scr::parser_glob_.currentCodePos); + } else { + scr::parser_glob_.currentSourcePosCount = 0; + scr::parser_glob_.currentCodePos = game::scrCompilePub->opcodePos; + opcode_lookup = + &scr::parser_glob_.opcodeLookup[scr::parser_glob_.opcodeLookupLen]; + opcode_lookup->sourcePosIndex = scr::parser_glob_.sourcePosLookupLen; + opcode_lookup->codePos = scr::parser_glob_.currentCodePos; + opcode_lookup->cumulOffset = + static_cast(scr::get_cumul_offset()); + // TODO: Assign a value to opcode_lookup->localVars + } + + const auto source_pos_lookup_index = + scr::parser_glob_.currentSourcePosCount + opcode_lookup->sourcePosIndex; + source_pos_lookup = + &scr::parser_glob_.sourcePosLookup[source_pos_lookup_index]; + source_pos_lookup->sourcePos = source_pos; + + if (source_pos == static_cast(-1)) { + assert(scr::parser_glob_.delayedSourceIndex == -1); + assert(type & game::SOURCE_TYPE_BREAKPOINT); + + scr::parser_glob_.delayedSourceIndex = + static_cast(source_pos_lookup_index); + } else if (source_pos == static_cast(-2)) { + scr::parser_glob_.threadStartSourceIndex = + static_cast(source_pos_lookup_index); + } else if (scr::parser_glob_.delayedSourceIndex >= 0 && + (type & game::SOURCE_TYPE_BREAKPOINT)) { + scr::parser_glob_.sourcePosLookup[scr::parser_glob_.delayedSourceIndex] + .sourcePos = source_pos; + scr::parser_glob_.delayedSourceIndex = -1; + } + + source_pos_lookup->type |= type; + ++scr::parser_glob_.currentSourcePosCount; + opcode_lookup->sourcePosCount = + static_cast(scr::parser_glob_.currentSourcePosCount); + ++scr::parser_glob_.opcodeLookupLen; + ++scr::parser_glob_.sourcePosLookupLen; +} + +void remove_opcode_pos() { + if (!scr::developer_) { + return; + } + + if (game::scrCompilePub->developer_statement == 2) { + assert(!game::scrVarPub->developer_script); + return; + } + + assert(scr::parser_glob_.opcodeLookup); + assert(scr::parser_glob_.sourcePosLookup); + assert(game::scrCompilePub->opcodePos); + assert(scr::parser_glob_.sourcePosLookupLen); + + --scr::parser_glob_.sourcePosLookupLen; + assert(scr::parser_glob_.opcodeLookupLen); + + --scr::parser_glob_.opcodeLookupLen; + + assert(scr::parser_glob_.currentSourcePosCount); + + --scr::parser_glob_.currentSourcePosCount; + + auto* opcode_lookup = + &scr::parser_glob_.opcodeLookup[scr::parser_glob_.opcodeLookupLen]; + + assert(scr::parser_glob_.currentCodePos == game::scrCompilePub->opcodePos); + assert(opcode_lookup->sourcePosIndex + + scr::parser_glob_.currentSourcePosCount == + scr::parser_glob_.sourcePosLookupLen); + assert(opcode_lookup->codePos == (char*)scr::parser_glob_.currentCodePos); + + if (!scr::parser_glob_.currentSourcePosCount) { + scr::parser_glob_.currentCodePos = nullptr; + } + + opcode_lookup->sourcePosCount = + static_cast(scr::parser_glob_.currentSourcePosCount); +} + +void add_thread_start_opcode_pos(const unsigned int source_pos) { + if (!scr::developer_) { + return; + } + + if (game::scrCompilePub->developer_statement == 2) { + assert(!game::scrVarPub->developer_script); + return; + } + + assert(scr::parser_glob_.threadStartSourceIndex >= 0); + auto* source_pos_lookup = + &scr::parser_glob_ + .sourcePosLookup[scr::parser_glob_.threadStartSourceIndex]; + source_pos_lookup->sourcePos = source_pos; + + assert(!source_pos_lookup->type); + + source_pos_lookup->type = 8; + scr::parser_glob_.threadStartSourceIndex = -1; +} + +void runtime_error_internal(int channel, const char* code_pos, + unsigned int index, const char* msg) { + static_assert(offsetof(game::scrVmPub_t, function_count) == 0x8); + static_assert(offsetof(game::scrVmPub_t, function_frame_start) == 0x20); + + assert(game::Scr_IsInOpcodeMemory(code_pos)); + + game::Com_PrintError(channel, + "\n******* script runtime error *******\n%s: ", msg); + scr::print_prev_code_pos(channel, code_pos, index); + + if (game::scrVmPub->function_count) { + for (auto i = game::scrVmPub->function_count - 1; i >= 1; --i) { + game::Com_PrintError(channel, "called from:\n"); + scr::print_prev_code_pos( + game::CON_CHANNEL_DONT_FILTER, + game::scrVmPub->function_frame_start[i].fs.pos, + game::scrVmPub->function_frame_start[i].fs.localId == 0); + } + + game::Com_PrintError(channel, "started from:\n"); + scr::print_prev_code_pos(game::CON_CHANNEL_DONT_FILTER, + game::scrVmPub->function_frame_start[0].fs.pos, 1); + } + + *game::com_fixedConsolePosition = false; + game::Com_PrintError(channel, "************************************\n"); +} + +void runtime_error(const char* code_pos, unsigned int index, const char* msg, + const char* dialog_message) { + static_assert(offsetof(game::scrVmPub_t, terminal_error) == 0x16); + + bool abort_on_error; + const char* dialog_message_separator; + + if (!scr::developer_) { + assert(game::Scr_IsInOpcodeMemory(code_pos)); + + if (!(*dvars::com_developer)->current.enabled) { + return; + } + } + + if (game::scrVmPub->debugCode) { + game::Com_Printf(game::CON_CHANNEL_PARSERSCRIPT, "%s\n", msg); + if (!game::scrVmPub->terminal_error) { + return; + } + goto error; + } + + abort_on_error = game::scrVmPub->terminal_error; + runtime_error_internal(abort_on_error ? game::CON_CHANNEL_LOGFILEONLY + : game::CON_CHANNEL_PARSERSCRIPT, + code_pos, index, msg); + if (abort_on_error) { + error: + if (!dialog_message) { + dialog_message = ""; + dialog_message_separator = ""; + } else { + dialog_message_separator = "\n"; + } + + game::Com_Error( + game::scrVmPub->terminal_error ? game::ERR_SCRIPT_DROP + : game::ERR_SCRIPT, + "\x15script runtime error\n(see console for details)\n%s%s%s", msg, + dialog_message_separator, dialog_message); + } +} + +void compile_error(unsigned int source_pos, const char* msg, ...) { + char line[1024]{}; + char text[1024]{}; + int col; + va_list argptr; + + va_start(argptr, msg); + vsnprintf_s(text, _TRUNCATE, msg, argptr); + va_end(argptr); + + if (game::scrVarPub->evaluate) { + if (!game::scrVarPub->error_message) { + game::scrVarPub->error_message = game::va("%s", text); + } + } else { + game::Scr_ShutdownAllocNode(); + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, "\n"); + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, + "******* script compile error *******\n"); + + if (!scr::developer_ || !scr::parser_pub_.sourceBuf) { + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, "%s\n", text); + line[0] = '\0'; + + game::Com_Printf(game::CON_CHANNEL_PARSERSCRIPT, + "************************************\n"); + game::Com_Error( + game::ERR_SCRIPT_DROP, + "\x15" + "script compile error\n%s\n%s\n(see console for details)\n", + text, line); + } else { + assert(scr::parser_pub_.sourceBuf); + + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, "%s: ", text); + scr::print_source_pos(game::CON_CHANNEL_PARSERSCRIPT, + scr::parser_pub_.scriptfilename, + scr::parser_pub_.sourceBuf, source_pos); + const auto line_number = scr::get_line_info(scr::parser_pub_.sourceBuf, + source_pos, &col, line); + game::Com_Error( + game::ERR_SCRIPT_DROP, + "\x15" + "script compile error\n%s\n%s(%d):\n %s\n(see console for details)\n", + text, scr::parser_pub_.scriptfilename, line_number, line); + } + } +} + +void compile_error2(const char* code_pos, const char* msg, ...) { + char line[1024]{}; + char text[1024]{}; + va_list argptr; + + assert(!game::scrVarPub->evaluate); + assert(game::Scr_IsInOpcodeMemory(code_pos)); + + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, "\n"); + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, + "******* script compile error *******\n"); + + va_start(argptr, msg); + vsnprintf_s(text, _TRUNCATE, msg, argptr); + va_end(argptr); + + game::Com_PrintError(game::CON_CHANNEL_PARSERSCRIPT, "%s: ", text); + + scr::print_prev_code_pos(game::CON_CHANNEL_PARSERSCRIPT, code_pos, 0); + + game::Com_Printf(game::CON_CHANNEL_PARSERSCRIPT, + "************************************\n"); + + scr::get_text_source_pos(scr::parser_pub_.sourceBuf, code_pos, line); + + game::Com_Error(game::ERR_SCRIPT_DROP, + "\x15" + "script compile error\n%s\n%s\n(see console for details)\n", + text, line); +} + +void mt_reset_stub() { + utils::hook::invoke(0x4DF3A0); + + scr::init_opcode_lookup(); + hunk::init_debug_memory(); +} + +void sl_shutdown_system_stub(unsigned int user) { + utils::hook::invoke(0x4B1260, user); + + scr::shutdown_opcode_lookup(); + hunk::shutdown_debug_memory(); +} + +void __declspec(naked) emit_thread_internal_stub() { + __asm { + pushad; + + push [esp + 0x20 + 0x8] // sourcePos; + call add_thread_start_opcode_pos; + add esp, 0x4; + + popad; + + cmp ds:0x158BFB8, 2; + + push 0x612477; + ret; + } +} +} // namespace + +class error final : public component_interface { +public: + void post_load() override { + std::vector> patches; + + const auto p = [&patches](const std::size_t a, void* b) { + patches.emplace_back(a, b); + }; + + utils::hook(0x43ACE0, scr::print_prev_code_pos, HOOK_JUMP) + .install() // hook* + ->quick(); + + utils::hook(0x612470, emit_thread_internal_stub, HOOK_JUMP) + .install() // hook* + ->quick(); + utils::hook::nop(0x612470 + 5, 2); + + utils::hook(0x4767A0, compile_error, HOOK_JUMP) + .install() // hook* + ->quick(); + utils::hook::nop(0x4767A0 + 5, 5); + + p(0x4F362D, scr::settings_stub); // ?? + p(0x4F3642, scr::settings_stub); // ?? + p(0x6042FB, scr::settings_stub); // Com_Init_Try_Block_Function + + p(0x4B0F07, mt_reset_stub); // Scr_BeginLoadScripts + + p(0x4D690B, sl_shutdown_system_stub); + + p(0x4FFEAA, scr::load_script_internal_stub); // ScriptCompile + p(0x46CDA9, scr::load_script_internal_stub); // Scr_LoadScript + + p(0x61602D, runtime_error); // VM_Notify + p(0x6194B6, runtime_error); // VM_ExecuteInternal + p(0x619FAE, runtime_error); // VM_Execute_0 + + p(0x609D8C, compile_error2); // Scr_CheckAnimsDefined + p(0x60AC86, compile_error2); // LinkThread + p(0x60AC9D, compile_error2); // ^^ + p(0x60ACB2, compile_error2); // ^^ + + p(0x6124FD, add_opcode_pos); // EmitThreadInternal + p(0x612506, add_opcode_pos); // EmitThreadInternal + + p(0x6120E2, add_opcode_pos); // EmitStatement + + p(0x612043, add_opcode_pos); // EmitBinaryEqualsOperatorExpression + p(0x612029, add_opcode_pos); // ^^ + + p(0x611FAA, add_opcode_pos); // EmitDecStatement + p(0x611F97, add_opcode_pos); // ^^ + + p(0x611ECA, add_opcode_pos); // EmitIncStatement + p(0x611EB7, add_opcode_pos); // ^^ + + p(0x611DEA, add_opcode_pos); // EmitAssignmentStatement + + p(0x611AB9, add_opcode_pos); // EmitArrayVariableRef + p(0x611AB1, add_opcode_pos); // ^^ + + p(0x611625, add_opcode_pos); // EmitSwitchStatement + + p(0x61145A, add_opcode_pos); // EmitEndOnStatement + p(0x61144E, add_opcode_pos); // ^^ + + p(0x61135B, add_opcode_pos); // EmitNotifyStatement + p(0x61134F, add_opcode_pos); // ^^ + p(0x611341, add_opcode_pos); // ^^ + p(0x61123B, add_opcode_pos); // ^^ + + p(0x6110DC, add_opcode_pos); // EmitWaittillmatchStatement + p(0x6110C5, add_opcode_pos); // ^^ + p(0x6110B7, add_opcode_pos); // ^^ + p(0x6110AB, add_opcode_pos); // ^^ + p(0x6110A3, add_opcode_pos); // ^^ + + p(0x610EAE, add_opcode_pos); // EmitWaittillStatement + p(0x610EA1, add_opcode_pos); // ^^ + p(0x610E95, add_opcode_pos); // ^^ + p(0x610E8D, add_opcode_pos); // ^^ + + p(0x610CB2, add_opcode_pos); // EmitForStatement + p(0x610C9E, add_opcode_pos); // ^^ + p(0x610AE9, add_opcode_pos); // ^^ + p(0x610AB2, remove_opcode_pos); // ^^ (EmitOpcode inlined?) + + p(0x6107F9, add_opcode_pos); // EmitWhileStatement + p(0x6107E5, add_opcode_pos); // ^^ + p(0x61069D, add_opcode_pos); // ^^ + p(0x610666, remove_opcode_pos); // ^^ (EmitOpcode inlined?) + + p(0x610475, add_opcode_pos); // EmitIfElseStatement + p(0x6103F1, add_opcode_pos); // ^^ + p(0x610359, add_opcode_pos); // ^^ + p(0x6102CD, add_opcode_pos); // ^^ + p(0x610296, remove_opcode_pos); // ^^ (EmitOpcode inlined?) + + p(0x6101B8, add_opcode_pos); // EmitIfStatement + p(0x610156, add_opcode_pos); // ^^ + p(0x61011E, remove_opcode_pos); // ^^ (EmitOpcode inlined?) + + p(0x61006E, add_opcode_pos); // EmitWaitStatement + p(0x610062, add_opcode_pos); // ^^ + p(0x61005A, add_opcode_pos); // ^^ + + p(0x60FF89, add_opcode_pos); // EmitReturnStatement + + p(0x60FB40, add_opcode_pos); // EmitOrEvalVariableExpression + + p(0x60FA9F, add_opcode_pos); // ?? + + p(0x60F9F0, add_opcode_pos); // ?? + p(0x60F9E8, add_opcode_pos); // ^^ + + p(0x60F991, add_opcode_pos); // ?? + + p(0x60F7C0, add_opcode_pos); // EmitContinueStatement + + p(0x60F690, add_opcode_pos); // EmitBreakStatement + + p(0x60F2B2, add_opcode_pos); // ?? + + p(0x60F24A, add_opcode_pos); // EmitWaittillFrameEnd + p(0x60F242, add_opcode_pos); // ^^ + + p(0x60EFDE, add_opcode_pos); // ?? + + p(0x60EEC7, add_opcode_pos); // ?? + + p(0x60EDA7, add_opcode_pos); // ?? + + p(0x60EC5B, add_opcode_pos); // EmitOrEvalPrimitiveExpressionList + + p(0x60EA1E, add_opcode_pos); // ?? + p(0x60E9AD, add_opcode_pos); // ^^ + + p(0x60E5E2, add_opcode_pos); // ?? + + p(0x60E595, add_opcode_pos); // ?? + + p(0x60E4CD, add_opcode_pos); // ?? + + p(0x60E3F5, add_opcode_pos); // ?? + + p(0x60E270, add_opcode_pos); // ?? + p(0x60E24D, add_opcode_pos); // ^^ + + p(0x60E210, add_opcode_pos); // ?? + p(0x60E1ED, add_opcode_pos); // ^^ + + p(0x60E155, add_opcode_pos); // ?? + + p(0x60DFB5, add_opcode_pos); // ?? + + p(0x60DE13, add_opcode_pos); // ?? + p(0x60DDDB, remove_opcode_pos); // ^^ + + p(0x60DC83, add_opcode_pos); // ?? + p(0x60DC4B, remove_opcode_pos); // ^^ + + p(0x60DB09, add_opcode_pos); // ?? + p(0x60DAFD, add_opcode_pos); // ^^ + + p(0x60D968, add_opcode_pos); // ?? + p(0x60D8E7, remove_opcode_pos); // ^^ + p(0x60D832, remove_opcode_pos); // ^^ + + p(0x60D787, add_opcode_pos); // EmitGetFunction + p(0x60D6C7, add_opcode_pos); // ^^ + p(0x60D5A7, add_opcode_pos); // ^^ + + p(0x60D3AF, add_opcode_pos); // EmitArrayVariable + p(0x60D3A3, add_opcode_pos); // ^^ + + p(0x60D33D, add_opcode_pos); // ?? + p(0x60D30B, add_opcode_pos); // ^^ + p(0x60D2E4, add_opcode_pos); // ^^ + + p(0x60D1A9, add_opcode_pos); // ?? + + utils::hook(0x60D109, add_opcode_pos, HOOK_JUMP) + .install() // hook* + ->quick(); // EmitClearArray + p(0x60D0F9, add_opcode_pos); // ^^ + + p(0x60D051, add_opcode_pos); // ?? + + p(0x60CFA6, add_opcode_pos); // EmitLocalVariableRef + + p(0x60CF50, add_opcode_pos); // EmitLocalVariableRef + + p(0x60CEB1, add_opcode_pos); // ?? + + p(0x60CE11, add_opcode_pos); // ?? + + p(0x60CD71, add_opcode_pos); // ?? + + p(0x60CCC9, add_opcode_pos); // ?? + + p(0x60CC19, add_opcode_pos); // ?? + + p(0x60CB69, add_opcode_pos); // ?? + + p(0x60CAB9, add_opcode_pos); // ?? + + p(0x60CA09, add_opcode_pos); // ?? + + p(0x60C964, add_opcode_pos); // ?? + + p(0x60C8A1, add_opcode_pos); // ?? + + p(0x60C801, add_opcode_pos); // ?? + + p(0x60C761, add_opcode_pos); // ?? + + p(0x60C6C0, add_opcode_pos); // ?? + + p(0x60C0BD, remove_opcode_pos); // EmitOpcode + p(0x60C07E, remove_opcode_pos); // ^^ + p(0x60C067, remove_opcode_pos); // ^^ + p(0x60C004, remove_opcode_pos); // ^^ + p(0x60BFE9, remove_opcode_pos); // ^^ + p(0x60BFBE, remove_opcode_pos); // ^^ + p(0x60BFA4, remove_opcode_pos); // ^^ + p(0x60BF8D, remove_opcode_pos); // ^^ + p(0x60BF6A, remove_opcode_pos); // ^^ + p(0x60BF54, remove_opcode_pos); // ^^ + p(0x60BEC7, remove_opcode_pos); // ^^ + p(0x60BEB0, remove_opcode_pos); // ^^ + p(0x60BE8F, remove_opcode_pos); // ^^ + p(0x60BE6E, remove_opcode_pos); // ^^ + + p(0x60AB02, add_opcode_pos); // EmitFunction + + p(0x60A703, add_opcode_pos); // EmitGetFloat inlined + p(0x60A6CE, add_opcode_pos); // EmitValue (?? inlined) + + p(0x60A653, add_opcode_pos); // EmitGetAnimation + + p(0x60A5F4, add_opcode_pos); // EmitGetVector + + p(0x60A582, add_opcode_pos); // EmitGetIString + + p(0x60A512, add_opcode_pos); // EmitGetString + + p(0x60A0FD, add_opcode_pos); // EmitGetInteger + p(0x60A0D0, add_opcode_pos); // ^^ + p(0x60A09C, add_opcode_pos); // ^^ + p(0x60A069, add_opcode_pos); // ^^ + p(0x60A033, add_opcode_pos); // ^^ + p(0x60A00F, add_opcode_pos); // ^^ + + for (const auto& patch : patches) { + utils::hook(patch.first, patch.second, HOOK_CALL).install()->quick(); + } + } +}; +} // namespace gsc + +REGISTER_COMPONENT(gsc::error) diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index 000bbff..e0cbe30 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -69,9 +69,6 @@ private: // Build os path stuff utils::hook::set(0x6300BF, 0xEB); - // Show intro (or not) - utils::hook::set(0x6035BD, 0x0); - // raw -> main utils::hook::set(0x50A0B2, 0x723390); diff --git a/src/client/component/player_movement.cpp b/src/client/component/player_movement.cpp index 1a7924f..fe10cd9 100644 --- a/src/client/component/player_movement.cpp +++ b/src/client/component/player_movement.cpp @@ -109,6 +109,59 @@ void pm_player_trace_stub(game::pmove_t* pm, game::trace_t* results, 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) { const auto* client = game::GetEntity(entref)->client; if (!client) { @@ -154,6 +207,13 @@ public: .install() ->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); register_dvars(); } @@ -172,6 +232,11 @@ public: "pm_playerCollision", true, game::DVAR_NONE, "Push intersecting players away from each other"); dvars::pm_elevators = game::Dvar_RegisterBool( "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 } }; diff --git a/src/client/component/steam_proxy.cpp b/src/client/component/steam_proxy.cpp index 2e11643..56d1865 100644 --- a/src/client/component/steam_proxy.cpp +++ b/src/client/component/steam_proxy.cpp @@ -2,7 +2,6 @@ #include "loader/component_loader.hpp" #include -#include #include #include @@ -14,28 +13,12 @@ #include "scheduler.hpp" namespace { -enum class ownership_state { - success, - unowned, - nosteam, - error, -}; - utils::binary_resource runner_file(RUNNER, "iw4sp-runner.exe"); - -bool is_disabled() { - static const auto disabled = utils::flags::has_flag("nosteam"); - return disabled; -} } // namespace class steam_proxy final : public component_interface { public: void post_load() override { - if (is_disabled()) { - return; - } - this->load_client(); this->clean_up_on_error(); @@ -63,9 +46,9 @@ public: private: utils::nt::library steam_client_module_{}; - steam::interface client_engine_ {}; - steam::interface client_user_ {}; - steam::interface client_utils_ {}; + steam::interface client_engine_{}; + steam::interface client_user_{}; + steam::interface client_utils_{}; void* steam_pipe_ = nullptr; void* global_user_ = nullptr; @@ -113,36 +96,25 @@ private: 14, this->steam_pipe_); // GetIClientUtils } - ownership_state start_mod(const std::string& title, - const std::size_t app_id) { + void start_mod(const std::string& title, const std::size_t app_id) { __try { - return this->start_mod_unsafe(title, app_id); + this->start_mod_unsafe(title, app_id); } __except (EXCEPTION_EXECUTE_HANDLER) { this->do_cleanup(); - return ownership_state::error; } } - ownership_state start_mod_unsafe(const std::string& title, - std::size_t app_id) { + void start_mod_unsafe(const std::string& title, std::size_t app_id) { if (!this->client_utils_ || !this->client_user_) - return ownership_state::nosteam; + return; if (!this->client_user_.invoke("BIsSubscribedApp", app_id)) { -#ifdef _DEBUG app_id = 480; // Spacewar -#else - return ownership_state::unowned; -#endif - } - - if (is_disabled()) { - return ownership_state::success; } this->client_utils_.invoke("SetAppIDForCurrentPipe", app_id, false); - char our_directory[MAX_PATH] = {0}; + char our_directory[MAX_PATH]{}; GetCurrentDirectoryA(sizeof(our_directory), our_directory); const auto path = runner_file.get_extracted_file(); @@ -160,8 +132,6 @@ private: this->client_user_.invoke("SpawnProcess", path.data(), cmd_line, our_directory, game_id.bits, title.data(), app_id, 0, 0, 0); - - return ownership_state::success; } void do_cleanup() { diff --git a/src/client/component/ui.cpp b/src/client/component/ui.cpp new file mode 100644 index 0000000..b280e6e --- /dev/null +++ b/src/client/component/ui.cpp @@ -0,0 +1,629 @@ +#include +#include "loader/component_loader.hpp" + +#include "botlib/l_precomp.hpp" + +#include + +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 struct KeywordHashEntry { + const char* keyword; + int (*func)(T*, int); +}; + +KeywordHashEntry** menu_parse_keyword_hash; + +std::vector 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 keyword_hash_key(const char* keyword) { + auto hash = 0; + for (auto i = 0; keyword[i]; ++i) { + hash += + (i + HASH_SEED) * std::tolower(static_cast(keyword[i])); + } + return (hash + (hash >> 8)) & (128 - 1); +} + +template +KeywordHashEntry* keyword_hash_find(KeywordHashEntry** table, + const char* keyword) { + auto hash = keyword_hash_key(keyword); + KeywordHashEntry* 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(horz_align); + item->window.rect.vertAlign = static_cast(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(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(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(g_load.menuList.menuCount) >= + std::extent_v) { + 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(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(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(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**>( + 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) diff --git a/src/client/component/ultra_wide.cpp b/src/client/component/ultra_wide.cpp index 83cecb6..610a514 100644 --- a/src/client/component/ultra_wide.cpp +++ b/src/client/component/ultra_wide.cpp @@ -25,32 +25,32 @@ void set_aspect_ratio() { __declspec(naked) void set_aspect_ratio_stub() { __asm { - mov eax, [eax + 0x10]; - cmp eax, 4; + mov eax, [eax + 0x10]; + cmp eax, 4; - mov dword ptr ds:0x1C91A68, edx; - mov dword ptr ds:0x1C91A6C, esi; - mov dword ptr ds:0x1C91A74, ecx; + mov dword ptr ds:0x1C91A68, edx; + mov dword ptr ds:0x1C91A6C, esi; + mov dword ptr ds:0x1C91A74, ecx; - ja default_case; - je custom_ratio; + ja default_case; + je custom_ratio; - push 0x50AE6C; - ret; + push 0x50AE6C; + ret; - default_case: - push 0x50AF6C; - ret; + default_case: + push 0x50AF6C; + ret; - custom_ratio: - pushad; - call set_aspect_ratio; - popad; + custom_ratio: + pushad; + call set_aspect_ratio; + popad; - mov eax, 1; // set widescreen to 1 + mov eax, 1; // set widescreen to 1 - push 0x50AF05; - ret; + push 0x50AF05; + ret; } } diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index af578f9..b38bf86 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -9,6 +9,9 @@ const game::dvar_t* pm_rocketJump = nullptr; const game::dvar_t* pm_rocketJumpScale = nullptr; const game::dvar_t* pm_playerCollision = 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_drawVersionX = nullptr; @@ -27,4 +30,9 @@ const game::dvar_t** sv_mapname = const game::dvar_t** version = reinterpret_cast(0x145D690); + +const game::dvar_t** com_developer = + reinterpret_cast(0x145D648); +const game::dvar_t** com_developer_script = + reinterpret_cast(0x145EC58); } // namespace dvars diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index 33c2339..a5511d1 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -9,6 +9,9 @@ extern const game::dvar_t* pm_rocketJump; extern const game::dvar_t* pm_rocketJumpScale; extern const game::dvar_t* pm_playerCollision; 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_drawVersionX; @@ -24,4 +27,7 @@ extern const game::dvar_t** g_specialops; extern const game::dvar_t** sv_mapname; extern const game::dvar_t** version; + +extern const game::dvar_t** com_developer; +extern const game::dvar_t** com_developer_script; } // namespace dvars diff --git a/src/client/game/engine/fast_critical_section.cpp b/src/client/game/engine/fast_critical_section.cpp index 7594088..71eac2b 100644 --- a/src/client/game/engine/fast_critical_section.cpp +++ b/src/client/game/engine/fast_critical_section.cpp @@ -5,28 +5,29 @@ namespace game::engine { fast_critical_section_scope_read::fast_critical_section_scope_read( - FastCriticalSection* cs) + FastCriticalSection* cs) noexcept : cs_(cs) { if (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_) { Sys_UnlockRead(this->cs_); } } fast_critical_section_scope_write::fast_critical_section_scope_write( - FastCriticalSection* cs) + FastCriticalSection* cs) noexcept : cs_(cs) { if (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_) { Sys_UnlockWrite(this->cs_); } diff --git a/src/client/game/engine/fast_critical_section.hpp b/src/client/game/engine/fast_critical_section.hpp index 6ddc6ad..6860bbf 100644 --- a/src/client/game/engine/fast_critical_section.hpp +++ b/src/client/game/engine/fast_critical_section.hpp @@ -3,8 +3,8 @@ namespace game::engine { class fast_critical_section_scope_read { public: - fast_critical_section_scope_read(FastCriticalSection* cs); - ~fast_critical_section_scope_read(); + fast_critical_section_scope_read(FastCriticalSection* cs) noexcept; + ~fast_critical_section_scope_read() noexcept; private: FastCriticalSection* cs_; @@ -12,8 +12,8 @@ private: class fast_critical_section_scope_write { public: - fast_critical_section_scope_write(FastCriticalSection* cs); - ~fast_critical_section_scope_write(); + fast_critical_section_scope_write(FastCriticalSection* cs) noexcept; + ~fast_critical_section_scope_write() noexcept; private: FastCriticalSection* cs_; diff --git a/src/client/game/engine/scoped_critical_section.cpp b/src/client/game/engine/scoped_critical_section.cpp index 7d3ad02..7718886 100644 --- a/src/client/game/engine/scoped_critical_section.cpp +++ b/src/client/game/engine/scoped_critical_section.cpp @@ -4,7 +4,7 @@ namespace game::engine { 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) { if (type == SCOPED_CRITSECT_NORMAL) { Sys_EnterCriticalSection(this->s_); @@ -17,35 +17,37 @@ scoped_critical_section::scoped_critical_section( Sys_LeaveCriticalSection(this->s_); this->is_scoped_release_ = true; } + 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_) { Sys_EnterCriticalSection(this->s_); + } } else { Sys_LeaveCriticalSection(this->s_); } } -void scoped_critical_section::enter_crit_sect() { +void scoped_critical_section::enter_crit_sect() noexcept { assert(!this->has_ownership_); this->has_ownership_ = true; Sys_EnterCriticalSection(this->s_); } -void scoped_critical_section::leave_crit_sect() { +void scoped_critical_section::leave_crit_sect() noexcept { assert(this->has_ownership_); this->has_ownership_ = false; 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_); const auto result = Sys_TryEnterCriticalSection(this->s_); @@ -53,11 +55,11 @@ bool scoped_critical_section::try_enter_crit_sect() { return result; } -bool scoped_critical_section::has_ownership() const { +bool scoped_critical_section::has_ownership() const noexcept { 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_; } } // namespace game::engine diff --git a/src/client/game/engine/scoped_critical_section.hpp b/src/client/game/engine/scoped_critical_section.hpp index 2b89178..aab3b29 100644 --- a/src/client/game/engine/scoped_critical_section.hpp +++ b/src/client/game/engine/scoped_critical_section.hpp @@ -3,15 +3,16 @@ namespace game::engine { class scoped_critical_section { public: - scoped_critical_section(CriticalSection s, ScopedCriticalSectionType type); - ~scoped_critical_section(); + scoped_critical_section(CriticalSection s, + ScopedCriticalSectionType type) noexcept; + ~scoped_critical_section() noexcept; - void enter_crit_sect(); - void leave_crit_sect(); - bool try_enter_crit_sect(); + void enter_crit_sect() noexcept; + void leave_crit_sect() noexcept; + bool try_enter_crit_sect() noexcept; - [[nodiscard]] bool has_ownership() const; - [[nodiscard]] bool is_scoped_release() const; + [[nodiscard]] bool has_ownership() const noexcept; + [[nodiscard]] bool is_scoped_release() const noexcept; private: CriticalSection s_; diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index e92ad50..b696f9f 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -81,6 +81,12 @@ void Sys_UnlockWrite(FastCriticalSection* critSect) { 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) { const static DWORD PC_Int_Parse_t = 0x62DF10; int result{}; @@ -116,4 +122,18 @@ int PC_Float_Parse(int handle, float* f) { 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 diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 9876654..0159e24 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -39,16 +39,25 @@ bool Sys_TryEnterCriticalSection(CriticalSection critSect); void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); void Sys_UnlockWrite(FastCriticalSection* critSect); +void Sys_SnapVector(float* v); int PC_Int_Parse(int handle, int* i); int PC_Float_Parse(int handle, float* f); +void Menu_FreeItemMemory(itemDef_s* item); + // Global definitions constexpr auto CMD_MAX_NESTING = 8; +constexpr auto MAX_POSSIBLE_LOCAL_CLIENTS = 1; + constexpr std::size_t MAX_LOCAL_CLIENTS = 1; 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 #include "symbols.hpp" diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 5715a4d..c5681a5 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -14,22 +14,342 @@ struct scr_entref_t { 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 { const char* actionString; - void(__cdecl* actionFunc)(scr_entref_t); + void (*actionFunc)(scr_entref_t); int type; }; struct BuiltinFunctionDef { const char* actionString; - void(__cdecl* actionFunc)(); + void (*actionFunc)(); 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 { VAR_STRING = 0x2, VAR_FLOAT = 0x5, @@ -490,12 +810,455 @@ struct GameWorldSp { 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 { const char* name; 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 { const char* value; const char* name; @@ -510,6 +1273,7 @@ union XAssetHeader { LocalizeEntry* localize; WeaponCompleteDef* weapon; RawFile* rawfile; + StringTable* stringTable; void* data; }; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 50cad0f..c9f709d 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -8,11 +8,16 @@ WEAK symbol Com_Printf{0x41BD20}; WEAK symbol Com_PrintWarning{0x406320}; WEAK symbol Com_PrintError{0x4C6980}; WEAK symbol Com_DPrintf{0x42B1F0}; +WEAK symbol Com_PrintMessage{ + 0x456B70}; WEAK symbol Com_Error{0x43DD90}; WEAK symbol Com_OpenLogFile{0x603030}; WEAK symbol Com_Compress{0x4316A0}; WEAK symbol Com_EventLoop{0x4987C0}; WEAK symbol Com_ServerPacketEvent{0x47FD30}; +WEAK symbol Com_BeginParseSession{0x4A5C90}; +WEAK symbol Com_EndParseSession{0x4D12C0}; +WEAK symbol Com_Parse{0x486600}; WEAK symbol va{0x4869F0}; @@ -31,6 +36,7 @@ WEAK symbol Sys_IsServerThread{0x4590E0}; WEAK symbol Sys_IsDatabaseThread{0x4C9380}; WEAK symbol Sys_SetValue{0x483310}; WEAK symbol Sys_Sleep{0x4CFBE0}; +WEAK symbol Sys_Error{0x40BFF0}; WEAK symbol BigShort{0x40E7E0}; WEAK symbol ShortNoSwap{0x4261A0}; @@ -94,7 +100,23 @@ WEAK symbol Scr_Error{0x4E9C50}; WEAK symbol Scr_ObjectError{0x470600}; WEAK symbol Scr_ParamError{ 0x42C880}; +WEAK symbol Scr_ShutdownAllocNode{0x486BC0}; +WEAK symbol Scr_CreateCanonicalFilename{ + 0x43A5E0}; + +WEAK symbol + FindVariable{0x4B78B0}; +WEAK symbol + RemoveVariable{0x4C2DD0}; +WEAK symbol FindObject{ + 0x49A980}; WEAK symbol GetEntity{0x4678C0}; +WEAK symbol + GetVariable{0x482290}; +WEAK symbol + GetNewVariable{0x4F1990}; +WEAK symbol GetObject{ + 0x4370B0}; WEAK symbol Scr_GetNumParam{0x4443F0}; WEAK symbol Scr_ClearOutParams{0x4A3A00}; @@ -110,6 +132,7 @@ WEAK symbol Scr_AddFloat{0x4986E0}; WEAK symbol Scr_GetType{0x464EE0}; WEAK symbol Scr_RegisterFunction{0x4F59C0}; WEAK symbol Scr_GetFunc{0x438E10}; +WEAK symbol Scr_IsInOpcodeMemory{0x47D1D0}; WEAK symbol @@ -120,17 +143,35 @@ WEAK symbol Scr_GetFunctionHandle{ WEAK symbol Scr_ExecThread{0x41A2C0}; WEAK symbol Scr_FreeThread{0x4C44A0}; +WEAK symbol ScriptParse{0x4956B0}; +WEAK symbol + ScriptCompile{0x4FFDA0}; + +WEAK symbol TempMalloc{0x4EA7C0}; + // SL WEAK symbol SL_ConvertToString{0x40E990}; WEAK symbol SL_AddRefToString{0x4C4BD0}; WEAK symbol SL_RemoveRefToString{0x4698E0}; +// NET WEAK symbol NET_AdrToString{0x4BF490}; WEAK symbol NET_ErrorString{0x430390}; // Memory WEAK symbol Hunk_AllocateTempMemory{0x492DF0}; WEAK symbol Hunk_AllocAlignInternal{0x486C40}; +WEAK symbol Hunk_AllocateTempMemoryHigh{0x403B40}; +WEAK symbol + Hunk_UserCreate{0x4F1A10}; +WEAK symbol Hunk_UserAlloc{ + 0x469410}; + +WEAK symbol free_expression{0x436260}; + +WEAK symbol _free{0x674BC5}; // Zone WEAK symbol Z_VirtualAllocInternal{0x4D9CF0}; @@ -150,6 +191,7 @@ WEAK symbol DB_ReadRawFile{ 0x46DA60}; +WEAK symbol DB_GetRawFileLen{0x4D2E60}; // FS WEAK symbol _FS_ReadFile{0x4A5480}; @@ -215,16 +257,44 @@ WEAK symbol I_strnicmp{0x491E60}; WEAK symbol Field_Clear{0x45C350}; +// String +WEAK symbol StringTable_HashString{0x498080}; + +// Vec3 +WEAK symbol Vec3Scale{ + 0x429220}; + // Variables WEAK symbol cmd_args{0x144FED0}; WEAK symbol sv_cmd_args{0x145ABA0}; WEAK symbol g_entities{0xEAAC38}; WEAK symbol g_clients{0x10911E8}; +WEAK symbol scrVmPub{0x190DDF0}; +WEAK symbol scrVarPub{0x18E7508}; +WEAK symbol scrCompilePub{0x156BF88}; +WEAK symbol scrCompileGlob{0x158CFC8}; +WEAK symbol scrAnimPub{0x156BB68}; + +WEAK symbol g_loadedImpureScript{0x168F308}; +WEAK symbol g_EndPos{0x1912598}; + +WEAK symbol g_largeLocalBuf{0x195AAF8}; + +WEAK symbol g_largeLocalPos{0x1963998}; +WEAK symbol g_maxLargeLocalPos{0x195AAFC}; + +WEAK symbol g_largeLocalRightPos{0x195AAE8}; +WEAK symbol g_minLargeLocalRightPos{0x195AB00}; + +WEAK symbol g_dwTlsIndex{0x1BFC750}; + WEAK symbol com_frameTime{0x145EC7C}; WEAK symbol cin_skippable{0x73264C}; +WEAK symbol com_fixedConsolePosition{0x145EC10}; + WEAK symbol g_consoleField{0x88C700}; WEAK symbol conDrawInputGlob{0x86E788}; WEAK symbol con{0x86ED88}; @@ -243,17 +313,9 @@ WEAK symbol ip_socket{0x1A040C8}; WEAK symbol sourceFiles{0x7440E8}; WEAK symbol numtokens{0x7441F0}; +WEAK symbol uiInfoArray{0x1920470}; + WEAK symbol DB_GetXAssetSizeHandlers{0x733408}; WEAK symbol DB_XAssetPool{0x7337F8}; WEAK symbol g_poolSize{0x733510}; - -WEAK symbol g_largeLocalBuf{0x195AAF8}; - -WEAK symbol g_largeLocalPos{0x1963998}; -WEAK symbol g_maxLargeLocalPos{0x195AAFC}; - -WEAK symbol g_largeLocalRightPos{0x195AAE8}; -WEAK symbol g_minLargeLocalRightPos{0x195AB00}; - -WEAK symbol g_dwTlsIndex{0x1BFC750}; } // namespace game diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index ae151cb..6aab782 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -21,15 +21,25 @@ #undef min #endif -#include +#undef GetObject + #include #include #include + +#include +#include +#include +#include #include #include +#include #include #include -#include +#include +#include +#include +#include #pragma comment(lib, "ntdll.lib") #pragma comment(lib, "Crypt32.lib") diff --git a/src/common/utils/csv.cpp b/src/common/utils/csv.cpp new file mode 100644 index 0000000..483a135 --- /dev/null +++ b/src/common/utils/csv.cpp @@ -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 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 diff --git a/src/common/utils/csv.hpp b/src/common/utils/csv.hpp new file mode 100644 index 0000000..05565ad --- /dev/null +++ b/src/common/utils/csv.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include + +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> 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 diff --git a/src/common/utils/nt.cpp b/src/common/utils/nt.cpp index fed6dce..36a7b56 100644 --- a/src/common/utils/nt.cpp +++ b/src/common/utils/nt.cpp @@ -57,10 +57,9 @@ std::vector library::get_section_headers() const { for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section) { - if (section) + if (section) { headers.push_back(section); - else - OutputDebugStringA("There was an invalid section :O"); + } } return headers; @@ -97,8 +96,9 @@ bool library::is_valid() const { } std::string library::get_name() const { - if (!this->is_valid()) + if (!this->is_valid()) { return {}; + } auto path = this->get_path(); const auto pos = path.find_last_of("/\\"); @@ -109,8 +109,9 @@ std::string library::get_name() const { } std::string library::get_path() const { - if (!this->is_valid()) + if (!this->is_valid()) { return {}; + } char name[MAX_PATH] = {0}; GetModuleFileNameA(this->module_, name, sizeof name); @@ -119,8 +120,9 @@ std::string library::get_path() const { } std::string library::get_folder() const { - if (!this->is_valid()) + if (!this->is_valid()) { return {}; + } const auto path = std::filesystem::path(this->get_path()); return path.parent_path().generic_string(); @@ -202,6 +204,15 @@ std::string library::get_dll_directory() { return directory; } +bool is_wine() { + static const auto has_wine_export = []() -> bool { + const library ntdll("ntdll.dll"); + return ntdll.get_proc("wine_get_version"); + }(); + + return has_wine_export; +} + void raise_hard_exception() { int data = false; const library ntdll("ntdll.dll"); diff --git a/src/common/utils/nt.hpp b/src/common/utils/nt.hpp index f55fb7e..2b4f311 100644 --- a/src/common/utils/nt.hpp +++ b/src/common/utils/nt.hpp @@ -101,6 +101,8 @@ private: HMODULE module_; }; +bool is_wine(); + __declspec(noreturn) void raise_hard_exception(); std::string load_resource(int id);