Use builtin LUI event handler + fixes

This commit is contained in:
Federico Cecchetto 2022-03-22 20:27:20 +01:00
parent cc4c8b242d
commit d88a48b666
14 changed files with 173 additions and 120 deletions

View File

@ -1,4 +1,3 @@
@echo off
git submodule update --init --recursive
tools\premake5 %* vs2022
pause
tools\premake5 %* vs2022

View File

@ -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 <utils/hook.hpp>
@ -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<void>(local_client_num, x, y);
}
}

View File

@ -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<void*>(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);

View File

@ -10,6 +10,4 @@ namespace ui_scripting
void enable_error_hook();
void disable_error_hook();
void notify(const event& e);
}

View File

@ -86,6 +86,10 @@ namespace game
WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x1403D32D0};
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x1405F0EE0};
WEAK symbol<bool(int clientNum, const char* name, hks::lua_State* s)> LUI_BeginEvent{0x1403155E0};
WEAK symbol<void(hks::lua_State* s)> LUI_EndEvent{0x140316890};
WEAK symbol<void()> LUI_EnterCriticalSection{0x140316980};
WEAK symbol<void()> LUI_LeaveCriticalSection{0x14031BC20};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x1405EE1A0};
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x140759BA0};
@ -135,6 +139,8 @@ namespace game
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x1405A9FE0};
WEAK symbol<int()> Sys_Milliseconds{0x140650720};
WEAK symbol<bool()> Sys_IsMainThread{0x1405AA020};
WEAK symbol<void(int critSec)> Sys_EnterCriticalSection{0x140624240};
WEAK symbol<void(int critSec)> Sys_LeaveCriticalSection{0x1406242C0};
WEAK symbol<const char*(const char* string)> UI_SafeTranslateString{0x1405A2930};
WEAK symbol<int(int localClientNum, const char* sound)> UI_PlayLocalSoundAlias{0x140606080};
@ -202,5 +208,8 @@ namespace game
int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x1402C84B0};
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x1402E4520};
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x1402DCE50};
WEAK symbol<void(lua_State* s, int index, const char* k)> hksi_lua_setfield{0x1402DEA30};
WEAK symbol<int(lua_State* s, int nargs, int nresults, int errfunc)> hksi_lua_pcall{0x1402DDE50};
WEAK symbol<void(lua_State* s, HksObject* lfp)> closePendingUpvalues{0x1402CBAD0};
}
}

View File

@ -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<table>();
const auto root = engine.get("GetLuiRoot").as<function>().call({})[0].as<userdata>();
const auto process_event = root.get("processEvent").as<function>();
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<int>(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<int>(arguments.size()), -1, 0);
game::hks::vm_call_internal(state, num_args, -1, 0);
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
return get_return_values(count);
}
catch (const std::exception& e)
{
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());
}
}
}

View File

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

View File

@ -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>("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>("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>("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)

View File

@ -41,7 +41,6 @@ namespace ui_scripting::lua
std::unordered_set<std::string> loaded_scripts_;
scheduler scheduler_;
event_handler event_handler_;
void load_script(const std::string& script);
};

View File

@ -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<std::unique_ptr<context>> scripts{};
@ -88,27 +67,6 @@ namespace ui_scripting::lua::engine
get_scripts().clear();
}
void ui_event(const std::string& type, const std::vector<int>& 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())

View File

@ -6,8 +6,5 @@ namespace ui_scripting::lua::engine
{
void start();
void stop();
void ui_event(const std::string&, const std::vector<int>&);
void notify(const event& e);
void run_frame();
}

View File

@ -56,4 +56,5 @@ namespace ui_scripting
};
using arguments = std::vector<script_value>;
using event_arguments = std::unordered_map<std::string, script_value>;
}

View File

@ -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<uint64_t>(this->num_args_ + 1)];
this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t;
this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr;
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1];
}
}

View File

@ -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_;
};
}