diff --git a/README.md b/README.md index 9a5b352c..b805c1f7 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,28 @@
-## Download +## Installation -**NOTE**: Cracked/Pirated versions of the game are NOT compatible with this mod, if you run such a version and have issues/crashes launching the client refer to [this issue](https://github.com/fedddddd/h2-mod/issues/111). +**NOTE**: Cracked/Pirated versions of the game are **NOT** compatible with this mod, if you run such a version and have issues/crashes when running the client read **Step 2**. -- **[Click here to get the latest release](https://ci.appveyor.com/api/projects/fedddddd/h2-mod/artifacts/build%2Fbin%2Fx64%2FRelease%2Fh2-mod.exe?branch=develop&job=Environment%3A%20APPVEYOR_BUILD_WORKER_IMAGE%3DVisual%20Studio%202022%2C%20PREMAKE_ACTION%3Dvs2022%2C%20CI%3D1%3B%20Configuration%3A%20Release)** -- **You will need to drop this in your Call of Duty: Modern Warfare 2 Campaign Remastered installation folder. If you don't have Call of Duty: Modern Warfare 2 Campaign Remastered, get those game files first.** +1. Download the latest **[release](https://github.com/fedddddd/h2-mod/releases/latest/download/h2-mod.exe)** or **[develop](https://ci.appveyor.com/api/projects/fedddddd/h2-mod/artifacts/build%2Fbin%2Fx64%2FRelease%2Fh2-mod.exe?branch=develop&job=Environment%3A%20APPVEYOR_BUILD_WORKER_IMAGE%3DVisual%20Studio%202022%2C%20PREMAKE_ACTION%3Dvs2022%2C%20CI%3D1%3B%20Configuration%3A%20Release)** build +2. Drop the file in your **Call of Duty: Modern Warfare 2 Campaign Remastered** installation folder. + If you don't have the game installed (or own a cracked/pirated copy of it) you can download it for free from the official **Battle.Net** servers using [Battle.Net Installer](https://github.com/barncastle/Battle.Net-Installer) and executing this command: + + ``` + .\bnetinstaller.exe --prod lazr --uid lazarus --lang enUS --dir "YOUR INSTALL PATH" + ``` + + **Make sure to replace "YOUR INSTALL PATH" with an actual installation path of your choice.** +3. Run **h2-mod.exe** and make sure you press **"YES"** when asked whether to install updates. + +## Common issues + +- If you get crashes that show errors like **"Create2DTexture(...) failed ..."** or **"IDXGISwapChain::Present failed: ..."** when loading certain maps try: + * Disabling shader preloading + * Lowering graphics settings + * Freeing up RAM (close programs) + * Updating your GPU drivers ## Compile from source @@ -24,14 +40,12 @@ - Update the submodules and run `premake5 vs2019` or simply use the delivered `generate.bat`. - Build via solution file in `build\h2-mod.sln`. -### Premake arguments + ### Premake arguments -| Argument | Description | -|:----------------------------|:-----------------------------------------------| -| `--copy-to=PATH` | Optional, copy the EXE to a custom folder after build, define the path here if wanted. | -| `--dev-build` | Enable development builds of the client. | - -
+ | Argument | Description | + |:----------------------------|:-----------------------------------------------| + | `--copy-to=PATH` | Optional, copy the EXE to a custom folder after build, define the path here if wanted. | + | `--dev-build` | Enable development builds of the client. | ## Disclaimer diff --git a/data/ui_scripts/mods/loading.lua b/data/ui_scripts/mods/loading.lua index c83ea272..7918d7f0 100644 --- a/data/ui_scripts/mods/loading.lua +++ b/data/ui_scripts/mods/loading.lua @@ -2,12 +2,12 @@ game:addlocalizedstring("MENU_MODS", "MODS") game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.") game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3") -game:addlocalizedstring("LUA_MENU_OPEN_STORE", "Open store") -game:addlocalizedstring("LUA_MENU_OPEN_STORE_DESC", "Download and install mods.") game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^3&&1") game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods") game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload") game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.") +game:addlocalizedstring("LUA_MENU_WORKSHOP", "Workshop") +game:addlocalizedstring("LUA_MENU_WORKSHOP_DESC", "Download and install mods.") function createdivider(menu, text) local element = LUI.UIElement.new( { @@ -26,7 +26,10 @@ function createdivider(menu, text) title_bar_text = Engine.ToUpperCase(text) })) + element.text = element:getFirstChild():getFirstChild():getNextSibling() + menu.list:addElement(element) + return element end function string:truncate(length) @@ -48,12 +51,17 @@ LUI.addmenubutton("main_campaign", { function getmodname(path) local name = path - local desc = Engine.Localize("@LUA_MENU_MOD_DESC_DEFAULT", name) + game:addlocalizedstring(name, name) + game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") + local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name) local infofile = path .. "/info.json" if (io.fileexists(infofile)) then pcall(function() local data = json.decode(io.readfile(infofile)) + game:addlocalizedstring(data.description, data.description) + game:addlocalizedstring(data.author, data.author) + game:addlocalizedstring(data.version, data.version) desc = Engine.Localize("@LUA_MENU_MOD_DESC", data.description, data.author, data.version) name = data.name @@ -63,7 +71,7 @@ function getmodname(path) return name, desc end -LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) +LUI.MenuBuilder.registerType("mods_menu", function(a1) local menu = LUI.MenuTemplate.new(a1, { menu_title = "@MENU_MODS", exclusiveController = 0, @@ -72,20 +80,21 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) showTopRightSmallBar = true }) - menu:AddButton("@LUA_MENU_OPEN_STORE", function() - if (LUI.MenuBuilder.m_types_build["mod_store_menu"]) then - LUI.FlowManager.RequestAddMenu(nil, "mod_store_menu") + menu:AddButton("@LUA_MENU_WORKSHOP", function() + if (LUI.MenuBuilder.m_types_build["mods_workshop_menu"]) then + LUI.FlowManager.RequestAddMenu(nil, "mods_workshop_menu") end end, nil, true, nil, { - desc_text = Engine.Localize("@LUA_MENU_OPEN_STORE_DESC") + desc_text = Engine.Localize("@LUA_MENU_WORKSHOP_DESC") }) local modfolder = game:getloadedmod() if (modfolder ~= "") then - createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", getmodname(modfolder):truncate(24))) + local name = getmodname(modfolder) + createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", name:truncate(24))) menu:AddButton("@LUA_MENU_UNLOAD", function() - game:executecommand("unloadmod") + Engine.Exec("unloadmod") end, nil, true, nil, { desc_text = Engine.Localize("@LUA_MENU_UNLOAD_DESC") }) @@ -102,7 +111,7 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) if (mods[i] ~= modfolder) then game:addlocalizedstring(name, name) menu:AddButton(name, function() - game:executecommand("loadmod " .. mods[i]) + Engine.Exec("loadmod " .. mods[i]) end, nil, true, nil, { desc_text = desc }) @@ -121,4 +130,4 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) return menu -end +end) diff --git a/data/ui_scripts/patches/__init__.lua b/data/ui_scripts/patches/__init__.lua new file mode 100644 index 00000000..9002be83 --- /dev/null +++ b/data/ui_scripts/patches/__init__.lua @@ -0,0 +1,27 @@ +local maps = { + "af_caves", + "af_chase", + "airport", + "arcadia", + "boneyard", + "cliffhanger", + "contingency", + "dc_whitehouse", + "dcburning", + "dcemp", + "ending", + "estate", + "favela", + "favela_escape", + "gulag", + "invasion", + "oilrig", + "roadkill", + "trainer", + "museum", +} + +for i = 1, #maps do + local string = "LUA_MENU_SP_LOCATION_" .. maps[i]:upper() + game:addlocalizedstring(string, string) +end diff --git a/data/ui_scripts/settings/__init__.lua b/data/ui_scripts/settings/__init__.lua index 00cd8125..26b798ac 100644 --- a/data/ui_scripts/settings/__init__.lua +++ b/data/ui_scripts/settings/__init__.lua @@ -55,8 +55,8 @@ LUI.addmenubutton("pc_controls", { LUI.MenuBuilder.m_types_build["settings_menu"] = function(a1) local menu = LUI.MenuTemplate.new(a1, { menu_title = "@MENU_GENERAL", - menu_list_divider_top_offset = -(LUI.H1MenuTab.tabChangeHoldingElementHeight + luiglobals.H1MenuDims.spacing), - menu_width = luiglobals.GenericMenuDims.OptionMenuWidth + menu_list_divider_top_offset = -(LUI.H1MenuTab.tabChangeHoldingElementHeight + H1MenuDims.spacing), + menu_width = GenericMenuDims.OptionMenuWidth }) createdivider(menu, "@LUA_MENU_UPDATES") diff --git a/deps/GSL b/deps/GSL index 38372367..2bfd4950 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit 383723676cd548d615159701ac3d050f8dd1e128 +Subproject commit 2bfd4950802a223dde37a08a205812b6dfdfeb61 diff --git a/deps/asmjit b/deps/asmjit index 21a31b8a..a4cb51b5 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 21a31b8a338da3341d2b423f85913597b8ec3d63 +Subproject commit a4cb51b532af0f8137c4182914244c3b05d7745f diff --git a/deps/curl b/deps/curl index 47048e02..af2dac82 160000 --- a/deps/curl +++ b/deps/curl @@ -1 +1 @@ -Subproject commit 47048e02878c59367db1d42813f32dcce543eed3 +Subproject commit af2dac82988f95e10351d13af8d4693ea4175183 diff --git a/deps/libtommath b/deps/libtommath index 66de8642..5108f123 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 66de86426e9cdb88526974c765108f01554af2b0 +Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a diff --git a/deps/rapidjson b/deps/rapidjson index 8261c1dd..fcb23c2d 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 8261c1ddf43f10de00fd8c9a67811d1486b2c784 +Subproject commit fcb23c2dbf561ec0798529be4f66394d3e4996d8 diff --git a/deps/sol2 b/deps/sol2 index 50b62c93..64096348 160000 --- a/deps/sol2 +++ b/deps/sol2 @@ -1 +1 @@ -Subproject commit 50b62c9346750b7c2c406c9e4c546f96b0bf021d +Subproject commit 64096348465b980e2f1d0e5ba9cbeea8782e8f27 diff --git a/deps/zlib b/deps/zlib index cacf7f1d..21767c65 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f +Subproject commit 21767c654d31d2dccdde4330529775c6c5fd5389 diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 6fba69f6..4acdef6a 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -303,70 +303,77 @@ namespace command return; } - try + const std::string arg = params.get(1); + const std::string arg2 = params.get(2); + const auto count = params.size(); + + scheduler::once([=]() { - const auto arg = params.get(1); - const scripting::entity player = scripting::call("getentbynum", {0}).as(); - auto ps = game::g_entities[0].client; + printf("%i\n", game::Sys_IsMainThread()); - if (arg == "ammo"s) + try { - const auto weapon = player.call("getcurrentweapon").as(); - player.call("givemaxammo", {weapon}); - } - else if (arg == "allammo"s) - { - const auto weapons = player.call("getweaponslist").as(); - for (auto i = 0; i < weapons.size(); i++) - { - player.call("givemaxammo", {weapons[i]}); - } - } - else if (arg == "health"s) - { - if (params.size() > 2) - { - const auto amount = atoi(params.get(2)); - const auto health = player.get("health").as(); - player.set("health", {health + amount}); - } - else - { - const auto amount = game::Dvar_FindVar("g_player_maxhealth")->current.integer; - player.set("health", {amount}); - } - } - else if (arg == "all"s) - { - const auto type = game::XAssetType::ASSET_TYPE_WEAPON; - fastfiles::enum_assets(type, [&player, type](const game::XAssetHeader header) - { - const auto asset = game::XAsset{type, header}; - const auto* const asset_name = game::DB_GetXAssetName(&asset); + const scripting::entity player = scripting::call("getentbynum", {0}).as(); + auto ps = game::g_entities[0].client; - player.call("giveweapon", {asset_name}); - }, true); - } - else - { - const auto wp = game::G_GetWeaponForName(arg); - if (wp) + if (arg == "ammo") { - if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0)) + const auto weapon = player.call("getcurrentweapon").as(); + player.call("givemaxammo", {weapon}); + } + else if (arg == "allammo") + { + const auto weapons = player.call("getweaponslist").as(); + for (auto i = 0; i < weapons.size(); i++) { - game::G_InitializeAmmo(ps, wp, 0); - game::G_SelectWeapon(0, wp); + player.call("givemaxammo", {weapons[i]}); } } + else if (arg == "health") + { + if (count > 2) + { + const auto amount = atoi(arg2.data()); + const auto health = player.get("health").as(); + player.set("health", {health + amount}); + } + else + { + const auto amount = game::Dvar_FindVar("g_player_maxhealth")->current.integer; + player.set("health", {amount}); + } + } + else if (arg == "all") + { + const auto type = game::XAssetType::ASSET_TYPE_WEAPON; + fastfiles::enum_assets(type, [&player, type](const game::XAssetHeader header) + { + const auto asset = game::XAsset{type, header}; + const auto* const asset_name = game::DB_GetXAssetName(&asset); + player.call("giveweapon", {asset_name}); + }, true); + } else { - game::CG_GameMessage(0, "Weapon does not exist"); + const auto wp = game::G_GetWeaponForName(arg.data()); + if (wp) + { + if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0)) + { + game::G_InitializeAmmo(ps, wp, 0); + game::G_SelectWeapon(0, wp); + } + } + else + { + game::CG_GameMessage(0, "Weapon does not exist"); + } } } - } - catch (...) - { - } + catch (...) + { + } + }, scheduler::pipeline::server); }); add("dropweapon", [](const params& params) @@ -376,15 +383,18 @@ namespace command return; } - try + scheduler::once([]() { - const scripting::entity player = scripting::call("getentbynum", {0}).as(); - const auto weapon = player.call("getcurrentweapon"); - player.call("dropitem", {weapon}); - } - catch (...) - { - } + try + { + const scripting::entity player = scripting::call("getentbynum", {0}).as(); + const auto weapon = player.call("getcurrentweapon"); + player.call("dropitem", {weapon}); + } + catch (...) + { + } + }, scheduler::pipeline::server); }); add("take", [](const params& params) @@ -400,23 +410,26 @@ namespace command return; } - const auto weapon = params.get(1); + const std::string weapon = params.get(1); - try + scheduler::once([=]() { - const auto player = scripting::call("getentbynum", {0}).as(); - if (weapon == "all"s) + try { - player.call("takeallweapons"); + const auto player = scripting::call("getentbynum", {0}).as(); + if (weapon == "all"s) + { + player.call("takeallweapons"); + } + else + { + player.call("takeweapon", {weapon}); + } } - else + catch (...) { - player.call("takeweapon", {weapon}); } - } - catch (...) - { - } + }, scheduler::pipeline::server); }); add("kill", [](const params& params) diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index cba191cc..321364a5 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -27,13 +27,18 @@ namespace filesystem return {}; } - bool read_file(const std::string& path, std::string* data) + bool read_file(const std::string& path, std::string* data, std::string* real_path) { for (const auto& search_path : get_search_paths()) { const auto path_ = search_path + "/" + path; if (utils::io::read_file(path_, data)) { + if (real_path != nullptr) + { + *real_path = path_; + } + return true; } } diff --git a/src/client/component/filesystem.hpp b/src/client/component/filesystem.hpp index 718e5377..3e9c5b02 100644 --- a/src/client/component/filesystem.hpp +++ b/src/client/component/filesystem.hpp @@ -4,5 +4,5 @@ namespace filesystem { std::unordered_set& get_search_paths(); std::string read_file(const std::string& path); - bool read_file(const std::string& path, std::string* data); + bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr); } \ No newline at end of file diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp index 78d8f401..3e1a6a6c 100644 --- a/src/client/component/game_console.cpp +++ b/src/client/component/game_console.cpp @@ -386,11 +386,13 @@ namespace game_console va_end(ap); const auto formatted = std::string(va_buffer); + printf(va_buffer); + const auto lines = utils::string::split(formatted, '\n'); for (auto& line : lines) { - print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line)); + print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line), false); } } diff --git a/src/client/component/gui.cpp b/src/client/component/gui.cpp index f8187cbe..1cba1a67 100644 --- a/src/client/component/gui.cpp +++ b/src/client/component/gui.cpp @@ -68,8 +68,15 @@ namespace gui void new_gui_frame() { - ImGui::GetIO().MouseDrawCursor = toggled || *game::keyCatchers & 0x1; - *game::keyCatchers |= 0x10 * toggled; + ImGui::GetIO().MouseDrawCursor = toggled; + if (toggled) + { + *game::keyCatchers |= 0x10; + } + else + { + *game::keyCatchers &= ~0x10; + } ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); diff --git a/src/client/component/gui_console.cpp b/src/client/component/gui_console.cpp index 13943c7a..60b425cb 100644 --- a/src/client/component/gui_console.cpp +++ b/src/client/component/gui_console.cpp @@ -106,7 +106,7 @@ namespace gui::console } } - if (text[text.size() - 1] == '\n') + if (!text.empty() && text[text.size() - 1] == '\n') { text.pop_back(); } diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index 7b14cc46..3f34bf2e 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -5,7 +5,6 @@ #include "game_console.hpp" #include "gui.hpp" -#include "game/ui_scripting/lua/engine.hpp" #include "game/ui_scripting/execution.hpp" #include @@ -26,12 +25,6 @@ namespace input void cl_char_event_stub(const int local_client_num, const int key) { - ui_scripting::notify("keypress", - { - {"keynum", key}, - {"key", game::Key_KeynumToString(key, 0, 1)}, - }); - if (!game_console::console_char_event(local_client_num, key)) { return; @@ -42,17 +35,17 @@ namespace input return; } - cl_char_event_hook.invoke(local_client_num, key); - } - - void cl_key_event_stub(const int local_client_num, const int key, const int down) - { - ui_scripting::notify(down ? "keydown" : "keyup", + ui_scripting::notify("keypress", { {"keynum", key}, {"key", game::Key_KeynumToString(key, 0, 1)}, }); + cl_char_event_hook.invoke(local_client_num, key); + } + + void cl_key_event_stub(const int local_client_num, const int key, const int down) + { if (!game_console::console_key_event(local_client_num, key, down)) { return; @@ -63,6 +56,15 @@ namespace input return; } + if (!(*game::keyCatchers & 1) && !(*game::keyCatchers & 0x10)) + { + ui_scripting::notify(down ? "keydown" : "keyup", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); + } + cl_key_event_hook.invoke(local_client_num, key, down); } diff --git a/src/client/component/logger.cpp b/src/client/component/logger.cpp index 554ce013..5d0a5f8c 100644 --- a/src/client/component/logger.cpp +++ b/src/client/component/logger.cpp @@ -146,6 +146,7 @@ namespace logger utils::hook::jump(0x14032C630, print_warning, true); utils::hook::jump(0x14032AEF0, lui_print, true); com_error_hook.create(0x1405A2D80, com_error_stub); + utils::hook::jump(0x14013A98C, print); } }; } diff --git a/src/client/component/scheduler.cpp b/src/client/component/scheduler.cpp index ed0301b8..07ae3b07 100644 --- a/src/client/component/scheduler.cpp +++ b/src/client/component/scheduler.cpp @@ -87,6 +87,7 @@ namespace scheduler utils::hook::detour r_end_frame_hook; utils::hook::detour g_run_frame_hook; utils::hook::detour main_frame_hook; + utils::hook::detour hks_frame_hook; void execute(const pipeline type) { @@ -97,11 +98,6 @@ namespace scheduler void r_end_frame_stub() { execute(pipeline::renderer); - if (game::Sys_IsMainThread()) - { - execute(pipeline::lui); - } - r_end_frame_hook.invoke(); } @@ -116,6 +112,16 @@ namespace scheduler main_frame_hook.invoke(); execute(pipeline::main); } + + void hks_frame_stub() + { + const auto state = *game::hks::lua_state; + if (state) + { + execute(pipeline::lui); + } + hks_frame_hook.invoke(); + } } void schedule(const std::function& callback, const pipeline type, @@ -186,6 +192,7 @@ namespace scheduler r_end_frame_hook.create(0x14076D7B0, scheduler::r_end_frame_stub); g_run_frame_hook.create(0x1404CB030, scheduler::server_frame_stub); main_frame_hook.create(0x140417FA0, scheduler::main_frame_stub); + hks_frame_hook.create(0x140327880, scheduler::hks_frame_stub); } void pre_destroy() override diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index ee6d4831..6b6056c8 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -7,172 +7,468 @@ #include "scheduler.hpp" #include "command.hpp" -#include "ui_scripting.hpp" +#include "filesystem.hpp" +#include "localized_strings.hpp" +#include "scripting.hpp" +#include "fastfiles.hpp" +#include "mods.hpp" +#include "updater.hpp" +#include "game_console.hpp" -#include "game/ui_scripting/lua/engine.hpp" #include "game/ui_scripting/execution.hpp" -#include "game/ui_scripting/lua/error.hpp" +#include "game/scripting/execution.hpp" + +#include "ui_scripting.hpp" #include #include +#include namespace ui_scripting { namespace { - std::unordered_map converted_functions; + const auto lui_common = utils::nt::load_resource(LUI_COMMON); + const auto lui_updater = utils::nt::load_resource(LUI_UPDATER); + const auto lua_json = utils::nt::load_resource(LUA_JSON); + + std::unordered_map> converted_functions; - utils::hook::detour hksi_lual_error_hook; - utils::hook::detour hksi_lual_error_hook2; utils::hook::detour hks_start_hook; utils::hook::detour hks_shutdown_hook; - utils::hook::detour hks_allocator_hook; - utils::hook::detour lui_error_hook; - utils::hook::detour hksi_hks_error_hook; - utils::hook::detour hks_frame_hook; + utils::hook::detour hks_package_require_hook; - int error_hook_enabled = 0; - - void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...) + struct script { - char va_buffer[2048] = {0}; + std::string name; + std::string root; + }; - va_list ap; - va_start(ap, fmt); - vsprintf_s(va_buffer, fmt, ap); - va_end(ap); + struct globals_t + { + std::string in_require_script; + std::vector