diff --git a/deps/extra/gsc-tool/interface.cpp b/deps/extra/gsc-tool/interface.cpp index fe4482c..fa95b94 100644 --- a/deps/extra/gsc-tool/interface.cpp +++ b/deps/extra/gsc-tool/interface.cpp @@ -11,18 +11,8 @@ namespace gsc return compiler; } - std::unique_ptr decompiler() - { - return std::make_unique(); - } - std::unique_ptr assembler() { return std::make_unique(); } - - std::unique_ptr disassembler() - { - return std::make_unique(); - } } diff --git a/deps/extra/gsc-tool/interface.hpp b/deps/extra/gsc-tool/interface.hpp index 133e6ae..4d98ad3 100644 --- a/deps/extra/gsc-tool/interface.hpp +++ b/deps/extra/gsc-tool/interface.hpp @@ -3,7 +3,5 @@ namespace gsc { std::unique_ptr compiler(); - std::unique_ptr decompiler(); std::unique_ptr assembler(); - std::unique_ptr disassembler(); } diff --git a/src/game/game.cpp b/src/game/game.cpp index ea5a1b3..a6b4263 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -13,6 +13,7 @@ namespace game DB_LoadXAssets_t DB_LoadXAssets; DB_FindXAssetHeader_t DB_FindXAssetHeader; + DB_IsXAssetDefault_t DB_IsXAssetDefault; Dvar_RegisterBool_t Dvar_RegisterBool; Dvar_RegisterString_t Dvar_RegisterString; @@ -46,6 +47,12 @@ namespace game Sys_ShowConsole_t Sys_ShowConsole; Sys_Error_t Sys_Error; Sys_Milliseconds_t Sys_Milliseconds; + Sys_Sleep_t Sys_Sleep; + + PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug; + + Hunk_AllocateTempMemoryHighInternal_t Hunk_AllocateTempMemoryHighInternal; + Hunk_FreeTempMemory_t Hunk_FreeTempMemory; VM_Notify_t VM_Notify; @@ -78,6 +85,7 @@ namespace game Com_Quit_f_t Com_Quit_f; FS_Printf_t FS_Printf; + FS_ReadFile_t FS_ReadFile; player_die_t player_die; @@ -127,6 +135,8 @@ namespace game int* dvarCount; dvar_t** sortedDvars; + FastCriticalSection* db_hashCritSect; + int Vec4Compare(const float* a, const float* b) { return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; @@ -528,6 +538,18 @@ namespace game return id == *threadId[THREAD_CONTEXT_SERVER]; } + void Sys_LockRead(FastCriticalSection* critSect) + { + InterlockedIncrement(&critSect->readCount); + while (critSect->writeCount) Sys_Sleep(0); + } + + void Sys_UnlockRead(FastCriticalSection* critSect) + { + assert(critSect->readCount > 0); + InterlockedDecrement(&critSect->readCount); + } + void FS_FCloseFile(int h) { reinterpret_cast(SELECT_VALUE(0x415160, 0x5AF170))(h); @@ -606,6 +628,38 @@ namespace game { assert(*fs_searchpaths); } + + XAssetEntry* db_find_x_asset_entry(XAssetType type_, const char* name) + { + static DWORD func = SELECT_VALUE(0x585400, 0x4CA450); + XAssetEntry* result{}; + + __asm + { + pushad + push name + mov edi, type_ + call func + add esp, 0x4 + mov result, eax + popad + } + + return result; + } + + XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name) + { + return db_find_x_asset_entry(type, name); + } + + int DB_XAssetExists(XAssetType type, const char* name) + { + Sys_LockRead(db_hashCritSect); + auto* asset_entry = DB_FindXAssetEntry(type, name); + Sys_UnlockRead(db_hashCritSect); + return asset_entry != nullptr; + } } launcher::mode mode = launcher::mode::none; @@ -642,6 +696,7 @@ namespace game native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020)); native::DB_FindXAssetHeader = native::DB_FindXAssetHeader_t(SELECT_VALUE(0x4FF000, 0x4CA620)); + native::DB_IsXAssetDefault = native::DB_IsXAssetDefault_t(SELECT_VALUE(0x4868E0, 0x4CA800)); native::Dvar_RegisterBool = native::Dvar_RegisterBool_t(SELECT_VALUE(0x4914D0, 0x5BE9F0)); native::Dvar_RegisterString = native::Dvar_RegisterString_t(SELECT_VALUE(0x5197F0, 0x5BEC90)); @@ -676,6 +731,12 @@ namespace game native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590)); native::Sys_Error = native::Sys_Error_t(SELECT_VALUE(0x490D90, 0x5CC3B0)); native::Sys_Milliseconds = native::Sys_Milliseconds_t(SELECT_VALUE(0x4A1610, 0x5CE740)); + native::Sys_Sleep = native::Sys_Sleep_t(SELECT_VALUE(0x438600, 0x55F460)); + + native::PMem_AllocFromSource_NoDebug = native::PMem_AllocFromSource_NoDebug_t(SELECT_VALUE(0x449E50, 0x5C15C0)); + + native::Hunk_AllocateTempMemoryHighInternal = native::Hunk_AllocateTempMemoryHighInternal_t(SELECT_VALUE(0x517870, 0x5B6C60)); + native::Hunk_FreeTempMemory = native::Hunk_FreeTempMemory_t(SELECT_VALUE(0x434A40, 0x5B6F90)); native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720)); @@ -716,6 +777,7 @@ namespace game native::Com_Quit_f = native::Com_Quit_f_t(SELECT_VALUE(0x4F48B0, 0x5556B0)); native::FS_Printf = native::FS_Printf_t(SELECT_VALUE(0x421E90, 0x5AF7C0)); + native::FS_ReadFile = native::FS_ReadFile_t(SELECT_VALUE(0x4D8DF0, 0x5B1FB0)); native::player_die = native::player_die_t(SELECT_VALUE(0x0, 0x503460)); @@ -770,5 +832,7 @@ namespace game native::dvarCount = reinterpret_cast(SELECT_VALUE(0x1C42398, 0x59CCDD8)); native::sortedDvars = reinterpret_cast(SELECT_VALUE(0x1C423C0, 0x59CCE00)); + + native::db_hashCritSect = reinterpret_cast(SELECT_VALUE(0xFA9E7C, 0x18596E4)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index a7054eb..3a9e08e 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -29,6 +29,9 @@ namespace game typedef XAssetHeader (*DB_FindXAssetHeader_t)(XAssetType type, const char* name, int allowCreateDefault); extern DB_FindXAssetHeader_t DB_FindXAssetHeader; + typedef int (*DB_IsXAssetDefault_t)(XAssetType type, const char* name); + extern DB_IsXAssetDefault_t DB_IsXAssetDefault; + typedef const dvar_t* (*Dvar_RegisterBool_t)(const char* dvarName, bool value, unsigned __int16 flags, const char* description); extern Dvar_RegisterBool_t Dvar_RegisterBool; @@ -97,6 +100,18 @@ namespace game typedef int (*Sys_Milliseconds_t)(); extern Sys_Milliseconds_t Sys_Milliseconds; + typedef void (*Sys_Sleep_t)(int msec); + extern Sys_Sleep_t Sys_Sleep; + + typedef void* (*PMem_AllocFromSource_NoDebug_t)(unsigned int size, unsigned int alignment, unsigned int type, int source); + extern PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug; + + typedef void* (*Hunk_AllocateTempMemoryHighInternal_t)(unsigned int size); + extern Hunk_AllocateTempMemoryHighInternal_t Hunk_AllocateTempMemoryHighInternal; + + typedef void (*Hunk_FreeTempMemory_t)(void* buf); + extern Hunk_FreeTempMemory_t Hunk_FreeTempMemory; + typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); extern VM_Notify_t VM_Notify; @@ -158,6 +173,9 @@ namespace game typedef void (*FS_Printf_t)(int h, const char* fmt, ...); extern FS_Printf_t FS_Printf; + typedef int (*FS_ReadFile_t)(const char* qpath, char** buffer); + extern FS_ReadFile_t FS_ReadFile; + typedef void (*player_die_t)(gentity_s* self, const gentity_s* inflictor, gentity_s* attacker, int damage, int meansOfDeath, const Weapon* iWeapon, bool isAlternate, const float* vDir, const hitLocation_t hitLoc, int psTimeOffset); extern player_die_t player_die; @@ -210,6 +228,8 @@ namespace game extern int* dvarCount; extern dvar_t** sortedDvars; + extern FastCriticalSection* db_hashCritSect; + // Global Definitions & Functions constexpr auto JUMP_LAND_SLOWDOWN_TIME = 1800; @@ -309,6 +329,9 @@ namespace game int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread); int FS_CreatePath(char* OSPath); void FS_CheckFileSystemStarted(); + + XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name); + int DB_XAssetExists(XAssetType type, const char* name); } bool is_mp(); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index fb937aa..2748a0d 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -389,10 +389,48 @@ namespace game unsigned char* bytecode; }; + struct RawFile + { + const char* name; + int compressedLen; + int len; + const char* buffer; + }; + union XAssetHeader { void* data; ScriptFile* scriptfile; + RawFile* rawfile; + }; + + struct XAsset + { + XAssetType type; + XAssetHeader header; + }; + + struct XAssetEntry + { + XAsset asset; + char zoneIndex; + volatile char inuseMask; + bool printedMissingAsset; + unsigned __int16 nextHash; + unsigned __int16 nextOverride; + }; + + struct TempPriority + { + void* threadHandle; + int oldPriority; + }; + + struct FastCriticalSection + { + volatile long readCount; + volatile long writeCount; + TempPriority tempPriority; }; enum errorParm_t @@ -437,6 +475,14 @@ namespace game LOCMSG_NOERR, }; + enum PMem_Source + { + PMEM_SOURCE_EXTERNAL, + PMEM_SOURCE_DATABASE, + PMEM_SOURCE_MOVIE, + PMEM_SOURCE_SCRIPT, + }; + struct cmd_function_t { cmd_function_t* next; diff --git a/src/module/gsc/script_error.cpp b/src/module/gsc/script_error.cpp index 7b4fa61..5053e50 100644 --- a/src/module/gsc/script_error.cpp +++ b/src/module/gsc/script_error.cpp @@ -51,7 +51,7 @@ namespace gsc } else { - unknown_function_error = std::format("unknown script '{}'", scripting::current_file); + unknown_function_error = std::format("unknown script '{}'.gsc", scripting::current_file); } } diff --git a/src/module/gsc/script_loading.cpp b/src/module/gsc/script_loading.cpp index e60ec04..c1da12a 100644 --- a/src/module/gsc/script_loading.cpp +++ b/src/module/gsc/script_loading.cpp @@ -1,12 +1,180 @@ #include +#include #include "game/game.hpp" #include "script_loading.hpp" +#include "module/log_file.hpp" +#include "module/scripting.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + namespace gsc { + namespace + { + auto compiler = ::gsc::compiler(); + auto assembler = ::gsc::assembler(); + + utils::memory::allocator script_file_allocator; + + std::unordered_map loaded_scripts; + + void clear() + { + loaded_scripts.clear(); + script_file_allocator.clear(); + } + + bool read_script_file(const std::string& name, std::string* data) + { + char* buffer{}; + const auto file_len = game::native::FS_ReadFile(name.data(), &buffer); + if (file_len > 0 && buffer) + { + data->append(buffer, file_len); + game::native::Hunk_FreeTempMemory(buffer); + return true; + } + + return false; + } + + game::native::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) + { + if (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end()) + { + return itr->second; + } + + std::string source_buffer; + if (!read_script_file(real_name + ".gsc", &source_buffer)) + { + return nullptr; + } + + std::vector data; + data.assign(source_buffer.begin(), source_buffer.end()); + + try + { + compiler->compile(real_name, data); + } + catch (const std::exception& ex) + { + log_file::info("*********** script compile error *************\n"); + log_file::info("failed to compile '%s':\n%s", real_name.data(), ex.what()); + log_file::info("**********************************************\n"); + return nullptr; + } + + auto assembly = compiler->output(); + + try + { + assembler->assemble(real_name, assembly); + } + catch (const std::exception& ex) + { + log_file::info("*********** script compile error *************\n"); + log_file::info("failed to assemble '%s':\n%s", real_name.data(), ex.what()); + log_file::info("**********************************************\n"); + return nullptr; + } + + const auto script_file_ptr = static_cast(script_file_allocator.allocate(sizeof(game::native::ScriptFile))); + script_file_ptr->name = file_name; + + const auto stack = assembler->output_stack(); + script_file_ptr->len = static_cast(stack.size()); + + const auto script = assembler->output_script(); + script_file_ptr->bytecodeLen = static_cast(script.size()); + + const auto compressed = xsk::utils::zlib::compress(stack); + const auto stack_size = compressed.size(); + const auto byte_code_size = script.size() + 1; + + script_file_ptr->buffer = static_cast(game::native::Hunk_AllocateTempMemoryHighInternal(stack_size)); + std::memcpy(const_cast(script_file_ptr->buffer), compressed.data(), compressed.size()); + + script_file_ptr->bytecode = static_cast(game::native::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 0, game::native::PMEM_SOURCE_SCRIPT)); + std::memcpy(script_file_ptr->bytecode, script.data(), script.size()); + + script_file_ptr->compressedLen = static_cast(compressed.size()); + + loaded_scripts[real_name] = script_file_ptr; + + return script_file_ptr; + } + + std::string get_script_file_name(const std::string& name) + { + const auto id = xsk::gsc::iw5::resolver::token_id(name); + if (!id) + { + return name; + } + + return std::to_string(id); + } + + int db_is_x_asset_default(game::native::XAssetType type, const char* name) + { + if (loaded_scripts.contains(name)) + { + return 0; + } + + return game::native::DB_IsXAssetDefault(type, name); + } + } + game::native::ScriptFile* find_script(game::native::XAssetType type, const char* name, int allow_create_default) { + std::string real_name = name; + const auto id = static_cast(std::strtol(name, nullptr, 10)); + if (id) + { + real_name = xsk::gsc::iw5::resolver::token_name(id); + } + + auto* script = load_custom_script(name, real_name); + if (script) + { + return script; + } + return game::native::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile; } + + class script_loading final : public module + { + public: + void post_load() override + { + // ProcessScript + utils::hook(SELECT_VALUE(0x44685E, 0x56B13E), find_script, HOOK_CALL).install()->quick(); + utils::hook(SELECT_VALUE(0x446868, 0x56B148), db_is_x_asset_default, HOOK_CALL).install()->quick(); + + scripting::on_shutdown([](int free_scripts) -> void + { + if (free_scripts) + { + xsk::gsc::iw5::resolver::cleanup(); + clear(); + } + }); + } + }; } + +REGISTER_MODULE(gsc::script_loading) diff --git a/src/module/patches.cpp b/src/module/patches.cpp index dcc464c..9bd843c 100644 --- a/src/module/patches.cpp +++ b/src/module/patches.cpp @@ -37,7 +37,7 @@ private: utils::hook::set(0x663B5A, 0xEB); utils::hook::set(0x663C54, 0xEB); - // archive "name" dvar + // Archive "name" dvar utils::hook::set(0x4296F9, game::native::DVAR_ARCHIVE); utils::hook(0x44C640, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick(); @@ -48,10 +48,17 @@ private: // Note: on SP the max value is already 1000 utils::hook(0x55411F, &dvar_register_com_max_fps, HOOK_CALL).install()->quick(); - // archive "name" dvar + // Archive "name" dvar utils::hook::set(0x492C82, game::native::DVAR_USERINFO | game::native::DVAR_ARCHIVE); utils::hook(0x5C9980, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick(); + + // Unpure client detected + utils::hook::set(0x57228C, 0xEB); + + // Allow any IWD file to be loaded + utils::hook::nop(0x5B090F, 6); + utils::hook::nop(0x5B092C, 6); } void patch_dedi() const diff --git a/src/module/scripting.cpp b/src/module/scripting.cpp index 250fd7d..6ee7f4e 100644 --- a/src/module/scripting.cpp +++ b/src/module/scripting.cpp @@ -24,6 +24,8 @@ namespace scripting std::uint32_t current_file_id = 0; std::string current_script_file; + std::vector> shutdown_callbacks; + std::string get_token(unsigned int id) { return find_token(id); @@ -93,6 +95,22 @@ namespace scripting utils::hook::invoke(SELECT_VALUE(0x446850, 0x56B130), filename); } + + void g_shutdown_game_stub(int free_scripts) + { + utils::hook::invoke(SELECT_VALUE(0x528A90, 0x50C100), free_scripts); + + if (free_scripts) + { + script_function_table_sort.clear(); + script_function_table.clear(); + } + + for (const auto& callback : shutdown_callbacks) + { + callback(free_scripts); + } + } } std::string get_token(unsigned int id) @@ -100,6 +118,11 @@ namespace scripting return find_token(id); } + void on_shutdown(const std::function& callback) + { + shutdown_callbacks.push_back(callback); + } + class scripting_class final : public module { public: @@ -131,6 +154,9 @@ namespace scripting utils::hook(SELECT_VALUE(0x44690A, 0x56B1EA), &scr_set_thread_position, HOOK_CALL).install()->quick(); utils::hook(SELECT_VALUE(0x4232A8, 0x561748), &process_script, HOOK_CALL).install()->quick(); + + utils::hook(SELECT_VALUE(0x651E1B, 0x573C82), &g_shutdown_game_stub, HOOK_CALL).install()->quick(); + utils::hook(SELECT_VALUE(0x651ECC, 0x573D3A), &g_shutdown_game_stub, HOOK_CALL).install()->quick(); } void pre_destroy() override diff --git a/src/module/scripting.hpp b/src/module/scripting.hpp index 2f2a480..5a1dd29 100644 --- a/src/module/scripting.hpp +++ b/src/module/scripting.hpp @@ -9,4 +9,6 @@ namespace scripting extern std::string current_file; std::string get_token(unsigned int id); + + void on_shutdown(const std::function& callback); } diff --git a/src/std_include.hpp b/src/std_include.hpp index 2f1f3b2..248a7c4 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include