diff --git a/README.md b/README.md
index 4f3d7011..b459ea06 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Proof of concept for an Modern Warfare Remastered client.
## Compile from source
- Clone the Git repo. Do NOT download it as ZIP, that won't work.
-- Update the submodules and run `premake5 vs2019` or simply use the delivered `generate.bat`.
+- Update the submodules and run `premake5 vs2022` or simply use the delivered `generate.bat`.
- Build via solution file in `build\h1-mod.sln`.
### Premake arguments
diff --git a/data/.gitkeep b/data/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/ui_scripts/server_list/__init__.lua b/data/ui_scripts/server_list/__init__.lua
new file mode 100644
index 00000000..a5532b0a
--- /dev/null
+++ b/data/ui_scripts/server_list/__init__.lua
@@ -0,0 +1,143 @@
+local Lobby = luiglobals.Lobby
+local SystemLinkJoinMenu = LUI.mp_menus.SystemLinkJoinMenu
+
+if (not SystemLinkJoinMenu) then
+ return
+end
+
+local offsets = {
+ 10,
+ 500,
+ 950,
+ 700,
+ 1100,
+}
+
+local columns = {
+ "@MENU_HOST_NAME",
+ "@MENU_MAP",
+ "Players",
+ "@MENU_TYPE1",
+ "Ping",
+}
+
+SystemLinkJoinMenu.AddHeaderButton = function(menu, f12_arg1, width)
+ local state = CoD.CreateState(0, f12_arg1, nil, nil, CoD.AnchorTypes.TopLeft)
+ state.width = width
+ local element = LUI.UIElement.new(state)
+ local button = SystemLinkJoinMenu.CreateButton("header", 24)
+
+ button:addElement(LUI.Divider.new(CoD.CreateState(nil, 0, nil, nil, CoD.AnchorTypes.TopLeftRight), 40, LUI.Divider.Grey))
+ button:makeNotFocusable()
+ button:addElement(LUI.Divider.new(CoD.CreateState(nil, 0, nil, nil, CoD.AnchorTypes.BottomLeftRight), 40, LUI.Divider.Grey))
+
+ local gettext = function(i)
+ return Engine.Localize(columns[i])
+ end
+
+ for i = 1, #offsets do
+ SystemLinkJoinMenu.MakeText(button.textHolder, offsets[i], gettext(i), nil)
+ end
+
+ element:addElement(button)
+ menu:addElement(element)
+end
+
+SystemLinkJoinMenu.AddServerButton = function(menu, controller, index)
+ local button = SystemLinkJoinMenu.CreateButton(index or "header", 24)
+ button:makeFocusable()
+ button.index = index
+ button:addEventHandler("button_action", SystemLinkJoinMenu.OnJoinGame)
+
+ local gettext = function(i)
+ return Lobby.GetServerData(controller, index, i - 1)
+ end
+
+ for i = 1, #offsets do
+ SystemLinkJoinMenu.MakeText(button.textHolder, offsets[i], gettext(i), luiglobals.Colors.h1.medium_grey)
+ end
+
+ menu.list:addElement(button)
+ return button
+end
+
+SystemLinkJoinMenu.MakeText = function(menu, f5_arg1, text, color)
+ local state = CoD.CreateState(f5_arg1, nil, f5_arg1 + 200, nil, CoD.AnchorTypes.Left)
+ state.font = CoD.TextSettings.TitleFontSmall.Font
+ state.top = -6
+ state.height = 14
+ state.alignment = LUI.Alignment.Left
+ state.glow = LUI.GlowState.None
+ state.color = color
+
+ local el = LUI.UIText.new(state)
+ el:registerAnimationState("focused", {
+ color = luiglobals.Colors.white
+ })
+
+ el:registerEventHandler("focused", function(element, event)
+ element:animateToState("focused", 0)
+ end)
+
+ el:registerEventHandler("unfocused", function(element, event)
+ element:animateToState("default", 0)
+ end)
+
+ el:setText(text)
+ menu:addElement(el)
+end
+
+function menu_systemlink_join(f19_arg0, f19_arg1)
+ local width = 1145
+
+ local menu = LUI.MenuTemplate.new(f19_arg0, {
+ menu_title = "@PLATFORM_SYSTEM_LINK_TITLE",
+ menu_width = width,
+ menu_top_indent = 20,
+ disableDeco = true,
+ spacing = 1
+ })
+
+ SystemLinkJoinMenu.AddHeaderButton(menu, 80, width)
+ SystemLinkJoinMenu.AddLowerCounter(menu, width)
+ SystemLinkJoinMenu.UpdateCounterText(menu, nil)
+ Lobby.BuildServerList(Engine.GetFirstActiveController())
+
+ menu.list:registerEventHandler(LUI.UIScrollIndicator.UpdateEvent, function(element, event)
+ SystemLinkJoinMenu.UpdateCounterText(menu, event)
+ end)
+
+ SystemLinkJoinMenu.UpdateGameList(menu)
+ menu:registerEventHandler("updateGameList", SystemLinkJoinMenu.UpdateGameList)
+ menu:addElement(LUI.UITimer.new(250, "updateGameList"))
+
+ LUI.ButtonHelperText.ClearHelperTextObjects(menu.help, {
+ side = "all"
+ })
+
+ menu:AddHelp({
+ name = "add_button_helper_text",
+ button_ref = "button_alt1",
+ helper_text = Engine.Localize("@MENU_SB_TOOLTIP_BTN_REFRESH"),
+ side = "right",
+ clickable = true,
+ priority = -1000
+ }, function(f21_arg0, f21_arg1)
+ SystemLinkJoinMenu.RefreshServers(f21_arg0, f21_arg1, menu)
+ end)
+
+ menu:AddHelp({
+ name = "add_button_helper_text",
+ button_ref = "button_action",
+ helper_text = Engine.Localize("@MENU_JOIN_GAME1"),
+ side = "left",
+ clickable = false,
+ priority = -1000
+ }, nil, nil, true)
+
+ menu:AddBackButton()
+
+ return menu
+end
+
+LUI.MenuBuilder.m_types_build["menu_systemlink_join"] = menu_systemlink_join
diff --git a/generate.bat b/generate.bat
index b9ffdf6f..06c74119 100644
--- a/generate.bat
+++ b/generate.bat
@@ -1,3 +1,3 @@
@echo off
git submodule update --init --recursive
-tools\premake5 %* vs2019
\ No newline at end of file
+tools\premake5 %* vs2022
\ No newline at end of file
diff --git a/premake5.lua b/premake5.lua
index b3f026af..16043516 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -227,10 +227,12 @@ targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}"
configurations {"Debug", "Release"}
-architecture "x64"
+language "C++"
+cppdialect "C++20"
+
+architecture "x86_64"
platforms "x64"
-buildoptions "/std:c++latest"
systemversion "latest"
symbols "On"
staticruntime "On"
@@ -248,25 +250,22 @@ end
flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"}
+filter "platforms:x64"
+ defines {"_WINDOWS", "WIN32"}
+filter {}
-configuration "windows"
-defines {"_WINDOWS", "WIN32"}
+filter "configurations:Release"
+ optimize "Size"
+ buildoptions {"/GL"}
+ linkoptions { "/IGNORE:4702", "/LTCG" }
+ defines {"NDEBUG"}
+ flags {"FatalCompileWarnings"}
+filter {}
-configuration "Release"
-optimize "Size"
-buildoptions {"/GL"}
-linkoptions { "/IGNORE:4702", "/LTCG" }
-
-defines {"NDEBUG"}
-
-flags {"FatalCompileWarnings"}
-
-configuration "Debug"
-optimize "Debug"
-
-defines {"DEBUG", "_DEBUG"}
-
-configuration {}
+filter "configurations:Debug"
+ optimize "Debug"
+ defines {"DEBUG", "_DEBUG"}
+filter {}
project "common"
kind "StaticLib"
diff --git a/src/client/component/console.cpp b/src/client/component/console.cpp
index 0d887199..174beaf8 100644
--- a/src/client/component/console.cpp
+++ b/src/client/component/console.cpp
@@ -254,7 +254,7 @@ namespace console
{
std::string cmd;
- while (true)
+ while (!this->terminate_runner_)
{
std::getline(std::cin, cmd);
command::execute(cmd);
diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp
index 6e63f082..dd9f920f 100644
--- a/src/client/component/dedicated.cpp
+++ b/src/client/component/dedicated.cpp
@@ -196,9 +196,6 @@ namespace dedicated
// Disable r_preloadShaders
dvars::override::register_bool("r_preloadShaders", false, game::DVAR_FLAG_READ);
- // Don't allow sv_hostname to be changed by the game
- dvars::disable::set_string("sv_hostname");
-
// Stop crashing from sys_errors
utils::hook::jump(0x140511520, sys_error_stub);
diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp
index 7d55c989..5925a64a 100644
--- a/src/client/component/discord.cpp
+++ b/src/client/component/discord.cpp
@@ -49,17 +49,21 @@ namespace discord
discord_presence.details = utils::string::va("%s on %s", gametype, mapname);
- auto host_name = game::Dvar_FindVar("sv_hostname")->current.string;
- auto max_clients = game::Dvar_FindVar("sv_maxclients")->current.integer;
+ char clean_hostname[0x100] = {0};
+ utils::string::strip(game::Dvar_FindVar("sv_hostname")->current.string,
+ clean_hostname, sizeof(clean_hostname));
+ auto max_clients = party::server_client_count();
+
+ // When true, we are in Private Match
if (game::SV_Loaded())
{
- max_clients = party::server_client_count();
+ strcpy_s(clean_hostname, "Private Match");
+ max_clients = game::Dvar_FindVar("sv_maxclients")->current.integer;
}
- auto clients = *(reinterpret_cast(0x14621BE00));
- discord_presence.partySize = clients;
+ discord_presence.partySize = *reinterpret_cast(0x1429864C4);
discord_presence.partyMax = max_clients;
- discord_presence.state = host_name;
+ discord_presence.state = clean_hostname;
discord_presence.largeImageKey = map;
}
else if (game::environment::is_sp())
diff --git a/src/client/component/dvars.cpp b/src/client/component/dvars.cpp
index 4a086a27..d9c77fdf 100644
--- a/src/client/component/dvars.cpp
+++ b/src/client/component/dvars.cpp
@@ -435,7 +435,7 @@ namespace dvars
dvar_set_float_hook.create(SELECT_VALUE(0x1403C7420, 0x1404FD360), &dvar_set_float);
dvar_set_int_hook.create(SELECT_VALUE(0x1403C76C0, 0x1404FD5E0), &dvar_set_int);
dvar_set_string_hook.create(SELECT_VALUE(0x1403C7900, 0x1404FD8D0), &dvar_set_string);
- dvar_set_from_string_hook.create(SELECT_VALUE(0, 0x1404FD520), &dvar_set_from_string);
+ dvar_set_from_string_hook.create(SELECT_VALUE(0x1403C7620, 0x1404FD520), &dvar_set_from_string);
}
};
}
diff --git a/src/client/component/exception.cpp b/src/client/component/exception.cpp
index 61210ad7..028bb238 100644
--- a/src/client/component/exception.cpp
+++ b/src/client/component/exception.cpp
@@ -16,7 +16,6 @@
#include
#include "game/dvars.hpp"
-#include
namespace exception
{
@@ -255,17 +254,6 @@ namespace exception
{
dvars::cg_legacyCrashHandling = dvars::register_bool("cg_legacyCrashHandling",
false, game::DVAR_FLAG_SAVED, true);
-
- // Could cause memory leaks but fixes possible out of memory (12) errors
- const auto has_flag = utils::flags::has_flag("memoryfix");
- if (has_flag)
- {
- utils::hook::jump(0x140578BE0, malloc);
- utils::hook::jump(0x140578B00, _aligned_malloc);
- utils::hook::jump(0x140578C40, free);
- utils::hook::jump(0x140578D30, realloc);
- utils::hook::jump(0x140578B60, _aligned_realloc);
- }
}
};
}
diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp
new file mode 100644
index 00000000..d4eda37f
--- /dev/null
+++ b/src/client/component/fastfiles.cpp
@@ -0,0 +1,49 @@
+#include
+#include "loader/component_loader.hpp"
+#include "fastfiles.hpp"
+
+#include "command.hpp"
+#include "console.hpp"
+
+#include
+#include
+
+namespace fastfiles
+{
+ static utils::concurrency::container current_fastfile;
+
+ namespace
+ {
+ utils::hook::detour db_try_load_x_file_internal_hook;
+
+ void db_try_load_x_file_internal(const char* zone_name, const int flags)
+ {
+ printf("Loading fastfile %s\n", zone_name);
+ current_fastfile.access([&](std::string& fastfile)
+ {
+ fastfile = zone_name;
+ });
+ db_try_load_x_file_internal_hook.invoke(zone_name, flags);
+ }
+ }
+
+ std::string get_current_fastfile()
+ {
+ return current_fastfile.access([&](std::string& fastfile)
+ {
+ return fastfile;
+ });
+ }
+
+ class component final : public component_interface
+ {
+ public:
+ void post_unpack() override
+ {
+ db_try_load_x_file_internal_hook.create(
+ SELECT_VALUE(0x1401CDDD0, 0x1402BFFE0), &db_try_load_x_file_internal);
+ }
+ };
+}
+
+REGISTER_COMPONENT(fastfiles::component)
diff --git a/src/client/component/fastfiles.hpp b/src/client/component/fastfiles.hpp
new file mode 100644
index 00000000..ac26d2ec
--- /dev/null
+++ b/src/client/component/fastfiles.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "game/game.hpp"
+
+namespace fastfiles
+{
+ std::string get_current_fastfile();
+}
diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp
index e4189453..289a07a8 100644
--- a/src/client/component/game_console.cpp
+++ b/src/client/component/game_console.cpp
@@ -555,7 +555,7 @@ namespace game_console
{
if (key == game::keyNum_t::K_F10)
{
- if (game::mp::svs_clients[local_client_num].header.state >= 1)
+ if (!game::Com_InFrontEnd())
{
return false;
}
diff --git a/src/client/component/lui.cpp b/src/client/component/lui.cpp
index 1407d471..d3a7edca 100644
--- a/src/client/component/lui.cpp
+++ b/src/client/component/lui.cpp
@@ -15,11 +15,6 @@ namespace lui
public:
void post_unpack() override
{
- if (!game::environment::is_mp())
- {
- return;
- }
-
// Don't show create cod account popup
//utils::hook::set(0x14017C957, 0); // H1(1.4)
diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp
index cd7abd98..47ca2b20 100644
--- a/src/client/component/network.cpp
+++ b/src/client/component/network.cpp
@@ -27,12 +27,19 @@ namespace network
const auto cmd_string = utils::string::to_lower(command);
auto& callbacks = get_callbacks();
const auto handler = callbacks.find(cmd_string);
+
if (handler == callbacks.end())
{
return false;
}
const auto offset = cmd_string.size() + 5;
+
+ if (message->cursize <= offset)
+ {
+ return false;
+ }
+
const std::string_view data(message->data + offset, message->cursize - offset);
handler->second(*address, data);
diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp
index 1bea10b2..a8ee11fc 100644
--- a/src/client/component/patches.cpp
+++ b/src/client/component/patches.cpp
@@ -2,18 +2,18 @@
#include "loader/component_loader.hpp"
#include "dvars.hpp"
+#include "version.h"
+#include "command.hpp"
+#include "console.hpp"
+#include "network.hpp"
+#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include
#include
-#include
-
-#include "version.h"
-#include
-#include
-#include
+#include
namespace patches
{
@@ -258,6 +258,19 @@ namespace patches
// Prevent clients from sending invalid reliableAcknowledge
// utils::hook::call(0x1404899C6, sv_execute_client_message_stub); // H1(1.4)
+
+ // "fix" for rare 'Out of memory error' error
+ if (utils::flags::has_flag("memoryfix"))
+ {
+ utils::hook::jump(0x140578BE0, malloc);
+ utils::hook::jump(0x140578B00, _aligned_malloc);
+ utils::hook::jump(0x140578C40, free);
+ utils::hook::jump(0x140578D30, realloc);
+ utils::hook::jump(0x140578B60, _aligned_realloc);
+ }
+
+ // Change default hostname and make it replicated
+ dvars::override::register_string("sv_hostname", "^2H1-Mod^7 Default Server", game::DVAR_FLAG_REPLICATED);
}
};
}
diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp
index bbf55a86..dace554a 100644
--- a/src/client/component/server_list.cpp
+++ b/src/client/component/server_list.cpp
@@ -151,6 +151,11 @@ namespace server_list
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
}
+ if (column == 4)
+ {
+ return servers[i].game_type.empty() ? "" : utils::string::va("%i", servers[i].ping);
+ }
+
return "";
}
diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp
new file mode 100644
index 00000000..941dda10
--- /dev/null
+++ b/src/client/component/ui_scripting.cpp
@@ -0,0 +1,180 @@
+#include
+#include "loader/component_loader.hpp"
+
+#include "game/game.hpp"
+#include "game/dvars.hpp"
+
+#include "scheduler.hpp"
+#include "command.hpp"
+
+#include "ui_scripting.hpp"
+
+#include "game/ui_scripting/lua/engine.hpp"
+#include "game/ui_scripting/execution.hpp"
+#include "game/ui_scripting/lua/error.hpp"
+
+#include
+#include
+
+namespace ui_scripting
+{
+ namespace
+ {
+ std::unordered_map converted_functions;
+
+ utils::hook::detour hksi_lual_error_hook;
+ utils::hook::detour hksi_lual_error_hook2;
+ utils::hook::detour hks_start_hook;
+ utils::hook::detour hks_shutdown_hook;
+ utils::hook::detour hks_allocator_hook;
+ utils::hook::detour hks_frame_hook;
+
+ bool error_hook_enabled = false;
+
+ void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...)
+ {
+ char va_buffer[2048] = {0};
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsprintf_s(va_buffer, fmt, ap);
+ va_end(ap);
+
+ const auto formatted = std::string(va_buffer);
+
+ if (!error_hook_enabled)
+ {
+ return hksi_lual_error_hook.invoke(s, formatted.data());
+ }
+ else
+ {
+ throw std::runtime_error(formatted);
+ }
+ }
+
+ void* hks_start_stub(char a1)
+ {
+ const auto _1 = gsl::finally([]()
+ {
+ ui_scripting::lua::engine::start();
+ });
+
+ return hks_start_hook.invoke(a1);
+ }
+
+ void hks_shutdown_stub()
+ {
+ ui_scripting::lua::engine::stop();
+ hks_shutdown_hook.invoke();
+ }
+
+ void* hks_allocator_stub(void* userData, void* oldMemory, unsigned __int64 oldSize, unsigned __int64 newSize)
+ {
+ const auto closure = reinterpret_cast(oldMemory);
+ if (converted_functions.find(closure) != converted_functions.end())
+ {
+ converted_functions.erase(closure);
+ }
+
+ return hks_allocator_hook.invoke(userData, oldMemory, oldSize, newSize);
+ }
+
+ void hks_frame_stub()
+ {
+ const auto state = *game::hks::lua_state;
+ if (state)
+ {
+ ui_scripting::lua::engine::run_frame();
+ }
+ }
+ }
+
+ int main_function_handler(game::hks::lua_State* state)
+ {
+ const auto value = state->m_apistack.base[-1];
+ if (value.t != game::hks::TCFUNCTION)
+ {
+ return 0;
+ }
+
+ const auto closure = reinterpret_cast(value.v.cClosure);
+ if (converted_functions.find(closure) == converted_functions.end())
+ {
+ return 0;
+ }
+
+ const auto function = converted_functions[closure];
+ const auto count = static_cast(state->m_apistack.top - state->m_apistack.base);
+ const auto arguments = get_return_values(count);
+ const auto s = function.lua_state();
+
+ std::vector 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)
+ {
+ converted_functions[closure] = function;
+ }
+
+ void clear_converted_functions()
+ {
+ converted_functions.clear();
+ }
+
+ void enable_error_hook()
+ {
+ error_hook_enabled = true;
+ }
+
+ void disable_error_hook()
+ {
+ error_hook_enabled = false;
+ }
+
+ class component final : public component_interface
+ {
+ public:
+
+ void post_unpack() override
+ {
+ if (game::environment::is_dedi())
+ {
+ return;
+ }
+
+ hks_start_hook.create(SELECT_VALUE(0x1400E4B40, 0x140176A40), hks_start_stub);
+ hks_shutdown_hook.create(SELECT_VALUE(0x1400DD3D0, 0x14016CA80), hks_shutdown_stub);
+ hksi_lual_error_hook.create(SELECT_VALUE(0x1400A5EA0, 0x14012F300), hksi_lual_error_stub);
+ hks_allocator_hook.create(SELECT_VALUE(0x14009B570, 0x14012BAC0), hks_allocator_stub);
+ hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), hks_frame_stub);
+
+ if (game::environment::is_mp())
+ {
+ hksi_lual_error_hook2.create(0x1401366B0, hksi_lual_error_stub);
+ }
+
+ command::add("lui_restart", []()
+ {
+ utils::hook::invoke(SELECT_VALUE(0x1400DD3D0, 0x14016CA80));
+ utils::hook::invoke(SELECT_VALUE(0x1400E6170, 0x1401780D0));
+ });
+ }
+ };
+}
+
+REGISTER_COMPONENT(ui_scripting::component)
\ No newline at end of file
diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp
new file mode 100644
index 00000000..2a48f6ec
--- /dev/null
+++ b/src/client/component/ui_scripting.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include "game/ui_scripting/lua/value_conversion.hpp"
+
+namespace ui_scripting
+{
+ int main_function_handler(game::hks::lua_State* state);
+ void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function);
+ void clear_converted_functions();
+
+ void enable_error_hook();
+ void disable_error_hook();
+}
\ No newline at end of file
diff --git a/src/client/game/demonware/data_types.hpp b/src/client/game/demonware/data_types.hpp
index c7ba4929..fb108190 100644
--- a/src/client/game/demonware/data_types.hpp
+++ b/src/client/game/demonware/data_types.hpp
@@ -141,4 +141,33 @@ namespace demonware
buffer->read_string(&this->timezone);
}
};
+
+ // made up name
+ class bdFile final : public bdTaskResult
+ {
+ public:
+ uint64_t owner_id;
+ std::string platform;
+ std::string filename;
+ uint32_t unk;
+ std::string data;
+
+ void serialize(byte_buffer* buffer) override
+ {
+ buffer->write_uint64(this->owner_id);
+ buffer->write_string(this->platform);
+ buffer->write_string(this->filename);
+ buffer->write_uint32(this->unk);
+ buffer->write_blob(this->data);
+ }
+
+ void deserialize(byte_buffer* buffer) override
+ {
+ buffer->read_uint64(&this->owner_id);
+ buffer->read_string(&this->platform);
+ buffer->read_string(&this->filename);
+ buffer->read_uint32(&this->unk);
+ buffer->read_blob(&this->data);
+ }
+ };
}
diff --git a/src/client/game/demonware/services/bdStorage.cpp b/src/client/game/demonware/services/bdStorage.cpp
index 5a07ae03..2e7f026c 100644
--- a/src/client/game/demonware/services/bdStorage.cpp
+++ b/src/client/game/demonware/services/bdStorage.cpp
@@ -66,17 +66,24 @@ namespace demonware
void bdStorage::list_publisher_files(service_server* server, byte_buffer* buffer)
{
+#ifdef DEBUG
+ utils::io::write_file("demonware/bdStorage/list_publisher_files", buffer->get_buffer());
+#endif
+
uint32_t date;
uint16_t num_results, offset;
- std::string filename, data;
+ std::string unk, filename, data;
- int out{};
- buffer->read(2, &out);
+ buffer->read_string(&unk);
buffer->read_uint32(&date);
buffer->read_uint16(&num_results);
buffer->read_uint16(&offset);
buffer->read_string(&filename);
+#ifdef DEBUG
+ printf("[DW]: [bdStorage]: list publisher files: %s\n", filename.data());
+#endif
+
auto reply = server->create_reply(this->task_id());
if (this->load_publisher_resource(filename, data))
@@ -99,9 +106,12 @@ namespace demonware
void bdStorage::get_publisher_file(service_server* server, byte_buffer* buffer)
{
- std::string filename;
- int out{};
- buffer->read(2, &out);
+#ifdef DEBUG
+ utils::io::write_file("demonware/bdStorage/get_publisher_file", buffer->get_buffer());
+#endif
+
+ std::string unk, filename;
+ buffer->read_string(&unk);
buffer->read_string(&filename);
#ifdef DEBUG
@@ -133,55 +143,104 @@ namespace demonware
void bdStorage::set_user_file(service_server* server, byte_buffer* buffer) const
{
- bool priv;
+#ifdef DEBUG
+ utils::io::write_file("demonware/bdStorage/set_user_file", buffer->get_buffer());
+#endif
+
uint64_t owner;
- std::string game, filename, data;
+ uint32_t numfiles;
+ std::string game, platform;
buffer->read_string(&game);
- buffer->read_string(&filename);
- buffer->read_bool(&priv);
- buffer->read_blob(&data);
buffer->read_uint64(&owner);
-
- const auto path = get_user_file_path(filename);
- utils::io::write_file(path, data);
-
- auto* info = new bdFileInfo;
-
- info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data());
- info->filename = filename;
- info->create_time = uint32_t(time(nullptr));
- info->modified_time = info->create_time;
- info->file_size = uint32_t(data.size());
- info->owner_id = owner;
- info->priv = priv;
+ buffer->read_string(&platform);
+ buffer->read_uint32(&numfiles);
auto reply = server->create_reply(this->task_id());
- reply->add(info);
+
+ for (uint32_t i = 0; i < numfiles; i++)
+ {
+ std::string filename, data;
+ uint32_t unk;
+ bool priv;
+
+ buffer->read_string(&filename);
+ buffer->read_blob(&data);
+ buffer->read_uint32(&unk);
+ buffer->read_bool(&priv);
+
+ const auto path = get_user_file_path(filename);
+ utils::io::write_file(path, data);
+
+ auto* info = new bdFileInfo;
+
+ info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data());
+ info->filename = filename;
+ info->create_time = uint32_t(time(nullptr));
+ info->modified_time = info->create_time;
+ info->file_size = uint32_t(data.size());
+ info->owner_id = uint64_t(owner);
+ info->priv = priv;
+
+#ifdef DEBUG
+ printf("[DW]: [bdStorage]: set user file: %s\n", filename.data());
+#endif
+
+ reply->add(info);
+ }
+
reply->send();
}
void bdStorage::get_user_file(service_server* server, byte_buffer* buffer) const
{
- uint64_t owner{};
- std::string game, filename, platform, data;
-
- int out{};
- buffer->read(2, &out);
- buffer->read_string(&game);
- buffer->read_string(&filename);
- buffer->read_uint64(&owner);
- buffer->read_string(&platform);
-
#ifdef DEBUG
- printf("[DW]: [bdStorage]: user file: %s, %s, %s\n", game.data(), filename.data(), platform.data());
+ utils::io::write_file("demonware/bdStorage/get_user_file", buffer->get_buffer());
#endif
- const auto path = get_user_file_path(filename);
- if (utils::io::read_file(path, &data))
+ uint32_t unk32_0;
+ uint32_t numfiles, count = 0;
+ uint64_t owner;
+ std::string game, platform;
+
+ buffer->read_string(&game);
+ buffer->read_uint32(&unk32_0);
+ buffer->read_uint64(&owner);
+ buffer->read_string(&platform);
+ buffer->read_uint64(&owner);
+ buffer->read_string(&platform);
+ buffer->read_uint32(&numfiles);
+
+ auto reply = server->create_reply(this->task_id());
+
+ for (uint32_t i = 0; i < numfiles; i++)
+ {
+ std::string filename, data;
+ buffer->read_string(&filename);
+
+ const auto path = get_user_file_path(filename);
+ if (!utils::io::read_file(path, &data))
+ {
+ continue;
+ }
+
+ auto response = new bdFile;
+ response->owner_id = owner;
+ response->unk = 0;
+ response->platform = platform;
+ response->filename = filename;
+ response->data = data;
+
+ reply->add(response);
+ ++count;
+
+#ifdef DEBUG
+ printf("[DW]: [bdStorage]: get user file: %s, %s, %s\n", game.data(), filename.data(), platform.data());
+#endif
+ }
+
+ if (count == numfiles)
{
- auto reply = server->create_reply(this->task_id());
- reply->add(new bdFileData(data));
reply->send();
}
else
@@ -192,6 +251,10 @@ namespace demonware
void bdStorage::unk12(service_server* server, byte_buffer* buffer) const
{
+#ifdef DEBUG
+ utils::io::write_file("demonware/bdStorage/unk12", buffer->get_buffer());
+#endif
+
// TODO:
auto reply = server->create_reply(this->task_id());
reply->send();
diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp
index d5fe8bfc..842aebbc 100644
--- a/src/client/game/structs.hpp
+++ b/src/client/game/structs.hpp
@@ -1397,7 +1397,7 @@ namespace game
LiveClientDropType liveDropRequest; //269572
char __pad4[24];
TestClientType testClient; // 269600
- char __pad5[391700];
+ char __pad5[610012];
}; // size = 661304
}
diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp
index 9690f48b..5ca58188 100644
--- a/src/client/game/symbols.hpp
+++ b/src/client/game/symbols.hpp
@@ -29,8 +29,9 @@ namespace game
WEAK symbol BG_GetWeaponNameComplete{0x0, 0x140165580}; // H1MP
- WEAK symbol Com_Frame_Try_Block_Function{0, 0x1400D8310};
+ WEAK symbol Com_Frame_Try_Block_Function{0x1401CE8D0, 0x1400D8310};
WEAK symbol Com_GetCurrentCoDPlayMode{0, 0x1405039A0};
+ WEAK symbol Com_InFrontEnd{0x1400E4B30, 0x140176A30};
WEAK symbol Com_SetSlowMotion{0, 0x1400DB790};
WEAK symbol Com_Error{0x1403509C0, 0x1400D78A0};
WEAK symbol Com_Quit_f{0x140352BE0, 0x1400DA830};
@@ -74,7 +75,7 @@ namespace game
WEAK symbol G_Glass_Update{0, 0x14033A640};
WEAK symbol G_GetClientScore{0, 0x140342F90};
- WEAK symbol I_CleanStr{0x1403CD230, 0x140503D00};
+ WEAK symbol I_CleanStr{0x1403CD230, 0x140503D00};
WEAK symbol Key_KeynumToString{0x140187CC0, 0x14024FE10};
@@ -118,9 +119,9 @@ namespace game
WEAK symbol DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70};
- WEAK symbol LUI_OpenMenu{0, 0x1404CD210};
+ WEAK symbol LUI_OpenMenu{0x14039D5F0, 0x1404CD210};
- WEAK symbol Menu_IsMenuOpenAndVisible{0, 0x1404C7320};
+ WEAK symbol Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320};
WEAK symbol SL_FindString{0x140314AF0, 0x14043B470};
WEAK symbol SL_GetString{0x140314D90, 0x14043B840}; // H1MP
@@ -161,8 +162,8 @@ namespace game
WEAK symbol UI_GetMapDisplayName{0, 0x140408CC0};
WEAK symbol UI_GetGameTypeDisplayName{0, 0x1404086A0};
- WEAK symbol UI_RunMenuScript{0, 0x1404CFE60};
- WEAK symbol UI_TextWidth{0, 0x1404D21A0};
+ WEAK symbol UI_RunMenuScript{0x14039EFF0, 0x1404CFE60};
+ WEAK symbol UI_TextWidth{0x1403A0F20, 0x1404D21A0};
WEAK symbol UI_SafeTranslateString{0x140350430, 0x14041C580};
@@ -221,4 +222,19 @@ namespace game
{
WEAK symbol g_entities{0x14550DD90, 0};
}
+
+ namespace hks
+ {
+ WEAK symbol lua_state{0x141E2C2F8, 0x1426D3D08};
+ WEAK symbol hksi_lua_pushlstring{0x14004DA90, 0x1400624F0};
+ WEAK symbol hks_obj_getfield{0x14009C0A0, 0x14012C600};
+ WEAK symbol hks_obj_settable{0x14009D240, 0x14012D820};
+ WEAK symbol hks_obj_gettable{0x14009C580, 0x14012CAE0};
+ WEAK symbol vm_call_internal{0x1400C87A0, 0x140159EB0};
+ WEAK symbol Hashtable_Create{0x14008B3B0, 0x14011B320};
+ WEAK symbol cclosure_Create{0x14008B5D0, 0x14011B540};
+ WEAK symbol hksi_luaL_ref{0x1400A64D0, 0x140136D30};
+ WEAK symbol hksi_luaL_unref{0x14009EF10, 0x14012F610};
+ }
}
diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp
new file mode 100644
index 00000000..fff6b88e
--- /dev/null
+++ b/src/client/game/ui_scripting/execution.cpp
@@ -0,0 +1,161 @@
+#include
+#include "execution.hpp"
+#include "component/ui_scripting.hpp"
+
+#include
+
+namespace ui_scripting
+{
+ void push_value(const script_value& value)
+ {
+ const auto state = *game::hks::lua_state;
+ const auto value_ = value.get_raw();
+ *state->m_apistack.top = value_;
+ state->m_apistack.top++;
+ }
+
+ script_value get_return_value(int offset)
+ {
+ const auto state = *game::hks::lua_state;
+ return state->m_apistack.top[-1 - offset];
+ }
+
+ arguments get_return_values(int count)
+ {
+ arguments values;
+
+ for (auto i = count - 1; i >= 0; i--)
+ {
+ values.push_back(get_return_value(i));
+ }
+
+ if (values.size() == 0)
+ {
+ values.push_back({});
+ }
+
+ return values;
+ }
+
+ arguments call_script_function(const function& function, const arguments& arguments)
+ {
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(function);
+ for (auto i = arguments.begin(); i != arguments.end(); ++i)
+ {
+ push_value(*i);
+ }
+
+ const auto _1 = gsl::finally(&disable_error_hook);
+ enable_error_hook();
+
+ try
+ {
+ game::hks::vm_call_internal(state, static_cast(arguments.size()), -1, 0);
+ const auto count = static_cast(state->m_apistack.top - state->m_apistack.base);
+ return get_return_values(count);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error(std::string("Error executing script function: ") + e.what());
+ }
+ }
+
+ script_value get_field(const userdata& self, const script_value& key)
+ {
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(key);
+
+ 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)
+ {
+ throw std::runtime_error(std::string("Error getting userdata field: ") + e.what());
+ }
+ }
+
+ script_value get_field(const table& self, const script_value& key)
+ {
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(key);
+
+ 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)
+ {
+ throw std::runtime_error(std::string("Error getting table field: ") + e.what());
+ }
+ }
+
+ void set_field(const userdata& self, const script_value& key, const script_value& value)
+ {
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ 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)
+ {
+ throw std::runtime_error(std::string("Error setting userdata field: ") + e.what());
+ }
+ }
+
+ void set_field(const table& self, const script_value& key, const script_value& value)
+ {
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ 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)
+ {
+ throw std::runtime_error(std::string("Error setting table field: ") + e.what());
+ }
+ }
+}
diff --git a/src/client/game/ui_scripting/execution.hpp b/src/client/game/ui_scripting/execution.hpp
new file mode 100644
index 00000000..24f4dd72
--- /dev/null
+++ b/src/client/game/ui_scripting/execution.hpp
@@ -0,0 +1,18 @@
+#pragma once
+#include "game/game.hpp"
+#include "types.hpp"
+#include "script_value.hpp"
+
+namespace ui_scripting
+{
+ void push_value(const script_value& value);
+ script_value get_return_value(int offset);
+ arguments get_return_values(int count);
+
+ arguments call_script_function(const function& function, const arguments& arguments);
+
+ script_value get_field(const userdata& self, const script_value& key);
+ script_value get_field(const table& self, const script_value& key);
+ void set_field(const userdata& self, const script_value& key, const script_value& value);
+ void set_field(const table& self, const script_value& key, const script_value& value);
+}
diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp
new file mode 100644
index 00000000..def427e0
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/context.cpp
@@ -0,0 +1,205 @@
+#include
+#include "context.hpp"
+#include "error.hpp"
+#include "value_conversion.hpp"
+#include "../script_value.hpp"
+#include "../execution.hpp"
+
+#include "../../../component/ui_scripting.hpp"
+#include "../../../component/command.hpp"
+
+#include "component/game_console.hpp"
+#include "component/scheduler.hpp"
+
+#include
+#include
+#include
+
+namespace ui_scripting::lua
+{
+ namespace
+ {
+ void setup_types(sol::state& state, scheduler& scheduler)
+ {
+ struct game
+ {
+ };
+ auto game_type = state.new_usertype("game_");
+ state["game"] = game();
+
+ 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);
+ };
+
+ auto userdata_type = state.new_usertype("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["get"] = [](const userdata& userdata, const sol::this_state s,
+ const sol::lua_value& key)
+ {
+ return convert(s, userdata.get(convert({s, key})));
+ };
+
+ userdata_type["set"] = [](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}));
+ };
+
+ 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_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_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 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"];
+ }
+ }
+
+ context::context(std::string folder)
+ : folder_(std::move(folder))
+ , 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);
+
+ 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_;
+ };
+
+ setup_types(this->state_, this->scheduler_);
+
+ printf("Loading ui script '%s'\n", this->folder_.data());
+ this->load_script("__init__");
+ }
+
+ 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));
+ }
+}
diff --git a/src/client/game/ui_scripting/lua/context.hpp b/src/client/game/ui_scripting/lua/context.hpp
new file mode 100644
index 00000000..0876d3df
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/context.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#pragma warning(push)
+#pragma warning(disable: 4702)
+
+#define SOL_ALL_SAFETIES_ON 1
+#define SOL_PRINT_ERRORS 0
+#include
+
+#include "scheduler.hpp"
+
+namespace ui_scripting::lua
+{
+ class context
+ {
+ public:
+ context(std::string folder);
+ ~context();
+
+ context(context&&) noexcept = delete;
+ context& operator=(context&&) noexcept = delete;
+
+ context(const context&) = delete;
+ context& operator=(const context&) = delete;
+
+ void run_frame();
+
+ private:
+ sol::state state_{};
+ std::string folder_;
+ std::unordered_set loaded_scripts_;
+
+ scheduler scheduler_;
+
+ void load_script(const std::string& script);
+ };
+}
diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp
new file mode 100644
index 00000000..7e6d7f6c
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/engine.cpp
@@ -0,0 +1,61 @@
+#include
+#include "engine.hpp"
+#include "context.hpp"
+
+#include "../../../component/ui_scripting.hpp"
+#include "../../../component/game_module.hpp"
+
+#include
+
+namespace ui_scripting::lua::engine
+{
+ namespace
+ {
+ auto& get_scripts()
+ {
+ static std::vector> 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(script));
+ }
+ }
+ }
+ }
+
+ void start()
+ {
+ clear_converted_functions();
+ get_scripts().clear();
+ load_scripts(game_module::get_host_module().get_folder() + "/data/ui_scripts/");
+ load_scripts("h1-mod/ui_scripts/");
+ load_scripts("data/ui_scripts/");
+ }
+
+ void stop()
+ {
+ clear_converted_functions();
+ get_scripts().clear();
+ }
+
+ void run_frame()
+ {
+ for (auto& script : get_scripts())
+ {
+ script->run_frame();
+ }
+ }
+}
diff --git a/src/client/game/ui_scripting/lua/engine.hpp b/src/client/game/ui_scripting/lua/engine.hpp
new file mode 100644
index 00000000..bbcf427c
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/engine.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace ui_scripting::lua::engine
+{
+ void start();
+ void stop();
+ void run_frame();
+}
diff --git a/src/client/game/ui_scripting/lua/error.cpp b/src/client/game/ui_scripting/lua/error.cpp
new file mode 100644
index 00000000..d13b4896
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/error.cpp
@@ -0,0 +1,18 @@
+#include
+#include "error.hpp"
+
+namespace ui_scripting::lua
+{
+ void handle_error(const sol::protected_function_result& result)
+ {
+ if (!result.valid())
+ {
+ printf("************** UI Script execution error **************\n");
+
+ const sol::error err = result;
+ printf("%s\n", err.what());
+
+ printf("****************************************************\n");
+ }
+ }
+}
diff --git a/src/client/game/ui_scripting/lua/error.hpp b/src/client/game/ui_scripting/lua/error.hpp
new file mode 100644
index 00000000..28a5c453
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/error.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "context.hpp"
+
+namespace ui_scripting::lua
+{
+ void handle_error(const sol::protected_function_result& result);
+}
diff --git a/src/client/game/ui_scripting/lua/scheduler.cpp b/src/client/game/ui_scripting/lua/scheduler.cpp
new file mode 100644
index 00000000..18e779b6
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/scheduler.cpp
@@ -0,0 +1,122 @@
+#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_type["clear"] = [this](const task_handle& handle)
+ {
+ this->remove(handle);
+ };
+ }
+
+ 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::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(new_tasks.begin()),
+ std::move_iterator(new_tasks.end()));
+ new_tasks = {};
+ });
+ });
+ }
+}
diff --git a/src/client/game/ui_scripting/lua/scheduler.hpp b/src/client/game/ui_scripting/lua/scheduler.hpp
new file mode 100644
index 00000000..1935e25e
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/scheduler.hpp
@@ -0,0 +1,50 @@
+#pragma once
+#include
+
+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;
+ };
+
+ 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 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;
+ utils::concurrency::container new_callbacks_;
+ utils::concurrency::container callbacks_;
+ std::atomic_int64_t current_task_id_ = 0;
+
+ void remove(const task_handle& handle);
+ void merge_callbacks();
+ };
+}
diff --git a/src/client/game/ui_scripting/lua/value_conversion.cpp b/src/client/game/ui_scripting/lua/value_conversion.cpp
new file mode 100644
index 00000000..38376cdf
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/value_conversion.cpp
@@ -0,0 +1,144 @@
+#include
+#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())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {value.as()};
+ }
+
+ if (value.is())
+ {
+ return {convert_table(value.as())};
+ }
+
+ if (value.is())
+ {
+ return {convert_function(value.as())};
+ }
+
+ return {};
+ }
+
+ sol::lua_value convert(lua_State* state, const script_value& value)
+ {
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ if (value.is())
+ {
+ return {state, value.as()};
+ }
+
+ return {state, sol::lua_nil};
+ }
+}
diff --git a/src/client/game/ui_scripting/lua/value_conversion.hpp b/src/client/game/ui_scripting/lua/value_conversion.hpp
new file mode 100644
index 00000000..21a67e33
--- /dev/null
+++ b/src/client/game/ui_scripting/lua/value_conversion.hpp
@@ -0,0 +1,9 @@
+#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);
+}
diff --git a/src/client/game/ui_scripting/script_value.cpp b/src/client/game/ui_scripting/script_value.cpp
new file mode 100644
index 00000000..34d17020
--- /dev/null
+++ b/src/client/game/ui_scripting/script_value.cpp
@@ -0,0 +1,274 @@
+#include
+#include "execution.hpp"
+#include "types.hpp"
+#include "script_value.hpp"
+
+namespace ui_scripting
+{
+ /***************************************************************
+ * Constructors
+ **************************************************************/
+
+ script_value::script_value(const game::hks::HksObject& value)
+ : value_(value)
+ {
+ }
+
+ script_value::script_value(const int value)
+ {
+ game::hks::HksObject obj{};
+ obj.t = game::hks::TNUMBER;
+ obj.v.number = static_cast(value);
+
+ this->value_ = obj;
+ }
+
+ script_value::script_value(const unsigned int value)
+ {
+ game::hks::HksObject obj{};
+ obj.t = game::hks::TNUMBER;
+ obj.v.number = static_cast(value);
+
+ this->value_ = obj;
+ }
+
+ script_value::script_value(const bool value)
+ {
+ game::hks::HksObject obj{};
+ obj.t = game::hks::TBOOLEAN;
+ obj.v.boolean = value;
+
+ this->value_ = obj;
+ }
+
+ script_value::script_value(const float value)
+ {
+ game::hks::HksObject obj{};
+ obj.t = game::hks::TNUMBER;
+ obj.v.number = static_cast(value);
+
+ this->value_ = obj;
+ }
+
+ script_value::script_value(const double value)
+ : script_value(static_cast(value))
+ {
+ }
+
+ 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, (unsigned int)strlen(value));
+ obj = state->m_apistack.top[-1];
+
+ this->value_ = obj;
+ }
+
+ script_value::script_value(const std::string& value)
+ : script_value(value.data())
+ {
+ }
+
+ script_value::script_value(const lightuserdata& value)
+ {
+ this->value_.t = game::hks::TLIGHTUSERDATA;
+ this->value_.v.ptr = value.ptr;
+ }
+
+ script_value::script_value(const userdata& value)
+ {
+ this->value_.t = game::hks::TUSERDATA;
+ this->value_.v.ptr = value.ptr;
+ }
+
+ script_value::script_value(const table& value)
+ {
+ this->value_.t = game::hks::TTABLE;
+ this->value_.v.ptr = value.ptr;
+ }
+
+ script_value::script_value(const function& value)
+ {
+ this->value_.t = value.type;
+ this->value_.v.ptr = value.ptr;
+ }
+
+ /***************************************************************
+ * Integer
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ const auto number = this->get_raw().v.number;
+ return this->get_raw().t == game::hks::TNUMBER && static_cast(number) == number;
+ }
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->is();
+ }
+
+ template <>
+ int script_value::get() const
+ {
+ return static_cast(this->get_raw().v.number);
+ }
+
+ template <>
+ unsigned int script_value::get() const
+ {
+ return static_cast(this->get_raw().v.number);
+ }
+
+ /***************************************************************
+ * Boolean
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TBOOLEAN;
+ }
+
+ template <>
+ bool script_value::get() const
+ {
+ return this->get_raw().v.boolean;
+ }
+
+ /***************************************************************
+ * Float
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TNUMBER;
+ }
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->is();
+ }
+
+ template <>
+ float script_value::get() const
+ {
+ return this->get_raw().v.number;
+ }
+
+ template <>
+ double script_value::get() const
+ {
+ return static_cast(this->get_raw().v.number);
+ }
+
+ /***************************************************************
+ * String
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TSTRING;
+ }
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->is();
+ }
+
+ template <>
+ const char* script_value::get() const
+ {
+ return this->get_raw().v.str->m_data;
+ }
+
+ template <>
+ std::string script_value::get() const
+ {
+ return this->get();
+ }
+
+ /***************************************************************
+ * Lightuserdata
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TLIGHTUSERDATA;
+ }
+
+ template <>
+ lightuserdata script_value::get() const
+ {
+ return this->get_raw().v.ptr;
+ }
+
+ /***************************************************************
+ * Userdata
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TUSERDATA;
+ }
+
+ template <>
+ userdata script_value::get() const
+ {
+ return this->get_raw().v.ptr;
+ }
+
+ /***************************************************************
+ * Table
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TTABLE;
+ }
+
+ template <>
+ table script_value::get() const
+ {
+ return this->get_raw().v.table;
+ }
+
+ /***************************************************************
+ * Function
+ **************************************************************/
+
+ template <>
+ bool script_value::is() const
+ {
+ return this->get_raw().t == game::hks::TIFUNCTION
+ || this->get_raw().t == game::hks::TCFUNCTION;
+ }
+
+ template <>
+ function script_value::get() const
+ {
+ return { this->get_raw().v.cClosure, this->get_raw().t };
+ }
+
+ /***************************************************************
+ *
+ **************************************************************/
+
+ const game::hks::HksObject& script_value::get_raw() const
+ {
+ return this->value_;
+ }
+}
diff --git a/src/client/game/ui_scripting/script_value.hpp b/src/client/game/ui_scripting/script_value.hpp
new file mode 100644
index 00000000..3de52ddf
--- /dev/null
+++ b/src/client/game/ui_scripting/script_value.hpp
@@ -0,0 +1,56 @@
+#pragma once
+#include "game/game.hpp"
+
+namespace ui_scripting
+{
+ class lightuserdata;
+ class userdata;
+ class table;
+ class function;
+
+ class script_value
+ {
+ public:
+ script_value() = default;
+ script_value(const game::hks::HksObject& value);
+
+ script_value(int value);
+ script_value(unsigned int value);
+ script_value(bool value);
+
+ script_value(float value);
+ script_value(double value);
+
+ script_value(const char* value);
+ script_value(const std::string& value);
+
+ script_value(const lightuserdata& value);
+ script_value(const userdata& value);
+ script_value(const table& value);
+ script_value(const function& value);
+
+ template
+ bool is() const;
+
+ template
+ T as() const
+ {
+ if (!this->is())
+ {
+ throw std::runtime_error("Invalid type");
+ }
+
+ return get();
+ }
+
+ const game::hks::HksObject& get_raw() const;
+
+ private:
+ template
+ T get() const;
+
+ game::hks::HksObject value_{};
+ };
+
+ using arguments = std::vector;
+}
diff --git a/src/client/game/ui_scripting/types.cpp b/src/client/game/ui_scripting/types.cpp
new file mode 100644
index 00000000..37032b1b
--- /dev/null
+++ b/src/client/game/ui_scripting/types.cpp
@@ -0,0 +1,276 @@
+#include
+#include "types.hpp"
+#include "execution.hpp"
+
+namespace ui_scripting
+{
+ /***************************************************************
+ * Lightuserdata
+ **************************************************************/
+
+ lightuserdata::lightuserdata(void* ptr_)
+ : ptr(ptr_)
+ {
+ }
+
+ /***************************************************************
+ * Userdata
+ **************************************************************/
+
+ userdata::userdata(void* ptr_)
+ : ptr(ptr_)
+ {
+ this->add();
+ }
+
+ userdata::userdata(const userdata& other)
+ {
+ this->operator=(other);
+ }
+
+ userdata::userdata(userdata&& other) noexcept
+ {
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ userdata::~userdata()
+ {
+ this->release();
+ }
+
+ userdata& userdata::operator=(const userdata& other)
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ this->add();
+ }
+
+ return *this;
+ }
+
+ userdata& userdata::operator=(userdata&& other) noexcept
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ return *this;
+ }
+
+ void userdata::add()
+ {
+ game::hks::HksObject value{};
+ value.v.ptr = this->ptr;
+ value.t = game::hks::TUSERDATA;
+
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(value);
+
+ this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
+ }
+
+ void userdata::release()
+ {
+ if (this->ref)
+ {
+ game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
+ }
+ }
+
+ void userdata::set(const script_value& key, const script_value& value) const
+ {
+ set_field(*this, key, value);
+ }
+
+ script_value userdata::get(const script_value& key) const
+ {
+ return get_field(*this, key);
+ }
+
+ /***************************************************************
+ * Table
+ **************************************************************/
+
+ table::table()
+ {
+ const auto state = *game::hks::lua_state;
+ this->ptr = game::hks::Hashtable_Create(state, 0, 0);
+ this->add();
+ }
+
+ table::table(game::hks::HashTable* ptr_)
+ : ptr(ptr_)
+ {
+ this->add();
+ }
+
+ table::table(const table& other)
+ {
+ this->operator=(other);
+ }
+
+ table::table(table&& other) noexcept
+ {
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ table::~table()
+ {
+ this->release();
+ }
+
+ table& table::operator=(const table& other)
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ this->add();
+ }
+
+ return *this;
+ }
+
+ table& table::operator=(table&& other) noexcept
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ return *this;
+ }
+
+ void table::add()
+ {
+ game::hks::HksObject value{};
+ value.v.table = this->ptr;
+ value.t = game::hks::TTABLE;
+
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(value);
+
+ this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
+ }
+
+ void table::release()
+ {
+ if (this->ref)
+ {
+ game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
+ }
+ }
+
+ void table::set(const script_value& key, const script_value& value) const
+ {
+ set_field(*this, key, value);
+ }
+
+ script_value table::get(const script_value& key) const
+ {
+ return get_field(*this, key);
+ }
+
+ /***************************************************************
+ * Function
+ **************************************************************/
+
+ function::function(game::hks::cclosure* ptr_, game::hks::HksObjectType type_)
+ : ptr(ptr_)
+ , type(type_)
+ {
+ this->add();
+ }
+
+ function::function(const function& other)
+ {
+ this->operator=(other);
+ }
+
+ function::function(function&& other) noexcept
+ {
+ this->ptr = other.ptr;
+ this->type = other.type;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ function::~function()
+ {
+ this->release();
+ }
+
+ function& function::operator=(const function& other)
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->type = other.type;
+ this->ref = other.ref;
+ this->add();
+ }
+
+ return *this;
+ }
+
+ function& function::operator=(function&& other) noexcept
+ {
+ if (&other != this)
+ {
+ this->release();
+ this->ptr = other.ptr;
+ this->type = other.type;
+ this->ref = other.ref;
+ other.ref = 0;
+ }
+
+ return *this;
+ }
+
+ void function::add()
+ {
+ game::hks::HksObject value{};
+ value.v.cClosure = this->ptr;
+ value.t = this->type;
+
+ const auto state = *game::hks::lua_state;
+ state->m_apistack.top = state->m_apistack.base;
+
+ push_value(value);
+
+ this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000);
+ }
+
+ void function::release()
+ {
+ if (this->ref)
+ {
+ game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref);
+ }
+ }
+
+ arguments function::call(const arguments& arguments) const
+ {
+ return call_script_function(*this, arguments);
+ }
+}
diff --git a/src/client/game/ui_scripting/types.hpp b/src/client/game/ui_scripting/types.hpp
new file mode 100644
index 00000000..1924407f
--- /dev/null
+++ b/src/client/game/ui_scripting/types.hpp
@@ -0,0 +1,89 @@
+#pragma once
+#include "game/game.hpp"
+#include "script_value.hpp"
+
+namespace ui_scripting
+{
+ class lightuserdata
+ {
+ public:
+ lightuserdata(void*);
+ void* ptr;
+ };
+
+ class userdata
+ {
+ public:
+ userdata(void*);
+
+ userdata(const userdata& other);
+ userdata(userdata&& other) noexcept;
+
+ ~userdata();
+
+ userdata& operator=(const userdata& other);
+ userdata& operator=(userdata&& other) noexcept;
+
+ script_value get(const script_value& key) const;
+ void set(const script_value& key, const script_value& value) const;
+
+ void* ptr;
+
+ private:
+ void add();
+ void release();
+
+ int ref{};
+ };
+
+ class table
+ {
+ public:
+ table();
+ table(game::hks::HashTable* ptr_);
+
+ table(const table& other);
+ table(table&& other) noexcept;
+
+ ~table();
+
+ table& operator=(const table& other);
+ table& operator=(table&& other) noexcept;
+
+ script_value get(const script_value& key) const;
+ void set(const script_value& key, const script_value& value) const;
+
+ game::hks::HashTable* ptr;
+
+ private:
+ void add();
+ void release();
+
+ int ref{};
+ };
+
+ class function
+ {
+ public:
+ function(game::hks::cclosure*, game::hks::HksObjectType);
+
+ function(const function& other);
+ function(function&& other) noexcept;
+
+ ~function();
+
+ function& operator=(const function& other);
+ function& operator=(function&& other) noexcept;
+
+ arguments call(const arguments& arguments) const;
+
+ game::hks::cclosure* ptr;
+ game::hks::HksObjectType type;
+
+ private:
+ void add();
+ void release();
+
+ int ref{};
+ };
+}
diff --git a/src/client/main.cpp b/src/client/main.cpp
index 963e354a..519756b2 100644
--- a/src/client/main.cpp
+++ b/src/client/main.cpp
@@ -97,7 +97,7 @@ FARPROC load_binary(const launcher::mode mode)
if (!utils::io::read_file(binary, &data))
{
throw std::runtime_error(utils::string::va(
- "Failed to read game binary (%s)!\nPlease copy the h1x.exe into your Call of Duty: Modern Warfare Remastered installation folder and run it from there.",
+ "Failed to read game binary (%s)!\nPlease copy the h1-mod.exe into your Call of Duty: Modern Warfare Remastered installation folder and run it from there.",
binary.data()));
}
diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp
index a0d25e91..98a00a2d 100644
--- a/src/client/std_include.hpp
+++ b/src/client/std_include.hpp
@@ -10,6 +10,7 @@
#pragma warning(disable: 4702)
#pragma warning(disable: 4996)
#pragma warning(disable: 5054)
+#pragma warning(disable: 5056)
#pragma warning(disable: 6011)
#pragma warning(disable: 6297)
#pragma warning(disable: 6385)
diff --git a/tools/premake5.exe b/tools/premake5.exe
index 0e4954fd..c73da1fb 100644
Binary files a/tools/premake5.exe and b/tools/premake5.exe differ