diff --git a/generate.bat b/generate.bat index 8d2b86a4..06c74119 100644 --- a/generate.bat +++ b/generate.bat @@ -1,4 +1,3 @@ @echo off git submodule update --init --recursive -tools\premake5 %* vs2022 -pause \ No newline at end of file +tools\premake5 %* vs2022 \ No newline at end of file diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index a890cf43..7b14cc46 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -6,6 +6,7 @@ #include "game_console.hpp" #include "gui.hpp" #include "game/ui_scripting/lua/engine.hpp" +#include "game/ui_scripting/execution.hpp" #include @@ -25,7 +26,11 @@ namespace input void cl_char_event_stub(const int local_client_num, const int key) { - ui_scripting::lua::engine::ui_event("char", {key}); + ui_scripting::notify("keypress", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); if (!game_console::console_char_event(local_client_num, key)) { @@ -42,7 +47,11 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { - ui_scripting::lua::engine::ui_event("key", {key, down}); + ui_scripting::notify(down ? "keydown" : "keyup", + { + {"keynum", key}, + {"key", game::Key_KeynumToString(key, 0, 1)}, + }); if (!game_console::console_key_event(local_client_num, key, down)) { @@ -64,7 +73,6 @@ namespace input return; } - ui_scripting::lua::engine::ui_event("mousemove", {x, y}); cl_mouse_move_hook.invoke(local_client_num, x, y); } } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index f6cdba10..862277e7 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -30,8 +30,9 @@ namespace ui_scripting utils::hook::detour hks_allocator_hook; utils::hook::detour lui_error_hook; utils::hook::detour hksi_hks_error_hook; + utils::hook::detour hks_frame_hook; - bool error_hook_enabled = false; + int error_hook_enabled = 0; void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...) { @@ -107,6 +108,15 @@ namespace ui_scripting 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) @@ -158,17 +168,12 @@ namespace ui_scripting void enable_error_hook() { - error_hook_enabled = true; + error_hook_enabled++; } void disable_error_hook() { - error_hook_enabled = false; - } - - void notify(const event& e) - { - lua::engine::notify(e); + error_hook_enabled--; } class component final : public component_interface @@ -177,7 +182,7 @@ namespace ui_scripting void post_unpack() override { - scheduler::loop(ui_scripting::lua::engine::run_frame, scheduler::pipeline::lui); + hks_frame_hook.create(0x140327880, hks_frame_stub); hks_start_hook.create(0x140328BE0, hks_start_stub); hks_shutdown_hook.create(0x1403203B0, hks_shutdown_stub); hksi_lual_error_hook.create(0x1402E3E40, hksi_lual_error_stub); diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp index 8831d39d..417619fe 100644 --- a/src/client/component/ui_scripting.hpp +++ b/src/client/component/ui_scripting.hpp @@ -10,6 +10,4 @@ namespace ui_scripting void enable_error_hook(); void disable_error_hook(); - - void notify(const event& e); } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 920ab7d2..6a885aa0 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -86,6 +86,10 @@ namespace game WEAK symbol Key_KeynumToString{0x1403D32D0}; WEAK symbol LUI_OpenMenu{0x1405F0EE0}; + WEAK symbol LUI_BeginEvent{0x1403155E0}; + WEAK symbol LUI_EndEvent{0x140316890}; + WEAK symbol LUI_EnterCriticalSection{0x140316980}; + WEAK symbol LUI_LeaveCriticalSection{0x14031BC20}; WEAK symbol Menu_IsMenuOpenAndVisible{0x1405EE1A0}; WEAK symbol Material_RegisterHandle{0x140759BA0}; @@ -135,6 +139,8 @@ namespace game WEAK symbol Sys_IsDatabaseReady2{0x1405A9FE0}; WEAK symbol Sys_Milliseconds{0x140650720}; WEAK symbol Sys_IsMainThread{0x1405AA020}; + WEAK symbol Sys_EnterCriticalSection{0x140624240}; + WEAK symbol Sys_LeaveCriticalSection{0x1406242C0}; WEAK symbol UI_SafeTranslateString{0x1405A2930}; WEAK symbol UI_PlayLocalSoundAlias{0x140606080}; @@ -202,5 +208,8 @@ namespace game int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x1402C84B0}; WEAK symbol hksi_luaL_ref{0x1402E4520}; WEAK symbol hksi_luaL_unref{0x1402DCE50}; + WEAK symbol hksi_lua_setfield{0x1402DEA30}; + WEAK symbol hksi_lua_pcall{0x1402DDE50}; + WEAK symbol closePendingUpvalues{0x1402CBAD0}; } } \ No newline at end of file diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp index fff6b88e..fe6e3f7a 100644 --- a/src/client/game/ui_scripting/execution.cpp +++ b/src/client/game/ui_scripting/execution.cpp @@ -37,38 +37,79 @@ namespace ui_scripting return values; } + bool notify(const std::string& name, const event_arguments& arguments) + { + const auto state = *game::hks::lua_state; + if (!state) + { + return false; + } + + const auto _1 = gsl::finally(game::LUI_LeaveCriticalSection); + game::LUI_EnterCriticalSection(); + + try + { + const auto globals = table((*::game::hks::lua_state)->globals.v.table); + const auto engine = globals.get("Engine").as(); + const auto root = engine.get("GetLuiRoot").as().call({})[0].as(); + const auto process_event = root.get("processEvent").as(); + + table event{}; + event.set("name", name); + + for (const auto& arg : arguments) + { + event.set(arg.first, arg.second); + } + + process_event.call({root, event}); + return true; + } + catch (const std::exception& e) + { + printf("Error processing event '%s' %s\n", name.data(), e.what()); + return false; + } + } + 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; + stack stack; push_value(function); for (auto i = arguments.begin(); i != arguments.end(); ++i) { push_value(*i); } + const auto num_args = static_cast(arguments.size()); + stack.save(num_args + 1); + const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); try { - game::hks::vm_call_internal(state, static_cast(arguments.size()), -1, 0); + game::hks::vm_call_internal(state, num_args, -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()); + stack.fix(); + throw std::runtime_error("Error executing script function: "s + 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; + stack stack; push_value(key); + stack.save(1); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -85,16 +126,18 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error getting userdata field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error getting userdata field: "s + 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; + stack stack; push_value(key); + stack.save(1); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -111,14 +154,17 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error getting table field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error getting table field: "s + e.what()); } } void set_field(const userdata& self, const script_value& key, const script_value& value) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + + stack stack; + stack.save(0); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -133,14 +179,17 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error setting userdata field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error setting userdata field: "s + e.what()); } } void set_field(const table& self, const script_value& key, const script_value& value) { const auto state = *game::hks::lua_state; - state->m_apistack.top = state->m_apistack.base; + + stack stack; + stack.save(0); const auto _1 = gsl::finally(&disable_error_hook); enable_error_hook(); @@ -155,7 +204,8 @@ namespace ui_scripting } catch (const std::exception& e) { - throw std::runtime_error(std::string("Error setting table field: ") + e.what()); + stack.fix(); + throw std::runtime_error("Error setting table field: "s + e.what()); } } } diff --git a/src/client/game/ui_scripting/execution.hpp b/src/client/game/ui_scripting/execution.hpp index 24f4dd72..4a3d3562 100644 --- a/src/client/game/ui_scripting/execution.hpp +++ b/src/client/game/ui_scripting/execution.hpp @@ -9,6 +9,8 @@ namespace ui_scripting script_value get_return_value(int offset); arguments get_return_values(int count); + bool notify(const std::string& name, const event_arguments& arguments); + arguments call_script_function(const function& function, const arguments& arguments); script_value get_field(const userdata& self, const script_value& key); diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index e568e674..0e5d831c 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -210,7 +210,7 @@ namespace ui_scripting::lua }; } - void setup_game_type(sol::state& state, event_handler& handler, scheduler& scheduler) + void setup_game_type(sol::state& state, scheduler& scheduler) { struct game { @@ -246,28 +246,6 @@ namespace ui_scripting::lua return scheduler.add(callback, milliseconds, false); }; - game_type["onnotify"] = [&handler](const game&, const std::string& event, - const event_callback& callback) - { - event_listener listener{}; - listener.callback = callback; - listener.event = event; - listener.is_volatile = false; - - return handler.add_event_listener(std::move(listener)); - }; - - game_type["onnotifyonce"] = [&handler](const game&, const std::string& event, - const event_callback& callback) - { - event_listener listener{}; - listener.callback = callback; - listener.event = event; - listener.is_volatile = true; - - return handler.add_event_listener(std::move(listener)); - }; - game_type["isingame"] = []() { return ::game::CL_IsCgameInitialized() && ::game::g_entities[0].client; @@ -478,7 +456,7 @@ namespace ui_scripting::lua }; } - void setup_lui_types(sol::state& state, event_handler& handler, scheduler& scheduler) + void setup_lui_types(sol::state& state) { auto userdata_type = state.new_usertype("userdata_"); @@ -494,15 +472,15 @@ namespace ui_scripting::lua ); userdata_type[sol::meta_function::index] = [](const userdata& userdata, const sol::this_state s, - const std::string& name) + const sol::lua_value& key) { - return convert(s, userdata.get(name)); + return convert(s, userdata.get(convert({s, key}))); }; userdata_type[sol::meta_function::new_index] = [](const userdata& userdata, const sol::this_state s, - const std::string& name, const sol::lua_value& value) + const sol::lua_value& key, const sol::lua_value& value) { - userdata.set(name, convert({s, value})); + userdata.set(convert({s, key}), convert({s, value})); }; auto table_type = state.new_usertype
("table_"); @@ -519,27 +497,27 @@ namespace ui_scripting::lua ); table_type["get"] = [](const table& table, const sol::this_state s, - const std::string& name) + const sol::lua_value& key) { - return convert(s, table.get(name)); + return convert(s, table.get(convert({s, key}))); }; table_type["set"] = [](const table& table, const sol::this_state s, - const std::string& name, const sol::lua_value& value) + const sol::lua_value& key, const sol::lua_value& value) { - table.set(name, convert({s, value})); + table.set(convert({s, key}), convert({s, value})); }; table_type[sol::meta_function::index] = [](const table& table, const sol::this_state s, - const std::string& name) + const sol::lua_value& key) { - return convert(s, table.get(name)); + return convert(s, table.get(convert({s, key}))); }; table_type[sol::meta_function::new_index] = [](const table& table, const sol::this_state s, - const std::string& name, const sol::lua_value& value) + const sol::lua_value& key, const sol::lua_value& value) { - table.set(name, convert({s, value})); + table.set(convert({s, key}), convert({s, value})); }; auto function_type = state.new_usertype("function_"); @@ -598,8 +576,6 @@ namespace ui_scripting::lua context::context(std::string data, script_type type) : scheduler_(state_) - , event_handler_(state_) - { this->state_.open_libraries(sol::lib::base, sol::lib::package, @@ -612,8 +588,8 @@ namespace ui_scripting::lua setup_io(this->state_); setup_json(this->state_); setup_vector_type(this->state_); - setup_game_type(this->state_, this->event_handler_, this->scheduler_); - setup_lui_types(this->state_, this->event_handler_, this->scheduler_); + setup_game_type(this->state_, this->scheduler_); + setup_lui_types(this->state_); if (type == script_type::file) { @@ -650,7 +626,6 @@ namespace ui_scripting::lua { this->state_.collect_garbage(); this->scheduler_.clear(); - this->event_handler_.clear(); this->state_ = {}; } @@ -660,12 +635,6 @@ namespace ui_scripting::lua this->state_.collect_garbage(); } - void context::notify(const event& e) - { - this->scheduler_.dispatch(e); - this->event_handler_.dispatch(e); - } - void context::load_script(const std::string& script) { if (!this->loaded_scripts_.emplace(script).second) diff --git a/src/client/game/ui_scripting/lua/context.hpp b/src/client/game/ui_scripting/lua/context.hpp index 22c27e35..5ad4fb7c 100644 --- a/src/client/game/ui_scripting/lua/context.hpp +++ b/src/client/game/ui_scripting/lua/context.hpp @@ -41,7 +41,6 @@ namespace ui_scripting::lua std::unordered_set loaded_scripts_; scheduler scheduler_; - event_handler event_handler_; 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 index 304fb761..d6dbfe8d 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -17,27 +17,6 @@ namespace ui_scripting::lua::engine const auto lui_common = utils::nt::load_resource(LUI_COMMON); const auto lui_updater = utils::nt::load_resource(LUI_UPDATER); - void handle_key_event(const int key, const int down) - { - event event; - event.name = down - ? "keydown" - : "keyup"; - event.arguments = {key}; - - engine::notify(event); - } - - void handle_char_event(const int key) - { - std::string key_str = {(char)key}; - event event; - event.name = "keypress"; - event.arguments = {key_str}; - - engine::notify(event); - } - auto& get_scripts() { static std::vector> scripts{}; @@ -88,27 +67,6 @@ namespace ui_scripting::lua::engine get_scripts().clear(); } - void ui_event(const std::string& type, const std::vector& arguments) - { - if (type == "key") - { - handle_key_event(arguments[0], arguments[1]); - } - - if (type == "char") - { - handle_char_event(arguments[0]); - } - } - - void notify(const event& e) - { - for (auto& script : get_scripts()) - { - script->notify(e); - } - } - void run_frame() { for (auto& script : get_scripts()) diff --git a/src/client/game/ui_scripting/lua/engine.hpp b/src/client/game/ui_scripting/lua/engine.hpp index 27c5123e..e098f192 100644 --- a/src/client/game/ui_scripting/lua/engine.hpp +++ b/src/client/game/ui_scripting/lua/engine.hpp @@ -6,8 +6,5 @@ namespace ui_scripting::lua::engine { void start(); void stop(); - - void ui_event(const std::string&, const std::vector&); - void notify(const event& e); void run_frame(); } diff --git a/src/client/game/ui_scripting/script_value.hpp b/src/client/game/ui_scripting/script_value.hpp index 4d5dfcc9..f2c08cff 100644 --- a/src/client/game/ui_scripting/script_value.hpp +++ b/src/client/game/ui_scripting/script_value.hpp @@ -56,4 +56,5 @@ namespace ui_scripting }; using arguments = std::vector; + using event_arguments = std::unordered_map; } diff --git a/src/client/game/ui_scripting/types.cpp b/src/client/game/ui_scripting/types.cpp index 732c978a..273ffc9d 100644 --- a/src/client/game/ui_scripting/types.cpp +++ b/src/client/game/ui_scripting/types.cpp @@ -273,4 +273,38 @@ namespace ui_scripting { return call_script_function(*this, arguments); } + + /*************************************************************** + * Stack + **************************************************************/ + + stack::stack() + { + this->state = *game::hks::lua_state; + this->state->m_apistack.top = this->state->m_apistack.base; + } + + void stack::save(int num_args) + { + this->num_args_ = num_args; + this->num_calls_ = state->m_numberOfCCalls; + this->base_bottom_ = state->m_apistack.base - state->m_apistack.bottom; + this->top_bottom_ = state->m_apistack.top - state->m_apistack.bottom; + this->callstack_ = state->m_callStack.m_current - state->m_callStack.m_records; + } + + void stack::fix() + { + this->state->m_numberOfCCalls = this->num_calls_; + + game::hks::closePendingUpvalues(this->state, &this->state->m_apistack.bottom[this->top_bottom_ - this->num_args_]); + this->state->m_callStack.m_current = &this->state->m_callStack.m_records[this->callstack_]; + + this->state->m_apistack.base = &this->state->m_apistack.bottom[this->base_bottom_]; + this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ - static_cast(this->num_args_ + 1)]; + + this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t; + this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr; + this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1]; + } } \ No newline at end of file diff --git a/src/client/game/ui_scripting/types.hpp b/src/client/game/ui_scripting/types.hpp index 10f452f8..afb28fbd 100644 --- a/src/client/game/ui_scripting/types.hpp +++ b/src/client/game/ui_scripting/types.hpp @@ -86,4 +86,28 @@ namespace ui_scripting int ref{}; }; + + class stack final + { + public: + stack(); + + void save(int num_args); + void fix(); + + stack(stack&&) = delete; + stack(const stack&) = delete; + stack& operator=(stack&&) = delete; + stack& operator=(const stack&) = delete; + + private: + game::hks::lua_State* state; + + int num_args_; + int num_calls_; + + uint64_t base_bottom_; + uint64_t top_bottom_; + uint64_t callstack_; + }; } \ No newline at end of file