2023-01-01 19:12:14 -05:00
# include <std_include.hpp>
# include "loader/component_loader.hpp"
# include "game/game.hpp"
# include "game/ui_scripting/execution.hpp"
2023-02-12 13:32:33 -05:00
# include "command.hpp"
2023-01-01 19:12:14 -05:00
# include "ui_scripting.hpp"
# include "scheduler.hpp"
# include <utils/hook.hpp>
# include <utils/io.hpp>
# include <utils/finally.hpp>
namespace ui_scripting
{
namespace
{
2023-02-18 13:13:56 -05:00
std : : unordered_map < game : : hks : : cclosure * , std : : function < arguments ( const function_arguments & args ) > >
converted_functions ;
2023-01-01 19:12:14 -05:00
utils : : hook : : detour ui_cod_init_hook ;
2023-01-02 18:27:40 -05:00
utils : : hook : : detour ui_cod_lobbyui_init_hook ;
2023-01-01 19:12:14 -05:00
utils : : hook : : detour ui_shutdown_hook ;
utils : : hook : : detour hks_package_require_hook ;
utils : : hook : : detour lua_cod_getrawfile_hook ;
2023-02-11 11:31:21 -05:00
game : : dvar_t * dvar_cg_enable_unsafe_lua_functions ;
2023-02-11 14:40:26 -05:00
static bool unsafe_function_called_message_shown = false ;
2023-02-11 11:31:21 -05:00
2023-01-01 19:12:14 -05:00
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 ( [ & ]
2023-02-18 13:13:56 -05:00
{
state - > m_global - > m_bytecodeSharingMode = sharing_mode ;
} ) ;
2023-01-01 19:12:14 -05:00
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 )
{
2023-02-18 13:13:56 -05:00
const auto script_file = script . generic_string ( ) ;
2023-01-01 19:12:14 -05:00
if ( std : : filesystem : : is_regular_file ( script ) )
{
2023-02-18 13:13:56 -05:00
const std : : string file_path = script_file . substr ( script_file . find ( " ui_scripts " ) + 11 ) ;
globals . local_scripts [ file_path ] = script_file ;
2023-01-01 19:12:14 -05:00
}
else if ( std : : filesystem : : is_directory ( script ) )
{
2023-02-18 13:13:56 -05:00
load_local_script_files ( script_file ) ;
2023-01-01 19:12:14 -05:00
}
}
}
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 ;
2023-02-18 13:13:56 -05:00
const auto script_file = script . generic_string ( ) ;
if ( std : : filesystem : : is_directory ( script ) & & utils : : io : : read_file ( script_file + " /__init__.lua " , & data ) )
2023-01-01 19:12:14 -05:00
{
2023-02-18 13:13:56 -05:00
print_loading_script ( script_file ) ;
load_script ( script_file + " /__init__.lua " , data ) ;
2023-01-01 19:12:14 -05:00
}
}
}
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 ;
2023-02-13 15:50:19 -05:00
utils : : nt : : library host { } ;
2023-02-18 13:13:56 -05:00
load_scripts ( ( game : : get_appdata_path ( ) / " data/ui_scripts/ " ) . string ( ) ) ;
load_scripts ( ( host . get_folder ( ) / " boiii/ui_scripts/ " ) . string ( ) ) ;
2023-01-01 19:12:14 -05:00
}
2023-02-18 13:13:56 -05:00
2023-01-01 19:12:14 -05:00
void try_start ( )
{
try
{
start ( ) ;
}
catch ( const std : : exception & ex )
{
printf ( " Failed to load LUI scripts: %s \n " , ex . what ( ) ) ;
}
}
2023-01-02 18:27:40 -05:00
void ui_cod_init_stub ( bool frontend )
2023-01-01 19:12:14 -05:00
{
2023-01-02 18:27:40 -05:00
ui_cod_init_hook . invoke ( frontend ) ;
if ( game : : Com_IsRunningUILevel ( ) )
2023-02-02 17:28:48 -05:00
{
// Fetch the names of the local files so file overrides are already handled
globals = { } ;
2023-02-13 15:50:19 -05:00
utils : : nt : : library host { } ;
2023-02-18 13:13:56 -05:00
load_local_script_files ( ( game : : get_appdata_path ( ) / " data/ui_scripts/ " ) . string ( ) ) ;
load_local_script_files ( ( host . get_folder ( ) / " boiii/ui_scripts/ " ) . string ( ) ) ;
2023-01-02 18:27:40 -05:00
return ;
2023-02-02 17:28:48 -05:00
}
2023-01-02 18:27:40 -05:00
const auto _0 = utils : : finally ( & try_start ) ;
}
void ui_cod_lobbyui_init_stub ( )
{
ui_cod_lobbyui_init_hook . invoke ( ) ;
2023-01-01 19:12:14 -05:00
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 ) ;
}
2023-02-18 13:13:56 -05:00
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 )
2023-01-01 19:12:14 -05:00
{
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 ) ) ;
}
2023-02-18 13:13:56 -05:00
return utils : : hook : : invoke < int > ( 0x141D3AFB0 _g , state , compiler_options , reader , reader_data , debug_reader ,
debug_reader_data , chunk_name ) ;
2023-01-01 19:12:14 -05:00
}
game : : XAssetHeader lua_cod_getrawfile_stub ( char * filename )
{
2023-02-18 13:13:56 -05:00
game : : XAssetHeader header { . luaFile = nullptr } ;
2023-01-01 19:12:14 -05:00
2023-01-02 17:18:24 -05:00
if ( ! is_loaded_script ( globals . in_require_script ) & & ! is_local_script ( filename ) )
2023-01-01 19:12:14 -05:00
{
return lua_cod_getrawfile_hook . invoke < game : : XAssetHeader > ( filename ) ;
}
2023-01-02 17:18:24 -05:00
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_ ] ;
}
2023-01-01 19:12:14 -05:00
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 ;
}
2023-02-11 11:31:21 -05:00
int lua_unsafe_function_stub ( [[maybe_unused]] game : : hks : : lua_State * l )
2023-01-01 19:12:14 -05:00
{
2023-02-11 14:40:26 -05:00
if ( ! unsafe_function_called_message_shown )
2023-02-11 11:31:21 -05:00
{
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
2023-02-18 13:13:56 -05:00
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. \n This can also cause a lot of harm by the wrong people. \n \n If 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. \n In 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 " ) ;
2023-02-11 14:40:26 -05:00
unsafe_function_called_message_shown = true ;
2023-02-11 11:31:21 -05:00
}
2023-01-01 19:12:14 -05:00
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 ) ;
2023-01-02 18:27:40 -05:00
ui_cod_lobbyui_init_hook . create ( 0x141F2C620 _g , ui_cod_lobbyui_init_stub ) ;
2023-01-01 19:12:14 -05:00
ui_shutdown_hook . create ( 0x14270E9C0 _g , ui_shutdown_stub ) ;
lua_cod_getrawfile_hook . create ( 0x141F0F880 _g , lua_cod_getrawfile_stub ) ;
2023-02-18 13:13:56 -05:00
dvar_cg_enable_unsafe_lua_functions = game : : Dvar_RegisterBool (
game : : Dvar_GenerateHash ( " cg_enable_unsafe_lua_functions " ) , " cg_enable_unsafe_lua_functions " , false ,
2023-02-18 13:38:29 -05:00
static_cast < game : : dvarFlags_e > ( 0x1000 ) , " Enables the use of unsafe lua functions " ) ;
2023-02-11 11:31:21 -05:00
dvar_cg_enable_unsafe_lua_functions - > debugName = " cg_enable_unsafe_lua_functions " ;
2023-02-18 13:13:56 -05:00
scheduler : : once ( [ ] ( )
{
2023-02-11 14:40:26 -05:00
game : : dvar_t * dvar_callstack_ship = game : : Dvar_FindVar ( " ui_error_callstack_ship " ) ;
dvar_callstack_ship - > flags = ( game : : dvarFlags_e ) 0 ;
2023-02-18 13:13:56 -05:00
game : : dvar_t * dvar_report_delay = game : : Dvar_FindVar ( " ui_error_report_delay " ) ;
2023-02-11 14:40:26 -05:00
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 ) ;
2023-02-12 13:32:33 -05:00
command : : add ( " luiReload " , [ ] ( auto & params )
2023-02-18 13:13:56 -05:00
{
auto frontend = game : : Com_IsRunningUILevel ( ) ;
2023-02-12 13:32:33 -05:00
2023-02-18 13:13:56 -05:00
if ( frontend )
{
converted_functions . clear ( ) ;
2023-02-12 13:32:33 -05:00
2023-02-18 13:13:56 -05:00
globals . loaded_scripts . clear ( ) ;
globals . local_scripts . clear ( ) ;
2023-02-12 13:32:33 -05:00
2023-02-18 13:13:56 -05:00
game : : UI_CoD_Shutdown ( ) ;
game : : UI_CoD_Init ( true ) ;
2023-02-12 13:32:33 -05:00
2023-02-18 13:13:56 -05:00
// 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 ) ;
2023-02-12 13:32:33 -05:00
2023-02-18 13:13:56 -05:00
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 ) ;
}
} ) ;
2023-02-11 14:40:26 -05:00
2023-02-18 13:13:56 -05:00
scheduler : : once ( [ ] ( )
{
2023-02-11 11:31:21 -05:00
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
}
2023-02-11 14:40:26 -05:00
} , scheduler : : pipeline : : dvars_loaded ) ;
2023-01-01 19:12:14 -05:00
}
} ;
}
REGISTER_COMPONENT ( ui_scripting : : component )