diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index ed04e863..ec1f4457 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -56,7 +56,6 @@ namespace scripting void player_spawn_stub(const game::gentity_s* player) { - command::execute("reloadmenus"); player_spawn_hook.invoke(player); lua::engine::start(); } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index ea0e524b..63e194e8 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -17,20 +17,110 @@ #include "game/ui_scripting/lua/engine.hpp" #include +#include namespace ui_scripting { + std::unordered_map> libs; + + namespace + { + utils::hook::detour hksi_open_lib_hook; + utils::hook::detour hksi_lual_error_hook; + utils::hook::detour hks_start_hook; + utils::hook::detour hks_shutdown_hook; + + bool error_hook_enabled = false; + + void* hksi_open_lib_stub(game::hks::lua_State* s, const char* libname, game::hks::luaL_Reg* l) + { + if (libname) + { + libs[libname] = {}; + for (auto i = l; i->name; ++i) + { + libs[libname][i->name] = i->function; + } + } + + return hksi_open_lib_hook.invoke(s, libname, l); + } + + void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...) + { + char va_buffer[0x200] = { 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 _ = gsl::finally([]() + { + ui_scripting::lua::engine::start(); + }); + + libs = {}; + return hks_start_hook.invoke(a1); + } + + void hks_shutdown_stub() + { + printf("shutdown\n"); + ui_scripting::lua::engine::stop(); + hks_shutdown_hook.invoke(); + } + } + + void enable_error_hook() + { + error_hook_enabled = true; + } + + void disable_error_hook() + { + error_hook_enabled = false; + } + + game::hks::lua_function find_function(const std::string& name) + { + const auto lower = utils::string::to_lower(name); + + for (const auto lib : libs) + { + for (const auto func : lib.second) + { + const auto lower_ = utils::string::to_lower(func.first); + if (lower == lower_) + { + return func.second; + } + } + } + + return 0; + } + class component final : public component_interface { public: void post_unpack() override { - scheduler::once([]() - { - ui_scripting::lua::engine::start(); - }, scheduler::pipeline::renderer); - scheduler::loop([]() { ui_scripting::lua::engine::run_frame(); @@ -64,6 +154,11 @@ namespace ui_scripting ui_scripting::lua::engine::close_menu(name); }, scheduler::pipeline::renderer); }); + + hks_start_hook.create(game::base_address + 0x328BE0, hks_start_stub); + hks_shutdown_hook.create(game::base_address + 0x3203B0, hks_shutdown_stub); + hksi_open_lib_hook.create(game::base_address + 0x2E4530, hksi_open_lib_stub); + hksi_lual_error_hook.create(game::base_address + 0x2E3E40, hksi_lual_error_stub); } }; } diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp index 21cd1ad8..c61d9bd1 100644 --- a/src/client/component/ui_scripting.hpp +++ b/src/client/component/ui_scripting.hpp @@ -3,4 +3,10 @@ namespace ui_scripting { + extern std::unordered_map> libs; + + void enable_error_hook(); + void disable_error_hook(); + + game::hks::lua_function find_function(const std::string& name); } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 6cc4f349..2f93bd37 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -914,5 +914,69 @@ namespace game uint64_t streams[4]; const char* name; }; + + namespace hks + { + struct InternString + { + unsigned __int64 m_flags; + unsigned __int64 m_lengthbits; + unsigned int m_hash; + char m_data[30]; + }; + union HksValue + { + void* cClosure; + void* closure; + void* userData; + void* table; + void* tstruct; + InternString* str; + void* thread; + void* ptr; + float number; + unsigned int native; + bool boolean; + }; + + enum HksType + { + HKS_LUA_TNONE = 0xFFFFFFFF, + HKS_LUA_TNIL = 0x0, + HKS_LUA_TBOOLEAN = 0x1, + HKS_LUA_TLIGHTUSERDATA = 0x2, + HKS_LUA_TNUMBER = 0x3, + HKS_LUA_TSTRING = 0x4, + HKS_LUA_TTABLE = 0x5, + HKS_LUA_TFUNCTION = 0x6, + HKS_LUA_TUSERDATA = 0x7, + HKS_LUA_TTHREAD = 0x8, + HKS_LUA_TUI64 = 0xB, + HKS_LUA_TSTRUCT = 0xC, + }; + + struct HksObject + { + HksType t; + HksValue v; + }; + + struct lua_State + { + char __pad0[72]; + HksObject* top; + HksObject* base; + HksObject* alloc_top; + HksObject* bottom; + }; + + using lua_function = int(__fastcall*)(lua_State*); + + struct luaL_Reg + { + const char* name; + lua_function function; + }; + } } \ No newline at end of file diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 61619ba6..9bf7b483 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -147,4 +147,11 @@ namespace game WEAK symbol scr_VarGlob{0xB617C00}; WEAK symbol scr_VmPub{0xBA9EE40}; WEAK symbol scr_function_stack{0xBAA93C0}; + + namespace hks + { + WEAK symbol lua_state{0x19D83E8}; + WEAK symbol hksi_lua_pushlstring{0x287410}; + WEAK symbol hks_obj_tolstring{0x287410}; + } } \ No newline at end of file diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp new file mode 100644 index 00000000..28235307 --- /dev/null +++ b/src/client/game/ui_scripting/execution.cpp @@ -0,0 +1,145 @@ +#include +#include "execution.hpp" + +#include "component/ui_scripting.hpp" + +#include + +namespace ui_scripting +{ + namespace + { + bool valid_state() + { + return *game::hks::lua_state != 0; + } + + void push_value(const value& value_) + { + if (!valid_state()) + { + throw std::runtime_error("Invalid lua state"); + } + + const auto state = *game::hks::lua_state; + + switch (value_.index()) + { + case 1: + { + const auto value = std::get(value_); + state->top->t = game::hks::HksType::HKS_LUA_TBOOLEAN; + state->top->v.boolean = value; + state->top++; + break; + } + case 2: + { + const auto value = std::get(value_); + state->top->t = game::hks::HksType::HKS_LUA_TNUMBER; + state->top->v.number = static_cast(value); + state->top++; + break; + } + case 3: + { + const auto value = std::get(value_); + state->top->t = game::hks::HksType::HKS_LUA_TNUMBER; + state->top->v.number = value; + state->top++; + break; + } + case 4: + { + const auto str = std::get(value_); + game::hks::hksi_lua_pushlstring(state, str.data(), (unsigned int)str.size()); + break; + } + } + } + } + + bool is_integer(float number) + { + return static_cast(number) == number; + } + + value get_return_value() + { + if (!valid_state()) + { + throw std::runtime_error("Invalid lua state"); + } + + const auto state = *game::hks::lua_state; + const auto top = &state->top[-1]; + + switch (top->t) + { + case game::hks::HksType::HKS_LUA_TBOOLEAN: + { + return {top->v.boolean}; + } + break; + case game::hks::HksType::HKS_LUA_TNUMBER: + { + const auto number = top->v.number; + if (is_integer(number)) + { + return {static_cast(top->v.number)}; + } + else + { + return {top->v.number}; + } + } + break; + case game::hks::HksType::HKS_LUA_TSTRING: + { + const auto value = top->v.str->m_data; + return {std::string(value)}; + } + break; + default: + { + return {}; + } + } + } + + value call(const std::string& name, const arguments& arguments) + { + if (!valid_state()) + { + throw std::runtime_error("Invalid lua state"); + } + + const auto function = ui_scripting::find_function(name); + if (!function) + { + throw std::runtime_error("Function " + name + " not found"); + } + + const auto _ = gsl::finally([]() + { + disable_error_hook(); + }); + + enable_error_hook(); + + for (const auto& value_ : arguments) + { + push_value(value_); + } + + try + { + function(*game::hks::lua_state); + return get_return_value(); + } + catch (const std::exception& e) + { + throw std::runtime_error("Error executing function " + name + ": " + 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..4f08416f --- /dev/null +++ b/src/client/game/ui_scripting/execution.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "game/game.hpp" + +namespace ui_scripting +{ + using value = std::variant; + using arguments = std::vector; + value call(const std::string& name, const arguments& arguments); +} diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 4fb743fb..3d95fd0e 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -2,6 +2,7 @@ #include "context.hpp" #include "error.hpp" #include "../../scripting/execution.hpp" +#include "../execution.hpp" #include "../../../component/ui_scripting.hpp" #include "../../../component/command.hpp" @@ -953,27 +954,6 @@ namespace ui_scripting::lua ::game::Dvar_SetCommand(hash, "", string_value.data()); }; - game_type["drawmaterial"] = [](const game&, float x, float y, float width, float height, float s0, float t0, float s1, float t1, - const sol::lua_value& color_value, const std::string& material) - { - const auto color = color_value.as>(); - float _color[4] = - { - color[0], - color[1], - color[2], - color[3], - }; - - const auto _material = ::game::Material_RegisterHandle(material.data()); - ::game::R_AddCmdDrawStretchPic(x, y, width, height, s0, t0, s1, t1, _color, _material); - }; - - game_type["playsound"] = [](const game&, const std::string& sound) - { - ::game::UI_PlayLocalSoundAlias(0, sound.data()); - }; - game_type["getwindowsize"] = [](const game&, const sol::this_state s) { const auto size = ::game::ScrPlace_GetViewPlacement()->realViewportSize; @@ -991,6 +971,49 @@ namespace ui_scripting::lua (::game::base_address + 0x71B970)(video.data(), 64, 0); }; + game_type[sol::meta_function::index] = [](const game&, const std::string& name) + { + return [name](const game&, const sol::this_state s, sol::variadic_args va) + { + arguments arguments; + + for (auto arg : va) + { + arguments.push_back(arg); + } + + const auto value = call(name, arguments); + if (value.index() == 0) + { + return sol::lua_value{s, sol::lua_nil}; + } + else + { + return sol::lua_value{s, value}; + } + }; + }; + + game_type["call"] = [](const game&, const sol::this_state s, const std::string& name, sol::variadic_args va) + { + arguments arguments; + + for (auto arg : va) + { + arguments.push_back(arg); + } + + const auto value = call(name, arguments); + if (value.index() == 0) + { + return sol::lua_value{s, sol::lua_nil}; + } + else + { + return sol::lua_value{s, value}; + } + }; + struct player { };