Merge pull request #154 from JariKCoding/main

UI Scripting, dvars cfg file, basic stats menu
This commit is contained in:
Maurice Heumann 2023-02-16 18:16:11 +01:00 committed by GitHub
commit c5511a2bae
18 changed files with 2979 additions and 28 deletions

View File

@ -0,0 +1,4 @@
-- Fix LUI_NULL_FUNCTION messages
function Engine.PIXBeginEvent() end
function Engine.PIXEndEvent() end

View File

@ -0,0 +1,290 @@
DataSources.MPStatsSettings = DataSourceHelpers.ListSetup( "MPStatsSettings", function ( controller )
local optionsTable = {}
table.insert( optionsTable, CoD.OptionsUtility.CreateDvarSettings( controller, "Unlock all loot", "Whether loot should be locked based on the player's stats or always unlocked.", "MPStatsSettings_unlock_loot", "cg_unlockall_loot", {
{
option = "MENU_DISABLED",
value = 0,
default = true
},
{
option = "MENU_ENABLED",
value = 1
},
}, nil, function(f1_arg0, f1_arg1, f1_arg2, dvarName, f1_arg4)
local oldValue = Engine.DvarInt( nil, dvarName )
local newValue = f1_arg1.value
UpdateInfoModels( f1_arg1 )
if oldValue == newValue then
return
end
Engine.SetDvar( dvarName, f1_arg1.value )
Engine.SetDvar( "ui_enableAllHeroes", f1_arg1.value )
end) )
return optionsTable
end)
if Dvar.cg_unlockall_loot:get() == true then
Engine.SetDvar( "ui_enableAllHeroes", 1 )
end
LUI.createMenu.MPStatsMenu = function ( controller )
local self = CoD.Menu.NewForUIEditor( "MPStatsMenu" )
if PreLoadFunc then
PreLoadFunc( self, controller )
end
self.soundSet = "ChooseDecal"
self:setOwner( controller )
self:setLeftRight( true, true, 0, 0 )
self:setTopBottom( true, true, 0, 0 )
self:playSound( "menu_open", controller )
self.buttonModel = Engine.CreateModel( Engine.GetModelForController( controller ), "MPStatsMenu.buttonPrompts" )
self.anyChildUsesUpdateState = true
local GameSettingsBackground = CoD.GameSettings_Background.new( self, controller )
GameSettingsBackground:setLeftRight( true, true, 0, 0 )
GameSettingsBackground:setTopBottom( true, true, 0, 0 )
GameSettingsBackground.MenuFrame.titleLabel:setText( Engine.Localize( "STATS SETTINGS" ) )
GameSettingsBackground.MenuFrame.cac3dTitleIntermediary0.FE3dTitleContainer0.MenuTitle.TextBox1.Label0:setText( Engine.Localize( "STATS SETTINGS" ) )
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeInfo:setAlpha( 0 )
GameSettingsBackground.GameSettingsSelectedItemInfo.GameModeName:setAlpha( 0 )
self:addElement( GameSettingsBackground )
self.GameSettingsBackground = GameSettingsBackground
local Options = CoD.Competitive_SettingsList.new( self, controller )
Options:setLeftRight( true, false, 26, 741 )
Options:setTopBottom( true, false, 135, 720 )
Options.Title.DescTitle:setText( Engine.Localize( "Stats" ) )
Options.ButtonList:setVerticalCount( 15 )
Options.ButtonList:setDataSource( "MPStatsSettings" )
self:addElement( Options )
self.Options = Options
self:AddButtonCallbackFunction( self, controller, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, nil, function ( element, menu, controller, model )
GoBack( self, controller )
SetPerControllerTableProperty( controller, "disableGameSettingsOptions", nil )
return true
end, function ( element, menu, controller )
CoD.Menu.SetButtonLabel( menu, Enum.LUIButton.LUI_KEY_XBB_PSCIRCLE, "MENU_BACK" )
return true
end, false )
GameSettingsBackground.MenuFrame:setModel( self.buttonModel, controller )
Options.id = "Options"
self:processEvent( {
name = "menu_loaded",
controller = controller
} )
self:processEvent( {
name = "update_state",
menu = self
} )
if not self:restoreState() then
self.Options:processEvent( {
name = "gain_focus",
controller = controller
} )
end
LUI.OverrideFunction_CallOriginalSecond( self, "close", function ( element )
element.GameSettingsBackground:close()
element.Options:close()
Engine.UnsubscribeAndFreeModel( Engine.GetModel( Engine.GetModelForController( controller ), "MPStatsMenu.buttonPrompts" ) )
end )
if PostLoadFunc then
PostLoadFunc( self, controller )
end
return self
end
CoD.LobbyButtons.MP_STATS = {
stringRef = "STATS",
action = function ( self, element, controller, param, menu )
SetPerControllerTableProperty( controller, "disableGameSettingsOptions", true )
OpenPopup( menu, "MPStatsMenu", controller )
end,
customId = "btnMPStats"
}
local IsGamescomDemo = function ()
return Dvar.ui_execdemo_gamescom:get()
end
local IsBetaDemo = function ()
return Dvar.ui_execdemo_beta:get()
end
local SetButtonState = function ( button, state )
if state == nil then
return
elseif state == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif state == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
local AddButton = function ( controller, options, button, isLargeButton )
button.disabled = false
button.hidden = false
button.selected = false
button.warning = false
if button.defaultState ~= nil then
if button.defaultState == CoD.LobbyButtons.DISABLED then
button.disabled = true
elseif button.defaultState == CoD.LobbyButtons.HIDDEN then
button.hidden = true
end
end
if button.disabledFunc ~= nil then
button.disabled = button.disabledFunc( controller )
end
if button.visibleFunc ~= nil then
button.hidden = not button.visibleFunc( controller )
end
if IsBetaDemo() then
SetButtonState( button, button.demo_beta )
elseif IsGamescomDemo() then
SetButtonState( button, button.demo_gamescom )
end
if button.hidden then
return
end
local lobbyNav = LobbyData.GetLobbyNav()
if button.selectedFunc ~= nil then
button.selected = button.selectedFunc( button.selectedParam )
elseif CoD.LobbyMenus.History[lobbyNav] ~= nil then
button.selected = CoD.LobbyMenus.History[lobbyNav] == button.customId
end
if button.newBreadcrumbFunc then
local f8_local1 = button.newBreadcrumbFunc
if type( f8_local1 ) == "string" then
f8_local1 = LUI.getTableFromPath( f8_local1 )
end
if f8_local1 then
button.isBreadcrumbNew = f8_local1( controller )
end
end
if button.warningFunc ~= nil then
button.warning = button.warningFunc( controller )
end
if button.starterPack == CoD.LobbyButtons.STARTERPACK_UPGRADE then
button.starterPackUpgrade = true
if IsStarterPack() then
button.disabled = false
end
end
table.insert( options, {
optionDisplay = button.stringRef,
action = button.action,
param = button.param,
customId = button.customId,
isLargeButton = isLargeButton,
isLastButtonInGroup = false,
disabled = button.disabled,
selected = button.selected,
isBreadcrumbNew = button.isBreadcrumbNew,
warning = button.warning,
requiredChunk = button.selectedParam,
starterPackUpgrade = button.starterPackUpgrade,
unloadMod = button.unloadMod
} )
end
local AddLargeButton = function ( controller, options, button )
AddButton( controller, options, button, true )
end
local AddSmallButton = function ( controller, options, button )
AddButton( controller, options, button, false )
end
local AddSpacer = function ( options )
if 0 < #options then
options[#options].isLastButtonInGroup = true
end
end
CoD.LobbyMenus.MPButtonsOnline = function ( f26_arg0, f26_arg1, f26_arg2 )
if f26_arg2 == 1 then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_FIND_MATCH )
AddSpacer( f26_arg1 )
end
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_CAC_NO_WARNING )
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_SPECIALISTS_NO_WARNING )
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_SCORESTREAKS )
if (Dvar.ui_execdemo_beta:get() or IsStarterPack()) and IsStoreAvailable() then
if CoD.isPC then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.STEAM_STORE )
else
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.STORE )
end
end
if Engine.DvarBool( nil, "inventory_test_button_visible" ) then
AddLargeButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_INVENTORY_TEST )
end
AddSpacer( f26_arg1 )
if not DisableBlackMarket() then
AddSmallButton( f26_arg0, f26_arg1, CoD.LobbyButtons.BLACK_MARKET )
end
AddSpacer( f26_arg1 )
AddSmallButton( f26_arg0, f26_arg1, CoD.LobbyButtons.MP_STATS )
end
local targetButtons = {
[LobbyData.UITargets.UI_MAIN.id] = CoD.LobbyMenus.ModeSelect,
[LobbyData.UITargets.UI_MODESELECT.id] = CoD.LobbyMenus.ModeSelect,
[LobbyData.UITargets.UI_CPLOBBYLANGAME.id] = CoD.LobbyMenus.CPButtonsLAN,
[LobbyData.UITargets.UI_CPLOBBYLANCUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsLANCUSTOM,
[LobbyData.UITargets.UI_CPLOBBYONLINE.id] = CoD.LobbyMenus.CPButtonsOnline,
[LobbyData.UITargets.UI_CPLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.CPButtonsPublicGame,
[LobbyData.UITargets.UI_CPLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsCustomGame,
[LobbyData.UITargets.UI_CP2LOBBYLANGAME.id] = CoD.LobbyMenus.CPZMButtonsLAN,
[LobbyData.UITargets.UI_CP2LOBBYLANCUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsLANCUSTOM,
[LobbyData.UITargets.UI_CP2LOBBYONLINE.id] = CoD.LobbyMenus.CPZMButtonsOnline,
[LobbyData.UITargets.UI_CP2LOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.CPZMButtonsPublicGame,
[LobbyData.UITargets.UI_CP2LOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.CPButtonsCustomGame,
[LobbyData.UITargets.UI_DOALOBBYLANGAME.id] = CoD.LobbyMenus.DOAButtonsLAN,
[LobbyData.UITargets.UI_DOALOBBYONLINE.id] = CoD.LobbyMenus.DOAButtonsOnline,
[LobbyData.UITargets.UI_DOALOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.DOAButtonsPublicGame,
[LobbyData.UITargets.UI_MPLOBBYLANGAME.id] = CoD.LobbyMenus.MPButtonsLAN,
[LobbyData.UITargets.UI_MPLOBBYMAIN.id] = CoD.LobbyMenus.MPButtonsMain,
[LobbyData.UITargets.UI_MPLOBBYONLINE.id] = CoD.LobbyMenus.MPButtonsOnline,
[LobbyData.UITargets.UI_MPLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.MPButtonsOnlinePublic,
[LobbyData.UITargets.UI_MPLOBBYONLINEMODGAME.id] = CoD.LobbyMenus.MPButtonsModGame,
[LobbyData.UITargets.UI_MPLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.MPButtonsCustomGame,
[LobbyData.UITargets.UI_MPLOBBYONLINEARENA.id] = CoD.LobbyMenus.MPButtonsArena,
[LobbyData.UITargets.UI_MPLOBBYONLINEARENAGAME.id] = CoD.LobbyMenus.MPButtonsArenaGame,
[LobbyData.UITargets.UI_FRLOBBYONLINEGAME.id] = CoD.LobbyMenus.FRButtonsOnlineGame,
[LobbyData.UITargets.UI_FRLOBBYLANGAME.id] = CoD.LobbyMenus.FRButtonsLANGame,
[LobbyData.UITargets.UI_ZMLOBBYLANGAME.id] = CoD.LobbyMenus.ZMButtonsLAN,
[LobbyData.UITargets.UI_ZMLOBBYONLINE.id] = CoD.LobbyMenus.ZMButtonsOnline,
[LobbyData.UITargets.UI_ZMLOBBYONLINEPUBLICGAME.id] = CoD.LobbyMenus.ZMButtonsPublicGame,
[LobbyData.UITargets.UI_ZMLOBBYONLINECUSTOMGAME.id] = CoD.LobbyMenus.ZMButtonsCustomGame,
[LobbyData.UITargets.UI_MPLOBBYONLINETHEATER.id] = CoD.LobbyMenus.ButtonsTheaterGame,
[LobbyData.UITargets.UI_ZMLOBBYONLINETHEATER.id] = CoD.LobbyMenus.ButtonsTheaterGame
}
CoD.LobbyMenus.AddButtonsForTarget = function ( controller, id )
local buttonFunc = targetButtons[id]
local model = nil
if Engine.IsLobbyActive( Enum.LobbyType.LOBBY_TYPE_GAME ) then
model = Engine.GetModel( DataSources.LobbyRoot.getModel( controller ), "gameClient.isHost" )
else
model = Engine.GetModel( DataSources.LobbyRoot.getModel( controller ), "privateClient.isHost" )
end
local isLeader = nil
if model ~= nil then
isLeader = Engine.GetModelValue( model )
else
isLeader = 1
end
local result = {}
buttonFunc( controller, result, isLeader )
return result
end

View File

@ -0,0 +1,91 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include "scheduler.hpp"
namespace dvars
{
namespace
{
bool initial_config_read = false;
utils::hook::detour dvar_register_new_hook;
utils::hook::detour dvar_set_variant_hook;
utils::hook::detour set_config_dvar_hook;
utils::hook::detour for_each_name_match_hook;
utils::hook::detour get_debug_name_hook;
const std::string get_config_file_path()
{
return "players/user/config.cfg";
}
void write_archive_dvars()
{
std::string config_buffer;
for (int i = 0; i < *game::g_dvarCount; ++i)
{
const auto* dvar = reinterpret_cast<const game::dvar_t*>(&game::s_dvarPool[160 * i]);
if (!dvar->debugName)
continue;
auto name = dvar->debugName;
auto value = game::Dvar_DisplayableValue(dvar);
config_buffer.append(utils::string::va("set %s %s\n", name, value));
}
if (config_buffer.length() == 0)
return;
utils::io::write_file(get_config_file_path(), config_buffer);
}
void dvar_set_variant_stub(game::dvar_t* dvar, game::DvarValue* value, unsigned int source)
{
dvar_set_variant_hook.invoke(dvar, value, source);
if (initial_config_read && dvar->debugName)
{
write_archive_dvars();
}
}
void read_archive_dvars()
{
const std::string path = get_config_file_path();
if (!utils::io::file_exists(path))
return;
std::string filedata;
utils::io::read_file(path, &filedata);
game::Cbuf_ExecuteBuffer(0, game::ControllerIndex_t::CONTROLLER_INDEX_0, filedata.c_str());
initial_config_read = true;
scheduler::execute(scheduler::pipeline::dvars_loaded);
}
}
class component final : public client_component
{
public:
void post_unpack() override
{
scheduler::once(read_archive_dvars, scheduler::pipeline::main);
dvar_set_variant_hook.create(0x1422C9A90_g, dvar_set_variant_stub);
}
};
}
REGISTER_COMPONENT(dvars::component)

View File

@ -10,13 +10,20 @@ namespace loot
{ {
namespace namespace
{ {
game::dvar_t* dvar_cg_unlockall_loot;
utils::hook::detour loot_getitemquantity_hook; utils::hook::detour loot_getitemquantity_hook;
utils::hook::detour liveinventory_getitemquantity_hook; utils::hook::detour liveinventory_getitemquantity_hook;
utils::hook::detour liveinventory_areextraslotspurchased_hook; utils::hook::detour liveinventory_areextraslotspurchased_hook;
int loot_getitemquantity_stub(const game::ControllerIndex_t /*controller_index*/, const game::eModes mode, int loot_getitemquantity_stub(const game::ControllerIndex_t controller_index, const game::eModes mode,
const int /*item_id*/) const int item_id)
{ {
if (!dvar_cg_unlockall_loot->current.enabled)
{
return loot_getitemquantity_hook.invoke<int>(controller_index, mode, item_id);
}
if (mode == game::eModes::MODE_ZOMBIES) if (mode == game::eModes::MODE_ZOMBIES)
{ {
return 999; return 999;
@ -28,8 +35,8 @@ namespace loot
int liveinventory_getitemquantity_stub(const game::ControllerIndex_t controller_index, const int item_id) int liveinventory_getitemquantity_stub(const game::ControllerIndex_t controller_index, const int item_id)
{ {
// Item id's for extra CaC slots, CWL camo's and paid specialist outfits // Item id's for extra CaC slots, CWL camo's and paid specialist outfits
if (item_id == 99003 || item_id >= 99018 && item_id <= 99021 || item_id == 99025 || item_id >= 90047 && if (dvar_cg_unlockall_loot->current.enabled && (item_id == 99003 || item_id >= 99018 && item_id <= 99021 || item_id == 99025||
item_id <= 90064) item_id >= 90047 && item_id <= 90064))
{ {
return 1; return 1;
} }
@ -37,15 +44,14 @@ namespace loot
return liveinventory_getitemquantity_hook.invoke<int>(controller_index, item_id); return liveinventory_getitemquantity_hook.invoke<int>(controller_index, item_id);
} }
bool liveinventory_areextraslotspurchased_stub(const game::ControllerIndex_t /*controller_index*/) bool liveinventory_areextraslotspurchased_stub(const game::ControllerIndex_t controller_index)
{ {
return true; if (dvar_cg_unlockall_loot->current.enabled)
} {
return true;
}
void set_dvars_on_startup() return liveinventory_areextraslotspurchased_hook.invoke<bool>(controller_index);
{
game::Dvar_SetFromStringByName("ui_enableAllHeroes", "1", true);
game::Dvar_SetFromStringByName("ui_allLootUnlocked", "1", true);
} }
}; };
@ -53,10 +59,19 @@ namespace loot
{ {
void post_unpack() override void post_unpack() override
{ {
scheduler::once(set_dvars_on_startup, scheduler::pipeline::main); dvar_cg_unlockall_loot = game::Dvar_RegisterBool(game::Dvar_GenerateHash("cg_unlockall_loot"), "cg_unlockall_loot", false, (game::dvarFlags_e)0x0, "Unlocks blackmarket loot");
dvar_cg_unlockall_loot->debugName = "cg_unlockall_loot";
loot_getitemquantity_hook.create(0x141E82C90_g, loot_getitemquantity_stub); loot_getitemquantity_hook.create(0x141E82C90_g, loot_getitemquantity_stub);
liveinventory_getitemquantity_hook.create(0x141E090C0_g, liveinventory_getitemquantity_stub); liveinventory_getitemquantity_hook.create(0x141E090C0_g, liveinventory_getitemquantity_stub);
liveinventory_areextraslotspurchased_hook.create(0x141E089E0_g, liveinventory_areextraslotspurchased_stub); liveinventory_areextraslotspurchased_hook.create(0x141E089E0_g, liveinventory_areextraslotspurchased_stub);
scheduler::once([]() {
if (dvar_cg_unlockall_loot->current.enabled)
{
game::Dvar_SetFromStringByName("ui_enableAllHeroes", "1", true);
}
}, scheduler::pipeline::dvars_loaded);
} }
}; };
}; };

View File

@ -91,11 +91,6 @@ namespace scheduler
utils::hook::detour g_run_frame_hook; utils::hook::detour g_run_frame_hook;
utils::hook::detour main_frame_hook; utils::hook::detour main_frame_hook;
void execute(const pipeline type)
{
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
}
void r_end_frame_stub() void r_end_frame_stub()
{ {
@ -114,6 +109,12 @@ namespace scheduler
main_frame_hook.invoke<void>(); main_frame_hook.invoke<void>();
execute(pipeline::main); execute(pipeline::main);
} }
}
void execute(const pipeline type)
{
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
} }
void schedule(const std::function<bool()>& callback, const pipeline type, void schedule(const std::function<bool()>& callback, const pipeline type,

View File

@ -14,13 +14,18 @@ namespace scheduler
server, server,
// The game's main thread // The game's main thread
main, main,
// Dvars are done loading from the config file
dvars_loaded,
count, count,
}; };
static const bool cond_continue = false; static const bool cond_continue = false;
static const bool cond_end = true; static const bool cond_end = true;
void execute(const pipeline type);
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async, void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async,
std::chrono::milliseconds delay = 0ms); std::chrono::milliseconds delay = 0ms);

View File

@ -0,0 +1,483 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/ui_scripting/execution.hpp"
#include "command.hpp"
#include "ui_scripting.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/finally.hpp>
namespace ui_scripting
{
namespace
{
std::unordered_map<game::hks::cclosure*, std::function<arguments(const function_arguments& args)>> converted_functions;
utils::hook::detour ui_cod_init_hook;
utils::hook::detour ui_cod_lobbyui_init_hook;
utils::hook::detour ui_shutdown_hook;
utils::hook::detour hks_package_require_hook;
utils::hook::detour lua_cod_getrawfile_hook;
game::dvar_t* dvar_cg_enable_unsafe_lua_functions;
static bool unsafe_function_called_message_shown = false;
struct globals_t
{
std::string in_require_script;
std::unordered_map<std::string, std::string> loaded_scripts;
std::unordered_map<std::string, std::string> 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<function>())
{
const auto results = lua["pcall"](load_results);
if (!results[0].as<bool>())
{
print_error(results[1].as<std::string>());
}
}
else if (load_results[1].is<std::string>())
{
print_error(load_results[1].as<std::string>());
}
}
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<game::hks::lua_function>(0x141D30290_g)); // hks::base_print
lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua;
utils::nt::library host{};
load_scripts(host.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(bool frontend)
{
ui_cod_init_hook.invoke(frontend);
if (game::Com_IsRunningUILevel())
{
// Fetch the names of the local files so file overrides are already handled
globals = {};
utils::nt::library host{};
load_local_script_files(host.get_folder().append("/data/ui_scripts/").string());
load_local_script_files("boiii/ui_scripts/");
load_local_script_files("data/ui_scripts/");
return;
}
const auto _0 = utils::finally(&try_start);
}
void ui_cod_lobbyui_init_stub()
{
ui_cod_lobbyui_init_hook.invoke();
const auto _0 = utils::finally(&try_start);
}
void ui_shutdown_stub()
{
converted_functions.clear();
globals = {};
return ui_shutdown_hook.invoke<void>();
}
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<void*>(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<int>(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_loaded_script(globals.in_require_script) && !is_local_script(filename))
{
return lua_cod_getrawfile_hook.invoke<game::XAssetHeader>(filename);
}
const std::string name_ = filename;
std::string target_script;
if (is_loaded_script(globals.in_require_script))
{
const auto folder = globals.in_require_script.substr(0, globals.in_require_script.find_last_of("/\\"));
target_script = folder + "/" + name_ + ".lua";
}
else
{
target_script = globals.local_scripts[name_];
}
if (utils::io::file_exists(target_script))
{
globals.load_raw_script = true;
globals.raw_script_name = target_script;
header.luaFile = reinterpret_cast<game::LuaFile*>(1);
return header;
}
return lua_cod_getrawfile_hook.invoke<game::XAssetHeader>(filename);
}
int luaopen_stub([[maybe_unused]] game::hks::lua_State* l)
{
return 0;
}
int lua_unsafe_function_stub([[maybe_unused]] game::hks::lua_State* l)
{
if (!unsafe_function_called_message_shown)
{
auto state = get_globals();
// TODO: Is it possible to do this with a confirm dialog? Doing this in LUI seems unsafe to me because mods will be able to change this aswell
state["LuaUtils"]["ShowMessageDialog"](0, 0, "The map/mod you are playing tried to run code that can be unsafe. This can include writing or reading files on your system, accessing environment variables, running system commands or loading a dll. These are usually used for storing data across games, integrating third party software like Discord or fetching data from a server to make the gameplay for dynamic.\nThis can also cause a lot of harm by the wrong people.\n\nIf you trust this map/mod and want to enable these features, open the following file in your Black Ops 3 installation: players/user/config.cfg.\nIn this file change 'set cg_enable_unsafe_lua_functions 0' to 'set cg_enable_unsafe_lua_functions 1' and restart Black Ops 3.", "Unsafe lua function called");
unsafe_function_called_message_shown = true;
}
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<int>(results.size());
}
catch (const std::exception& ex)
{
game::hks::hksi_luaL_error(state, ex.what());
}
return 0;
}
template <typename F>
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
{
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_cod_lobbyui_init_hook.create(0x141F2C620_g, ui_cod_lobbyui_init_stub);
ui_shutdown_hook.create(0x14270E9C0_g, ui_shutdown_stub);
lua_cod_getrawfile_hook.create(0x141F0F880_g, lua_cod_getrawfile_stub);
dvar_cg_enable_unsafe_lua_functions = game::Dvar_RegisterBool(game::Dvar_GenerateHash("cg_enable_unsafe_lua_functions"), "cg_enable_unsafe_lua_functions", false, (game::dvarFlags_e)0x1000, "Enables the use of unsafe lua functions");
dvar_cg_enable_unsafe_lua_functions->debugName = "cg_enable_unsafe_lua_functions";
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::renderer);
command::add("luiReload", [](auto& params)
{
auto frontend = game::Com_IsRunningUILevel();
if (frontend)
{
converted_functions.clear();
globals.loaded_scripts.clear();
globals.local_scripts.clear();
game::UI_CoD_Shutdown();
game::UI_CoD_Init(true);
// Com_LoadFrontEnd stripped
game::Lua_CoD_LoadLuaFile(*game::hks::lua_state, "ui_mp.T6.main");
game::UI_AddMenu(game::UI_CoD_GetRootNameForController(0), "main", -1, *game::hks::lua_state);
game::UI_CoD_LobbyUI_Init();
}
else
{
// TODO: Find a way to do a full shutdown & restart like in frontend, that opens up the loading screen that can't be easily closed
game::CG_LUIHUDRestart(0);
}
});
scheduler::once([]() {
if (!dvar_cg_enable_unsafe_lua_functions->current.enabled)
{
// Do not allow the HKS vm to open LUA's libraries
// Disable unsafe functions
utils::hook::jump(0x141D34190_g, luaopen_stub); // debug
utils::hook::jump(0x141D300B0_g, lua_unsafe_function_stub); // base_loadfile
utils::hook::jump(0x141D31EE0_g, lua_unsafe_function_stub); // base_load
utils::hook::jump(0x141D2CF00_g, lua_unsafe_function_stub); // string_dump
utils::hook::jump(0x141FD3220_g, lua_unsafe_function_stub); // engine_openurl
utils::hook::jump(0x141D2AFF0_g, lua_unsafe_function_stub); // os_getenv
utils::hook::jump(0x141D2B790_g, lua_unsafe_function_stub); // os_exit
utils::hook::jump(0x141D2B7C0_g, lua_unsafe_function_stub); // os_remove
utils::hook::jump(0x141D2BB70_g, lua_unsafe_function_stub); // os_rename
utils::hook::jump(0x141D2B360_g, lua_unsafe_function_stub); // os_tmpname
utils::hook::jump(0x141D2B0F0_g, lua_unsafe_function_stub); // os_sleep
utils::hook::jump(0x141D2AF90_g, lua_unsafe_function_stub); // os_execute
utils::hook::jump(0x141D2AFF0_g, lua_unsafe_function_stub); // os_getenv
// io helpers
utils::hook::jump(0x141D32390_g, lua_unsafe_function_stub); // io_tostring
utils::hook::jump(0x141D2FDC0_g, lua_unsafe_function_stub); // io_close_file
utils::hook::jump(0x141D2FD50_g, lua_unsafe_function_stub); // io_flush
utils::hook::jump(0x141D31260_g, lua_unsafe_function_stub); // io_lines
utils::hook::jump(0x141D305C0_g, lua_unsafe_function_stub); // io_read_file
utils::hook::jump(0x141D305C0_g, lua_unsafe_function_stub); // io_read_file
utils::hook::jump(0x141D320A0_g, lua_unsafe_function_stub); // io_seek_file
utils::hook::jump(0x141D321E0_g, lua_unsafe_function_stub); // io_setvbuf
utils::hook::jump(0x141D2FCD0_g, lua_unsafe_function_stub); // io_write
// io functions
utils::hook::jump(0x141D2FD10_g, lua_unsafe_function_stub); // io_write
utils::hook::jump(0x141D30F40_g, lua_unsafe_function_stub); // io_read
utils::hook::jump(0x141D2FF00_g, lua_unsafe_function_stub); // io_close
utils::hook::jump(0x141D2FD90_g, lua_unsafe_function_stub); // io_flush
utils::hook::jump(0x141D313A0_g, lua_unsafe_function_stub); // io_lines
utils::hook::jump(0x141D31BA0_g, lua_unsafe_function_stub); // io_input
utils::hook::jump(0x141D31BC0_g, lua_unsafe_function_stub); // io_output
utils::hook::jump(0x141D31BE0_g, lua_unsafe_function_stub); // io_type
utils::hook::jump(0x141D31DD0_g, lua_unsafe_function_stub); // io_open
utils::hook::jump(0x141D31D70_g, lua_unsafe_function_stub); // io_tmpfile
utils::hook::jump(0x141D33C00_g, lua_unsafe_function_stub); // io_popen
utils::hook::jump(0x141D2D0C0_g, lua_unsafe_function_stub); // serialize_persist
utils::hook::jump(0x141D2D480_g, lua_unsafe_function_stub); // serialize_unpersist
utils::hook::jump(0x141D2F560_g, lua_unsafe_function_stub); // havokscript_compiler_settings
utils::hook::jump(0x141D2F660_g, lua_unsafe_function_stub); // havokscript_setgcweights
utils::hook::jump(0x141D2FB10_g, lua_unsafe_function_stub); // havokscript_getgcweights
utils::hook::jump(0x141D299C0_g, lua_unsafe_function_stub); // package_loadlib
}
}, scheduler::pipeline::dvars_loaded);
}
};
}
REGISTER_COMPONENT(ui_scripting::component)

View File

@ -0,0 +1,47 @@
#pragma once
namespace ui_scripting
{
template <class... Args, std::size_t... I>
auto wrap_function(const std::function<void(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
f(args[I]...);
return arguments{ {} };
};
}
template <class... Args, std::size_t... I>
auto wrap_function(const std::function<arguments(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return f(args[I]...);
};
}
template <typename R, class... Args, std::size_t... I>
auto wrap_function(const std::function<R(Args...)>& f, std::index_sequence<I...>)
{
return [f](const function_arguments& args)
{
return arguments{ f(args[I]...) };
};
}
template <typename R, class... Args>
auto wrap_function(const std::function<R(Args...)>& f)
{
return wrap_function(f, std::index_sequence_for<Args...>{});
}
template <class F>
auto wrap_function(F f)
{
return wrap_function(std::function(f));
}
template <typename F>
game::hks::cclosure* convert_function(F f);
}

View File

@ -1,7 +1,6 @@
#include <std_include.hpp> #include <std_include.hpp>
#include "game.hpp" #include "game.hpp"
#include <utils/nt.hpp>
namespace game namespace game
{ {

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include "structs.hpp"
#include <utils/nt.hpp>
namespace game namespace game
{ {
size_t get_base(); size_t get_base();

View File

@ -6,11 +6,6 @@
namespace game namespace game
{ {
#endif #endif
enum XAssetType
{
ASSET_TYPE_LEADERBOARD = 0x32,
ASSET_TYPE_DDL = 0x33,
};
enum ControllerIndex_t enum ControllerIndex_t
{ {
@ -421,6 +416,126 @@ namespace game
ERROR_SOFTRESTART_KEEPDW = 0x800, 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;
};
struct XZoneBuffer struct XZoneBuffer
{ {
const void* data; const void* data;
@ -502,6 +617,23 @@ namespace game
DVAR_TYPE_COUNT = 0x10, 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; typedef float vec_t;
union vec4_t union vec4_t
@ -525,7 +657,6 @@ namespace game
vec4_t vector; vec4_t vector;
const char* string; const char* string;
byte color[4]; byte color[4];
const dvar_t* indirect[3];
}; };
struct $7034703ED3857507327AE195CCA24A71 struct $7034703ED3857507327AE195CCA24A71
@ -877,6 +1008,512 @@ namespace game
JoinResult joinResult; 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; typedef uint32_t ScrVarCanonicalName_t;
enum svscmd_type enum svscmd_type
@ -1019,6 +1656,7 @@ namespace game
BeamDef* beamDef; BeamDef* beamDef;
StreamerHint* streamerHint;*/ StreamerHint* streamerHint;*/
void* data; void* data;
LuaFile* luaFile;
}; };
struct XAsset struct XAsset

View File

@ -21,13 +21,14 @@ namespace game
WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x1421499C0, 0x140505630}; WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x1421499C0, 0x140505630};
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8BD0}; WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8BD0};
WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7DD0}; WEAK symbol<bool(eModes mode)> Com_SessionMode_IsMode{0x1420F7DD0};
WEAK symbol<bool()> Com_IsRunningUILevel{0x142148DB0};
WEAK symbol<void(uint32_t localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{ WEAK symbol<void(uint32_t localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{
0x14214AF30 0x14214AF30
}; };
WEAK symbol<void(uint32_t localClientNum, const char* text)> Cbuf_AddText{0x1420EC8B0, 0x1404F75B0}; WEAK symbol<void(uint32_t localClientNum, const char* text)> Cbuf_AddText{0x1420EC8B0, 0x1404F75B0};
WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{ WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{
0x0, 0x1404F78D0 0x14133BE10, 0x1404F78D0
}; };
WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{ WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{
0x1420ED530, 0x1404F8210 0x1420ED530, 0x1404F8210
@ -74,15 +75,33 @@ namespace game
WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x1421844C0}; WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x1421844C0};
// Dvar // Dvar
WEAK symbol<const dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BD730, 0x140575540}; WEAK symbol<dvar_t*(const char* dvarName)> Dvar_FindVar{0x1422BD730, 0x140575540};
WEAK symbol<unsigned int(const char* str)> Dvar_GenerateHash{0x14133DBF0}; WEAK symbol<unsigned int(const char* str)> Dvar_GenerateHash{0x14133DBF0};
WEAK symbol<dvar_t*(unsigned int hash)> Dvar_FindMalleableVar{0x1422BD6A0}; WEAK symbol<dvar_t*(unsigned int hash)> Dvar_FindMalleableVar{0x1422BD6A0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetDebugName{0x1422BDCB0}; WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetDebugName{0x1422BDCB0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BFFF0, 0x140575E30}; WEAK symbol<const char*(const dvar_t* dvar)> Dvar_GetString{0x1422BFFF0, 0x140575E30};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x1422BCAE0};
WEAK symbol<bool(const dvar_t* dvar)> Dvar_GetBool{ 0x1422BD930 };
WEAK symbol<dvar_t*(dvarStrHash_t hash, const char* dvarName, bool value, dvarFlags_e flags, const char* description)> Dvar_RegisterBool{
0x1422D1360
};
WEAK symbol<void (void (*callback)(const dvar_t*, void*), void* userData)> Dvar_ForEach{ 0x1422BD760 };
WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x1422BF2C0, 0x140575C20}; WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x1422BF2C0, 0x140575C20};
WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{ WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{
0x1422C7F60 0x1422C7F60
}; };
WEAK symbol<char> s_dvarPool{ 0x157AC8220 };
WEAK symbol<int> g_dvarCount{ 0x157AC81CC };
// UI
WEAK symbol<void(bool frontend)> UI_CoD_Init{ 0x141F298B0, 0x0 };
WEAK symbol<void()> UI_CoD_LobbyUI_Init{ 0x141F2C620, 0x0 };
WEAK symbol<void()> UI_CoD_Shutdown{ 0x141F336B0, 0x0 };
WEAK symbol<void(const char*, const char*, int, game::hks::lua_State*)> UI_AddMenu{ 0x1427024B0, 0x0 };
WEAK symbol<const char* (int)> UI_CoD_GetRootNameForController{ 0x141F291E0, 0x0 };
WEAK symbol<void(game::hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{ 0x141F122C0, 0x0 };
WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{ 0x140F7E970 };
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{ 0x1413CCAE0 };
// Scr // Scr
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x14016F160}; WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x0, 0x14016F160};
@ -143,4 +162,24 @@ namespace game
bool I_isupper(int c); bool I_isupper(int c);
unsigned int Scr_CanonHash(const char* str); unsigned int Scr_CanonHash(const char* str);
namespace hks
{
WEAK symbol<lua_State*> lua_state { 0x159C78D88 };
WEAK symbol<void(lua_State* s, const char* str, unsigned int l)> hksi_lua_pushlstring{ 0x140A18430 };
WEAK symbol<void(lua_State* s, const HksObject* tbl, const HksObject* key, const HksObject* val)> hks_obj_settable{ 0x141D4B660 };
WEAK symbol<HksObject* (HksObject* result, lua_State* s, const HksObject* table, const HksObject* key)> hks_obj_gettable{ 0x141D4ABF0 };
WEAK symbol<void(lua_State* s, int nargs, int nresults, const unsigned int* pc)> vm_call_internal{ 0x141D71070 };
WEAK symbol<HashTable* (lua_State* s, unsigned int arraySize, unsigned int hashSize)> Hashtable_Create{ 0x141D3B5F0 };
WEAK symbol<cclosure* (lua_State* s, lua_function function, int num_upvalues, int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{ 0x141D3B7E0 };
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{ 0x141D4D1A0 };
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{ 0x141D4D320 };
WEAK symbol<int(lua_State* s, const HksCompilerSettings* options, const char* buff, unsigned __int64 sz, const char* name)> hksi_hksL_loadbuffer{ 0x141D4BD80 };
WEAK symbol<int(lua_State* s, const char* what, lua_Debug* ar)> hksi_lua_getinfo{ 0x141D4D960 };
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{ 0x141D4DC20 };
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{ 0x141D4D050 };
WEAK symbol<const char*> s_compilerTypeName{ 0x140A18430 };
}
} }

View File

@ -0,0 +1,169 @@
#include <std_include.hpp>
#include "execution.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<table>();
const auto root = engine.get("GetLuiRoot")()[0].as<userdata>();
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<int>(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);
}
}

View File

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

View File

@ -0,0 +1,400 @@
#include <std_include.hpp>
#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<float>(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<float>(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<float>(value);
this->value_ = obj;
}
script_value::script_value(const double value)
: script_value(static_cast<float>(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<std::uint32_t>(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<int>() const
{
const auto number = this->get_raw().v.number;
return this->get_raw().t == game::hks::TNUMBER && static_cast<int>(number) == number;
}
template <>
bool script_value::is<unsigned int>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
return static_cast<int>(this->get_raw().v.number);
}
template <>
unsigned int script_value::get() const
{
return static_cast<unsigned int>(this->get_raw().v.number);
}
/***************************************************************
* Boolean
**************************************************************/
template <>
bool script_value::is<bool>() 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<float>() const
{
return this->get_raw().t == game::hks::TNUMBER;
}
template <>
bool script_value::is<double>() const
{
return this->is<float>();
}
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 char*>() const
{
return this->get_raw().t == game::hks::TSTRING;
}
template <>
bool script_value::is<std::string>() const
{
return this->is<const char*>();
}
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<const char*>();
}
/***************************************************************
* Lightuserdata
**************************************************************/
template <>
bool script_value::is<lightuserdata>() 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<userdata>() 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<table>() 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<function>() 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<std::string>() == other.get<std::string>();
}
return this->get_raw().v.native == other.get_raw().v.native;
}
arguments script_value::operator()() const
{
return this->as<function>()();
}
arguments script_value::operator()(const arguments& arguments) const
{
return this->as<function>()(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)
{
}
}

View File

@ -0,0 +1,251 @@
#pragma once
#include "game/game.hpp"
#include <utils/string.hpp>
namespace ui_scripting
{
class lightuserdata;
class userdata_value;
class userdata;
class table_value;
class table;
class function;
class script_value;
template <typename T>
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<script_value>;
using event_arguments = std::unordered_map<std::string, script_value>;
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 <template<class, class> class C, class T, typename TableType = table>
script_value(const C<T, std::allocator<T>>& 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 <typename F>
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<class ...T>
arguments operator()(T... arguments) const
{
return this->as<function>().call({ arguments... });
}
template <size_t Size>
table_value operator[](const char(&key)[Size]) const
{
return { this->as<table>(), key };
}
template <typename T = script_value>
table_value operator[](const T& key) const
{
return { this->as<table>(), key };
}
template <typename T>
[[nodiscard]] bool is() const;
template <typename T>
T as() const
{
if (!this->is<T>())
{
const auto hks_typename = game::hks::s_compilerTypeName[this->get_raw().t + 2];
const auto typename_ = get_typename<T>();
throw std::runtime_error(utils::string::va("%s expected, got %s", typename_.data(), hks_typename));
}
return get<T>();
}
template <typename T>
operator T() const
{
return this->as<T>();
}
[[nodiscard]] const game::hks::HksObject& get_raw() const;
hks_object value_{};
private:
template <typename T>
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 <typename T>
T as() const
{
try
{
return this->value_.as<T>();
}
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 <typename T>
operator T() const
{
return this->as<T>();
}
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<std::size_t>(index) >= values_.size())
{
return { values_, {}, index };
}
return { values_, values_[index], index };
}
private:
arguments values_{};
};
}

View File

@ -0,0 +1,351 @@
#include <std_include.hpp>
#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({});
}
}

View File

@ -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 <typename F>
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<class ...T>
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{};
};
}