Merge pull request #269 from fedddddd/develop

Release v2.0.5
This commit is contained in:
fed 2022-04-30 19:27:52 +00:00 committed by GitHub
commit 96d8a0b00c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1550 additions and 1973 deletions

View File

@ -11,12 +11,28 @@
<br/> <br/>
## 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)** 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
- **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.** 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 ## Compile from source
@ -24,14 +40,12 @@
- Update the submodules and run `premake5 vs2019` or simply use the delivered `generate.bat`. - Update the submodules and run `premake5 vs2019` or simply use the delivered `generate.bat`.
- Build via solution file in `build\h2-mod.sln`. - Build via solution file in `build\h2-mod.sln`.
### Premake arguments ### Premake arguments
| Argument | Description | | Argument | Description |
|:----------------------------|:-----------------------------------------------| |:----------------------------|:-----------------------------------------------|
| `--copy-to=PATH` | Optional, copy the EXE to a custom folder after build, define the path here if wanted. | | `--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. | | `--dev-build` | Enable development builds of the client. |
<br/>
## Disclaimer ## Disclaimer

View File

@ -2,12 +2,12 @@ game:addlocalizedstring("MENU_MODS", "MODS")
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.") game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.") game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3") 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_LOADED_MOD", "Loaded mod: ^3&&1")
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods") game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload") game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.") 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) function createdivider(menu, text)
local element = LUI.UIElement.new( { local element = LUI.UIElement.new( {
@ -26,7 +26,10 @@ function createdivider(menu, text)
title_bar_text = Engine.ToUpperCase(text) title_bar_text = Engine.ToUpperCase(text)
})) }))
element.text = element:getFirstChild():getFirstChild():getNextSibling()
menu.list:addElement(element) menu.list:addElement(element)
return element
end end
function string:truncate(length) function string:truncate(length)
@ -48,12 +51,17 @@ LUI.addmenubutton("main_campaign", {
function getmodname(path) function getmodname(path)
local name = 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" local infofile = path .. "/info.json"
if (io.fileexists(infofile)) then if (io.fileexists(infofile)) then
pcall(function() pcall(function()
local data = json.decode(io.readfile(infofile)) 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", desc = Engine.Localize("@LUA_MENU_MOD_DESC",
data.description, data.author, data.version) data.description, data.author, data.version)
name = data.name name = data.name
@ -63,7 +71,7 @@ function getmodname(path)
return name, desc return name, desc
end end
LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1) LUI.MenuBuilder.registerType("mods_menu", function(a1)
local menu = LUI.MenuTemplate.new(a1, { local menu = LUI.MenuTemplate.new(a1, {
menu_title = "@MENU_MODS", menu_title = "@MENU_MODS",
exclusiveController = 0, exclusiveController = 0,
@ -72,20 +80,21 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
showTopRightSmallBar = true showTopRightSmallBar = true
}) })
menu:AddButton("@LUA_MENU_OPEN_STORE", function() menu:AddButton("@LUA_MENU_WORKSHOP", function()
if (LUI.MenuBuilder.m_types_build["mod_store_menu"]) then if (LUI.MenuBuilder.m_types_build["mods_workshop_menu"]) then
LUI.FlowManager.RequestAddMenu(nil, "mod_store_menu") LUI.FlowManager.RequestAddMenu(nil, "mods_workshop_menu")
end end
end, nil, true, nil, { 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() local modfolder = game:getloadedmod()
if (modfolder ~= "") then 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() menu:AddButton("@LUA_MENU_UNLOAD", function()
game:executecommand("unloadmod") Engine.Exec("unloadmod")
end, nil, true, nil, { end, nil, true, nil, {
desc_text = Engine.Localize("@LUA_MENU_UNLOAD_DESC") 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 if (mods[i] ~= modfolder) then
game:addlocalizedstring(name, name) game:addlocalizedstring(name, name)
menu:AddButton(name, function() menu:AddButton(name, function()
game:executecommand("loadmod " .. mods[i]) Engine.Exec("loadmod " .. mods[i])
end, nil, true, nil, { end, nil, true, nil, {
desc_text = desc desc_text = desc
}) })
@ -121,4 +130,4 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu) menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
return menu return menu
end end)

View File

@ -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

View File

@ -55,8 +55,8 @@ LUI.addmenubutton("pc_controls", {
LUI.MenuBuilder.m_types_build["settings_menu"] = function(a1) LUI.MenuBuilder.m_types_build["settings_menu"] = function(a1)
local menu = LUI.MenuTemplate.new(a1, { local menu = LUI.MenuTemplate.new(a1, {
menu_title = "@MENU_GENERAL", menu_title = "@MENU_GENERAL",
menu_list_divider_top_offset = -(LUI.H1MenuTab.tabChangeHoldingElementHeight + luiglobals.H1MenuDims.spacing), menu_list_divider_top_offset = -(LUI.H1MenuTab.tabChangeHoldingElementHeight + H1MenuDims.spacing),
menu_width = luiglobals.GenericMenuDims.OptionMenuWidth menu_width = GenericMenuDims.OptionMenuWidth
}) })
createdivider(menu, "@LUA_MENU_UPDATES") createdivider(menu, "@LUA_MENU_UPDATES")

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 383723676cd548d615159701ac3d050f8dd1e128 Subproject commit 2bfd4950802a223dde37a08a205812b6dfdfeb61

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit 21a31b8a338da3341d2b423f85913597b8ec3d63 Subproject commit a4cb51b532af0f8137c4182914244c3b05d7745f

2
deps/curl vendored

@ -1 +1 @@
Subproject commit 47048e02878c59367db1d42813f32dcce543eed3 Subproject commit af2dac82988f95e10351d13af8d4693ea4175183

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 66de86426e9cdb88526974c765108f01554af2b0 Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit 8261c1ddf43f10de00fd8c9a67811d1486b2c784 Subproject commit fcb23c2dbf561ec0798529be4f66394d3e4996d8

2
deps/sol2 vendored

@ -1 +1 @@
Subproject commit 50b62c9346750b7c2c406c9e4c546f96b0bf021d Subproject commit 64096348465b980e2f1d0e5ba9cbeea8782e8f27

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f Subproject commit 21767c654d31d2dccdde4330529775c6c5fd5389

View File

@ -303,70 +303,77 @@ namespace command
return; 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); printf("%i\n", game::Sys_IsMainThread());
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
auto ps = game::g_entities[0].client;
if (arg == "ammo"s) try
{ {
const auto weapon = player.call("getcurrentweapon").as<std::string>(); const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
player.call("givemaxammo", {weapon}); auto ps = game::g_entities[0].client;
}
else if (arg == "allammo"s)
{
const auto weapons = player.call("getweaponslist").as<scripting::array>();
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<int>();
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);
player.call("giveweapon", {asset_name}); if (arg == "ammo")
}, true);
}
else
{
const auto wp = game::G_GetWeaponForName(arg);
if (wp)
{ {
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0)) const auto weapon = player.call("getcurrentweapon").as<std::string>();
player.call("givemaxammo", {weapon});
}
else if (arg == "allammo")
{
const auto weapons = player.call("getweaponslist").as<scripting::array>();
for (auto i = 0; i < weapons.size(); i++)
{ {
game::G_InitializeAmmo(ps, wp, 0); player.call("givemaxammo", {weapons[i]});
game::G_SelectWeapon(0, wp);
} }
} }
else if (arg == "health")
{
if (count > 2)
{
const auto amount = atoi(arg2.data());
const auto health = player.get("health").as<int>();
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 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) add("dropweapon", [](const params& params)
@ -376,15 +383,18 @@ namespace command
return; return;
} }
try scheduler::once([]()
{ {
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>(); try
const auto weapon = player.call("getcurrentweapon"); {
player.call("dropitem", {weapon}); const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
} const auto weapon = player.call("getcurrentweapon");
catch (...) player.call("dropitem", {weapon});
{ }
} catch (...)
{
}
}, scheduler::pipeline::server);
}); });
add("take", [](const params& params) add("take", [](const params& params)
@ -400,23 +410,26 @@ namespace command
return; 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<scripting::entity>(); try
if (weapon == "all"s)
{ {
player.call("takeallweapons"); const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
if (weapon == "all"s)
{
player.call("takeallweapons");
}
else
{
player.call("takeweapon", {weapon});
}
} }
else catch (...)
{ {
player.call("takeweapon", {weapon});
} }
} }, scheduler::pipeline::server);
catch (...)
{
}
}); });
add("kill", [](const params& params) add("kill", [](const params& params)

View File

@ -27,13 +27,18 @@ namespace filesystem
return {}; 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()) for (const auto& search_path : get_search_paths())
{ {
const auto path_ = search_path + "/" + path; const auto path_ = search_path + "/" + path;
if (utils::io::read_file(path_, data)) if (utils::io::read_file(path_, data))
{ {
if (real_path != nullptr)
{
*real_path = path_;
}
return true; return true;
} }
} }

View File

@ -4,5 +4,5 @@ namespace filesystem
{ {
std::unordered_set<std::string>& get_search_paths(); std::unordered_set<std::string>& get_search_paths();
std::string read_file(const std::string& path); 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);
} }

View File

@ -386,11 +386,13 @@ namespace game_console
va_end(ap); va_end(ap);
const auto formatted = std::string(va_buffer); const auto formatted = std::string(va_buffer);
printf(va_buffer);
const auto lines = utils::string::split(formatted, '\n'); const auto lines = utils::string::split(formatted, '\n');
for (auto& line : lines) 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);
} }
} }

View File

@ -68,8 +68,15 @@ namespace gui
void new_gui_frame() void new_gui_frame()
{ {
ImGui::GetIO().MouseDrawCursor = toggled || *game::keyCatchers & 0x1; ImGui::GetIO().MouseDrawCursor = toggled;
*game::keyCatchers |= 0x10 * toggled; if (toggled)
{
*game::keyCatchers |= 0x10;
}
else
{
*game::keyCatchers &= ~0x10;
}
ImGui_ImplDX11_NewFrame(); ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame(); ImGui_ImplWin32_NewFrame();

View File

@ -106,7 +106,7 @@ namespace gui::console
} }
} }
if (text[text.size() - 1] == '\n') if (!text.empty() && text[text.size() - 1] == '\n')
{ {
text.pop_back(); text.pop_back();
} }

View File

@ -5,7 +5,6 @@
#include "game_console.hpp" #include "game_console.hpp"
#include "gui.hpp" #include "gui.hpp"
#include "game/ui_scripting/lua/engine.hpp"
#include "game/ui_scripting/execution.hpp" #include "game/ui_scripting/execution.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
@ -26,12 +25,6 @@ namespace input
void cl_char_event_stub(const int local_client_num, const int key) 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)) if (!game_console::console_char_event(local_client_num, key))
{ {
return; return;
@ -42,17 +35,17 @@ namespace input
return; return;
} }
cl_char_event_hook.invoke<void>(local_client_num, key); ui_scripting::notify("keypress",
}
void cl_key_event_stub(const int local_client_num, const int key, const int down)
{
ui_scripting::notify(down ? "keydown" : "keyup",
{ {
{"keynum", key}, {"keynum", key},
{"key", game::Key_KeynumToString(key, 0, 1)}, {"key", game::Key_KeynumToString(key, 0, 1)},
}); });
cl_char_event_hook.invoke<void>(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)) if (!game_console::console_key_event(local_client_num, key, down))
{ {
return; return;
@ -63,6 +56,15 @@ namespace input
return; 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<void>(local_client_num, key, down); cl_key_event_hook.invoke<void>(local_client_num, key, down);
} }

View File

@ -146,6 +146,7 @@ namespace logger
utils::hook::jump(0x14032C630, print_warning, true); utils::hook::jump(0x14032C630, print_warning, true);
utils::hook::jump(0x14032AEF0, lui_print, true); utils::hook::jump(0x14032AEF0, lui_print, true);
com_error_hook.create(0x1405A2D80, com_error_stub); com_error_hook.create(0x1405A2D80, com_error_stub);
utils::hook::jump(0x14013A98C, print);
} }
}; };
} }

View File

@ -87,6 +87,7 @@ namespace scheduler
utils::hook::detour r_end_frame_hook; utils::hook::detour r_end_frame_hook;
utils::hook::detour g_run_frame_hook; utils::hook::detour g_run_frame_hook;
utils::hook::detour main_frame_hook; utils::hook::detour main_frame_hook;
utils::hook::detour hks_frame_hook;
void execute(const pipeline type) void execute(const pipeline type)
{ {
@ -97,11 +98,6 @@ namespace scheduler
void r_end_frame_stub() void r_end_frame_stub()
{ {
execute(pipeline::renderer); execute(pipeline::renderer);
if (game::Sys_IsMainThread())
{
execute(pipeline::lui);
}
r_end_frame_hook.invoke<void>(); r_end_frame_hook.invoke<void>();
} }
@ -116,6 +112,16 @@ namespace scheduler
main_frame_hook.invoke<void>(); main_frame_hook.invoke<void>();
execute(pipeline::main); execute(pipeline::main);
} }
void hks_frame_stub()
{
const auto state = *game::hks::lua_state;
if (state)
{
execute(pipeline::lui);
}
hks_frame_hook.invoke<void>();
}
} }
void schedule(const std::function<bool()>& callback, const pipeline type, void schedule(const std::function<bool()>& callback, const pipeline type,
@ -186,6 +192,7 @@ namespace scheduler
r_end_frame_hook.create(0x14076D7B0, scheduler::r_end_frame_stub); r_end_frame_hook.create(0x14076D7B0, scheduler::r_end_frame_stub);
g_run_frame_hook.create(0x1404CB030, scheduler::server_frame_stub); g_run_frame_hook.create(0x1404CB030, scheduler::server_frame_stub);
main_frame_hook.create(0x140417FA0, scheduler::main_frame_stub); main_frame_hook.create(0x140417FA0, scheduler::main_frame_stub);
hks_frame_hook.create(0x140327880, scheduler::hks_frame_stub);
} }
void pre_destroy() override void pre_destroy() override

View File

@ -7,172 +7,468 @@
#include "scheduler.hpp" #include "scheduler.hpp"
#include "command.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/execution.hpp"
#include "game/ui_scripting/lua/error.hpp" #include "game/scripting/execution.hpp"
#include "ui_scripting.hpp"
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/io.hpp>
namespace ui_scripting namespace ui_scripting
{ {
namespace namespace
{ {
std::unordered_map<game::hks::cclosure*, sol::protected_function> 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<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>> 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_start_hook;
utils::hook::detour hks_shutdown_hook; utils::hook::detour hks_shutdown_hook;
utils::hook::detour hks_allocator_hook; utils::hook::detour hks_package_require_hook;
utils::hook::detour lui_error_hook;
utils::hook::detour hksi_hks_error_hook;
utils::hook::detour hks_frame_hook;
int error_hook_enabled = 0; struct script
void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...)
{ {
char va_buffer[2048] = {0}; std::string name;
std::string root;
};
va_list ap; struct globals_t
va_start(ap, fmt); {
vsprintf_s(va_buffer, fmt, ap); std::string in_require_script;
va_end(ap); std::vector<script> loaded_scripts;
bool load_raw_script{};
std::string raw_script_name{};
};
const auto formatted = std::string(va_buffer); globals_t globals{};
if (!error_hook_enabled) bool is_loaded_script(const std::string& name)
{
for (auto i = globals.loaded_scripts.begin(); i != globals.loaded_scripts.end(); ++i)
{ {
return hksi_lual_error_hook.invoke<void>(s, formatted.data()); if (i->name == name)
{
return true;
}
} }
throw std::runtime_error(formatted); return false;
} }
void hksi_hks_error_stub(game::hks::lua_State* s, int a2) std::string get_root_script(const std::string& name)
{ {
if (!error_hook_enabled) for (auto i = globals.loaded_scripts.begin(); i != globals.loaded_scripts.end(); ++i)
{ {
return hksi_hks_error_hook.invoke<void>(s, a2); if (i->name == name)
{
return i->root;
}
} }
throw std::runtime_error("unknown error"); return {};
} }
void lui_error_stub(game::hks::lua_State* s) table get_globals()
{ {
if (!error_hook_enabled) const auto state = *game::hks::lua_state;
{ return state->globals.v.table;
return lui_error_hook.invoke<void>(s);
}
const auto count = static_cast<int>(s->m_apistack.top - s->m_apistack.base);
const auto arguments = get_return_values(count);
std::string error_str = "LUI Error";
if (count && arguments[0].is<std::string>())
{
error_str = arguments[0].as<std::string>();
}
throw std::runtime_error(error_str);
} }
void* hks_start_stub(char a1) void print_error(const std::string& error)
{ {
const auto _ = gsl::finally([]() game_console::print(game_console::con_type_error, "************** LUI script execution error **************\n");
game_console::print(game_console::con_type_error, "%s\n", error.data());
game_console::print(game_console::con_type_error, "********************************************************\n");
}
void print_loading_script(const std::string& name)
{
game_console::print(game_console::con_type_info, "Loading LUI script '%s'\n", name.data());
}
std::string get_current_script()
{
const auto state = *game::hks::lua_state;
game::hks::lua_Debug info{};
game::hks::hksi_lua_getstack(state, 1, &info);
game::hks::hksi_lua_getinfo(state, "nSl", &info);
return info.short_src;
}
int load_buffer(const std::string& name, const std::string& data)
{
const auto state = *game::hks::lua_state;
const auto sharing_mode = state->m_global->m_bytecodeSharingMode;
state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON;
const auto _0 = gsl::finally([&]()
{ {
ui_scripting::lua::engine::start(); state->m_global->m_bytecodeSharingMode = sharing_mode;
}); });
return hks_start_hook.invoke<void*>(a1); game::hks::HksCompilerSettings compiler_settings{};
return game::hks::hksi_hksL_loadbuffer(state, &compiler_settings, data.data(), data.size(), name.data());
}
void load_script(const std::string& name, const std::string& data)
{
globals.loaded_scripts.push_back({name, name});
const auto lua = get_globals();
const auto load_results = lua["loadstring"](data, name);
if (load_results[0].is<function>())
{
const auto results = lua["pcall"](load_results);
if (!results[0].as<bool>())
{
print_error(results[1].as<std::string>());
}
}
else if (load_results[1].is<std::string>())
{
print_error(load_results[1].as<std::string>());
}
}
void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir))
{
return;
}
const auto scripts = utils::io::list_files(script_dir);
for (const auto& script : scripts)
{
std::string data{};
if (std::filesystem::is_directory(script) && utils::io::read_file(script + "/__init__.lua", &data))
{
print_loading_script(script);
load_script(script + "/__init__.lua", data);
}
}
}
void setup_functions()
{
const auto lua = get_globals();
lua["io"]["fileexists"] = utils::io::file_exists;
lua["io"]["writefile"] = utils::io::write_file;
lua["io"]["movefile"] = utils::io::move_file;
lua["io"]["filesize"] = utils::io::file_size;
lua["io"]["createdirectory"] = utils::io::create_directory;
lua["io"]["directoryexists"] = utils::io::directory_exists;
lua["io"]["directoryisempty"] = utils::io::directory_is_empty;
lua["io"]["listfiles"] = utils::io::list_files;
lua["io"]["removefile"] = utils::io::remove_file;
lua["io"]["removedirectory"] = utils::io::remove_directory;
lua["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
using game = table;
auto game_type = game();
lua["game"] = game_type;
game_type["addlocalizedstring"] = [](const game&, const std::string& a, const std::string& b)
{
localized_strings::override(a, b);
};
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
{
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
{
table[key] = value;
});
};
game_type["sharedget"] = [](const game&, const std::string& key)
{
std::string result;
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
{
result = table[key];
});
return result;
};
game_type["sharedclear"] = [](const game&)
{
scripting::shared_table.access([](scripting::shared_table_t& table)
{
table.clear();
});
};
game_type["assetlist"] = [](const game&, const std::string& type_string)
{
auto table_ = table();
auto index = 1;
auto type_index = -1;
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
{
if (type_string == ::game::g_assetNames[i])
{
type_index = i;
}
}
if (type_index == -1)
{
throw std::runtime_error("Asset type does not exist");
}
const auto type = static_cast<::game::XAssetType>(type_index);
fastfiles::enum_assets(type, [type, &table_, &index](const ::game::XAssetHeader header)
{
const auto asset = ::game::XAsset{ type, header };
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
table_[index++] = asset_name;
}, true);
return table_;
};
game_type["getweapondisplayname"] = [](const game&, const std::string& name)
{
const auto alternate = name.starts_with("alt_");
const auto weapon = ::game::G_GetWeaponForName(name.data());
char buffer[0x400] = { 0 };
::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400);
return std::string(buffer);
};
game_type["getloadedmod"] = [](const game&)
{
return mods::mod_path;
};
game_type["addlocalizedstring"] = [](const game&, const std::string& string,
const std::string& value)
{
localized_strings::override(string, value);
};
using player = table;
auto player_type = player();
lua["player"] = player_type;
player_type["notify"] = [](const player&, const std::string& name, const variadic_args& va)
{
if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client)
{
throw std::runtime_error("Not in game");
}
const auto to_string = get_globals()["tostring"];
const auto arguments = get_return_values();
std::vector<std::string> args{};
for (const auto& value : va)
{
args.push_back(to_string(value)[0].as<std::string>());
}
::scheduler::once([name, args]()
{
try
{
std::vector<scripting::script_value> arguments{};
for (const auto& arg : args)
{
arguments.push_back(arg);
}
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
scripting::notify(player, name, arguments);
}
catch (...)
{
}
}, ::scheduler::pipeline::server);
};
auto updater_table = table();
lua["updater"] = updater_table;
updater_table["relaunch"] = updater::relaunch;
updater_table["sethastriedupdate"] = updater::set_has_tried_update;
updater_table["gethastriedupdate"] = updater::get_has_tried_update;
updater_table["autoupdatesenabled"] = updater::auto_updates_enabled;
updater_table["startupdatecheck"] = updater::start_update_check;
updater_table["isupdatecheckdone"] = updater::is_update_check_done;
updater_table["getupdatecheckstatus"] = updater::get_update_check_status;
updater_table["isupdateavailable"] = updater::is_update_available;
updater_table["startupdatedownload"] = updater::start_update_download;
updater_table["isupdatedownloaddone"] = updater::is_update_download_done;
updater_table["getupdatedownloadstatus"] = updater::get_update_download_status;
updater_table["cancelupdate"] = updater::cancel_update;
updater_table["isrestartrequired"] = updater::is_restart_required;
updater_table["getlasterror"] = updater::get_last_error;
updater_table["getcurrentfile"] = updater::get_current_file;
}
void start()
{
globals = {};
const auto lua = get_globals();
lua["EnableGlobals"]();
setup_functions();
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(0x1402B81C0));
lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua;
load_script("lui_common", lui_common);
load_script("lui_updater", lui_updater);
load_script("lua_json", lua_json);
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path + "/ui_scripts/");
}
}
void try_start()
{
try
{
start();
}
catch (const std::exception& e)
{
game_console::print(game_console::con_type_error, "Failed to load LUI scripts: %s\n", e.what());
}
}
void hks_start_stub(char a1)
{
const auto _0 = gsl::finally(&try_start);
return hks_start_hook.invoke<void>(a1);
} }
void hks_shutdown_stub() void hks_shutdown_stub()
{ {
ui_scripting::lua::engine::stop(); converted_functions.clear();
hks_shutdown_hook.invoke<void*>(); globals = {};
hks_shutdown_hook.invoke<void>();
} }
void* hks_allocator_stub(void* userData, void* oldMemory, unsigned __int64 oldSize, unsigned __int64 newSize) void* hks_package_require_stub(game::hks::lua_State* state)
{ {
const auto closure = reinterpret_cast<game::hks::cclosure*>(oldMemory); const auto script = get_current_script();
if (converted_functions.find(closure) != converted_functions.end()) const auto root = get_root_script(script);
globals.in_require_script = root;
return hks_package_require_hook.invoke<void*>(state);
}
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{
if (!is_loaded_script(globals.in_require_script))
{ {
converted_functions.erase(closure); return game::DB_FindXAssetHeader(type, name, allow_create_default);
} }
return hks_allocator_hook.invoke<void*>(userData, oldMemory, oldSize, newSize); const auto folder = globals.in_require_script.substr(0, globals.in_require_script.find_last_of("/\\"));
const std::string name_ = name;
const std::string target_script = folder + "/" + name_ + ".lua";
if (utils::io::file_exists(target_script))
{
globals.load_raw_script = true;
globals.raw_script_name = target_script;
return static_cast<game::XAssetHeader>(reinterpret_cast<game::LuaFile*>(1));
}
else if (name_.starts_with("ui/LUI/"))
{
return game::DB_FindXAssetHeader(type, name, allow_create_default);
}
return static_cast<game::XAssetHeader>(nullptr);
} }
void hks_frame_stub() int hks_load_stub(game::hks::lua_State* state, void* compiler_options,
void* reader, void* reader_data, const char* chunk_name)
{ {
const auto state = *game::hks::lua_state; if (globals.load_raw_script)
if (state)
{ {
ui_scripting::lua::engine::run_frame(); globals.load_raw_script = false;
globals.loaded_scripts.push_back({globals.raw_script_name, globals.in_require_script});
return load_buffer(globals.raw_script_name, utils::io::read_file(globals.raw_script_name));
}
else
{
return utils::hook::invoke<int>(0x1402D9410, state, compiler_options, reader,
reader_data, chunk_name);
} }
} }
}
int main_function_handler(game::hks::lua_State* state) int main_handler(game::hks::lua_State* state)
{
const auto value = state->m_apistack.base[-1];
if (value.t != game::hks::TCFUNCTION)
{ {
const auto value = state->m_apistack.base[-1];
if (value.t != game::hks::TCFUNCTION)
{
return 0;
}
const auto closure = value.v.cClosure;
if (converted_functions.find(closure) == converted_functions.end())
{
return 0;
}
const auto& function = converted_functions[closure];
try
{
const auto args = get_return_values();
const auto results = function(args);
for (const auto& result : results)
{
push_value(result);
}
return static_cast<int>(results.size());
}
catch (const std::exception& e)
{
game::hks::hksi_luaL_error(state, e.what());
}
return 0; return 0;
} }
const auto closure = value.v.cClosure;
if (converted_functions.find(closure) == converted_functions.end())
{
return 0;
}
const auto function = converted_functions[closure];
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
const auto arguments = get_return_values(count);
const auto s = function.lua_state();
std::vector<sol::lua_value> converted_args;
for (const auto& argument : arguments)
{
converted_args.push_back(lua::convert(s, argument));
}
const auto results = function(sol::as_args(converted_args));
lua::handle_error(results);
for (const auto& result : results)
{
push_value(lua::convert({s, result}));
}
return results.return_count();
} }
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function) template <typename F>
game::hks::cclosure* convert_function(F f)
{ {
converted_functions[closure] = function; const auto state = *game::hks::lua_state;
} const auto closure = game::hks::cclosure_Create(state, main_handler, 0, 0, 0);
converted_functions[closure] = wrap_function(f);
void clear_converted_functions() return closure;
{
converted_functions.clear();
}
void enable_error_hook()
{
error_hook_enabled++;
}
void disable_error_hook()
{
error_hook_enabled--;
} }
class component final : public component_interface class component final : public component_interface
@ -181,14 +477,13 @@ namespace ui_scripting
void post_unpack() override void post_unpack() override
{ {
hks_frame_hook.create(0x140327880, hks_frame_stub); utils::hook::call(0x14030BF2B, db_find_xasset_header_stub);
utils::hook::call(0x14030C079, db_find_xasset_header_stub);
utils::hook::call(0x14030C104, hks_load_stub);
hks_package_require_hook.create(0x1402B4DA0, hks_package_require_stub);
hks_start_hook.create(0x140328BE0, hks_start_stub); hks_start_hook.create(0x140328BE0, hks_start_stub);
hks_shutdown_hook.create(0x1403203B0, hks_shutdown_stub); hks_shutdown_hook.create(0x1403203B0, hks_shutdown_stub);
hksi_lual_error_hook.create(0x1402E3E40, hksi_lual_error_stub);
hksi_lual_error_hook2.create(0x1402DCB40, hksi_lual_error_stub);
hks_allocator_hook.create(0x1402D92A0, hks_allocator_stub);
lui_error_hook.create(0x1402B9D90, lui_error_stub);
hksi_hks_error_hook.create(0x1402DBC00, hksi_hks_error_stub);
} }
}; };
} }

View File

@ -1,13 +1,47 @@
#pragma once #pragma once
#include "game/ui_scripting/lua/value_conversion.hpp"
#include "game/ui_scripting/event.hpp"
namespace ui_scripting namespace ui_scripting
{ {
int main_function_handler(game::hks::lua_State* state); template <class... Args, std::size_t... I>
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function); auto wrap_function(const std::function<void(Args...)>& f, std::index_sequence<I...>)
void clear_converted_functions(); {
return [f](const function_arguments& args)
{
f(args[I]...);
return arguments{{}};
};
}
void enable_error_hook(); template <class... Args, std::size_t... I>
void disable_error_hook(); auto wrap_function(const std::function<arguments(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return f(args[I]...);
};
}
template <typename R, class... Args, std::size_t... I>
auto wrap_function(const std::function<R(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return arguments{f(args[I]...)};
};
}
template <typename R, class... Args>
auto wrap_function(const std::function<R(Args...)>& f)
{
return wrap_function(f, std::index_sequence_for<Args...>{});
}
template <class F>
auto wrap_function(F f)
{
return wrap_function(std::function(f));
}
template <typename F>
game::hks::cclosure* convert_function(F f);
} }

View File

@ -3,6 +3,7 @@
#include "scheduler.hpp" #include "scheduler.hpp"
#include "updater.hpp" #include "updater.hpp"
#include "game/ui_scripting/execution.hpp"
#include "version.h" #include "version.h"
@ -70,6 +71,14 @@ namespace updater
return main; return main;
} }
void notify(const std::string& name)
{
scheduler::once([=]()
{
ui_scripting::notify(name, {});
}, scheduler::pipeline::lui);
}
void set_update_check_status(bool done, bool success, const std::string& error = {}) void set_update_check_status(bool done, bool success, const std::string& error = {})
{ {
update_data.access([done, success, error](update_data_t& data_) update_data.access([done, success, error](update_data_t& data_)
@ -77,6 +86,8 @@ namespace updater
data_.check.done = done; data_.check.done = done;
data_.check.success = success; data_.check.success = success;
data_.error = error; data_.error = error;
notify("update_check_done");
}); });
} }
@ -87,6 +98,7 @@ namespace updater
data_.download.done = done; data_.download.done = done;
data_.download.success = success; data_.download.success = success;
data_.error = error; data_.error = error;
notify("update_done");
}); });
} }
@ -330,6 +342,8 @@ namespace updater
data_.check.success = true; data_.check.success = true;
data_.required_files = required_files; data_.required_files = required_files;
}); });
notify("update_check_done");
}, scheduler::pipeline::async); }, scheduler::pipeline::async);
} }

View File

@ -35,12 +35,6 @@ namespace scripting::lua
for (auto i = tasks.begin(); i != tasks.end();) for (auto i = tasks.begin(); i != tasks.end();)
{ {
if (i->is_deleted)
{
i = tasks.erase(i);
continue;
}
if (i->event != event.name || i->entity != event.entity) if (i->event != event.name || i->entity != event.entity)
{ {
++i; ++i;

View File

@ -1081,6 +1081,10 @@ namespace game
namespace hks namespace hks
{ {
struct lua_State;
struct HashTable;
struct cclosure;
struct GenericChunkHeader struct GenericChunkHeader
{ {
unsigned __int64 m_flags; unsigned __int64 m_flags;
@ -1106,9 +1110,6 @@ namespace game
char m_data[30]; char m_data[30];
}; };
struct HashTable;
struct cclosure;
union HksValue union HksValue
{ {
cclosure* cClosure; cclosure* cClosure;
@ -1120,6 +1121,8 @@ namespace game
void* thread; void* thread;
void* ptr; void* ptr;
float number; float number;
long long i64;
unsigned long long ui64;
unsigned int native; unsigned int native;
bool boolean; bool boolean;
}; };
@ -1206,11 +1209,11 @@ namespace game
enum HksError enum HksError
{ {
HKS_NO_ERROR = 0x0, HKS_NO_ERROR = 0x0,
LUA_ERRSYNTAX = 0xFFFFFFFC, HKS_ERRSYNTAX = 0xFFFFFFFC,
LUA_ERRFILE = 0xFFFFFFFB, HKS_ERRFILE = 0xFFFFFFFB,
LUA_ERRRUN = 0xFFFFFF9C, HKS_ERRRUN = 0xFFFFFF9C,
LUA_ERRMEM = 0xFFFFFF38, HKS_ERRMEM = 0xFFFFFF38,
LUA_ERRERR = 0xFFFFFED4, HKS_ERRERR = 0xFFFFFED4,
HKS_THROWING_ERROR = 0xFFFFFE0C, HKS_THROWING_ERROR = 0xFFFFFE0C,
HKS_GC_YIELD = 0x1, HKS_GC_YIELD = 0x1,
}; };
@ -1233,24 +1236,6 @@ namespace game
int is_tail_call; int is_tail_call;
}; };
struct lua_State : ChunkHeader
{
void* m_global;
CallStack m_callStack;
ApiStack m_apistack;
UpValue* pending;
HksObject globals;
HksObject m_cEnv;
CallSite* m_callsites;
int m_numberOfCCalls;
void* m_context;
InternString* m_name;
lua_State* m_nextState;
lua_State* m_nextStateStack;
Status m_status;
HksError m_error;
};
using lua_function = int(__fastcall*)(lua_State*); using lua_function = int(__fastcall*)(lua_State*);
struct luaL_Reg struct luaL_Reg
@ -1289,5 +1274,231 @@ namespace game
InternString* m_name; InternString* m_name;
HksObject m_upvalues[1]; HksObject m_upvalues[1];
}; };
enum HksCompilerSettings_BytecodeSharingFormat
{
BYTECODE_DEFAULT = 0x0,
BYTECODE_INPLACE = 0x1,
BYTECODE_REFERENCED = 0x2,
};
enum HksCompilerSettings_IntLiteralOptions
{
INT_LITERALS_NONE = 0x0,
INT_LITERALS_LUD = 0x1,
INT_LITERALS_32BIT = 0x1,
INT_LITERALS_UI64 = 0x2,
INT_LITERALS_64BIT = 0x2,
INT_LITERALS_ALL = 0x3,
};
struct HksCompilerSettings
{
int m_emitStructCode;
const char** m_stripNames;
int m_emitGlobalMemoization;
int _m_isHksGlobalMemoTestingMode;
HksCompilerSettings_BytecodeSharingFormat m_bytecodeSharingFormat;
HksCompilerSettings_IntLiteralOptions m_enableIntLiterals;
int(__fastcall* m_debugMap)(const char*, int);
};
enum HksBytecodeSharingMode
{
HKS_BYTECODE_SHARING_OFF = 0x0,
HKS_BYTECODE_SHARING_ON = 0x1,
HKS_BYTECODE_SHARING_SECURE = 0x2,
};
struct HksGcWeights
{
int m_removeString;
int m_finalizeUserdataNoMM;
int m_finalizeUserdataGcMM;
int m_cleanCoroutine;
int m_removeWeak;
int m_markObject;
int m_traverseString;
int m_traverseUserdata;
int m_traverseCoroutine;
int m_traverseWeakTable;
int m_freeChunk;
int m_sweepTraverse;
};
struct GarbageCollector_Stack
{
void* m_storage;
unsigned int m_numEntries;
unsigned int m_numAllocated;
};
struct ProtoList
{
void** m_protoList;
unsigned __int16 m_protoSize;
unsigned __int16 m_protoAllocSize;
};
struct GarbageCollector
{
int m_target;
int m_stepsLeft;
int m_stepLimit;
HksGcWeights m_costs;
int m_unit;
_SETJMP_FLOAT128(*m_jumpPoint)[16];
lua_State* m_mainState;
lua_State* m_finalizerState;
void* m_memory;
int m_phase;
GarbageCollector_Stack m_resumeStack;
GarbageCollector_Stack m_greyStack;
GarbageCollector_Stack m_remarkStack;
GarbageCollector_Stack m_weakStack;
int m_finalizing;
HksObject m_safeTableValue;
lua_State* m_startOfStateStackList;
lua_State* m_endOfStateStackList;
lua_State* m_currentState;
HksObject m_safeValue;
void* m_compiler;
void* m_bytecodeReader;
void* m_bytecodeWriter;
int m_pauseMultiplier;
int m_stepMultiplier;
bool m_stopped;
int(__fastcall* m_gcPolicy)(lua_State*);
unsigned __int64 m_pauseTriggerMemoryUsage;
int m_stepTriggerCountdown;
unsigned int m_stringTableIndex;
unsigned int m_stringTableSize;
UserData* m_lastBlackUD;
UserData* m_activeUD;
};
enum MemoryManager_ChunkColor
{
RED = 0x0,
BLACK = 0x1,
};
struct ChunkList
{
ChunkHeader m_prevToStart;
};
enum Hks_DeleteCheckingMode
{
HKS_DELETE_CHECKING_OFF = 0x0,
HKS_DELETE_CHECKING_ACCURATE = 0x1,
HKS_DELETE_CHECKING_SAFE = 0x2,
};
struct MemoryManager
{
void* (__fastcall* m_allocator)(void*, void*, unsigned __int64, unsigned __int64);
void* m_allocatorUd;
MemoryManager_ChunkColor m_chunkColor;
unsigned __int64 m_used;
unsigned __int64 m_highwatermark;
ChunkList m_allocationList;
ChunkList m_sweepList;
ChunkHeader* m_lastKeptChunk;
lua_State* m_state;
ChunkList m_deletedList;
int m_deleteMode;
Hks_DeleteCheckingMode m_deleteCheckingMode;
};
struct StaticStringCache
{
HksObject m_objects[41];
};
enum HksBytecodeEndianness
{
HKS_BYTECODE_DEFAULT_ENDIAN = 0x0,
HKS_BYTECODE_BIG_ENDIAN = 0x1,
HKS_BYTECODE_LITTLE_ENDIAN = 0x2,
};
struct RuntimeProfileData_Stats
{
unsigned __int64 hksTime;
unsigned __int64 callbackTime;
unsigned __int64 gcTime;
unsigned __int64 cFinalizerTime;
unsigned __int64 compilerTime;
unsigned int hkssTimeSamples;
unsigned int callbackTimeSamples;
unsigned int gcTimeSamples;
unsigned int compilerTimeSamples;
unsigned int num_newuserdata;
unsigned int num_tablerehash;
unsigned int num_pushstring;
unsigned int num_pushcfunction;
unsigned int num_newtables;
};
struct RuntimeProfileData
{
__int64 stackDepth;
__int64 callbackDepth;
unsigned __int64 lastTimer;
RuntimeProfileData_Stats frameStats;
unsigned __int64 gcStartTime;
unsigned __int64 finalizerStartTime;
unsigned __int64 compilerStartTime;
unsigned __int64 compilerStartGCTime;
unsigned __int64 compilerStartGCFinalizerTime;
unsigned __int64 compilerCallbackStartTime;
__int64 compilerDepth;
void* outFile;
lua_State* rootState;
};
struct HksGlobal
{
MemoryManager m_memory;
GarbageCollector m_collector;
StringTable m_stringTable;
HksBytecodeSharingMode m_bytecodeSharingMode;
unsigned int m_tableVersionInitializer;
HksObject m_registry;
ProtoList m_protoList;
HashTable* m_structProtoByName;
ChunkList m_userDataList;
lua_State* m_root;
StaticStringCache m_staticStringCache;
void* m_debugger;
void* m_profiler;
RuntimeProfileData m_runProfilerData;
HksCompilerSettings m_compilerSettings;
int(__fastcall* m_panicFunction)(lua_State*);
void* m_luaplusObjectList;
int m_heapAssertionFrequency;
int m_heapAssertionCount;
void (*m_logFunction)(lua_State*, const char*, ...);
HksBytecodeEndianness m_bytecodeDumpEndianness;
};
struct lua_State : ChunkHeader
{
HksGlobal* m_global;
CallStack m_callStack;
ApiStack m_apistack;
UpValue* pending;
HksObject globals;
HksObject m_cEnv;
CallSite* m_callsites;
int m_numberOfCCalls;
void* m_context;
InternString* m_name;
lua_State* m_nextState;
lua_State* m_nextStateStack;
Status m_status;
HksError m_error;
};
} }
} }

View File

@ -211,5 +211,11 @@ namespace game
WEAK symbol<void(lua_State* s, int index, const char* k)> hksi_lua_setfield{0x1402DEA30}; WEAK symbol<void(lua_State* s, int index, const char* k)> hksi_lua_setfield{0x1402DEA30};
WEAK symbol<int(lua_State* s, int nargs, int nresults, int errfunc)> hksi_lua_pcall{0x1402DDE50}; WEAK symbol<int(lua_State* s, int nargs, int nresults, int errfunc)> hksi_lua_pcall{0x1402DDE50};
WEAK symbol<void(lua_State* s, HksObject* lfp)> closePendingUpvalues{0x1402CBAD0}; WEAK symbol<void(lua_State* s, HksObject* lfp)> closePendingUpvalues{0x1402CBAD0};
WEAK symbol<int(lua_State* s, const HksCompilerSettings* options, const char* buff,
unsigned __int64 sz, const char* name)> hksi_hksL_loadbuffer{0x1402DB8B0};
WEAK symbol<int(lua_State* s, const char* what, lua_Debug* ar)> hksi_lua_getinfo{0x1402DD1F0};
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{0x1402DD4C0};
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{0x1402E3E40};
WEAK symbol<const char*> typenames{0x140BE9F50};
} }
} }

View File

@ -1,11 +0,0 @@
#pragma once
#include "script_value.hpp"
namespace ui_scripting
{
struct event
{
std::string name;
arguments arguments;
};
}

View File

@ -1,16 +1,54 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "execution.hpp" #include "execution.hpp"
#include "component/ui_scripting.hpp" #include "component/ui_scripting.hpp"
#include "component/game_console.hpp"
#include <utils/string.hpp> #include <utils/string.hpp>
namespace ui_scripting namespace ui_scripting
{ {
namespace
{
script_value get_field(void* ptr, game::hks::HksObjectType type, const script_value& key)
{
const auto state = *game::hks::lua_state;
const auto top = state->m_apistack.top;
push_value(key);
game::hks::HksObject value{};
game::hks::HksObject obj{};
obj.t = type;
obj.v.ptr = ptr;
game::hks::hks_obj_gettable(&value, state, &obj, &state->m_apistack.top[-1]);
state->m_apistack.top = top;
return value;
}
void set_field(void* ptr, game::hks::HksObjectType type, const script_value& key, const script_value& value)
{
const auto state = *game::hks::lua_state;
game::hks::HksObject obj{};
obj.t = type;
obj.v.ptr = ptr;
game::hks::hks_obj_settable(state, &obj, &key.get_raw(), &value.get_raw());
}
}
void push_value(const script_value& value) void push_value(const script_value& value)
{ {
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
const auto value_ = value.get_raw(); *state->m_apistack.top = value.get_raw();
*state->m_apistack.top = value_; state->m_apistack.top++;
}
void push_value(const game::hks::HksObject& value)
{
const auto state = *game::hks::lua_state;
*state->m_apistack.top = value;
state->m_apistack.top++; state->m_apistack.top++;
} }
@ -20,13 +58,16 @@ namespace ui_scripting
return state->m_apistack.top[-1 - offset]; return state->m_apistack.top[-1 - offset];
} }
arguments get_return_values(int count) arguments get_return_values()
{ {
const auto state = *game::hks::lua_state;
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
arguments values; arguments values;
for (auto i = count - 1; i >= 0; i--) for (auto i = count - 1; i >= 0; i--)
{ {
values.push_back(get_return_value(i)); const auto v = get_return_value(i);
values.push_back(v);
} }
if (values.size() == 0) if (values.size() == 0)
@ -40,7 +81,7 @@ namespace ui_scripting
bool notify(const std::string& name, const event_arguments& arguments) bool notify(const std::string& name, const event_arguments& arguments)
{ {
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
if (!state) if (state == nullptr)
{ {
return false; return false;
} }
@ -52,32 +93,34 @@ namespace ui_scripting
{ {
const auto globals = table((*::game::hks::lua_state)->globals.v.table); const auto globals = table((*::game::hks::lua_state)->globals.v.table);
const auto engine = globals.get("Engine").as<table>(); const auto engine = globals.get("Engine").as<table>();
const auto root = engine.get("GetLuiRoot").as<function>().call({})[0].as<userdata>(); const auto root = engine.get("GetLuiRoot")()[0].as<userdata>();
const auto process_event = root.get("processEvent").as<function>(); const auto process_event = root.get("processEvent");
table event{}; table event{};
event.set("name", name); event.set("name", name);
event.set("dispatchChildren", true);
for (const auto& arg : arguments) for (const auto& arg : arguments)
{ {
event.set(arg.first, arg.second); event.set(arg.first, arg.second);
} }
process_event.call({root, event}); process_event(root, event);
return true; return true;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
printf("Error processing event '%s' %s\n", name.data(), e.what()); game_console::print(game_console::con_type_error, "Error processing event '%s' %s\n", name.data(), e.what());
return false;
} }
return false;
} }
arguments call_script_function(const function& function, const arguments& arguments) arguments call_script_function(const function& function, const arguments& arguments)
{ {
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
const auto top = state->m_apistack.top;
stack stack;
push_value(function); push_value(function);
for (auto i = arguments.begin(); i != arguments.end(); ++i) for (auto i = arguments.begin(); i != arguments.end(); ++i)
{ {
@ -85,127 +128,30 @@ namespace ui_scripting
} }
const auto num_args = static_cast<int>(arguments.size()); const auto num_args = static_cast<int>(arguments.size());
stack.save(num_args + 1);
const auto _1 = gsl::finally(&disable_error_hook); game::hks::vm_call_internal(state, num_args, -1, 0);
enable_error_hook(); const auto args = get_return_values();
state->m_apistack.top = top;
try return args;
{
game::hks::vm_call_internal(state, num_args, -1, 0);
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
return get_return_values(count);
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error("Error executing script function: "s + e.what());
}
} }
script_value get_field(const userdata& self, const script_value& key) script_value get_field(const userdata& self, const script_value& key)
{ {
const auto state = *game::hks::lua_state; return get_field(self.ptr, game::hks::TUSERDATA, key);
stack stack;
push_value(key);
stack.save(1);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject value{};
game::hks::HksObject userdata{};
userdata.t = game::hks::TUSERDATA;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_gettable(&value, state, &userdata, &state->m_apistack.top[-1]);
return value;
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error("Error getting userdata field: "s + e.what());
}
} }
script_value get_field(const table& self, const script_value& key) script_value get_field(const table& self, const script_value& key)
{ {
const auto state = *game::hks::lua_state; return get_field(self.ptr, game::hks::TTABLE, key);
stack stack;
push_value(key);
stack.save(1);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject value{};
game::hks::HksObject userdata{};
userdata.t = game::hks::TTABLE;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_gettable(&value, state, &userdata, &state->m_apistack.top[-1]);
return value;
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error("Error getting table field: "s + e.what());
}
} }
void set_field(const userdata& self, const script_value& key, const script_value& value) void set_field(const userdata& self, const script_value& key, const script_value& value)
{ {
const auto state = *game::hks::lua_state; set_field(self.ptr, game::hks::TUSERDATA, key, value);
stack stack;
stack.save(0);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject userdata{};
userdata.t = game::hks::TUSERDATA;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_settable(state, &userdata, &key.get_raw(), &value.get_raw());
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error("Error setting userdata field: "s + e.what());
}
} }
void set_field(const table& self, const script_value& key, const script_value& value) void set_field(const table& self, const script_value& key, const script_value& value)
{ {
const auto state = *game::hks::lua_state; set_field(self.ptr, game::hks::TTABLE, key, value);
stack stack;
stack.save(0);
const auto _1 = gsl::finally(&disable_error_hook);
enable_error_hook();
game::hks::HksObject userdata{};
userdata.t = game::hks::TTABLE;
userdata.v.ptr = self.ptr;
try
{
game::hks::hks_obj_settable(state, &userdata, &key.get_raw(), &value.get_raw());
}
catch (const std::exception& e)
{
stack.fix();
throw std::runtime_error("Error setting table field: "s + e.what());
}
} }
} }

View File

@ -6,8 +6,10 @@
namespace ui_scripting namespace ui_scripting
{ {
void push_value(const script_value& value); void push_value(const script_value& value);
void push_value(const game::hks::HksObject& value);
script_value get_return_value(int offset); script_value get_return_value(int offset);
arguments get_return_values(int count); arguments get_return_values();
bool notify(const std::string& name, const event_arguments& arguments); bool notify(const std::string& name, const event_arguments& arguments);

View File

@ -1,648 +0,0 @@
#include <std_include.hpp>
#include "context.hpp"
#include "error.hpp"
#include "value_conversion.hpp"
#include "../../scripting/execution.hpp"
#include "../script_value.hpp"
#include "../execution.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/command.hpp"
#include "../../../component/fastfiles.hpp"
#include "../../../component/updater.hpp"
#include "../../../component/localized_strings.hpp"
#include "../../../component/mods.hpp"
#include "component/game_console.hpp"
#include "component/scheduler.hpp"
#include <utils/string.hpp>
#include <utils/nt.hpp>
#include <utils/io.hpp>
#include <utils/http.hpp>
#include <utils/cryptography.hpp>
#include <version.h>
namespace ui_scripting::lua
{
namespace
{
const auto json_script = utils::nt::load_resource(LUA_JSON);
scripting::script_value script_convert(const sol::lua_value& value)
{
if (value.is<int>())
{
return {value.as<int>()};
}
if (value.is<unsigned int>())
{
return {value.as<unsigned int>()};
}
if (value.is<bool>())
{
return {value.as<bool>()};
}
if (value.is<double>())
{
return {value.as<double>()};
}
if (value.is<float>())
{
return {value.as<float>()};
}
if (value.is<std::string>())
{
return {value.as<std::string>()};
}
if (value.is<scripting::vector>())
{
return {value.as<scripting::vector>()};
}
return {};
}
bool valid_dvar_name(const std::string& name)
{
for (const auto c : name)
{
if (!isalnum(c) && c != '_')
{
return false;
}
}
return true;
}
void setup_io(sol::state& state)
{
state["io"]["fileexists"] = utils::io::file_exists;
state["io"]["writefile"] = utils::io::write_file;
state["io"]["movefile"] = utils::io::move_file;
state["io"]["filesize"] = utils::io::file_size;
state["io"]["createdirectory"] = utils::io::create_directory;
state["io"]["directoryexists"] = utils::io::directory_exists;
state["io"]["directoryisempty"] = utils::io::directory_is_empty;
state["io"]["listfiles"] = utils::io::list_files;
state["io"]["copyfolder"] = utils::io::copy_folder;
state["io"]["removefile"] = utils::io::remove_file;
state["io"]["removedirectory"] = utils::io::remove_directory;
state["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
}
void setup_json(sol::state& state)
{
const auto json = state.safe_script(json_script, &sol::script_pass_on_error);
handle_error(json);
state["json"] = json;
}
void setup_vector_type(sol::state& state)
{
auto vector_type = state.new_usertype<scripting::vector>("vector", sol::constructors<scripting::vector(float, float, float)>());
vector_type["x"] = sol::property(&scripting::vector::get_x, &scripting::vector::set_x);
vector_type["y"] = sol::property(&scripting::vector::get_y, &scripting::vector::set_y);
vector_type["z"] = sol::property(&scripting::vector::get_z, &scripting::vector::set_z);
vector_type["r"] = sol::property(&scripting::vector::get_x, &scripting::vector::set_x);
vector_type["g"] = sol::property(&scripting::vector::get_y, &scripting::vector::set_y);
vector_type["b"] = sol::property(&scripting::vector::get_z, &scripting::vector::set_z);
vector_type[sol::meta_function::addition] = sol::overload(
[](const scripting::vector& a, const scripting::vector& b)
{
return scripting::vector(
a.get_x() + b.get_x(),
a.get_y() + b.get_y(),
a.get_z() + b.get_z()
);
},
[](const scripting::vector& a, const int value)
{
return scripting::vector(
a.get_x() + value,
a.get_y() + value,
a.get_z() + value
);
}
);
vector_type[sol::meta_function::subtraction] = sol::overload(
[](const scripting::vector& a, const scripting::vector& b)
{
return scripting::vector(
a.get_x() - b.get_x(),
a.get_y() - b.get_y(),
a.get_z() - b.get_z()
);
},
[](const scripting::vector& a, const int value)
{
return scripting::vector(
a.get_x() - value,
a.get_y() - value,
a.get_z() - value
);
}
);
vector_type[sol::meta_function::multiplication] = sol::overload(
[](const scripting::vector& a, const scripting::vector& b)
{
return scripting::vector(
a.get_x() * b.get_x(),
a.get_y() * b.get_y(),
a.get_z() * b.get_z()
);
},
[](const scripting::vector& a, const int value)
{
return scripting::vector(
a.get_x() * value,
a.get_y() * value,
a.get_z() * value
);
}
);
vector_type[sol::meta_function::division] = sol::overload(
[](const scripting::vector& a, const scripting::vector& b)
{
return scripting::vector(
a.get_x() / b.get_x(),
a.get_y() / b.get_y(),
a.get_z() / b.get_z()
);
},
[](const scripting::vector& a, const int value)
{
return scripting::vector(
a.get_x() / value,
a.get_y() / value,
a.get_z() / value
);
}
);
vector_type[sol::meta_function::equal_to] = [](const scripting::vector& a, const scripting::vector& b)
{
return a.get_x() == b.get_x() &&
a.get_y() == b.get_y() &&
a.get_z() == b.get_z();
};
vector_type[sol::meta_function::length] = [](const scripting::vector& a)
{
return sqrt((a.get_x() * a.get_x()) + (a.get_y() * a.get_y()) + (a.get_z() * a.get_z()));
};
vector_type[sol::meta_function::to_string] = [](const scripting::vector& a)
{
return utils::string::va("{x: %f, y: %f, z: %f}", a.get_x(), a.get_y(), a.get_z());
};
}
void setup_game_type(sol::state& state, scheduler& scheduler)
{
struct game
{
};
auto game_type = state.new_usertype<game>("game_");
state["game"] = game();
game_type["time"] = []()
{
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
return now.count();
};
game_type["executecommand"] = [](const game&, const std::string& command)
{
command::execute(command, false);
};
game_type["onframe"] = [&scheduler](const game&, const sol::protected_function& callback)
{
return scheduler.add(callback, 0, false);
};
game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, true);
};
game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, false);
};
game_type["isingame"] = []()
{
return ::game::CL_IsCgameInitialized() && ::game::g_entities[0].client;
};
game_type["getdvar"] = [](const game&, const sol::this_state s, const std::string& name)
{
const auto dvar = ::game::Dvar_FindVar(name.data());
if (!dvar)
{
return sol::lua_value{s, sol::lua_nil};
}
const std::string value = ::game::Dvar_ValueToString(dvar, nullptr, &dvar->current);
return sol::lua_value{s, value};
};
game_type["getdvarint"] = [](const game&, const sol::this_state s, const std::string& name)
{
const auto dvar = ::game::Dvar_FindVar(name.data());
if (!dvar)
{
return sol::lua_value{s, sol::lua_nil};
}
const auto value = atoi(::game::Dvar_ValueToString(dvar, nullptr, &dvar->current));
return sol::lua_value{s, value};
};
game_type["getdvarfloat"] = [](const game&, const sol::this_state s, const std::string& name)
{
const auto dvar = ::game::Dvar_FindVar(name.data());
if (!dvar)
{
return sol::lua_value{s, sol::lua_nil};
}
const auto value = atof(::game::Dvar_ValueToString(dvar, nullptr, &dvar->current));
return sol::lua_value{s, value};
};
game_type["setdvar"] = [](const game&, const std::string& name, const sol::lua_value& value)
{
if (!valid_dvar_name(name))
{
throw std::runtime_error("Invalid DVAR name, must be alphanumeric");
}
const auto hash = ::game::generateHashValue(name.data());
std::string string_value;
if (value.is<bool>())
{
string_value = utils::string::va("%i", value.as<bool>());
}
else if (value.is<int>())
{
string_value = utils::string::va("%i", value.as<int>());
}
else if (value.is<float>())
{
string_value = utils::string::va("%f", value.as<float>());
}
else if (value.is<scripting::vector>())
{
const auto v = value.as<scripting::vector>();
string_value = utils::string::va("%f %f %f",
v.get_x(),
v.get_y(),
v.get_z()
);
}
if (value.is<std::string>())
{
string_value = value.as<std::string>();
}
::game::Dvar_SetCommand(hash, "", string_value.data());
};
game_type["getwindowsize"] = [](const game&, const sol::this_state s)
{
const auto size = ::game::ScrPlace_GetViewPlacement()->realViewportSize;
auto screen = sol::table::create(s.lua_state());
screen["x"] = size[0];
screen["y"] = size[1];
return screen;
};
game_type["playmenuvideo"] = [](const game&, const std::string& video)
{
reinterpret_cast<void (*)(const char* a1, int a2, int a3)>
(0x14071B970)(video.data(), 64, 0);
};
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
{
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
{
table[key] = value;
});
};
game_type["sharedget"] = [](const game&, const std::string& key)
{
std::string result;
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
{
result = table[key];
});
return result;
};
game_type["sharedclear"] = [](const game&)
{
scripting::shared_table.access([](scripting::shared_table_t& table)
{
table.clear();
});
};
game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string)
{
auto table = sol::table::create(s.lua_state());
auto index = 1;
auto type_index = -1;
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
{
if (type_string == ::game::g_assetNames[i])
{
type_index = i;
}
}
if (type_index == -1)
{
throw std::runtime_error("Asset type does not exist");
}
const auto type = static_cast<::game::XAssetType>(type_index);
fastfiles::enum_assets(type, [type, &table, &index](const ::game::XAssetHeader header)
{
const auto asset = ::game::XAsset{type, header};
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
table[index++] = asset_name;
}, true);
return table;
};
game_type["getweapondisplayname"] = [](const game&, const std::string& name)
{
const auto alternate = name.starts_with("alt_");
const auto weapon = ::game::G_GetWeaponForName(name.data());
char buffer[0x400] = {0};
::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400);
return std::string(buffer);
};
game_type["getloadedmod"] = [](const game&)
{
return mods::mod_path;
};
game_type["addlocalizedstring"] = [](const game&, const std::string& string,
const std::string& value)
{
localized_strings::override(string, value);
};
struct player
{
};
auto player_type = state.new_usertype<player>("player_");
state["player"] = player();
player_type["notify"] = [](const player&, const sol::this_state s, const std::string& name, sol::variadic_args va)
{
if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client)
{
throw std::runtime_error("Not in game");
}
::scheduler::once([s, name, args = std::vector<sol::object>(va.begin(), va.end())]()
{
try
{
std::vector<scripting::script_value> arguments{};
for (auto arg : args)
{
arguments.push_back(script_convert({s, arg}));
}
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
scripting::notify(player, name, arguments);
}
catch (...)
{
}
}, ::scheduler::pipeline::server);
};
}
void setup_lui_types(sol::state& state)
{
auto userdata_type = state.new_usertype<userdata>("userdata_");
userdata_type["new"] = sol::property(
[](const userdata& userdata, const sol::this_state s)
{
return convert(s, userdata.get("new"));
},
[](const userdata& userdata, const sol::this_state s, const sol::lua_value& value)
{
userdata.set("new", convert({s, value}));
}
);
userdata_type[sol::meta_function::index] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, userdata.get(convert({s, key})));
};
userdata_type[sol::meta_function::new_index] = [](const userdata& userdata, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
userdata.set(convert({s, key}), convert({s, value}));
};
auto table_type = state.new_usertype<table>("table_");
table_type["new"] = sol::property(
[](const table& table, const sol::this_state s)
{
return convert(s, table.get("new"));
},
[](const table& table, const sol::this_state s, const sol::lua_value& value)
{
table.set("new", convert({s, value}));
}
);
table_type["get"] = [](const table& table, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, table.get(convert({s, key})));
};
table_type["set"] = [](const table& table, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
table.set(convert({s, key}), convert({s, value}));
};
table_type[sol::meta_function::index] = [](const table& table, const sol::this_state s,
const sol::lua_value& key)
{
return convert(s, table.get(convert({s, key})));
};
table_type[sol::meta_function::new_index] = [](const table& table, const sol::this_state s,
const sol::lua_value& key, const sol::lua_value& value)
{
table.set(convert({s, key}), convert({s, value}));
};
auto function_type = state.new_usertype<function>("function_");
function_type[sol::meta_function::call] = [](const function& function, const sol::this_state s, sol::variadic_args va)
{
arguments arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
const auto values = function.call(arguments);
std::vector<sol::lua_value> returns;
for (const auto& value : values)
{
returns.push_back(convert(s, value));
}
return sol::as_returns(returns);
};
state["luiglobals"] = table((*::game::hks::lua_state)->globals.v.table);
state["CoD"] = state["luiglobals"]["CoD"];
state["LUI"] = state["luiglobals"]["LUI"];
state["Engine"] = state["luiglobals"]["Engine"];
state["Game"] = state["luiglobals"]["Game"];
auto updater_table = sol::table::create(state.lua_state());
updater_table["relaunch"] = updater::relaunch;
updater_table["sethastriedupdate"] = updater::set_has_tried_update;
updater_table["gethastriedupdate"] = updater::get_has_tried_update;
updater_table["autoupdatesenabled"] = updater::auto_updates_enabled;
updater_table["startupdatecheck"] = updater::start_update_check;
updater_table["isupdatecheckdone"] = updater::is_update_check_done;
updater_table["getupdatecheckstatus"] = updater::get_update_check_status;
updater_table["isupdateavailable"] = updater::is_update_available;
updater_table["startupdatedownload"] = updater::start_update_download;
updater_table["isupdatedownloaddone"] = updater::is_update_download_done;
updater_table["getupdatedownloadstatus"] = updater::get_update_download_status;
updater_table["cancelupdate"] = updater::cancel_update;
updater_table["isrestartrequired"] = updater::is_restart_required;
updater_table["getlasterror"] = updater::get_last_error;
updater_table["getcurrentfile"] = updater::get_current_file;
state["updater"] = updater_table;
}
}
context::context(std::string data, script_type type)
: scheduler_(state_)
{
this->state_.open_libraries(sol::lib::base,
sol::lib::package,
sol::lib::io,
sol::lib::string,
sol::lib::os,
sol::lib::math,
sol::lib::table);
setup_io(this->state_);
setup_json(this->state_);
setup_vector_type(this->state_);
setup_game_type(this->state_, this->scheduler_);
setup_lui_types(this->state_);
if (type == script_type::file)
{
this->folder_ = data;
this->state_["include"] = [this](const std::string& file)
{
this->load_script(file);
};
sol::function old_require = this->state_["require"];
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
this->state_["require"] = [base_path, old_require](const std::string& path)
{
return old_require(base_path + path);
};
this->state_["scriptdir"] = [this]()
{
return this->folder_;
};
printf("Loading ui script '%s'\n", this->folder_.data());
this->load_script("__init__");
}
if (type == script_type::code)
{
handle_error(this->state_.safe_script(data, &sol::script_pass_on_error));
}
}
context::~context()
{
this->state_.collect_garbage();
this->scheduler_.clear();
this->state_ = {};
}
void context::run_frame()
{
this->scheduler_.run_frame();
this->state_.collect_garbage();
}
void context::load_script(const std::string& script)
{
if (!this->loaded_scripts_.emplace(script).second)
{
return;
}
const auto file = (std::filesystem::path{this->folder_} / (script + ".lua")).generic_string();
handle_error(this->state_.safe_script_file(file, &sol::script_pass_on_error));
}
}

View File

@ -1,47 +0,0 @@
#pragma once
#include "../event.hpp"
#pragma warning(push)
#pragma warning(disable: 4702)
#define SOL_ALL_SAFETIES_ON 1
#define SOL_PRINT_ERRORS 0
#include <sol/sol.hpp>
#include "scheduler.hpp"
#include "event_handler.hpp"
namespace ui_scripting::lua
{
enum script_type
{
file,
code
};
class context
{
public:
context(std::string data, script_type type);
~context();
context(context&&) noexcept = delete;
context& operator=(context&&) noexcept = delete;
context(const context&) = delete;
context& operator=(const context&) = delete;
void run_frame();
void notify(const event& e);
private:
sol::state state_{};
std::string folder_;
std::unordered_set<std::string> loaded_scripts_;
scheduler scheduler_;
void load_script(const std::string& script);
};
}

View File

@ -1,77 +0,0 @@
#include <std_include.hpp>
#include "engine.hpp"
#include "context.hpp"
#include "../../../component/scheduler.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/filesystem.hpp"
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/nt.hpp>
namespace ui_scripting::lua::engine
{
namespace
{
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
const auto lui_updater = utils::nt::load_resource(LUI_UPDATER);
auto& get_scripts()
{
static std::vector<std::unique_ptr<context>> scripts{};
return scripts;
}
void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir))
{
return;
}
const auto scripts = utils::io::list_files(script_dir);
for (const auto& script : scripts)
{
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
{
get_scripts().push_back(std::make_unique<context>(script, script_type::file));
}
}
}
void load_code(const std::string& code)
{
get_scripts().push_back(std::make_unique<context>(code, script_type::code));
}
}
void start()
{
clear_converted_functions();
get_scripts().clear();
load_code(lui_common);
load_code(lui_updater);
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path + "/ui_scripts/");
}
}
void stop()
{
clear_converted_functions();
get_scripts().clear();
}
void run_frame()
{
for (auto& script : get_scripts())
{
script->run_frame();
}
}
}

View File

@ -1,10 +0,0 @@
#pragma once
#include "../event.hpp"
namespace ui_scripting::lua::engine
{
void start();
void stop();
void run_frame();
}

View File

@ -1,24 +0,0 @@
#include <std_include.hpp>
#include "error.hpp"
namespace ui_scripting::lua
{
void handle_error(const sol::protected_function_result& result)
{
if (!result.valid())
{
try
{
printf("************** UI Script execution error **************\n");
const sol::error err = result;
printf("%s\n", err.what());
printf("****************************************************\n");
}
catch (...)
{
}
}
}
}

View File

@ -1,8 +0,0 @@
#pragma once
#include "context.hpp"
namespace ui_scripting::lua
{
void handle_error(const sol::protected_function_result& result);
}

View File

@ -1,175 +0,0 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
#include "event_handler.hpp"
#include "value_conversion.hpp"
namespace ui_scripting::lua
{
event_handler::event_handler(sol::state& state)
: state_(state)
{
auto event_listener_handle_type = state.new_usertype<event_listener_handle>("event_listener_handle");
event_listener_handle_type["clear"] = [this](const event_listener_handle& handle)
{
this->remove(handle);
};
event_listener_handle_type["endon"] = [this](const event_listener_handle& handle, const std::string& event)
{
this->add_endon_condition(handle, event);
};
}
void event_handler::dispatch(const event& event)
{
bool has_built_arguments = false;
event_arguments arguments{};
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
this->handle_endon_conditions(event);
for (auto i = tasks.begin(); i != tasks.end();)
{
if (i->event != event.name)
{
++i;
continue;
}
if (!i->is_deleted)
{
if (!has_built_arguments)
{
has_built_arguments = true;
arguments = this->build_arguments(event);
}
handle_error(i->callback(sol::as_args(arguments)));
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
event_listener_handle event_handler::add_event_listener(event_listener&& listener)
{
const uint64_t id = ++this->current_listener_id_;
listener.id = id;
listener.is_deleted = false;
new_callbacks_.access([&listener](task_list& tasks)
{
tasks.emplace_back(std::move(listener));
});
return {id};
}
void event_handler::add_endon_condition(const event_listener_handle& handle, const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.endon_conditions.emplace_back(event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void event_handler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
void event_handler::remove(const event_listener_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void event_handler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
void event_handler::handle_endon_conditions(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for (auto& task : tasks)
{
for (auto& condition : task.endon_conditions)
{
if (condition == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access(deleter);
}
event_arguments event_handler::build_arguments(const event& event) const
{
event_arguments arguments;
for (const auto& argument : event.arguments)
{
arguments.emplace_back(convert(this->state_, argument));
}
return arguments;
}
}

View File

@ -1,58 +0,0 @@
#pragma once
#include <utils/concurrency.hpp>
namespace ui_scripting::lua
{
using event_arguments = std::vector<sol::lua_value>;
using event_callback = sol::protected_function;
class event_listener_handle
{
public:
uint64_t id = 0;
};
class event_listener final : public event_listener_handle
{
public:
std::string event = {};
event_callback callback = {};
bool is_volatile = false;
bool is_deleted = false;
std::vector<std::string> endon_conditions{};
};
class event_handler final
{
public:
event_handler(sol::state& state);
event_handler(event_handler&&) noexcept = delete;
event_handler& operator=(event_handler&&) noexcept = delete;
event_handler(const scheduler&) = delete;
event_handler& operator=(const event_handler&) = delete;
void dispatch(const event& event);
event_listener_handle add_event_listener(event_listener&& listener);
void clear();
private:
sol::state& state_;
std::atomic_int64_t current_listener_id_ = 0;
using task_list = std::vector<event_listener>;
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
void remove(const event_listener_handle& handle);
void merge_callbacks();
void handle_endon_conditions(const event& event);
void add_endon_condition(const event_listener_handle& handle, const std::string& event);
event_arguments build_arguments(const event& event) const;
};
}

View File

@ -1,171 +0,0 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
namespace ui_scripting::lua
{
scheduler::scheduler(sol::state& state)
{
auto task_handle_type = state.new_usertype<task_handle>("task_handle");
task_handle_type["clear"] = [this](const task_handle& handle)
{
this->remove(handle);
};
task_handle_type["endon"] = [this](const task_handle& handle, const std::string& event)
{
this->add_endon_condition(handle, event);
};
}
void scheduler::dispatch(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for (auto& task : tasks)
{
for (auto& condition : task.endon_conditions)
{
if (condition == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access([&](task_list& tasks)
{
deleter(tasks);
new_callbacks_.access(deleter);
});
}
void scheduler::run_frame()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->delay)
{
++i;
continue;
}
i->last_call = now;
if (!i->is_deleted)
{
handle_error(i->callback());
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
void scheduler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds,
const bool is_volatile)
{
return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile);
}
task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay,
const bool is_volatile)
{
const uint64_t id = ++this->current_task_id_;
task task;
task.is_volatile = is_volatile;
task.callback = callback;
task.delay = delay;
task.last_call = std::chrono::steady_clock::now();
task.id = id;
task.is_deleted = false;
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
return {id};
}
void scheduler::add_endon_condition(const task_handle& handle, const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.endon_conditions.emplace_back(event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void scheduler::remove(const task_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void scheduler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <utils/concurrency.hpp>
namespace ui_scripting::lua
{
class context;
class task_handle
{
public:
uint64_t id = 0;
};
class task final : public task_handle
{
public:
std::chrono::steady_clock::time_point last_call{};
sol::protected_function callback{};
std::chrono::milliseconds delay{};
bool is_volatile = false;
bool is_deleted = false;
std::vector<std::string> endon_conditions{};
};
class scheduler final
{
public:
scheduler(sol::state& state);
scheduler(scheduler&&) noexcept = delete;
scheduler& operator=(scheduler&&) noexcept = delete;
scheduler(const scheduler&) = delete;
scheduler& operator=(const scheduler&) = delete;
void dispatch(const event& event);
void run_frame();
void clear();
task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile);
task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile);
private:
using task_list = std::vector<task>;
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
std::atomic_int64_t current_task_id_ = 0;
void add_endon_condition(const task_handle& handle, const std::string& event);
void remove(const task_handle& handle);
void merge_callbacks();
};
}

View File

@ -1,144 +0,0 @@
#include <std_include.hpp>
#include "value_conversion.hpp"
#include "../execution.hpp"
#include "../../../component/ui_scripting.hpp"
namespace ui_scripting::lua
{
namespace
{
table convert_table(const sol::table& t)
{
table res{};
t.for_each([res](const sol::object& key, const sol::object& value)
{
res.set(convert(key), convert(value));
});
return res;
}
script_value convert_function(const sol::protected_function& function)
{
const auto closure = game::hks::cclosure_Create(*game::hks::lua_state, main_function_handler, 0, 0, 0);
add_converted_function(closure, function);
game::hks::HksObject value{};
value.t = game::hks::TCFUNCTION;
value.v.cClosure = closure;
return value;
}
}
script_value convert(const sol::lua_value& value)
{
if (value.is<bool>())
{
return {value.as<bool>()};
}
if (value.is<int>())
{
return {value.as<int>()};
}
if (value.is<unsigned int>())
{
return {value.as<unsigned int>()};
}
if (value.is<double>())
{
return {value.as<double>()};
}
if (value.is<float>())
{
return {value.as<float>()};
}
if (value.is<std::string>())
{
return {value.as<std::string>()};
}
if (value.is<lightuserdata>())
{
return {value.as<lightuserdata>()};
}
if (value.is<userdata>())
{
return {value.as<userdata>()};
}
if (value.is<table>())
{
return {value.as<table>()};
}
if (value.is<function>())
{
return {value.as<function>()};
}
if (value.is<sol::table>())
{
return {convert_table(value.as<sol::table>())};
}
if (value.is<sol::protected_function>())
{
return {convert_function(value.as<sol::protected_function>())};
}
return {};
}
sol::lua_value convert(lua_State* state, const script_value& value)
{
if (value.is<int>())
{
return {state, value.as<int>()};
}
if (value.is<float>())
{
return {state, value.as<float>()};
}
if (value.is<bool>())
{
return {state, value.as<bool>()};
}
if (value.is<std::string>())
{
return {state, value.as<std::string>()};
}
if (value.is<lightuserdata>())
{
return {state, value.as<lightuserdata>()};
}
if (value.is<userdata>())
{
return {state, value.as<userdata>()};
}
if (value.is<table>())
{
return {state, value.as<table>()};
}
if (value.is<function>())
{
return {state, value.as<function>()};
}
return {state, sol::lua_nil};
}
}

View File

@ -1,9 +0,0 @@
#pragma once
#include "context.hpp"
#include "../script_value.hpp"
namespace ui_scripting::lua
{
script_value convert(const sol::lua_value& value);
sol::lua_value convert(lua_State* state, const script_value& value);
}

View File

@ -2,9 +2,79 @@
#include "execution.hpp" #include "execution.hpp"
#include "types.hpp" #include "types.hpp"
#include "script_value.hpp" #include "script_value.hpp"
#include "../../component/ui_scripting.hpp"
namespace ui_scripting namespace ui_scripting
{ {
hks_object::hks_object(const game::hks::HksObject& value)
{
this->assign(value);
}
hks_object::hks_object(const hks_object& other) noexcept
{
this->operator=(other);
}
hks_object::hks_object(hks_object&& other) noexcept
{
this->operator=(std::move(other));
}
hks_object& hks_object::operator=(const hks_object& other) noexcept
{
if (this != &other)
{
this->release();
this->assign(other.value_);
}
return *this;
}
hks_object& hks_object::operator=(hks_object&& other) noexcept
{
if (this != &other)
{
this->release();
this->value_ = other.value_;
other.value_.t = game::hks::TNONE;
}
return *this;
}
hks_object::~hks_object()
{
this->release();
}
const game::hks::HksObject& hks_object::get() const
{
return this->value_;
}
void hks_object::assign(const game::hks::HksObject& value)
{
this->value_ = value;
const auto state = *game::hks::lua_state;
const auto top = state->m_apistack.top;
push_value(this->value_);
this->ref_ = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
state->m_apistack.top = top;
}
void hks_object::release()
{
if (this->ref_)
{
game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref_);
this->value_.t = game::hks::TNONE;
}
}
/*************************************************************** /***************************************************************
* Constructors * Constructors
**************************************************************/ **************************************************************/
@ -32,6 +102,24 @@ namespace ui_scripting
this->value_ = obj; this->value_ = obj;
} }
script_value::script_value(const long long value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TUI64;
obj.v.i64 = value;
this->value_ = obj;
}
script_value::script_value(const unsigned long long value)
{
game::hks::HksObject obj{};
obj.t = game::hks::TUI64;
obj.v.ui64 = value;
this->value_ = obj;
}
script_value::script_value(const bool value) script_value::script_value(const bool value)
{ {
game::hks::HksObject obj{}; game::hks::HksObject obj{};
@ -55,32 +143,29 @@ namespace ui_scripting
{ {
} }
script_value::script_value(const char* value)
{
game::hks::HksObject obj{};
const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base;
game::hks::hksi_lua_pushlstring(state, value, static_cast<unsigned int>(strlen(value)));
obj = state->m_apistack.top[-1];
this->value_ = obj;
}
script_value::script_value(const char* value, unsigned int len) script_value::script_value(const char* value, unsigned int len)
{ {
game::hks::HksObject obj{}; game::hks::HksObject obj{};
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base; if (state == nullptr)
{
return;
}
const auto top = state->m_apistack.top;
game::hks::hksi_lua_pushlstring(state, value, len); game::hks::hksi_lua_pushlstring(state, value, len);
obj = state->m_apistack.top[-1]; obj = state->m_apistack.top[-1];
state->m_apistack.top = top;
this->value_ = obj; this->value_ = obj;
} }
script_value::script_value(const char* value)
: script_value(value, static_cast<unsigned int>(strlen(value)))
{
}
script_value::script_value(const std::string& value) script_value::script_value(const std::string& value)
: script_value(value.data(), static_cast<unsigned int>(value.size())) : script_value(value.data(), static_cast<unsigned int>(value.size()))
{ {
@ -88,26 +173,38 @@ namespace ui_scripting
script_value::script_value(const lightuserdata& value) script_value::script_value(const lightuserdata& value)
{ {
this->value_.t = game::hks::TLIGHTUSERDATA; game::hks::HksObject obj{};
this->value_.v.ptr = value.ptr; obj.t = game::hks::TLIGHTUSERDATA;
obj.v.ptr = value.ptr;
this->value_ = obj;
} }
script_value::script_value(const userdata& value) script_value::script_value(const userdata& value)
{ {
this->value_.t = game::hks::TUSERDATA; game::hks::HksObject obj{};
this->value_.v.ptr = value.ptr; obj.t = game::hks::TUSERDATA;
obj.v.ptr = value.ptr;
this->value_ = obj;
} }
script_value::script_value(const table& value) script_value::script_value(const table& value)
{ {
this->value_.t = game::hks::TTABLE; game::hks::HksObject obj{};
this->value_.v.ptr = value.ptr; obj.t = game::hks::TTABLE;
obj.v.ptr = value.ptr;
this->value_ = obj;
} }
script_value::script_value(const function& value) script_value::script_value(const function& value)
{ {
this->value_.t = value.type; game::hks::HksObject obj{};
this->value_.v.ptr = value.ptr; obj.t = value.type;
obj.v.ptr = value.ptr;
this->value_ = obj;
} }
/*************************************************************** /***************************************************************
@ -139,6 +236,34 @@ namespace ui_scripting
return static_cast<unsigned int>(this->get_raw().v.number); return static_cast<unsigned int>(this->get_raw().v.number);
} }
/***************************************************************
* Integer 64
**************************************************************/
template <>
bool script_value::is<long long>() const
{
return this->get_raw().t == game::hks::TUI64;
}
template <>
bool script_value::is<unsigned long long>() const
{
return this->is<long long>();
}
template <>
long long script_value::get() const
{
return static_cast<long long>(this->get_raw().v.ui64);
}
template <>
unsigned long long script_value::get() const
{
return static_cast<unsigned long long>(this->get_raw().v.ui64);
}
/*************************************************************** /***************************************************************
* Boolean * Boolean
**************************************************************/ **************************************************************/
@ -273,7 +398,7 @@ namespace ui_scripting
template <> template <>
function script_value::get() const function script_value::get() const
{ {
return { this->get_raw().v.cClosure, this->get_raw().t }; return {this->get_raw().v.cClosure, this->get_raw().t};
} }
/*************************************************************** /***************************************************************
@ -282,10 +407,10 @@ namespace ui_scripting
const game::hks::HksObject& script_value::get_raw() const const game::hks::HksObject& script_value::get_raw() const
{ {
return this->value_; return this->value_.get();
} }
bool script_value::operator==(const script_value& other) bool script_value::operator==(const script_value& other) const
{ {
if (this->get_raw().t != other.get_raw().t) if (this->get_raw().t != other.get_raw().t)
{ {
@ -299,4 +424,26 @@ namespace ui_scripting
return this->get_raw().v.native == other.get_raw().v.native; return this->get_raw().v.native == other.get_raw().v.native;
} }
arguments script_value::operator()() const
{
return this->as<function>()();
}
arguments script_value::operator()(const arguments& arguments) const
{
return this->as<function>()(arguments);
}
function_argument::function_argument(const arguments& args, const script_value& value, const int index)
: values_(args)
, value_(value)
, index_(index)
{
}
function_arguments::function_arguments(const arguments& values)
: values_(values)
{
}
} }

View File

@ -1,12 +1,92 @@
#pragma once #pragma once
#include "game/game.hpp" #include "game/game.hpp"
#include <utils/string.hpp>
namespace ui_scripting namespace ui_scripting
{ {
class lightuserdata; class lightuserdata;
class userdata_value;
class userdata; class userdata;
class table_value;
class table; class table;
class function; class function;
class script_value;
namespace
{
template <typename T>
std::string get_typename()
{
auto& info = typeid(T);
if (info == typeid(std::string) ||
info == typeid(const char*))
{
return "string";
}
if (info == typeid(lightuserdata))
{
return "lightuserdata";
}
if (info == typeid(userdata))
{
return "userdata";
}
if (info == typeid(table))
{
return "table";
}
if (info == typeid(function))
{
return "function";
}
if (info == typeid(int) ||
info == typeid(float) ||
info == typeid(unsigned int))
{
return "number";
}
if (info == typeid(bool))
{
return "boolean";
}
return info.name();
}
}
class hks_object
{
public:
hks_object() = default;
hks_object(const game::hks::HksObject& value);
hks_object(const hks_object& other) noexcept;
hks_object(hks_object&& other) noexcept;
hks_object& operator=(const hks_object& other) noexcept;
hks_object& operator=(hks_object&& other) noexcept;
~hks_object();
const game::hks::HksObject& get() const;
private:
void assign(const game::hks::HksObject& value);
void release();
game::hks::HksObject value_{game::hks::TNONE, {}};
int ref_{};
};
using arguments = std::vector<script_value>;
using event_arguments = std::unordered_map<std::string, script_value>;
class script_value class script_value
{ {
@ -16,6 +96,8 @@ namespace ui_scripting
script_value(int value); script_value(int value);
script_value(unsigned int value); script_value(unsigned int value);
script_value(long long value);
script_value(unsigned long long value);
script_value(bool value); script_value(bool value);
script_value(float value); script_value(float value);
@ -30,7 +112,52 @@ namespace ui_scripting
script_value(const table& value); script_value(const table& value);
script_value(const function& value); script_value(const function& value);
bool operator==(const script_value& other); template <template<class, class> class C, class T, typename TableType = table>
script_value(const C<T, std::allocator<T>>& container)
{
TableType table_{};
int index = 1;
for (const auto& value : container)
{
table_.set(index++, value);
}
game::hks::HksObject obj{};
obj.t = game::hks::TTABLE;
obj.v.ptr = table_.ptr;
this->value_ = obj;
}
template <typename F>
script_value(F f)
: script_value(function(f))
{
}
bool operator==(const script_value& other) const;
arguments operator()() const;
arguments operator()(const arguments& arguments) const;
template<class ...T>
arguments operator()(T... arguments) const
{
return this->as<function>().call({arguments...});
}
template <size_t Size>
table_value operator[](const char(&key)[Size]) const
{
return {this->as<table>(), key};
}
template <typename T = script_value>
table_value operator[](const T& key) const
{
return {this->as<table>(), key};
}
template <typename T> template <typename T>
bool is() const; bool is() const;
@ -40,21 +167,90 @@ namespace ui_scripting
{ {
if (!this->is<T>()) if (!this->is<T>())
{ {
throw std::runtime_error("Invalid type"); throw std::runtime_error(utils::string::va("%s expected, got %s",
typeid(T).name(), game::hks::typenames[this->get_raw().t + 2]));
} }
return get<T>(); return get<T>();
} }
template <typename T>
operator T() const
{
return this->as<T>();
}
const game::hks::HksObject& get_raw() const; const game::hks::HksObject& get_raw() const;
private: hks_object value_{};
template <typename T> template <typename T>
T get() const; T get() const;
game::hks::HksObject value_{};
}; };
using arguments = std::vector<script_value>; class variadic_args : public arguments
using event_arguments = std::unordered_map<std::string, script_value>; {
};
class function_argument
{
public:
function_argument(const arguments& args, const script_value& value, const int index);
template <typename T>
T as() const
{
if (!this->value_.is<T>())
{
const auto hks_typename = game::hks::typenames[this->value_.get_raw().t + 2];
const auto typename_ = get_typename<T>();
throw std::runtime_error(utils::string::va("bad argument #%d (%s expected, got %s)",
this->index_ + 1, typename_.data(), hks_typename));
}
return this->value_.get<T>();
}
template <>
variadic_args as() const
{
variadic_args args{};
for (auto i = this->index_; i < this->values_.size(); i++)
{
args.push_back(this->values_[i]);
}
return args;
}
template <typename T>
operator T() const
{
return this->as<T>();
}
private:
arguments values_{};
script_value value_{};
int index_{};
};
class function_arguments
{
public:
function_arguments(const arguments& values);
function_argument operator[](const int index) const
{
if (index >= values_.size())
{
return {values_, {}, index};
}
return {values_, values_[index], index};
}
private:
arguments values_{};
};
} }

View File

@ -1,6 +1,7 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "types.hpp" #include "types.hpp"
#include "execution.hpp" #include "execution.hpp"
#include "../../component/ui_scripting.hpp"
namespace ui_scripting namespace ui_scripting
{ {
@ -73,11 +74,12 @@ namespace ui_scripting
value.t = game::hks::TUSERDATA; value.t = game::hks::TUSERDATA;
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base; const auto top = state->m_apistack.top;
push_value(value); push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
state->m_apistack.top = top;
} }
void userdata::release() void userdata::release()
@ -98,6 +100,29 @@ namespace ui_scripting
return get_field(*this, key); return get_field(*this, key);
} }
userdata_value userdata::operator[](const script_value& key) const
{
return {*this, key};
}
userdata_value::userdata_value(const userdata& table, const script_value& key)
: userdata_(table)
, key_(key)
{
this->value_ = this->userdata_.get(key).get_raw();
}
void userdata_value::operator=(const script_value& value)
{
this->userdata_.set(this->key_, value);
this->value_ = value.get_raw();
}
bool userdata_value::operator==(const script_value& value)
{
return this->userdata_.get(this->key_) == value;
}
/*************************************************************** /***************************************************************
* Table * Table
**************************************************************/ **************************************************************/
@ -165,11 +190,12 @@ namespace ui_scripting
value.t = game::hks::TTABLE; value.t = game::hks::TTABLE;
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base; const auto top = state->m_apistack.top;
push_value(value); push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
state->m_apistack.top = top;
} }
void table::release() void table::release()
@ -185,15 +211,57 @@ namespace ui_scripting
set_field(*this, key, value); set_field(*this, key, value);
} }
table_value table::operator[](const script_value& key) const
{
return {*this, key};
}
script_value table::get(const script_value& key) const script_value table::get(const script_value& key) const
{ {
return get_field(*this, key); return get_field(*this, key);
} }
table_value::table_value(const table& table, const script_value& key)
: table_(table)
, key_(key)
{
this->value_ = this->table_.get(key).get_raw();
}
void table_value::operator=(const script_value& value)
{
this->table_.set(this->key_, value);
this->value_ = value.get_raw();
}
void table_value::operator=(const table_value& value)
{
this->table_.set(this->key_, value);
this->value_ = value.get_raw();
}
bool table_value::operator==(const script_value& value)
{
return this->table_.get(this->key_) == value;
}
bool table_value::operator==(const table_value& value)
{
return this->table_.get(this->key_) == value;
}
/*************************************************************** /***************************************************************
* Function * Function
**************************************************************/ **************************************************************/
function::function(game::hks::lua_function func)
{
const auto state = *game::hks::lua_state;
this->ptr = game::hks::cclosure_Create(state, func, 0, 0, 0);
this->type = game::hks::HksObjectType::TCFUNCTION;
this->add();
}
function::function(game::hks::cclosure* ptr_, game::hks::HksObjectType type_) function::function(game::hks::cclosure* ptr_, game::hks::HksObjectType type_)
: ptr(ptr_) : ptr(ptr_)
, type(type_) , type(type_)
@ -254,11 +322,12 @@ namespace ui_scripting
value.t = this->type; value.t = this->type;
const auto state = *game::hks::lua_state; const auto state = *game::hks::lua_state;
state->m_apistack.top = state->m_apistack.base; const auto top = state->m_apistack.top;
push_value(value); push_value(value);
this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
state->m_apistack.top = top;
} }
void function::release() void function::release()
@ -274,37 +343,13 @@ namespace ui_scripting
return call_script_function(*this, arguments); return call_script_function(*this, arguments);
} }
/*************************************************************** arguments function::operator()(const arguments& arguments) const
* Stack
**************************************************************/
stack::stack()
{ {
this->state = *game::hks::lua_state; return this->call(arguments);
this->state->m_apistack.top = this->state->m_apistack.base;
} }
void stack::save(int num_args) arguments function::operator()() const
{ {
this->num_args_ = num_args; return this->call({});
this->num_calls_ = state->m_numberOfCCalls;
this->base_bottom_ = state->m_apistack.base - state->m_apistack.bottom;
this->top_bottom_ = state->m_apistack.top - state->m_apistack.bottom;
this->callstack_ = state->m_callStack.m_current - state->m_callStack.m_records;
}
void stack::fix()
{
this->state->m_numberOfCCalls = this->num_calls_;
game::hks::closePendingUpvalues(this->state, &this->state->m_apistack.bottom[this->top_bottom_ - this->num_args_]);
this->state->m_callStack.m_current = &this->state->m_callStack.m_records[this->callstack_];
this->state->m_apistack.base = &this->state->m_apistack.bottom[this->base_bottom_];
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ - static_cast<uint64_t>(this->num_args_ + 1)];
this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t;
this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr;
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1];
} }
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "game/game.hpp" #include "game/game.hpp"
#include "script_value.hpp" #include "script_value.hpp"
#include "../../component/ui_scripting.hpp"
namespace ui_scripting namespace ui_scripting
{ {
@ -11,6 +12,8 @@ namespace ui_scripting
void* ptr; void* ptr;
}; };
class userdata_value;
class userdata class userdata
{ {
public: public:
@ -27,6 +30,8 @@ namespace ui_scripting
script_value get(const script_value& key) const; script_value get(const script_value& key) const;
void set(const script_value& key, const script_value& value) const; void set(const script_value& key, const script_value& value) const;
userdata_value operator[](const script_value& key) const;
void* ptr; void* ptr;
private: private:
@ -36,6 +41,19 @@ namespace ui_scripting
int ref{}; int ref{};
}; };
class userdata_value : public script_value
{
public:
userdata_value(const userdata& table, const script_value& key);
void operator=(const script_value& value);
bool operator==(const script_value& value);
private:
userdata userdata_;
script_value key_;
};
class table_value;
class table class table
{ {
public: public:
@ -53,6 +71,8 @@ namespace ui_scripting
script_value get(const script_value& key) const; script_value get(const script_value& key) const;
void set(const script_value& key, const script_value& value) const; void set(const script_value& key, const script_value& value) const;
table_value operator[](const script_value& key) const;
game::hks::HashTable* ptr; game::hks::HashTable* ptr;
private: private:
@ -62,11 +82,32 @@ namespace ui_scripting
int ref{}; int ref{};
}; };
class table_value : public script_value
{
public:
table_value(const table& table, const script_value& key);
void operator=(const script_value& value);
void operator=(const table_value& value);
bool operator==(const script_value& value);
bool operator==(const table_value& value);
private:
table table_;
script_value key_;
};
class function class function
{ {
public: public:
function(game::hks::lua_function);
function(game::hks::cclosure*, game::hks::HksObjectType); function(game::hks::cclosure*, game::hks::HksObjectType);
template <typename F>
function(F f)
{
this->ptr = ui_scripting::convert_function(f);
this->type = game::hks::TCFUNCTION;
}
function(const function& other); function(const function& other);
function(function&& other) noexcept; function(function&& other) noexcept;
@ -77,6 +118,16 @@ namespace ui_scripting
arguments call(const arguments& arguments) const; arguments call(const arguments& arguments) const;
arguments operator()(const arguments& arguments) const;
template<class ...T>
arguments operator()(T... arguments) const
{
return this->call({arguments...});
}
arguments operator()() const;
game::hks::cclosure* ptr; game::hks::cclosure* ptr;
game::hks::HksObjectType type; game::hks::HksObjectType type;
@ -86,28 +137,4 @@ namespace ui_scripting
int ref{}; int ref{};
}; };
class stack final
{
public:
stack();
void save(int num_args);
void fix();
stack(stack&&) = delete;
stack(const stack&) = delete;
stack& operator=(stack&&) = delete;
stack& operator=(const stack&) = delete;
private:
game::hks::lua_State* state;
int num_args_;
int num_calls_;
uint64_t base_bottom_;
uint64_t top_bottom_;
uint64_t callstack_;
};
} }

View File

@ -22,7 +22,7 @@
-- SOFTWARE. -- SOFTWARE.
-- --
local json = { _version = "0.1.2" } json = { _version = "0.1.2" }
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Encode -- Encode

View File

@ -1,6 +1,30 @@
menucallbacks = {} local menucallbacks = {}
originalmenus = {} local originalmenus = {}
stack = {} local stack = {}
function getchildren(element)
local children = {}
local first = element:getFirstChild()
while (first) do
table.insert(children, first)
first = first:getNextSibling()
end
return children
end
function printchildtree(element, indent, last)
indent = indent or ""
print(indent .. "+- " .. element.id .. " " .. (element:getText() or ""))
indent = indent .. (last and " " or "| ")
local children = getchildren(element)
for i = 1, #children do
printchildtree(children[i], indent, i == #children)
end
end
LUI.MenuBuilder.m_types_build["generic_waiting_popup_"] = function (menu, event) LUI.MenuBuilder.m_types_build["generic_waiting_popup_"] = function (menu, event)
local oncancel = stack.oncancel local oncancel = stack.oncancel
@ -14,7 +38,7 @@ LUI.MenuBuilder.m_types_build["generic_waiting_popup_"] = function (menu, event)
end end
}) })
popup.text = popup:getchildren()[7] popup.text = popup:getLastChild():getPreviousSibling():getPreviousSibling()
stack = { stack = {
ret = popup ret = popup
@ -74,10 +98,10 @@ LUI.onmenuopen = function(name, callback)
originalmenus[name] = LUI.MenuBuilder.m_types_build[name] originalmenus[name] = LUI.MenuBuilder.m_types_build[name]
LUI.MenuBuilder.m_types_build[name] = function(...) LUI.MenuBuilder.m_types_build[name] = function(...)
local args = {...} local args = {...}
local menu = originalmenus[name](table.unpack(args)) local menu = originalmenus[name](unpack(args))
for k, v in luiglobals.next, menucallbacks[name] do for k, v in next, menucallbacks[name] do
v(menu, table.unpack(args)) v(menu, unpack(args))
end end
return menu return menu
@ -134,7 +158,7 @@ LUI.openpopupmenu = function(menu, args)
end end
LUI.yesnopopup = function(data) LUI.yesnopopup = function(data)
for k, v in luiglobals.next, data do for k, v in next, data do
stack[k] = v stack[k] = v
end end
LUI.FlowManager.RequestPopupMenu(nil, "generic_yes_no_popup_") LUI.FlowManager.RequestPopupMenu(nil, "generic_yes_no_popup_")
@ -142,21 +166,9 @@ LUI.yesnopopup = function(data)
end end
LUI.confirmationpopup = function(data) LUI.confirmationpopup = function(data)
for k, v in luiglobals.next, data do for k, v in next, data do
stack[k] = v stack[k] = v
end end
LUI.FlowManager.RequestPopupMenu(nil, "generic_confirmation_popup_") LUI.FlowManager.RequestPopupMenu(nil, "generic_confirmation_popup_")
return stack.ret return stack.ret
end end
function userdata_:getchildren()
local children = {}
local first = self:getFirstChild()
while (first) do
table.insert(children, first)
first = first:getNextSibling()
end
return children
end

View File

@ -1,12 +1,16 @@
updatecancelled = false if (not Engine.InFrontend()) then
taskinterval = 100 return
end
updatecancelled = false
updater.cancelupdate() updater.cancelupdate()
function startupdatecheck(popup, autoclose) function startupdatecheck(popup, autoclose)
updatecancelled = false Engine.GetLuiRoot():registerEventHandler("update_check_done", function(element, event)
if (updatecancelled) then
return
end
local callback = function()
if (not updater.getupdatecheckstatus()) then if (not updater.getupdatecheckstatus()) then
if (autoclose) then if (autoclose) then
LUI.FlowManager.RequestLeaveMenu(popup) LUI.FlowManager.RequestLeaveMenu(popup)
@ -38,23 +42,18 @@ function startupdatecheck(popup, autoclose)
end end
end end
}) })
end end)
updater.startupdatecheck() updater.startupdatecheck()
createtask({
done = updater.isupdatecheckdone,
cancelled = isupdatecancelled,
callback = callback,
interval = taskinterval
})
end end
function startupdatedownload(popup, autoclose) function startupdatedownload(popup, autoclose)
updater.startupdatedownload()
local textupdate = nil local textupdate = nil
local previousfile = nil local previousfile = nil
textupdate = game:oninterval(function() local timer = LUI.UITimer.new(10, "update_file")
popup:addElement(timer)
popup:registerEventHandler("update_file", function()
local file = updater.getcurrentfile() local file = updater.getcurrentfile()
if (file == previousfile) then if (file == previousfile) then
return return
@ -62,10 +61,14 @@ function startupdatedownload(popup, autoclose)
file = previousfile file = previousfile
popup.text:setText("Downloading file " .. updater.getcurrentfile() .. "...") popup.text:setText("Downloading file " .. updater.getcurrentfile() .. "...")
end, 10) end)
local callback = function() Engine.GetLuiRoot():registerEventHandler("update_done", function(element, event)
textupdate:clear() timer:close()
if (updatecancelled) then
return
end
if (not updater.getupdatedownloadstatus()) then if (not updater.getupdatedownloadstatus()) then
if (autoclose) then if (autoclose) then
@ -95,14 +98,9 @@ function startupdatedownload(popup, autoclose)
if (autoclose) then if (autoclose) then
LUI.FlowManager.RequestLeaveMenu(popup) LUI.FlowManager.RequestLeaveMenu(popup)
end end
end end)
createtask({ updater.startupdatedownload()
done = updater.isupdatedownloaddone,
cancelled = isupdatecancelled,
callback = callback,
interval = taskinterval
})
end end
function updaterpopup(oncancel) function updaterpopup(oncancel)
@ -129,10 +127,6 @@ function createtask(data)
return interval return interval
end end
function isupdatecancelled()
return updatecancelled
end
function tryupdate(autoclose) function tryupdate(autoclose)
updatecancelled = false updatecancelled = false
local popup = updaterpopup(function() local popup = updaterpopup(function()
@ -149,10 +143,13 @@ function tryautoupdate()
end end
if (not updater.gethastriedupdate()) then if (not updater.gethastriedupdate()) then
game:ontimeout(function() local timer = LUI.UITimer.new(100, "tryupdate")
Engine.GetLuiRoot():addElement(timer)
Engine.GetLuiRoot():registerEventHandler("tryupdate", function()
timer:close()
updater.sethastriedupdate(true) updater.sethastriedupdate(true)
tryupdate(true) tryupdate(true)
end, 100) end)
end end
end end