diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp new file mode 100644 index 00000000..17986c09 --- /dev/null +++ b/src/client/component/ui_scripting.cpp @@ -0,0 +1,370 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include "game/ui_scripting/execution.hpp" + +#include "ui_scripting.hpp" +#include "scheduler.hpp" + +#include +#include +#include + +namespace ui_scripting +{ + namespace + { + std::unordered_map> converted_functions; + + utils::hook::detour ui_cod_init_hook; + utils::hook::detour ui_shutdown_hook; + utils::hook::detour hks_package_require_hook; + utils::hook::detour lua_cod_getrawfile_hook; + + struct globals_t + { + std::string in_require_script; + std::unordered_map loaded_scripts; + std::unordered_map local_scripts; + bool load_raw_script{}; + std::string raw_script_name{}; + }; + + globals_t globals; + + bool is_loaded_script(const std::string& name) + { + return globals.loaded_scripts.contains(name); + } + + bool is_local_script(const std::string& name) + { + return globals.local_scripts.contains(name); + } + + std::string get_root_script(const std::string& name) + { + const auto itr = globals.loaded_scripts.find(name); + return (itr == globals.loaded_scripts.end()) ? std::string() : itr->second; + } + + table get_globals() + { + const auto state = *game::hks::lua_state; + return state->globals.v.table; + } + + void print_error(const std::string& error) + { + printf("************** LUI script execution error **************\n"); + printf("%s\n", error.data()); + printf("********************************************************\n"); + } + + void print_loading_script(const std::string& name) + { + printf("Loading LUI script '%s'\n", name.data()); + } + + std::string get_current_script(game::hks::lua_State* state) + { + game::hks::lua_Debug info{}; + game::hks::hksi_lua_getstack(state, 1, &info); + game::hks::hksi_lua_getinfo(state, "nSl", &info); + return info.short_src; + } + + int load_buffer(const std::string& name, const std::string& data) + { + const auto state = *game::hks::lua_state; + const auto sharing_mode = state->m_global->m_bytecodeSharingMode; + state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON; + + const auto _0 = utils::finally([&] + { + state->m_global->m_bytecodeSharingMode = sharing_mode; + }); + + game::hks::HksCompilerSettings compiler_settings{}; + return game::hks::hksi_hksL_loadbuffer(state, &compiler_settings, data.data(), data.size(), name.data()); + } + + void load_script(const std::string& name, const std::string& data) + { + globals.loaded_scripts[name] = name; + + const auto state = *game::hks::lua_state; + const auto lua = get_globals(); + state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON; + const auto load_results = lua["loadstring"](data, name); + state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_SECURE; + + if (load_results[0].is()) + { + const auto results = lua["pcall"](load_results); + if (!results[0].as()) + { + print_error(results[1].as()); + } + } + else if (load_results[1].is()) + { + print_error(load_results[1].as()); + } + } + + void load_local_script_files(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_regular_file(script)) + { + const std::string file_path = script.substr(script.find("ui_scripts") + 11); + globals.local_scripts[file_path.c_str()] = script; + } + else if (std::filesystem::is_directory(script)) + { + load_local_script_files(script); + } + } + } + + void load_scripts(const std::string& script_dir) + { + if (!utils::io::directory_exists(script_dir)) + { + return; + } + + load_local_script_files(script_dir); + + const auto scripts = utils::io::list_files(script_dir); + + for (const auto& script : scripts) + { + std::string data; + + if (std::filesystem::is_directory(script) && utils::io::read_file(script + "/__init__.lua", &data)) + { + print_loading_script(script); + load_script(script + "/__init__.lua", data); + } + } + } + + void setup_functions() + { + const auto lua = get_globals(); + + using game = table; + auto game_type = game(); + lua["game"] = game_type; + } + + void enable_globals() + { + const auto lua = get_globals(); + const std::string code = + "local g = getmetatable(_G)\n" + "if not g then\n" + "g = {}\n" + "setmetatable(_G, g)\n" + "end\n" + "g.__newindex = nil\n"; + + const auto state = *game::hks::lua_state; + state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_ON; + lua["loadstring"](code)[0](); + state->m_global->m_bytecodeSharingMode = game::hks::HKS_BYTECODE_SHARING_SECURE; + } + + void start() + { + globals = {}; + + const auto lua = get_globals(); + enable_globals(); + + setup_functions(); + + lua["print"] = function(reinterpret_cast(0x141D30290_g)); // hks::base_print + lua["table"]["unpack"] = lua["unpack"]; + lua["luiglobals"] = lua; + + load_scripts(game::get_host_library().get_folder().append("/data/ui_scripts/").string()); + load_scripts("boiii/ui_scripts/"); + load_scripts("data/ui_scripts/"); + } + + void try_start() + { + try + { + start(); + } + catch (const std::exception& ex) + { + printf("Failed to load LUI scripts: %s\n", ex.what()); + } + } + + void ui_cod_init_stub(void* a1, void* a2) + { + ui_cod_init_hook.invoke(a1, a2); + const auto _0 = utils::finally(&try_start); + } + + void ui_shutdown_stub() + { + converted_functions.clear(); + globals = {}; + return ui_shutdown_hook.invoke(); + } + + void* hks_package_require_stub(game::hks::lua_State* state) + { + const auto script = get_current_script(state); + const auto root = get_root_script(script); + globals.in_require_script = root; + return hks_package_require_hook.invoke(state); + } + + int hks_load_stub(game::hks::lua_State* state, void* compiler_options, void* reader, void* reader_data, void* debug_reader, void* debug_reader_data, const char* chunk_name) + { + if (globals.load_raw_script) + { + globals.load_raw_script = false; + globals.loaded_scripts[globals.raw_script_name] = globals.in_require_script; + return load_buffer(globals.raw_script_name, utils::io::read_file(globals.raw_script_name)); + } + + return utils::hook::invoke(0x141D3AFB0_g, state, compiler_options, reader, reader_data, debug_reader, debug_reader_data, chunk_name); + } + + game::XAssetHeader lua_cod_getrawfile_stub(char* filename) + { + game::XAssetHeader header{ .luaFile = nullptr }; + + if (!is_local_script(filename)) + { + return lua_cod_getrawfile_hook.invoke(filename); + } + + std::string target_script = globals.local_scripts[filename]; + + if (utils::io::file_exists(target_script)) + { + globals.load_raw_script = true; + globals.raw_script_name = target_script; + header.luaFile = reinterpret_cast(1); + + return header; + } + + return lua_cod_getrawfile_hook.invoke(filename); + } + + int luaopen_stub([[maybe_unused]] game::hks::lua_State* l) + { + return 0; + } + + int hks_base_stub([[maybe_unused]] game::hks::lua_State* l) + { + return 0; + } + }; + + int main_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 = value.v.cClosure; + if (!converted_functions.contains(closure)) + { + return 0; + } + + const auto& function = converted_functions[closure]; + + try + { + const auto args = get_return_values(); + const auto results = function(args); + + for (const auto& result : results) + { + push_value(result); + } + + return static_cast(results.size()); + } + catch (const std::exception& ex) + { + game::hks::hksi_luaL_error(state, ex.what()); + } + + return 0; + } + + template + game::hks::cclosure* convert_function(F f) + { + const auto state = *game::hks::lua_state; + const auto closure = game::hks::cclosure_Create(state, main_handler, 0, 0, 0); + converted_functions[closure] = wrap_function(f); + return closure; + } + + class component final : public client_component + { + public: + void post_unpack() override + { + // Do not allow the HKS vm to open LUA's libraries + utils::hook::jump(0x141D33510_g, luaopen_stub); // io + utils::hook::jump(0x141D33D20_g, luaopen_stub); // os + utils::hook::jump(0x141D34B40_g, luaopen_stub); // serialize + utils::hook::jump(0x141D34B10_g, luaopen_stub); // havokscript + utils::hook::jump(0x141D34190_g, luaopen_stub); // debug + + // Disable unsafe functions + utils::hook::jump(0x141D300B0_g, hks_base_stub); // base_loadfile + utils::hook::jump(0x141D31EE0_g, hks_base_stub); // base_load + + utils::hook::jump(0x141D2CF00_g, hks_base_stub); // string_dump + utils::hook::jump(0x141D2AF90_g, hks_base_stub); // os_execute + + utils::hook::call(0x141D4979A_g, hks_load_stub); + + hks_package_require_hook.create(0x141D28EF0_g, hks_package_require_stub); + ui_cod_init_hook.create(0x141F298B0_g, ui_cod_init_stub); + ui_shutdown_hook.create(0x14270E9C0_g, ui_shutdown_stub); + lua_cod_getrawfile_hook.create(0x141F0F880_g, lua_cod_getrawfile_stub); + + scheduler::once([]() { + game::dvar_t* dvar_callstack_ship = game::Dvar_FindVar("ui_error_callstack_ship"); + dvar_callstack_ship->flags = (game::dvarFlags_e)0; + game::dvar_t* dvar_report_delay= game::Dvar_FindVar("ui_error_report_delay"); + dvar_report_delay->flags = (game::dvarFlags_e)0; + + game::Dvar_SetFromStringByName("ui_error_callstack_ship", "1", true); + game::Dvar_SetFromStringByName("ui_error_report_delay", "0", true); + }, scheduler::pipeline::main); + } + }; +} + +REGISTER_COMPONENT(ui_scripting::component) diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp new file mode 100644 index 00000000..4e6754a8 --- /dev/null +++ b/src/client/component/ui_scripting.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace ui_scripting +{ + template + game::hks::cclosure* convert_function(F f); +} diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index 76aba355..d6bad074 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -1,27 +1,23 @@ #include #include "game.hpp" -#include namespace game { - namespace + const utils::nt::library& get_host_library() { - const utils::nt::library& get_host_library() + static auto host_library = [] { - static const auto host_library = [] + utils::nt::library host{}; + if (!host || host == utils::nt::library::get_by_address(get_base)) { - utils::nt::library host{}; - if (!host || host == utils::nt::library::get_by_address(get_base)) - { - throw std::runtime_error("Invalid host application"); - } + throw std::runtime_error("Invalid host application"); + } - return host; - }(); + return host; + }(); - return host_library; - } + return host_library; } size_t get_base() diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 34b480c1..26769c24 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -1,7 +1,11 @@ #pragma once +#include "structs.hpp" +#include + namespace game { + const utils::nt::library& get_host_library(); size_t get_base(); bool is_server(); diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 35c0ed8a..11f0f388 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -417,6 +417,132 @@ namespace game ERROR_LUA = 0x200, ERROR_SOFTRESTART = 0x400, ERROR_SOFTRESTART_KEEPDW = 0x800, + }; + + enum XAssetType + { + ASSET_TYPE_PHYSPRESET = 0x0, + ASSET_TYPE_PHYSCONSTRAINTS = 0x1, + ASSET_TYPE_DESTRUCTIBLEDEF = 0x2, + ASSET_TYPE_XANIMPARTS = 0x3, + ASSET_TYPE_XMODEL = 0x4, + ASSET_TYPE_XMODELMESH = 0x5, + ASSET_TYPE_MATERIAL = 0x6, + ASSET_TYPE_COMPUTE_SHADER_SET = 0x7, + ASSET_TYPE_TECHNIQUE_SET = 0x8, + ASSET_TYPE_IMAGE = 0x9, + ASSET_TYPE_SOUND = 0xA, + ASSET_TYPE_SOUND_PATCH = 0xB, + ASSET_TYPE_CLIPMAP = 0xC, + ASSET_TYPE_COMWORLD = 0xD, + ASSET_TYPE_GAMEWORLD = 0xE, + ASSET_TYPE_MAP_ENTS = 0xF, + ASSET_TYPE_GFXWORLD = 0x10, + ASSET_TYPE_LIGHT_DEF = 0x11, + ASSET_TYPE_LENSFLARE_DEF = 0x12, + ASSET_TYPE_UI_MAP = 0x13, + ASSET_TYPE_FONT = 0x14, + ASSET_TYPE_FONTICON = 0x15, + ASSET_TYPE_LOCALIZE_ENTRY = 0x16, + ASSET_TYPE_WEAPON = 0x17, + ASSET_TYPE_WEAPONDEF = 0x18, + ASSET_TYPE_WEAPON_VARIANT = 0x19, + ASSET_TYPE_WEAPON_FULL = 0x1A, + ASSET_TYPE_CGMEDIA = 0x1B, + ASSET_TYPE_PLAYERSOUNDS = 0x1C, + ASSET_TYPE_PLAYERFX = 0x1D, + ASSET_TYPE_SHAREDWEAPONSOUNDS = 0x1E, + ASSET_TYPE_ATTACHMENT = 0x1F, + ASSET_TYPE_ATTACHMENT_UNIQUE = 0x20, + ASSET_TYPE_WEAPON_CAMO = 0x21, + ASSET_TYPE_CUSTOMIZATION_TABLE = 0x22, + ASSET_TYPE_CUSTOMIZATION_TABLE_FE_IMAGES = 0x23, + ASSET_TYPE_CUSTOMIZATION_TABLE_COLOR = 0x24, + ASSET_TYPE_SNDDRIVER_GLOBALS = 0x25, + ASSET_TYPE_FX = 0x26, + ASSET_TYPE_TAGFX = 0x27, + ASSET_TYPE_NEW_LENSFLARE_DEF = 0x28, + ASSET_TYPE_IMPACT_FX = 0x29, + ASSET_TYPE_IMPACT_SOUND = 0x2A, + ASSET_TYPE_PLAYER_CHARACTER = 0x2B, + ASSET_TYPE_AITYPE = 0x2C, + ASSET_TYPE_CHARACTER = 0x2D, + ASSET_TYPE_XMODELALIAS = 0x2E, + ASSET_TYPE_RAWFILE = 0x2F, + ASSET_TYPE_STRINGTABLE = 0x30, + ASSET_TYPE_STRUCTURED_TABLE = 0x31, + ASSET_TYPE_LEADERBOARD = 0x32, + ASSET_TYPE_DDL = 0x33, + ASSET_TYPE_GLASSES = 0x34, + ASSET_TYPE_TEXTURELIST = 0x35, + ASSET_TYPE_SCRIPTPARSETREE = 0x36, + ASSET_TYPE_KEYVALUEPAIRS = 0x37, + ASSET_TYPE_VEHICLEDEF = 0x38, + ASSET_TYPE_ADDON_MAP_ENTS = 0x39, + ASSET_TYPE_TRACER = 0x3A, + ASSET_TYPE_SLUG = 0x3B, + ASSET_TYPE_SURFACEFX_TABLE = 0x3C, + ASSET_TYPE_SURFACESOUNDDEF = 0x3D, + ASSET_TYPE_FOOTSTEP_TABLE = 0x3E, + ASSET_TYPE_ENTITYFXIMPACTS = 0x3F, + ASSET_TYPE_ENTITYSOUNDIMPACTS = 0x40, + ASSET_TYPE_ZBARRIER = 0x41, + ASSET_TYPE_VEHICLEFXDEF = 0x42, + ASSET_TYPE_VEHICLESOUNDDEF = 0x43, + ASSET_TYPE_TYPEINFO = 0x44, + ASSET_TYPE_SCRIPTBUNDLE = 0x45, + ASSET_TYPE_SCRIPTBUNDLELIST = 0x46, + ASSET_TYPE_RUMBLE = 0x47, + ASSET_TYPE_BULLETPENETRATION = 0x48, + ASSET_TYPE_LOCDMGTABLE = 0x49, + ASSET_TYPE_AIMTABLE = 0x4A, + ASSET_TYPE_ANIMSELECTORTABLESET = 0x4B, + ASSET_TYPE_ANIMMAPPINGTABLE = 0x4C, + ASSET_TYPE_ANIMSTATEMACHINE = 0x4D, + ASSET_TYPE_BEHAVIORTREE = 0x4E, + ASSET_TYPE_BEHAVIORSTATEMACHINE = 0x4F, + ASSET_TYPE_TTF = 0x50, + ASSET_TYPE_SANIM = 0x51, + ASSET_TYPE_LIGHT_DESCRIPTION = 0x52, + ASSET_TYPE_SHELLSHOCK = 0x53, + ASSET_TYPE_XCAM = 0x54, + ASSET_TYPE_BG_CACHE = 0x55, + ASSET_TYPE_TEXTURE_COMBO = 0x56, + ASSET_TYPE_FLAMETABLE = 0x57, + ASSET_TYPE_BITFIELD = 0x58, + ASSET_TYPE_ATTACHMENT_COSMETIC_VARIANT = 0x59, + ASSET_TYPE_MAPTABLE = 0x5A, + ASSET_TYPE_MAPTABLE_LOADING_IMAGES = 0x5B, + ASSET_TYPE_MEDAL = 0x5C, + ASSET_TYPE_MEDALTABLE = 0x5D, + ASSET_TYPE_OBJECTIVE = 0x5E, + ASSET_TYPE_OBJECTIVE_LIST = 0x5F, + ASSET_TYPE_UMBRA_TOME = 0x60, + ASSET_TYPE_NAVMESH = 0x61, + ASSET_TYPE_NAVVOLUME = 0x62, + ASSET_TYPE_BINARYHTML = 0x63, + ASSET_TYPE_LASER = 0x64, + ASSET_TYPE_BEAM = 0x65, + ASSET_TYPE_STREAMER_HINT = 0x66, + ASSET_TYPE_COUNT = 0x67, + ASSET_TYPE_STRING = 0x68, + ASSET_TYPE_ASSETLIST = 0x69, + ASSET_TYPE_REPORT = 0x6A, + ASSET_TYPE_DEPEND = 0x68, + ASSET_TYPE_FULL_COUNT = 0x6C, + }; + + struct LuaFile + { + const char* name; + int len; + const char* buffer; + }; + + union XAssetHeader + { + void* data; + LuaFile* luaFile; }; struct XZoneBuffer @@ -498,6 +624,23 @@ namespace game DVAR_TYPE_COLOR_LAB = 0xE, DVAR_TYPE_SESSIONMODE_BASE_DVAR = 0xF, DVAR_TYPE_COUNT = 0x10, + }; + + enum dvarFlags_e + { + DVAR_ARCHIVE = 1 << 0, + DVAR_USERINFO = 1 << 1, + DVAR_SYSTEMINFO = 1 << 2, + DVAR_CODINFO = 1 << 3, + DVAR_LATCH = 1 << 4, + DVAR_ROM = 1 << 5, + DVAR_SAVED = 1 << 6, + DVAR_INIT = 1 << 7, + DVAR_CHEAT = 1 << 8, + //DVAR_UNKNOWN = 1 << 9, + DVAR_EXTERNAL = 1 << 10, + //DVAR_UNKNOWN3x = 1 << 11-13, + DVAR_SESSIONMODE = 1 << 15 }; typedef float vec_t; @@ -873,7 +1016,513 @@ namespace game Agreement debugAgreement; JoinType joinType; JoinResult joinResult; - }; + }; + + + namespace hks + { + struct lua_State; + struct HashTable; + struct StringTable; + struct cclosure; + typedef int hksBool; + typedef char hksChar; + typedef unsigned __int8 hksByte; + typedef __int16 hksShort16; + typedef unsigned __int16 hksUshort16; + typedef float HksNumber; + typedef int hksInt32; + typedef unsigned int hksUint32; + typedef __int64 hksInt64; + typedef unsigned __int64 hksUint64; + + typedef int HksGcCost; + + + typedef size_t hksSize; + typedef void* (*lua_Alloc)(void*, void*, size_t, size_t); + typedef hksInt32(*lua_CFunction)(lua_State*); + + struct GenericChunkHeader + { + hksSize m_flags; + }; + + struct ChunkHeader : GenericChunkHeader + { + ChunkHeader* m_next; + }; + + struct ChunkList + { + ChunkHeader m_head; + }; + + struct UserData : ChunkHeader + { + unsigned __int64 m_envAndSizeOffsetHighBits; + unsigned __int64 m_metaAndSizeOffsetLowBits; + char m_data[8]; + }; + + struct InternString + { + unsigned __int64 m_flags; + unsigned __int64 m_lengthbits; + unsigned int m_hash; + char m_data[30]; + }; + + union HksValue + { + cclosure* cClosure; + void* closure; + UserData* userData; + HashTable* table; + void* tstruct; + InternString* str; + void* thread; + void* ptr; + float number; + unsigned int native; + bool boolean; + }; + + enum HksObjectType + { + TANY = 0xFFFFFFFE, + TNONE = 0xFFFFFFFF, + TNIL = 0x0, + TBOOLEAN = 0x1, + TLIGHTUSERDATA = 0x2, + TNUMBER = 0x3, + TSTRING = 0x4, + TTABLE = 0x5, + TFUNCTION = 0x6, // idk + TUSERDATA = 0x7, + TTHREAD = 0x8, + TIFUNCTION = 0x9, // Lua function + TCFUNCTION = 0xA, // C function + TUI64 = 0xB, + TSTRUCT = 0xC, + NUM_TYPE_OBJECTS = 0xE, + }; + + struct HksObject + { + HksObjectType t; + HksValue v; + }; + + const struct hksInstruction + { + unsigned int code; + }; + + struct ActivationRecord + { + HksObject* m_base; + const hksInstruction* m_returnAddress; + __int16 m_tailCallDepth; + __int16 m_numVarargs; + int m_numExpectedReturns; + }; + + struct CallStack + { + ActivationRecord* m_records; + ActivationRecord* m_lastrecord; + ActivationRecord* m_current; + const hksInstruction* m_current_lua_pc; + const hksInstruction* m_hook_return_addr; + int m_hook_level; + }; + + struct ApiStack + { + HksObject* top; + HksObject* base; + HksObject* alloc_top; + HksObject* bottom; + }; + + struct UpValue : ChunkHeader + { + HksObject m_storage; + HksObject* loc; + UpValue* m_next; + }; + + struct CallSite + { + _SETJMP_FLOAT128 m_jumpBuffer[16]; + CallSite* m_prev; + }; + + enum Status + { + NEW = 0x1, + RUNNING = 0x2, + YIELDED = 0x3, + DEAD_ERROR = 0x4, + }; + + enum HksError + { + HKS_NO_ERROR = 0, + HKS_ERRSYNTAX = -4, + HKS_ERRFILE = -5, + HKS_ERRRUN = -100, + HKS_ERRMEM = -200, + HKS_ERRERR = -300, + HKS_THROWING_ERROR = -500, + HKS_GC_YIELD = 1, + }; + + struct lua_Debug + { + int event; + const char* name; + const char* namewhat; + const char* what; + const char* source; + int currentline; + int nups; + int nparams; + int ishksfunc; + int linedefined; + int lastlinedefined; + char short_src[512]; + int callstack_level; + int is_tail_call; + }; + + using lua_function = int(__fastcall*)(lua_State*); + + struct luaL_Reg + { + const char* name; + lua_function function; + }; + + struct Node + { + HksObject m_key; + HksObject m_value; + }; + + struct StringPinner + { + struct Node + { + InternString* m_strings[32]; + Node* m_prev; + }; + + lua_State* const m_state; + StringPinner* const m_prev; + InternString** m_nextStringsPlace; + Node m_firstNode; + Node* m_currentNode; + }; + + struct StringTable + { + InternString** m_data; + unsigned int m_count; + unsigned int m_mask; + StringPinner* m_pinnedStrings; + }; + + struct Metatable + { + }; + + struct HashTable : ChunkHeader + { + Metatable* m_meta; + unsigned int m_version; + unsigned int m_mask; + Node* m_hashPart; + HksObject* m_arrayPart; + unsigned int m_arraySize; + Node* m_freeNode; + }; + + struct cclosure : ChunkHeader + { + lua_function m_function; + HashTable* m_env; + __int16 m_numUpvalues; + __int16 m_flags; + InternString* m_name; + HksObject m_upvalues[1]; + }; + + enum HksCompilerSettings_BytecodeSharingFormat + { + BYTECODE_DEFAULT = 0x0, + BYTECODE_INPLACE = 0x1, + BYTECODE_REFERENCED = 0x2, + }; + + enum HksCompilerSettings_IntLiteralOptions + { + INT_LITERALS_NONE = 0x0, + INT_LITERALS_LUD = 0x1, + INT_LITERALS_32BIT = 0x1, + INT_LITERALS_UI64 = 0x2, + INT_LITERALS_64BIT = 0x2, + INT_LITERALS_ALL = 0x3, + }; + + struct HksCompilerSettings + { + int m_emitStructCode; + const char** m_stripNames; + int m_emitGlobalMemoization; + int _m_isHksGlobalMemoTestingMode; + HksCompilerSettings_BytecodeSharingFormat m_bytecodeSharingFormat; + HksCompilerSettings_IntLiteralOptions m_enableIntLiterals; + int(*m_debugMap)(const char*, int); + }; + + enum HksBytecodeSharingMode : __int64 + { + HKS_BYTECODE_SHARING_OFF = 0, + HKS_BYTECODE_SHARING_ON = 1, + HKS_BYTECODE_SHARING_SECURE = 2 + }; + + struct HksGcWeights + { + int m_removeString; + int m_finalizeUserdataNoMM; + int m_finalizeUserdataGcMM; + int m_cleanCoroutine; + int m_removeWeak; + int m_markObject; + int m_traverseString; + int m_traverseUserdata; + int m_traverseCoroutine; + int m_traverseWeakTable; + int m_freeChunk; + int m_sweepTraverse; + }; + + struct GarbageCollector_Stack + { + void* m_storage; + unsigned int m_numEntries; + unsigned int m_numAllocated; + }; + + struct ProtoList + { + void** m_protoList; + unsigned __int16 m_protoSize; + unsigned __int16 m_protoAllocSize; + }; + + struct MemoryManager; + + struct GarbageCollector + { + struct ResumeStack + { + void* m_storage; + hksInt32 m_numEntries; + hksUint32 m_numAllocated; + }; + + struct GreyStack + { + HksObject* m_storage; + hksSize m_numEntries; + hksSize m_numAllocated; + }; + + struct RemarkStack + { + HashTable** m_storage; + hksSize m_numAllocated; + hksSize m_numEntries; + }; + + struct WeakStack_Entry + { + hksInt32 m_weakness; + HashTable* m_table; + }; + + struct WeakStack + { + WeakStack_Entry* m_storage; + hksInt32 m_numEntries; + hksUint32 m_numAllocated; + }; + + HksGcCost m_target; + HksGcCost m_stepsLeft; + HksGcCost m_stepLimit; + HksGcWeights m_costs; + HksGcCost m_unit; + void* m_jumpPoint; + lua_State* m_mainState; + lua_State* m_finalizerState; + MemoryManager* m_memory; + void* m_emergencyGCMemory; + hksInt32 m_phase; + ResumeStack m_resumeStack; + GreyStack m_greyStack; + RemarkStack m_remarkStack; + WeakStack m_weakStack; + hksBool m_finalizing; + HksObject m_safeTableValue; + lua_State* m_startOfStateStackList; + lua_State* m_endOfStateStackList; + lua_State* m_currentState; + HksObject m_safeValue; + void* m_compiler; + void* m_bytecodeReader; + void* m_bytecodeWriter; + hksInt32 m_pauseMultiplier; + HksGcCost m_stepMultiplier; + hksSize m_emergencyMemorySize; + bool m_stopped; + lua_CFunction m_gcPolicy; + hksSize m_pauseTriggerMemoryUsage; + hksInt32 m_stepTriggerCountdown; + hksUint32 m_stringTableIndex; + hksUint32 m_stringTableSize; + UserData* m_lastBlackUD; + UserData* m_activeUD; + }; + + enum MemoryManager_ChunkColor + { + RED = 0x0, + BLACK = 0x1, + }; + + enum Hks_DeleteCheckingMode + { + HKS_DELETE_CHECKING_OFF = 0x0, + HKS_DELETE_CHECKING_ACCURATE = 0x1, + HKS_DELETE_CHECKING_SAFE = 0x2, + }; + + struct MemoryManager + { + enum ChunkColor : __int32 + { + WHITE = 0x0, + BLACK = 0x1, + }; + + lua_Alloc m_allocator; + void* m_allocatorUd; + ChunkColor m_chunkColor; + hksSize m_used; + hksSize m_highwatermark; + ChunkList m_allocationList; + ChunkList m_sweepList; + ChunkHeader* m_lastKeptChunk; + lua_State* m_state; + }; + + struct StaticStringCache + { + HksObject m_objects[41]; + }; + + enum HksBytecodeEndianness + { + HKS_BYTECODE_DEFAULT_ENDIAN = 0x0, + HKS_BYTECODE_BIG_ENDIAN = 0x1, + HKS_BYTECODE_LITTLE_ENDIAN = 0x2, + }; + + struct RuntimeProfileData_Stats + { + unsigned __int64 hksTime; + unsigned __int64 callbackTime; + unsigned __int64 gcTime; + unsigned __int64 cFinalizerTime; + unsigned __int64 compilerTime; + unsigned int hkssTimeSamples; + unsigned int callbackTimeSamples; + unsigned int gcTimeSamples; + unsigned int compilerTimeSamples; + unsigned int num_newuserdata; + unsigned int num_tablerehash; + unsigned int num_pushstring; + unsigned int num_pushcfunction; + unsigned int num_newtables; + }; + + struct RuntimeProfileData + { + __int64 stackDepth; + __int64 callbackDepth; + unsigned __int64 lastTimer; + RuntimeProfileData_Stats frameStats; + unsigned __int64 gcStartTime; + unsigned __int64 finalizerStartTime; + unsigned __int64 compilerStartTime; + unsigned __int64 compilerStartGCTime; + unsigned __int64 compilerStartGCFinalizerTime; + unsigned __int64 compilerCallbackStartTime; + __int64 compilerDepth; + void* outFile; + lua_State* rootState; + }; + + struct HksGlobal + { + MemoryManager m_memory; + GarbageCollector m_collector; + StringTable m_stringTable; + __int64 padding3; + HksBytecodeSharingMode m_bytecodeSharingMode; + int padding; + HksObject m_registry; + ChunkList m_userDataList; + lua_State* m_root; + StaticStringCache m_staticStringCache; + void* m_debugger; + void* m_profiler; + RuntimeProfileData m_runProfilerData; + HksCompilerSettings m_compilerSettings; + int(*m_panicFunction)(lua_State*); + void* m_luaplusObjectList; + int m_heapAssertionFrequency; + int m_heapAssertionCount; + void (*m_logFunction)(lua_State*, const char*, ...); + void (*m_emergencyGCFailFunction)(lua_State*, size_t); + HksBytecodeEndianness m_bytecodeDumpEndianness; + int padding2; + }; + + struct lua_State + { + ChunkHeader baseclass; + HksGlobal* m_global; + CallStack m_callStack; + ApiStack m_apistack; + UpValue* pending; + HksObject globals; + HksObject m_cEnv; + CallSite* m_callsites; + int m_numberOfCCalls; + void* m_context; + InternString* m_name; + lua_State* m_nextState; + lua_State* m_nextStateStack; + Status m_status; + HksError m_error; + }; + } typedef uint32_t ScrVarCanonicalName_t; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 5c949be1..0eb48d68 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -139,4 +139,24 @@ namespace game bool I_isupper(int c); unsigned int Scr_CanonHash(const char* str); + + namespace hks + { + WEAK symbol lua_state { 0x159C78D88 }; + WEAK symbol hksi_lua_pushlstring{ 0x140A18430 }; + + WEAK symbol hks_obj_settable{ 0x141D4B660 }; + WEAK symbol hks_obj_gettable{ 0x141D4ABF0 }; + WEAK symbol vm_call_internal{ 0x141D71070 }; + WEAK symbol Hashtable_Create{ 0x141D3B5F0 }; + WEAK symbol cclosure_Create{ 0x141D3B7E0 }; + WEAK symbol hksi_luaL_ref{ 0x141D4D1A0 }; + WEAK symbol hksi_luaL_unref{ 0x141D4D320 }; + + WEAK symbol hksi_hksL_loadbuffer{ 0x141D4BD80 }; + WEAK symbol hksi_lua_getinfo{ 0x141D4D960 }; + WEAK symbol hksi_lua_getstack{ 0x141D4DC20 }; + WEAK symbol hksi_luaL_error{ 0x141D4D050 }; + WEAK symbol s_compilerTypeName{ 0x140A18430 }; + } } diff --git a/src/client/game/ui_scripting/execution.cpp b/src/client/game/ui_scripting/execution.cpp new file mode 100644 index 00000000..826fad10 --- /dev/null +++ b/src/client/game/ui_scripting/execution.cpp @@ -0,0 +1,171 @@ +#include +#include "execution.hpp" + +//#include "component/console.hpp" + +namespace ui_scripting +{ + namespace + { + script_value get_field(void* ptr, game::hks::HksObjectType type, const script_value& key) + { + const auto state = *game::hks::lua_state; + const auto top = state->m_apistack.top; + + push_value(key); + + game::hks::HksObject value{}; + game::hks::HksObject obj{}; + obj.t = type; + obj.v.ptr = ptr; + + game::hks::hks_obj_gettable(&value, state, &obj, &state->m_apistack.top[-1]); + state->m_apistack.top = top; + return value; + } + + void set_field(void* ptr, game::hks::HksObjectType type, const script_value& key, const script_value& value) + { + const auto state = *game::hks::lua_state; + + game::hks::HksObject obj{}; + obj.t = type; + obj.v.ptr = ptr; + + game::hks::hks_obj_settable(state, &obj, &key.get_raw(), &value.get_raw()); + } + } + + void push_value(const script_value& value) + { + const auto state = *game::hks::lua_state; + *state->m_apistack.top = value.get_raw(); + state->m_apistack.top++; + } + + void push_value(const game::hks::HksObject& value) + { + const auto state = *game::hks::lua_state; + *state->m_apistack.top = value; + state->m_apistack.top++; + } + + script_value get_return_value(std::int64_t offset) + { + const auto state = *game::hks::lua_state; + return state->m_apistack.top[-1 - offset]; + } + + arguments get_return_values() + { + const auto state = *game::hks::lua_state; + const auto count = state->m_apistack.top - state->m_apistack.base; + arguments values; + + for (auto i = count - 1; i >= 0; i--) + { + values.push_back(get_return_value(i)); + } + + if (values.empty()) + { + values.push_back({}); + } + + return values; + } + + arguments get_return_values(game::hks::HksObject* base) + { + const auto state = *game::hks::lua_state; + const auto count = state->m_apistack.top - base; + arguments values; + + for (auto i = count - 1; i >= 0; i--) + { + values.push_back(get_return_value(i)); + } + + if (values.empty()) + { + values.push_back({}); + } + + return values; + } + + bool notify(const std::string& name, const event_arguments& arguments) + { + const auto state = *game::hks::lua_state; + if (state == nullptr) + { + return false; + } + + //const auto _0 = 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")()[0].as(); + const auto process_event = root.get("processEvent"); + + table event{}; + event.set("name", name); + event.set("dispatchChildren", true); + + for (const auto& arg : arguments) + { + event.set(arg.first, arg.second); + } + + process_event(root, event); + return true; + } + catch (const std::exception& ex) + { + printf("Error processing event '%s' %s\n", name.data(), ex.what()); + } + + return false; + } + + arguments call_script_function(const function& function, const arguments& arguments) + { + const auto state = *game::hks::lua_state; + const auto top = state->m_apistack.top; + + push_value(function); + for (auto i = arguments.begin(); i != arguments.end(); ++i) + { + push_value(*i); + } + + game::hks::vm_call_internal(state, static_cast(arguments.size()), -1, nullptr); + const auto args = get_return_values(top); + state->m_apistack.top = top; + return args; + } + + script_value get_field(const userdata& self, const script_value& key) + { + return get_field(self.ptr, game::hks::TUSERDATA, key); + } + + script_value get_field(const table& self, const script_value& key) + { + return get_field(self.ptr, game::hks::TTABLE, key); + } + + void set_field(const userdata& self, const script_value& key, const script_value& value) + { + set_field(self.ptr, game::hks::TUSERDATA, key, value); + } + + void set_field(const table& self, const script_value& key, const script_value& value) + { + set_field(self.ptr, game::hks::TTABLE, key, value); + } +} diff --git a/src/client/game/ui_scripting/execution.hpp b/src/client/game/ui_scripting/execution.hpp new file mode 100644 index 00000000..d2e9cf0a --- /dev/null +++ b/src/client/game/ui_scripting/execution.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "game/game.hpp" +#include "types.hpp" +#include "script_value.hpp" + +namespace ui_scripting +{ + void push_value(const script_value& value); + void push_value(const game::hks::HksObject& value); + + script_value get_return_value(std::int64_t offset); + arguments get_return_values(); + arguments get_return_values(game::hks::HksObject* base); + + 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); + 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/script_value.cpp b/src/client/game/ui_scripting/script_value.cpp new file mode 100644 index 00000000..454c7dfc --- /dev/null +++ b/src/client/game/ui_scripting/script_value.cpp @@ -0,0 +1,400 @@ +#include +#include "execution.hpp" +#include "types.hpp" +#include "script_value.hpp" + +namespace ui_scripting +{ + hks_object::hks_object(const game::hks::HksObject& value) + { + this->assign(value); + } + + hks_object::hks_object(const hks_object& other) noexcept + { + this->operator=(other); + } + + hks_object::hks_object(hks_object&& other) noexcept + { + this->operator=(std::move(other)); + } + + hks_object& hks_object::operator=(const hks_object& other) noexcept + { + if (this != &other) + { + this->release(); + this->assign(other.value_); + } + + return *this; + } + + hks_object& hks_object::operator=(hks_object&& other) noexcept + { + if (this != &other) + { + this->release(); + this->value_ = other.value_; + other.value_.t = game::hks::TNONE; + } + + return *this; + } + + hks_object::~hks_object() + { + this->release(); + } + + const game::hks::HksObject& hks_object::get() const + { + return this->value_; + } + + void hks_object::assign(const game::hks::HksObject& value) + { + this->value_ = value; + + const auto state = *game::hks::lua_state; + const auto top = state->m_apistack.top; + + push_value(this->value_); + this->ref_ = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); + state->m_apistack.top = top; + } + + void hks_object::release() + { + if (this->ref_) + { + game::hks::hksi_luaL_unref(*game::hks::lua_state, -10000, this->ref_); + this->value_.t = game::hks::TNONE; + } + } + + /*************************************************************** + * 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, const std::size_t len) + { + game::hks::HksObject obj{}; + + const auto state = *game::hks::lua_state; + if (state == nullptr) + { + return; + } + + const auto top = state->m_apistack.top; + game::hks::hksi_lua_pushlstring(state, value, static_cast(len)); + obj = state->m_apistack.top[-1]; + state->m_apistack.top = top; + + this->value_ = obj; + } + + script_value::script_value(const char* value) + : script_value(value, std::strlen(value)) + { + } + + script_value::script_value(const std::string& value) + : script_value(value.data(), value.size()) + { + } + + script_value::script_value(const lightuserdata& value) + { + game::hks::HksObject obj{}; + obj.t = game::hks::TLIGHTUSERDATA; + obj.v.ptr = value.ptr; + + this->value_ = obj; + } + + script_value::script_value(const userdata& value) + { + game::hks::HksObject obj{}; + obj.t = game::hks::TUSERDATA; + obj.v.ptr = value.ptr; + + this->value_ = obj; + } + + script_value::script_value(const table& value) + { + game::hks::HksObject obj{}; + obj.t = game::hks::TTABLE; + obj.v.ptr = value.ptr; + + this->value_ = obj; + } + + script_value::script_value(const function& value) + { + game::hks::HksObject obj{}; + obj.t = value.type; + obj.v.ptr = value.ptr; + + this->value_ = obj; + } + + /*************************************************************** + * 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 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_.get(); + } + + bool script_value::operator==(const script_value& other) const + { + if (this->get_raw().t != other.get_raw().t) + { + return false; + } + + if (this->get_raw().t == game::hks::TSTRING) + { + return this->get() == other.get(); + } + + return this->get_raw().v.native == other.get_raw().v.native; + } + + arguments script_value::operator()() const + { + return this->as()(); + } + + arguments script_value::operator()(const arguments& arguments) const + { + return this->as()(arguments); + } + + function_argument::function_argument(const arguments& args, const script_value& value, const int index) + : values_(args), value_(value), index_(index) + { + } + + function_arguments::function_arguments(const arguments& values) + : values_(values) + { + } +} 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..479e8a11 --- /dev/null +++ b/src/client/game/ui_scripting/script_value.hpp @@ -0,0 +1,251 @@ +#pragma once +#include "game/game.hpp" + +#include + +namespace ui_scripting +{ + class lightuserdata; + class userdata_value; + class userdata; + class table_value; + class table; + class function; + class script_value; + + template + std::string get_typename() + { + auto& info = typeid(T); + + if (info == typeid(std::string) || + info == typeid(const char*)) + { + return "string"; + } + + if (info == typeid(lightuserdata)) + { + return "lightuserdata"; + } + + if (info == typeid(userdata)) + { + return "userdata"; + } + + if (info == typeid(table)) + { + return "table"; + } + + if (info == typeid(function)) + { + return "function"; + } + + if (info == typeid(int) || + info == typeid(float) || + info == typeid(unsigned int)) + { + return "number"; + } + + if (info == typeid(bool)) + { + return "boolean"; + } + + return info.name(); + } + + class hks_object + { + public: + hks_object() = default; + hks_object(const game::hks::HksObject& value); + hks_object(const hks_object& other) noexcept; + hks_object(hks_object&& other) noexcept; + + hks_object& operator=(const hks_object& other) noexcept; + hks_object& operator=(hks_object&& other) noexcept; + + ~hks_object(); + + const game::hks::HksObject& get() const; + + private: + void assign(const game::hks::HksObject& value); + void release(); + + game::hks::HksObject value_{ game::hks::TNONE, {} }; + int ref_{}; + }; + + using arguments = std::vector; + using event_arguments = std::unordered_map; + + 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, std::size_t len); + 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 class C, class T, typename TableType = table> + script_value(const C>& container) + { + TableType table_{}; + int index = 1; + + for (const auto& value : container) + { + table_.set(index++, value); + } + + game::hks::HksObject obj{}; + obj.t = game::hks::TTABLE; + obj.v.ptr = table_.ptr; + + this->value_ = obj; + } + + template + script_value(F f) + : script_value(function(f)) + { + } + + bool operator==(const script_value& other) const; + + arguments operator()() const; + arguments operator()(const arguments& arguments) const; + + template + arguments operator()(T... arguments) const + { + return this->as().call({ arguments... }); + } + + template + table_value operator[](const char(&key)[Size]) const + { + return { this->as
(), key }; + } + + template + table_value operator[](const T& key) const + { + return { this->as
(), key }; + } + + template + [[nodiscard]] bool is() const; + + template + T as() const + { + if (!this->is()) + { + const auto hks_typename = game::hks::s_compilerTypeName[this->get_raw().t + 2]; + const auto typename_ = get_typename(); + + throw std::runtime_error(utils::string::va("%s expected, got %s", typename_.data(), hks_typename)); + } + + return get(); + } + + template + operator T() const + { + return this->as(); + } + + [[nodiscard]] const game::hks::HksObject& get_raw() const; + + hks_object value_{}; + + private: + template + T get() const; + }; + + class variadic_args : public arguments + { + }; + + class function_argument + { + public: + function_argument(const arguments& args, const script_value& value, const int index); + + template + T as() const + { + try + { + return this->value_.as(); + } + catch (const std::exception& e) + { + throw std::runtime_error(utils::string::va("bad argument #%d (%s)", this->index_ + 1, e.what())); + } + } + + template <> + variadic_args as() const + { + variadic_args args{}; + for (auto i = this->index_; i < this->values_.size(); i++) + { + args.push_back(this->values_[i]); + } + return args; + } + + template + operator T() const + { + return this->as(); + } + + private: + arguments values_{}; + script_value value_{}; + int index_{}; + }; + + class function_arguments + { + public: + function_arguments(const arguments& values); + + function_argument operator[](const int index) const + { + if (static_cast(index) >= values_.size()) + { + return { values_, {}, index }; + } + + return { values_, values_[index], index }; + } + private: + arguments values_{}; + }; +} diff --git a/src/client/game/ui_scripting/types.cpp b/src/client/game/ui_scripting/types.cpp new file mode 100644 index 00000000..d51b7973 --- /dev/null +++ b/src/client/game/ui_scripting/types.cpp @@ -0,0 +1,351 @@ +#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; + const auto top = state->m_apistack.top; + + push_value(value); + + this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); + state->m_apistack.top = top; + } + + 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); + } + + userdata_value userdata::operator[](const script_value& key) const + { + return { *this, key }; + } + + userdata_value::userdata_value(const userdata& table, const script_value& key) + : userdata_(table), key_(key) + { + this->value_ = this->userdata_.get(key).get_raw(); + } + + void userdata_value::operator=(const script_value& value) + { + this->userdata_.set(this->key_, value); + this->value_ = value.get_raw(); + } + + bool userdata_value::operator==(const script_value& value) + { + return this->userdata_.get(this->key_) == value; + } + + /*************************************************************** + * 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; + const auto top = state->m_apistack.top; + + push_value(value); + + this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); + state->m_apistack.top = top; + } + + 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); + } + + table_value table::operator[](const script_value& key) const + { + return { *this, key }; + } + + script_value table::get(const script_value& key) const + { + return get_field(*this, key); + } + + table_value::table_value(const table& table, const script_value& key) + : table_(table), key_(key) + { + this->value_ = this->table_.get(key).get_raw(); + } + + void table_value::operator=(const script_value& value) + { + this->table_.set(this->key_, value); + this->value_ = value.get_raw(); + } + + void table_value::operator=(const table_value& value) + { + this->table_.set(this->key_, value); + this->value_ = value.get_raw(); + } + + bool table_value::operator==(const script_value& value) + { + return this->table_.get(this->key_) == value; + } + + bool table_value::operator==(const table_value& value) + { + return this->table_.get(this->key_) == value; + } + + /*************************************************************** + * Function + **************************************************************/ + + function::function(game::hks::lua_function func) + { + const auto state = *game::hks::lua_state; + this->ptr = game::hks::cclosure_Create(state, func, 0, 0, 0); + this->type = game::hks::HksObjectType::TCFUNCTION; + this->add(); + } + + 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; + const auto top = state->m_apistack.top; + + push_value(value); + + this->ref = game::hks::hksi_luaL_ref(*game::hks::lua_state, -10000); + state->m_apistack.top = top; + } + + 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); + } + + arguments function::operator()(const arguments& arguments) const + { + return this->call(arguments); + } + + arguments function::operator()() const + { + return this->call({}); + } +} diff --git a/src/client/game/ui_scripting/types.hpp b/src/client/game/ui_scripting/types.hpp new file mode 100644 index 00000000..3bf5226f --- /dev/null +++ b/src/client/game/ui_scripting/types.hpp @@ -0,0 +1,142 @@ +#pragma once +#include "game/game.hpp" +#include "script_value.hpp" + +#include "component/ui_scripting.hpp" + +namespace ui_scripting +{ + class lightuserdata + { + public: + lightuserdata(void*); + void* ptr; + }; + + class userdata_value; + + 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; + + userdata_value operator[](const script_value& key) const; + + void* ptr; + + private: + void add(); + void release(); + + int ref{}; + }; + + class userdata_value : public script_value + { + public: + userdata_value(const userdata& table, const script_value& key); + void operator=(const script_value& value); + bool operator==(const script_value& value); + private: + userdata userdata_; + script_value key_; + }; + + class table_value; + + 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; + + [[nodiscard]] script_value get(const script_value& key) const; + void set(const script_value& key, const script_value& value) const; + + table_value operator[](const script_value& key) const; + + game::hks::HashTable* ptr; + + private: + void add(); + void release(); + + int ref{}; + }; + + class table_value : public script_value + { + public: + table_value(const table& table, const script_value& key); + void operator=(const script_value& value); + void operator=(const table_value& value); + bool operator==(const script_value& value); + bool operator==(const table_value& value); + private: + table table_; + script_value key_; + }; + + + class function + { + public: + function(game::hks::lua_function); + function(game::hks::cclosure*, game::hks::HksObjectType); + + template + function(F f) + { + this->ptr = ui_scripting::convert_function(f); + this->type = game::hks::TCFUNCTION; + } + + 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; + + arguments operator()(const arguments& arguments) const; + + template + arguments operator()(T... arguments) const + { + return this->call({ arguments... }); + } + + arguments operator()() const; + + game::hks::cclosure* ptr; + game::hks::HksObjectType type; + + private: + void add(); + void release(); + + int ref{}; + }; +}