diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp index 7b15f93f..5b7c6d84 100644 --- a/src/client/component/command.cpp +++ b/src/client/component/command.cpp @@ -8,7 +8,6 @@ #include "command.hpp" #include "console.hpp" #include "game_console.hpp" -#include "gsc.hpp" #include "fastfiles.hpp" #include "filesystem.hpp" #include "scheduler.hpp" @@ -608,14 +607,6 @@ namespace command utils::hook::jump(SELECT_VALUE(0x3A7C80_b, 0x4E9F40_b), dvar_command_stub, true); add_commands_generic(); - - gsc::function::add("executecommand", [](const gsc::function_args& args) - { - const auto cmd = args[0].as(); - command::execute(cmd, true); - - return scripting::script_value{}; - }); } private: diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index aff4669f..deb63fb5 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -8,7 +8,6 @@ #include "game/dvars.hpp" #include "dvars.hpp" #include "console.hpp" -#include "gsc.hpp" #include #include @@ -224,12 +223,6 @@ namespace dedicated a.jmp(0x157DDF_b); }), true); - // return 0 so the game doesn't override the cfg - gsc::function::add("isusingmatchrulesdata", [](const gsc::function_args& args) - { - return 0; - }); - // delay console commands until the initialization is done // COULDN'T FOUND // utils::hook::call(0x1400D808C, execute_console_command); // utils::hook::nop(0x1400D80A4, 5); diff --git a/src/client/component/gsc.cpp b/src/client/component/gsc.cpp deleted file mode 100644 index 37743d55..00000000 --- a/src/client/component/gsc.cpp +++ /dev/null @@ -1,900 +0,0 @@ -#include -#include "loader/component_loader.hpp" - -#include "console.hpp" -#include "fastfiles.hpp" -#include "filesystem.hpp" -#include "logfile.hpp" -#include "gsc.hpp" -#include "scripting.hpp" - -#include "game/dvars.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace gsc -{ - builtin_function func_table[0x1000]{}; - builtin_method meth_table[0x1000]{}; - - namespace - { - utils::hook::detour scr_emit_function_hook; - - game::dvar_t* developer_script = nullptr; - - auto compiler = ::gsc::compiler(); - auto decompiler = ::gsc::decompiler(); - auto assembler = ::gsc::assembler(); - auto disassembler = ::gsc::disassembler(); - - std::unordered_map main_handles; - std::unordered_map init_handles; - std::unordered_map loaded_scripts; - - std::unordered_map functions; - std::unordered_map methods; - - std::optional gsc_error; - - bool force_error_print = false; - - auto function_id_start = 0x30A; - auto method_id_start = 0x8586; - - game::scr_entref_t saved_ent_ref; - - std::string unknown_function_error{}; - unsigned int current_filename{}; - - char* allocate_buffer(size_t size) - { - // PMem_AllocFromSource_NoDebug - return utils::hook::invoke(SELECT_VALUE(0x41FB50_b, 0x5A4DC0_b), size, 4, 1, 5); - } - - bool read_scriptfile(const std::string& name, std::string* data) - { - if (filesystem::read_file(name, data)) - { - return true; - } - - const auto name_str = name.data(); - - if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) && - !game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str)) - { - const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name_str, false); - const auto len = game::DB_GetRawFileLen(asset.rawfile); - data->resize(len); - game::DB_GetRawBuffer(asset.rawfile, data->data(), len); - if (len > 0) - { - data->pop_back(); - } - - return true; - } - - return false; - } - - game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) - { - if (loaded_scripts.find(real_name) != loaded_scripts.end()) - { - return loaded_scripts[real_name]; - } - - /* - without this check, gsc rawfiles that a map contains will be compiled. however, these aren't the correct files. - each rawfile has a scriptfile counterpart in asset pool that is meant to be used instead. - the gsc rawfiles are just leftover from creating the maps. - - (if you are creating a custom map, you can safely have gsc rawfiles without having scriptfile counterparts) - */ - if (real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart") - || (real_name.starts_with("maps/mp") && real_name.ends_with("_fx.gsc"))) - { - if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, real_name.data())) - { - return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, real_name.data(), false).scriptfile; - } - } - - std::string source_buffer{}; - if (!read_scriptfile(real_name + ".gsc", &source_buffer)) - { - return nullptr; - } - - auto data = std::vector{source_buffer.begin(), source_buffer.end()}; - - try - { - compiler->compile(real_name, data); - } - catch (const std::exception& e) - { - console::error("*********** script compile error *************\n"); - console::error("failed to compile '%s':\n%s", real_name.data(), e.what()); - console::error("**********************************************\n"); - return nullptr; - } - - auto assembly = compiler->output(); - - try - { - assembler->assemble(real_name, assembly); - } - catch (const std::exception& e) - { - console::error("*********** script compile error *************\n"); - console::error("failed to assemble '%s':\n%s", real_name.data(), e.what()); - console::error("**********************************************\n"); - return nullptr; - } - - const auto script_file_ptr = reinterpret_cast(allocate_buffer(sizeof(game::ScriptFile))); - script_file_ptr->name = file_name; - - const auto stack = assembler->output_stack(); - script_file_ptr->len = static_cast(stack.size()); - - const auto script = assembler->output_script(); - script_file_ptr->bytecodeLen = static_cast(script.size()); - - const auto script_size = script.size(); - const auto buffer_size = script_size + stack.size() + 2; - - const auto buffer = allocate_buffer(buffer_size); - std::memcpy(buffer, script.data(), script_size); - std::memcpy(&buffer[script_size], stack.data(), stack.size()); - - script_file_ptr->bytecode = &buffer[0]; - script_file_ptr->buffer = &buffer[script.size()]; - script_file_ptr->compressedLen = 0; - - loaded_scripts[real_name] = script_file_ptr; - - return script_file_ptr; - } - - std::string get_script_file_name(const std::string& name) - { - const auto id = xsk::gsc::h1::resolver::token_id(name); - if (!id) - { - return name; - } - - return std::to_string(id); - } - - std::vector decompile_script_file(const std::string& name, const std::string& real_name) - { - const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile; - if (!script_file) - { - throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name)); - } - - console::info("Decompiling scriptfile '%s'\n", real_name.data()); - - std::vector stack{script_file->buffer, script_file->buffer + script_file->len}; - std::vector bytecode{script_file->bytecode, script_file->bytecode + script_file->bytecodeLen}; - - auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast(stack.size())); - - disassembler->disassemble(name, bytecode, decompressed_stack); - auto output = disassembler->output(); - - decompiler->decompile(name, output); - - return decompiler->output(); - } - - void load_script(const std::string& name) - { - if (!game::Scr_LoadScript(name.data())) - { - return; - } - - const auto main_handle = game::Scr_GetFunctionHandle(name.data(), - xsk::gsc::h1::resolver::token_id("main")); - const auto init_handle = game::Scr_GetFunctionHandle(name.data(), - xsk::gsc::h1::resolver::token_id("init")); - - if (main_handle) - { - console::info("Loaded '%s::main'\n", name.data()); - main_handles[name] = main_handle; - } - - if (init_handle) - { - console::info("Loaded '%s::init'\n", name.data()); - init_handles[name] = init_handle; - } - } - - void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir) - { - std::filesystem::path script_dir_path = root_dir / script_dir; - if (!utils::io::directory_exists(script_dir_path.generic_string())) - { - return; - } - - const auto scripts = utils::io::list_files(script_dir_path.generic_string()); - for (const auto& script : scripts) - { - if (!script.ends_with(".gsc")) - { - continue; - } - - std::filesystem::path path(script); - const auto relative = path.lexically_relative(root_dir).generic_string(); - const auto base_name = relative.substr(0, relative.size() - 4); - - load_script(base_name); - } - } - - void clear() - { - main_handles.clear(); - init_handles.clear(); - loaded_scripts.clear(); - } - - void load_gametype_script_stub(void* a1, void* a2) - { - utils::hook::invoke(SELECT_VALUE(0x2B9DA0_b, 0x18BC00_b), a1, a2); - - if (game::VirtualLobby_Loaded()) - { - return; - } - - clear(); - - for (const auto& path : filesystem::get_search_paths()) - { - load_scripts(path, "scripts/"); - if (game::environment::is_sp()) - { - load_scripts(path, "scripts/sp/"); - } - else - { - load_scripts(path, "scripts/mp/"); - } - } - } - - int db_is_xasset_default(game::XAssetType type, const char* name) - { - if (loaded_scripts.find(name) != loaded_scripts.end()) - { - return 0; - } - - return game::DB_IsXAssetDefault(type, name); - } - - void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size) - { - if (rawfile->len > 0 && rawfile->compressedLen == 0) - { - std::memset(buf, 0, size); - std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size)); - return; - } - - utils::hook::invoke(SELECT_VALUE(0x1F1E00_b, 0x396080_b), rawfile, buf, size); - } - - std::optional> find_function(const char* pos) - { - for (const auto& file : scripting::script_function_table_sort) - { - for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i) - { - const auto next = std::next(i); - if (pos >= i->second && pos < next->second) - { - return {std::make_pair(i->first, file.first)}; - } - } - } - - return {}; - } - - void print_callstack() - { - for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame) - { - const auto pos = frame == game::scr_VmPub->function_frame - ? game::scr_function_stack->pos - : frame->fs.pos; - const auto function = find_function(pos); - - if (function.has_value()) - { - console::warn("\tat function \"%s\" in file \"%s.gsc\"", - function.value().first.data(), function.value().second.data()); - } - else - { - console::warn("\tat unknown location %p", pos); - } - } - } - - std::optional get_opcode_name(const std::uint8_t opcode) - { - try - { - return {xsk::gsc::h1::resolver::opcode_name(opcode)}; - } - catch (...) - { - return {}; - } - } - - std::uint16_t get_function_id() - { - const auto pos = game::scr_function_stack->pos; - return *reinterpret_cast( - reinterpret_cast(pos - 2)); - } - - void builtin_call_error(const std::string& error_str) - { - const auto function_id = get_function_id(); - - if (function_id > 0x1000) - { - console::warn("in call to builtin method \"%s\"%s", - xsk::gsc::h1::resolver::method_name(function_id).data(), error_str.data()); - } - else - { - console::warn("in call to builtin function \"%s\"%s", - xsk::gsc::h1::resolver::function_name(function_id).data(), error_str.data()); - } - } - - void vm_error_stub(void* a1) - { - if (!developer_script->current.enabled && !force_error_print) - { - utils::hook::invoke(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1); - return; - } - - console::warn("*********** script runtime error *************\n"); - - const auto opcode_id = *reinterpret_cast(SELECT_VALUE(0xC4015E8_b, 0xB7B8968_b)); - const std::string error_str = gsc_error.has_value() - ? utils::string::va(": %s", gsc_error.value().data()) - : ""; - - if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF)) - { - builtin_call_error(error_str); - } - else - { - const auto opcode = get_opcode_name(opcode_id); - if (opcode.has_value()) - { - console::warn("while processing instruction %s%s\n", - opcode.value().data(), error_str.data()); - } - else - { - console::warn("while processing instruction 0x%X%s\n", - opcode_id, error_str.data()); - } - } - - force_error_print = false; - gsc_error = {}; - - print_callstack(); - console::warn("**********************************************\n"); - utils::hook::invoke(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1); - } - - void get_unknown_function_error(const char* code_pos) - { - const auto function = find_function(code_pos); - if (function.has_value()) - { - const auto& pos = function.value(); - unknown_function_error = utils::string::va( - "while processing function '%s' in script '%s':\nunknown script '%s'", - pos.first.data(), pos.second.data(), scripting::current_file.data() - ); - } - else - { - unknown_function_error = utils::string::va( - "unknown script '%s'", - scripting::current_file.data() - ); - } - } - - std::string get_filename_name() - { - const auto filename_str = game::SL_ConvertToString( - static_cast(current_filename)); - const auto id = std::atoi(filename_str); - if (id == 0) - { - return filename_str; - } - - return scripting::get_token(id); - } - - void get_unknown_function_error(unsigned int thread_name) - { - const auto filename = get_filename_name(); - const auto name = scripting::get_token(thread_name); - - unknown_function_error = utils::string::va( - "while processing script '%s':\nunknown function '%s::%s'", - scripting::current_file.data(), filename.data(), name.data() - ); - } - - void unknown_function_stub(const char* code_pos) - { - get_unknown_function_error(code_pos); - game::Com_Error(game::ERR_DROP, "script link error\n%s", - unknown_function_error.data()); - } - - unsigned int find_variable_stub(unsigned int parent_id, unsigned int thread_name) - { - const auto res = game::FindVariable(parent_id, thread_name); - if (!res) - { - get_unknown_function_error(thread_name); - game::Com_Error(game::ERR_DROP, "script link error\n%s", - unknown_function_error.data()); - } - return res; - } - - void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos) - { - current_filename = filename; - scr_emit_function_hook.invoke(filename, thread_name, code_pos); - } - - function_args get_arguments() - { - std::vector args; - - for (auto i = 0; static_cast(i) < game::scr_VmPub->outparamcount; ++i) - { - const auto value = game::scr_VmPub->top[-i]; - args.push_back(value); - } - - return args; - } - - void return_value(const scripting::script_value& value) - { - if (game::scr_VmPub->outparamcount) - { - game::Scr_ClearOutParams(); - } - - scripting::push_value(value); - } - - void call_custom_function(const uint16_t id) - { - auto error = false; - - try - { - const auto& function = functions[id]; - const auto result = function(get_arguments()); - const auto type = result.get_raw().type; - - if (type) - { - return_value(result); - } - } - catch (const std::exception& e) - { - error = true; - force_error_print = true; - gsc_error = e.what(); - } - - if (error) - { - game::Scr_ErrorInternal(); - } - } - - void call_custom_method(const uint16_t id) - { - auto error = false; - - try - { - const auto& method = methods[id]; - const auto result = method(saved_ent_ref, get_arguments()); - const auto type = result.get_raw().type; - - if (type) - { - return_value(result); - } - } - catch (const std::exception& e) - { - error = true; - force_error_print = true; - gsc_error = e.what(); - } - - if (error) - { - game::Scr_ErrorInternal(); - } - } - - void vm_call_builtin_function_stub(builtin_function function) - { - const auto function_id = get_function_id(); - const auto is_custom_function = functions.find(function_id) != functions.end(); - - if (!is_custom_function) - { - function(); - } - else - { - call_custom_function(function_id); - } - } - - game::scr_entref_t get_entity_id_stub(unsigned int ent_id) - { - const auto ref = game::Scr_GetEntityIdRef(ent_id); - saved_ent_ref = ref; - return ref; - } - - void vm_call_builtin_method_stub(builtin_method method) - { - const auto function_id = get_function_id(); - const auto is_custom_function = methods.find(function_id) != methods.end(); - - if (!is_custom_function) - { - method(saved_ent_ref); - } - else - { - call_custom_method(function_id); - } - } - } - - game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/) - { - if (game::VirtualLobby_Loaded()) - { - return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile; - } - - std::string real_name = name; - const auto id = static_cast(std::atoi(name)); - if (id) - { - real_name = xsk::gsc::h1::resolver::token_name(id); - } - - auto* script = load_custom_script(name, real_name); - if (script) - { - return script; - } - - return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile; - } - - void load_main_handles() - { - for (auto& function_handle : main_handles) - { - console::info("Executing '%s::main'\n", function_handle.first.data()); - game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0)); - } - } - - void load_init_handles() - { - for (auto& function_handle : init_handles) - { - console::info("Executing '%s::init'\n", function_handle.first.data()); - game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0)); - } - } - - namespace function - { - void add(const std::string& name, script_function function) - { - if (xsk::gsc::h1::resolver::find_function(name)) - { - const auto id = xsk::gsc::h1::resolver::function_id(name); - functions[id] = function; - } - else - { - const auto id = ++function_id_start; - xsk::gsc::h1::resolver::add_function(name, static_cast(id)); - functions[id] = function; - } - } - } - - namespace method - { - void add(const std::string& name, script_method method) - { - if (xsk::gsc::h1::resolver::find_method(name)) - { - const auto id = xsk::gsc::h1::resolver::method_id(name); - methods[id] = method; - } - else - { - const auto id = ++method_id_start; - xsk::gsc::h1::resolver::add_method(name, static_cast(id)); - methods[id] = method; - } - } - } - - function_args::function_args(std::vector values) - : values_(values) - { - } - - unsigned int function_args::size() const - { - return static_cast(this->values_.size()); - } - - std::vector function_args::get_raw() const - { - return this->values_; - } - - scripting::value_wrap function_args::get(const int index) const - { - if (index >= this->values_.size()) - { - throw std::runtime_error(utils::string::va("parameter %d does not exist", index)); - } - - return { this->values_[index], index }; - } - - class component final : public component_interface - { - public: - void post_unpack() override - { - developer_script = dvars::register_bool("developer_script", false, 0, "Print GSC errors"); - - // Allow custom scripts to include other custom scripts - xsk::gsc::h1::resolver::init([](const auto& include_name) - { - const auto real_name = include_name + ".gsc"; - - std::string file_buffer; - if (!read_scriptfile(real_name, &file_buffer) || file_buffer.empty()) - { - const auto name = get_script_file_name(include_name); - if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data())) - { - return decompile_script_file(name, real_name); - } - else - { - throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name)); - } - } - - std::vector result; - result.assign(file_buffer.begin(), file_buffer.end()); - - return result; - }); - - // hook xasset functions to return our own custom scripts - utils::hook::call(SELECT_VALUE(0x3C7217_b, 0x50E357_b), find_script); - utils::hook::call(SELECT_VALUE(0x3C7227_b, 0x50E367_b), db_is_xasset_default); - - // loads scripts with an uncompressed stack - utils::hook::call(SELECT_VALUE(0x3C7280_b, 0x50E3C0_b), db_get_raw_buffer_stub); - - // load script handles - utils::hook::call(SELECT_VALUE(0x2BA152_b, 0x18C325_b), load_gametype_script_stub); - - utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub); - - // patch error to drop + give more information - utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), unknown_function_stub); // CompileError - utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), unknown_function_stub); // ^ - utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub); - scr_emit_function_hook.create(SELECT_VALUE(0x3BD680_b, 0x504660_b), scr_emit_function_stub); - - utils::hook::set(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count - - utils::hook::set(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4, - static_cast(reverse_b((&func_table)))); - utils::hook::set(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4, - static_cast(reverse_b((&func_table)))); - utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table); - utils::hook::set(SELECT_VALUE(0x3BDC1E_b, 0x504C4E_b), sizeof(func_table)); - - utils::hook::set(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4, - static_cast(reverse_b((&meth_table)))); - utils::hook::set(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4, - static_cast(reverse_b(&meth_table))); - utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table); - utils::hook::set(SELECT_VALUE(0x3BDC3F_b, 0x504C6F_b), sizeof(meth_table)); - - /* - trying to do a jump hook to push the ent reference (if it exists) and the builtin function/methods works, but - if longjmp is called because of a runtime error in our custom functions/methods, then the game just kinda dies - or gets incredibly slow but will eventually load. for functions, the workaround is easy but for methods, we still - have to remember the entity that called the method so the workaround is just hooking the Scr_GetEntityIdRef call - */ - utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8); - utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub); - - utils::hook::call(SELECT_VALUE(0x3CBA12_b, 0x512A72_b), get_entity_id_stub); - utils::hook::nop(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), 6); - utils::hook::nop(SELECT_VALUE(0x3CBA4E_b, 0x512AAE_b), 2); - utils::hook::call(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), vm_call_builtin_method_stub); - - function::add("print", [](const function_args& args) - { - std::string buffer{}; - - for (auto i = 0u; i < args.size(); ++i) - { - const auto str = args[i].to_string(); - buffer.append(str); - buffer.append("\t"); - } - console::info("%s\n", buffer.data()); - - return scripting::script_value{}; - }); - - function::add("assert", [](const function_args& args) - { - const auto expr = args[0].as(); - if (!expr) - { - throw std::runtime_error("assert fail"); - } - - return scripting::script_value{}; - }); - - function::add("assertex", [](const function_args& args) - { - const auto expr = args[0].as(); - if (!expr) - { - const auto error = args[1].as(); - throw std::runtime_error(error); - } - - return scripting::script_value{}; - }); - - function::add("replacefunc", [](const function_args& args) - { - const auto what = args[0].get_raw(); - const auto with = args[1].get_raw(); - - if (what.type != game::SCRIPT_FUNCTION) - { - throw std::runtime_error("replaceFunc: parameter 1 must be a function"); - } - - if (with.type != game::SCRIPT_FUNCTION) - { - throw std::runtime_error("replaceFunc: parameter 2 must be a function"); - } - - logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue); - - return scripting::script_value{}; - }); - - function::add("toupper", [](const function_args& args) - { - const auto string = args[0].as(); - return utils::string::to_upper(string); - }); - - function::add("logprint", [](const function_args& args) - { - std::string buffer{}; - - for (auto i = 0u; i < args.size(); ++i) - { - const auto string = args[i].as(); - buffer.append(string); - } - - game::G_LogPrintf("%s", buffer.data()); - - return scripting::script_value{}; - }); - - function::add("getfunction", [](const function_args& args) -> scripting::script_value - { - const auto filename = args[0].as(); - const auto function = args[1].as(); - - if (scripting::script_function_table[filename].find(function) != scripting::script_function_table[filename].end()) - { - return scripting::function{scripting::script_function_table[filename][function]}; - } - - return {}; - }); - - scripting::on_shutdown([](int free_scripts) - { - if (free_scripts) - { - xsk::gsc::h1::resolver::cleanup(); - clear(); - } - }); - } - }; -} - -REGISTER_COMPONENT(gsc::component) diff --git a/src/client/component/gsc/script_error.cpp b/src/client/component/gsc/script_error.cpp new file mode 100644 index 00000000..1c88774e --- /dev/null +++ b/src/client/component/gsc/script_error.cpp @@ -0,0 +1,128 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include "script_error.hpp" + +#include "component/scripting.hpp" + +#include + +namespace gsc +{ + namespace + { + utils::hook::detour scr_emit_function_hook; + + std::uint32_t current_filename = 0; + + std::string unknown_function_error; + + void scr_emit_function_stub(std::uint32_t filename, std::uint32_t thread_name, char* code_pos) + { + current_filename = filename; + scr_emit_function_hook.invoke(filename, thread_name, code_pos); + } + + std::string get_filename_name() + { + const auto filename_str = game::SL_ConvertToString(static_cast(current_filename)); + const auto id = std::atoi(filename_str); + if (!id) + { + return filename_str; + } + + return scripting::get_token(id); + } + + void get_unknown_function_error(const char* code_pos) + { + const auto function = find_function(code_pos); + if (function.has_value()) + { + const auto& pos = function.value(); + unknown_function_error = std::format( + "while processing function '{}' in script '{}':\nunknown script '{}'", + pos.first, pos.second, scripting::current_file + ); + } + else + { + unknown_function_error = std::format("unknown script '{}'", scripting::current_file); + } + } + + void get_unknown_function_error(std::uint32_t thread_name) + { + const auto filename = get_filename_name(); + const auto name = scripting::get_token(thread_name); + + unknown_function_error = std::format( + "while processing script '{}':\nunknown function '{}::{}'", + scripting::current_file, filename, name + ); + } + + void unknown_function_stub(const char* code_pos) + { + get_unknown_function_error(code_pos); + game::Com_Error(game::ERR_DROP, "script link error\n%s", + unknown_function_error.data()); + } + + std::uint32_t find_variable_stub(std::uint32_t parent_id, std::uint32_t thread_name) + { + const auto res = game::FindVariable(parent_id, thread_name); + if (!res) + { + get_unknown_function_error(thread_name); + game::Com_Error(game::ERR_DROP, "script link error\n%s", + unknown_function_error.data()); + } + return res; + } + } + + std::optional> find_function(const char* pos) + { + for (const auto& file : scripting::script_function_table_sort) + { + for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i) + { + const auto next = std::next(i); + if (pos >= i->second && pos < next->second) + { + return {std::make_pair(i->first, file.first)}; + } + } + } + + return {}; + } + + class error final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_sp()) + { + return; + } + + scr_emit_function_hook.create(SELECT_VALUE(0x3BD680_b, 0x504660_b), &scr_emit_function_stub); + + utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), unknown_function_stub); // CompileError (LinkFile) + utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), unknown_function_stub); // ^ + utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub); // Scr_EmitFunction + } + + void pre_destroy() override + { + scr_emit_function_hook.clear(); + } + }; +} + +REGISTER_COMPONENT(gsc::error) diff --git a/src/client/component/gsc/script_error.hpp b/src/client/component/gsc/script_error.hpp new file mode 100644 index 00000000..e8742026 --- /dev/null +++ b/src/client/component/gsc/script_error.hpp @@ -0,0 +1,7 @@ + +#pragma once + +namespace gsc +{ + std::optional> find_function(const char* pos); +} \ No newline at end of file diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp new file mode 100644 index 00000000..d2278b54 --- /dev/null +++ b/src/client/component/gsc/script_extension.cpp @@ -0,0 +1,471 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/dvars.hpp" +#include "game/game.hpp" +#include "game/scripting/execution.hpp" +#include "game/scripting/function.hpp" +#include "game/scripting/functions.hpp" +#include "game/scripting/lua/error.hpp" + +#include + +#include "component/command.hpp" +#include "component/console.hpp" +#include "component/scripting.hpp" +#include "component/logfile.hpp" + +#include +#include + +#include "script_extension.hpp" +#include "script_error.hpp" + +namespace gsc +{ + std::uint16_t function_id_start = 0x30A; + std::uint16_t method_id_start = 0x8586; + + builtin_function func_table[0x1000]; + builtin_method meth_table[0x1000]; + + const game::dvar_t* developer_script = nullptr; + + namespace + { + struct gsc_error : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + std::unordered_map functions; + std::unordered_map methods; + + bool force_error_print = false; + std::optional gsc_error_msg; + game::scr_entref_t saved_ent_ref; + + function_args get_arguments() + { + std::vector args; + + for (auto i = 0; static_cast(i) < game::scr_VmPub->outparamcount; ++i) + { + const auto value = game::scr_VmPub->top[-i]; + args.push_back(value); + } + + return args; + } + + void return_value(const scripting::script_value& value) + { + if (game::scr_VmPub->outparamcount) + { + game::Scr_ClearOutParams(); + } + + scripting::push_value(value); + } + + std::uint16_t get_function_id() + { + const auto pos = game::scr_function_stack->pos; + return *reinterpret_cast( + reinterpret_cast(pos - 2)); + } + + void execute_custom_function(const std::uint16_t id) + { + auto error = false; + + try + { + const auto& function = functions[id]; + const auto result = function(get_arguments()); + const auto type = result.get_raw().type; + + if (type) + { + return_value(result); + } + } + catch (const std::exception& e) + { + error = true; + force_error_print = true; + gsc_error_msg = e.what(); + } + + if (error) + { + game::Scr_ErrorInternal(); + } + } + + void execute_custom_method(const std::uint16_t id) + { + auto error = false; + + try + { + const auto& method = methods[id]; + const auto result = method(saved_ent_ref, get_arguments()); + const auto type = result.get_raw().type; + + if (type) + { + return_value(result); + } + } + catch (const std::exception& e) + { + error = true; + force_error_print = true; + gsc_error_msg = e.what(); + } + + if (error) + { + game::Scr_ErrorInternal(); + } + } + + void vm_call_builtin_function_stub(builtin_function function) + { + const auto function_id = get_function_id(); + const auto is_custom_function = functions.find(function_id) != functions.end(); + + if (!is_custom_function) + { + function(); + } + else + { + execute_custom_function(function_id); + } + } + + game::scr_entref_t get_entity_id_stub(std::uint32_t ent_id) + { + const auto ref = game::Scr_GetEntityIdRef(ent_id); + saved_ent_ref = ref; + return ref; + } + + void vm_call_builtin_method_stub(builtin_method method) + { + const auto function_id = get_function_id(); + const auto is_custom_function = methods.find(function_id) != methods.end(); + + if (!is_custom_function) + { + method(saved_ent_ref); + } + else + { + execute_custom_method(function_id); + } + } + + void builtin_call_error(const std::string& error) + { + const auto function_id = get_function_id(); + + if (function_id > 0x1000) + { + console::warn("in call to builtin method \"%s\"%s", + xsk::gsc::h1::resolver::method_name(function_id).data(), error.data()); + } + else + { + console::warn("in call to builtin function \"%s\"%s", + xsk::gsc::h1::resolver::function_name(function_id).data(), error.data()); + } + } + + std::optional get_opcode_name(const std::uint8_t opcode) + { + try + { + return {xsk::gsc::h1::resolver::opcode_name(opcode)}; + } + catch (...) + { + return {}; + } + } + + void print_callstack() + { + for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame) + { + const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos; + const auto function = find_function(frame->fs.pos); + + if (function.has_value()) + { + console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data()); + } + else + { + console::warn("\tat unknown location %p\n", pos); + } + } + } + + void vm_error_stub(int mark_pos) + { + if (!developer_script->current.enabled && !force_error_print) + { + utils::hook::invoke(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos); + return; + } + + console::warn("*********** script runtime error *************\n"); + + const auto opcode_id = *reinterpret_cast(SELECT_VALUE(0xC4015E8_b, 0xB7B8968_b)); + const std::string error_str = gsc_error_msg.has_value() + ? utils::string::va(": %s", gsc_error_msg.value().data()) + : ""; + + if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF)) + { + builtin_call_error(error_str); + } + else + { + const auto opcode = get_opcode_name(opcode_id); + if (opcode.has_value()) + { + console::warn("while processing instruction %s%s\n", opcode.value().data(), error_str.data()); + } + else + { + console::warn("while processing instruction 0x%X%s\n", opcode_id, error_str.data()); + } + } + + force_error_print = false; + gsc_error_msg = {}; + + print_callstack(); + console::warn("**********************************************\n"); + utils::hook::invoke(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos); + } + + void print(const function_args& args) + { + std::string buffer{}; + + for (auto i = 0u; i < args.size(); ++i) + { + const auto str = args[i].to_string(); + buffer.append(str); + buffer.append("\t"); + } + console::info("%s\n", buffer.data()); + } + } + + namespace function + { + void add(const std::string& name, script_function function) + { + if (xsk::gsc::h1::resolver::find_function(name)) + { + const auto id = xsk::gsc::h1::resolver::function_id(name); + functions[id] = function; + } + else + { + const auto id = ++function_id_start; + xsk::gsc::h1::resolver::add_function(name, static_cast(id)); + functions[id] = function; + } + } + } + + namespace method + { + void add(const std::string& name, script_method method) + { + if (xsk::gsc::h1::resolver::find_method(name)) + { + const auto id = xsk::gsc::h1::resolver::method_id(name); + methods[id] = method; + } + else + { + const auto id = ++method_id_start; + xsk::gsc::h1::resolver::add_method(name, static_cast(id)); + methods[id] = method; + } + } + } + + function_args::function_args(std::vector values) + : values_(values) + { + } + + std::uint32_t function_args::size() const + { + return static_cast(this->values_.size()); + } + + std::vector function_args::get_raw() const + { + return this->values_; + } + + scripting::value_wrap function_args::get(const int index) const + { + if (index >= this->values_.size()) + { + throw std::runtime_error(utils::string::va("parameter %d does not exist", index)); + } + + return {this->values_[index], index}; + } + + class extension final : public component_interface + { + public: + void post_unpack() override + { + utils::hook::set(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count + + utils::hook::set(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4, + static_cast(reverse_b((&func_table)))); + utils::hook::set(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4, + static_cast(reverse_b((&func_table)))); + utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table); + utils::hook::set(SELECT_VALUE(0x3BDC1E_b, 0x504C4E_b), sizeof(func_table)); + + utils::hook::set(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4, + static_cast(reverse_b((&meth_table)))); + utils::hook::set(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4, + static_cast(reverse_b(&meth_table))); + utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table); + utils::hook::set(SELECT_VALUE(0x3BDC3F_b, 0x504C6F_b), sizeof(meth_table)); + + if (game::environment::is_sp()) + { + return; + } + + developer_script = dvars::register_bool("developer_script", false, 0, "Enable developer script comments"); + + utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8); + utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub); + + utils::hook::call(SELECT_VALUE(0x3CBA12_b, 0x512A72_b), get_entity_id_stub); + utils::hook::nop(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), 6); + utils::hook::nop(SELECT_VALUE(0x3CBA4E_b, 0x512AAE_b), 2); + utils::hook::call(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), vm_call_builtin_method_stub); + + utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub); + + function::add("print", [](const function_args& args) + { + print(args); + return scripting::script_value{}; + }); + + function::add("println", [](const function_args& args) + { + print(args); + return scripting::script_value{}; + }); + + function::add("assert", [](const function_args& args) + { + const auto expr = args[0].as(); + if (!expr) + { + throw std::runtime_error("assert fail"); + } + + return scripting::script_value{}; + }); + + function::add("assertex", [](const function_args& args) + { + const auto expr = args[0].as(); + if (!expr) + { + const auto error = args[1].as(); + throw std::runtime_error(error); + } + + return scripting::script_value{}; + }); + + function::add("getfunction", [](const function_args& args) -> scripting::script_value + { + const auto filename = args[0].as(); + const auto function = args[1].as(); + + if (scripting::script_function_table[filename].find(function) != scripting::script_function_table[filename].end()) + { + return scripting::function{scripting::script_function_table[filename][function]}; + } + + return {}; + }); + + function::add("replacefunc", [](const function_args& args) + { + const auto what = args[0].get_raw(); + const auto with = args[1].get_raw(); + + if (what.type != game::SCRIPT_FUNCTION || with.type != game::SCRIPT_FUNCTION) + { + throw std::runtime_error("replaceFunc: parameter 1 must be a function"); + } + + logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue); + + return scripting::script_value{}; + }); + + function::add("toupper", [](const function_args& args) + { + const auto string = args[0].as(); + return utils::string::to_upper(string); + }); + + function::add("logprint", [](const function_args& args) + { + std::string buffer{}; + + for (auto i = 0u; i < args.size(); ++i) + { + const auto string = args[i].as(); + buffer.append(string); + } + + game::G_LogPrintf("%s", buffer.data()); + + return scripting::script_value{}; + }); + + function::add("executecommand", [](const function_args& args) + { + const auto cmd = args[0].as(); + command::execute(cmd, true); + + return scripting::script_value{}; + }); + + function::add("isusingmatchrulesdata", [](const function_args& args) + { + // return 0 so the game doesn't override the cfg + return 0; + }); + } + }; +} + +REGISTER_COMPONENT(gsc::extension) \ No newline at end of file diff --git a/src/client/component/gsc.hpp b/src/client/component/gsc/script_extension.hpp similarity index 86% rename from src/client/component/gsc.hpp rename to src/client/component/gsc/script_extension.hpp index 5043efeb..2aae4a2e 100644 --- a/src/client/component/gsc.hpp +++ b/src/client/component/gsc/script_extension.hpp @@ -32,10 +32,7 @@ namespace gsc extern builtin_function func_table[0x1000]; extern builtin_method meth_table[0x1000]; - game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/); - - void load_main_handles(); - void load_init_handles(); + extern const game::dvar_t* developer_script; namespace function { @@ -46,4 +43,4 @@ namespace gsc { void add(const std::string& name, script_method function); } -} +} \ No newline at end of file diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp new file mode 100644 index 00000000..8eb0abcf --- /dev/null +++ b/src/client/component/gsc/script_loading.cpp @@ -0,0 +1,388 @@ +#include +#include "loader/component_loader.hpp" + +#include "component/console.hpp" +#include "component/fastfiles.hpp" +#include "component/filesystem.hpp" +#include "component/logfile.hpp" +#include "component/scripting.hpp" + +#include "game/dvars.hpp" + +#include "game/scripting/array.hpp" +#include "game/scripting/execution.hpp" +#include "game/scripting/function.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace gsc +{ + namespace + { + auto compiler = ::gsc::compiler(); + auto decompiler = ::gsc::decompiler(); + auto assembler = ::gsc::assembler(); + auto disassembler = ::gsc::disassembler(); + + std::unordered_map main_handles; + std::unordered_map init_handles; + + std::unordered_map loaded_scripts; + + void clear() + { + main_handles.clear(); + init_handles.clear(); + loaded_scripts.clear(); + } + + bool read_script_file(const std::string& name, std::string* data) + { + if (filesystem::read_file(name, data)) + { + return true; + } + + const auto name_str = name.data(); + + if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) && + !game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str)) + { + const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name_str, false); + const auto len = game::DB_GetRawFileLen(asset.rawfile); + data->resize(len); + game::DB_GetRawBuffer(asset.rawfile, data->data(), len); + if (len > 0) + { + data->pop_back(); + } + + return true; + } + + return false; + } + + char* allocate_buffer(size_t size) + { + // PMem_AllocFromSource_NoDebug + return utils::hook::invoke(SELECT_VALUE(0x41FB50_b, 0x5A4DC0_b), size, 4, 1, 5); + } + + game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) + { + if (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end()) + { + return itr->second; + } + + /* + without this check, gsc rawfiles that a map contains will be compiled. however, these aren't the correct files. + each rawfile has a scriptfile counterpart in asset pool that is meant to be used instead. + the gsc rawfiles are just leftover from creating the maps. + + (if you are creating a custom map, you can safely have gsc rawfiles without having scriptfile counterparts) + */ + if (real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart") + || (real_name.starts_with("maps/mp") && real_name.ends_with("_fx.gsc"))) + { + if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, real_name.data())) + { + return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, real_name.data(), false).scriptfile; + } + } + + std::string source_buffer{}; + if (!read_script_file(real_name + ".gsc", &source_buffer)) + { + return nullptr; + } + + std::vector data; + data.assign(source_buffer.begin(), source_buffer.end()); + + try + { + compiler->compile(real_name, data); + } + catch (const std::exception& e) + { + console::error("*********** script compile error *************\n"); + console::error("failed to compile '%s':\n%s", real_name.data(), e.what()); + console::error("**********************************************\n"); + return nullptr; + } + + auto assembly = compiler->output(); + + try + { + assembler->assemble(real_name, assembly); + } + catch (const std::exception& e) + { + console::error("*********** script compile error *************\n"); + console::error("failed to assemble '%s':\n%s", real_name.data(), e.what()); + console::error("**********************************************\n"); + return nullptr; + } + + const auto script_file_ptr = reinterpret_cast(allocate_buffer(sizeof(game::ScriptFile))); + script_file_ptr->name = file_name; + + const auto stack = assembler->output_stack(); + script_file_ptr->len = static_cast(stack.size()); + + const auto script = assembler->output_script(); + script_file_ptr->bytecodeLen = static_cast(script.size()); + + const auto script_size = script.size(); + const auto buffer_size = script_size + stack.size() + 2; + + const auto buffer = allocate_buffer(buffer_size); + std::memcpy(buffer, script.data(), script_size); + std::memcpy(&buffer[script_size], stack.data(), stack.size()); + + script_file_ptr->bytecode = &buffer[0]; + script_file_ptr->buffer = reinterpret_cast(&buffer[script.size()]); + script_file_ptr->compressedLen = 0; + + loaded_scripts[real_name] = script_file_ptr; + + return script_file_ptr; + } + + std::string get_script_file_name(const std::string& name) + { + const auto id = xsk::gsc::h1::resolver::token_id(name); + if (!id) + { + return name; + } + + return std::to_string(id); + } + + std::vector decompile_script_file(const std::string& name, const std::string& real_name) + { + const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile; + if (!script_file) + { + throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name)); + } + + console::info("Decompiling scriptfile '%s'\n", real_name.data()); + + std::vector stack{script_file->buffer, script_file->buffer + script_file->len}; + std::vector bytecode{script_file->bytecode, script_file->bytecode + script_file->bytecodeLen}; + + auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast(stack.size())); + + disassembler->disassemble(name, bytecode, decompressed_stack); + auto output = disassembler->output(); + + decompiler->decompile(name, output); + + return decompiler->output(); + } + + void load_script(const std::string& name) + { + if (!game::Scr_LoadScript(name.data())) + { + return; + } + + const auto main_handle = game::Scr_GetFunctionHandle(name.data(), xsk::gsc::h1::resolver::token_id("main")); + const auto init_handle = game::Scr_GetFunctionHandle(name.data(), xsk::gsc::h1::resolver::token_id("init")); + + if (main_handle) + { + console::info("Loaded '%s::main'\n", name.data()); + main_handles[name] = main_handle; + } + + if (init_handle) + { + console::info("Loaded '%s::init'\n", name.data()); + init_handles[name] = init_handle; + } + } + + void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir) + { + std::filesystem::path script_dir_path = root_dir / script_dir; + if (!utils::io::directory_exists(script_dir_path.generic_string())) + { + return; + } + + const auto scripts = utils::io::list_files(script_dir_path.generic_string()); + for (const auto& script : scripts) + { + if (!script.ends_with(".gsc")) + { + continue; + } + + std::filesystem::path path(script); + const auto relative = path.lexically_relative(root_dir).generic_string(); + const auto base_name = relative.substr(0, relative.size() - 4); + + load_script(base_name); + } + } + + int db_is_x_asset_default(game::XAssetType type, const char* name) + { + if (loaded_scripts.contains(name)) + { + return 0; + } + + return game::DB_IsXAssetDefault(type, name); + } + + void gscr_load_gametype_script_stub(void* a1, void* a2) + { + utils::hook::invoke(SELECT_VALUE(0x2B9DA0_b, 0x18BC00_b), a1, a2); + + clear(); + + if (game::VirtualLobby_Loaded()) + { + return; + } + + for (const auto& path : filesystem::get_search_paths()) + { + load_scripts(path, "scripts/"); + if (game::environment::is_sp()) + { + load_scripts(path, "scripts/sp/"); + } + else + { + load_scripts(path, "scripts/mp/"); + } + } + } + + void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size) + { + if (rawfile->len > 0 && rawfile->compressedLen == 0) + { + std::memset(buf, 0, size); + std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size)); + return; + } + + utils::hook::invoke(SELECT_VALUE(0x1F1E00_b, 0x396080_b), rawfile, buf, size); + } + } + + void load_main_handles() + { + for (auto& function_handle : main_handles) + { + console::info("Executing '%s::main'\n", function_handle.first.data()); + game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0)); + } + } + + void load_init_handles() + { + for (auto& function_handle : init_handles) + { + console::info("Executing '%s::init'\n", function_handle.first.data()); + game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0)); + } + } + + game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default) + { + std::string real_name = name; + const auto id = static_cast(std::atoi(name)); + if (id) + { + real_name = xsk::gsc::h1::resolver::token_name(id); + } + + auto* script = load_custom_script(name, real_name); + if (script) + { + return script; + } + + return game::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile; + } + + class loading final : public component_interface + { + public: + void post_unpack() override + { + if (game::environment::is_sp()) + { + return; + } + + // allow custom scripts to include other custom scripts + xsk::gsc::h1::resolver::init([](const auto& include_name) + { + const auto real_name = include_name + ".gsc"; + + std::string file_buffer; + if (!read_script_file(real_name, &file_buffer) || file_buffer.empty()) + { + const auto name = get_script_file_name(include_name); + if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data())) + { + return decompile_script_file(name, real_name); + } + else + { + throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name)); + } + } + + std::vector result; + result.assign(file_buffer.begin(), file_buffer.end()); + + return result; + }); + + // hook xasset functions to return our own custom scripts + utils::hook::call(SELECT_VALUE(0x3C7217_b, 0x50E357_b), find_script); + utils::hook::call(SELECT_VALUE(0x3C7227_b, 0x50E367_b), db_is_x_asset_default); + + // GScr_LoadScripts + utils::hook::call(SELECT_VALUE(0x2BA152_b, 0x18C325_b), gscr_load_gametype_script_stub); + + // loads scripts with an uncompressed stack + utils::hook::call(SELECT_VALUE(0x3C7280_b, 0x50E3C0_b), db_get_raw_buffer_stub); + + scripting::on_shutdown([](int free_scripts) + { + if (free_scripts) + { + xsk::gsc::h1::resolver::cleanup(); + clear(); + } + }); + } + }; +} + +REGISTER_COMPONENT(gsc::loading) \ No newline at end of file diff --git a/src/client/component/io.cpp b/src/client/component/io.cpp index 44f8b961..b1ca0882 100644 --- a/src/client/component/io.cpp +++ b/src/client/component/io.cpp @@ -1,10 +1,10 @@ #include #include "loader/component_loader.hpp" -#include "console.hpp" -#include "gsc.hpp" -#include "logfile.hpp" -#include "scheduler.hpp" +#include "component/console.hpp" +#include "component/logfile.hpp" +#include "component/scheduler.hpp" +#include "component/gsc/script_extension.hpp" #include "game/dvars.hpp" #include "game/game.hpp" diff --git a/src/client/component/json.cpp b/src/client/component/json.cpp index 50ebe9af..3a879694 100644 --- a/src/client/component/json.cpp +++ b/src/client/component/json.cpp @@ -1,9 +1,12 @@ #include #include "loader/component_loader.hpp" -#include "scheduler.hpp" -#include "gsc.hpp" -#include "json.hpp" +#include "component/scheduler.hpp" +#include "component/gsc/script_extension.hpp" + +#include "game/scripting/array.hpp" +#include "game/scripting/execution.hpp" +#include "game/scripting/function.hpp" #include diff --git a/src/client/component/json.hpp b/src/client/component/json.hpp deleted file mode 100644 index 2c07f88a..00000000 --- a/src/client/component/json.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -namespace json -{ - std::string gsc_to_string(const scripting::script_value& _value); -} diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp index 720f404e..abce3289 100644 --- a/src/client/component/logfile.cpp +++ b/src/client/component/logfile.cpp @@ -1,10 +1,10 @@ #include #include "loader/component_loader.hpp" -#include "gsc.hpp" -#include "logfile.hpp" -#include "scripting.hpp" -#include "scheduler.hpp" +#include "component/logfile.hpp" +#include "component/scripting.hpp" +#include "component/scheduler.hpp" +#include "component/gsc/script_extension.hpp" #include "game/dvars.hpp" @@ -397,11 +397,11 @@ namespace logfile }, scheduler::pipeline::main); g_log_printf_hook.create(game::G_LogPrintf, g_log_printf_stub); - gsc::function::add("onplayersay", [](const gsc::function_args& args) -> scripting::script_value + gsc::function::add("onplayersay", [](const gsc::function_args& args) { const auto function = args[0].as(); say_callbacks.push_back(function); - return {}; + return scripting::script_value{}; }); scripting::on_shutdown([](int) diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp index 3b47eeba..165e56a9 100644 --- a/src/client/game/scripting/functions.cpp +++ b/src/client/game/scripting/functions.cpp @@ -1,8 +1,8 @@ #include #include "functions.hpp" -#include "../../component/console.hpp" -#include "../../component/gsc.hpp" +#include "component/console.hpp" +#include "component/gsc/script_extension.hpp" #include #include diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index a5e892af..155c7b5e 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1354,7 +1354,7 @@ namespace game int compressedLen; int len; int bytecodeLen; - const char* buffer; + char* buffer; char* bytecode; };