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