organize gsc components
This commit is contained in:
parent
6810cc54c7
commit
e94f25db54
@ -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<std::string>();
|
||||
command::execute(cmd, true);
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "game/dvars.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "console.hpp"
|
||||
#include "gsc.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
@ -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);
|
||||
|
@ -1,900 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <xsk/gsc/types.hpp>
|
||||
#include <xsk/gsc/interfaces/compiler.hpp>
|
||||
#include <xsk/gsc/interfaces/decompiler.hpp>
|
||||
#include <xsk/gsc/interfaces/assembler.hpp>
|
||||
#include <xsk/gsc/interfaces/disassembler.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <interface.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
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<std::string, unsigned int> main_handles;
|
||||
std::unordered_map<std::string, unsigned int> init_handles;
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
|
||||
std::unordered_map<unsigned int, script_function> functions;
|
||||
std::unordered_map<unsigned int, script_method> methods;
|
||||
|
||||
std::optional<std::string> 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<char*>(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<uint8_t>{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<game::ScriptFile*>(allocate_buffer(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
const auto stack = assembler->output_stack();
|
||||
script_file_ptr->len = static_cast<int>(stack.size());
|
||||
|
||||
const auto script = assembler->output_script();
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(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<std::uint8_t> 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<std::uint8_t> stack{script_file->buffer, script_file->buffer + script_file->len};
|
||||
std::vector<std::uint8_t> bytecode{script_file->bytecode, script_file->bytecode + script_file->bytecodeLen};
|
||||
|
||||
auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast<std::uint32_t>(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<void>(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<void>(SELECT_VALUE(0x1F1E00_b, 0x396080_b), rawfile, buf, size);
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> 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<std::string> 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<std::uint16_t*>(
|
||||
reinterpret_cast<size_t>(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<void>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
|
||||
return;
|
||||
}
|
||||
|
||||
console::warn("*********** script runtime error *************\n");
|
||||
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(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<void*>(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<game::scr_string_t>(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<void>(filename, thread_name, code_pos);
|
||||
}
|
||||
|
||||
function_args get_arguments()
|
||||
{
|
||||
std::vector<scripting::script_value> args;
|
||||
|
||||
for (auto i = 0; static_cast<unsigned int>(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::uint16_t>(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<std::uint16_t>(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<std::uint16_t>(id));
|
||||
methods[id] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function_args::function_args(std::vector<scripting::script_value> values)
|
||||
: values_(values)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned int function_args::size() const
|
||||
{
|
||||
return static_cast<unsigned int>(this->values_.size());
|
||||
}
|
||||
|
||||
std::vector<scripting::script_value> 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<std::uint8_t> 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<uint32_t>(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&func_table))));
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&func_table))));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BDC1E_b, 0x504C4E_b), sizeof(func_table));
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&meth_table))));
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b(&meth_table)));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table);
|
||||
utils::hook::set<uint32_t>(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<int>();
|
||||
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<int>();
|
||||
if (!expr)
|
||||
{
|
||||
const auto error = args[1].as<std::string>();
|
||||
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<std::string>();
|
||||
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<std::string>();
|
||||
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<std::string>();
|
||||
const auto function = args[1].as<std::string>();
|
||||
|
||||
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)
|
128
src/client/component/gsc/script_error.cpp
Normal file
128
src/client/component/gsc/script_error.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_error.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
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<void>(filename, thread_name, code_pos);
|
||||
}
|
||||
|
||||
std::string get_filename_name()
|
||||
{
|
||||
const auto filename_str = game::SL_ConvertToString(static_cast<game::scr_string_t>(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<std::pair<std::string, std::string>> 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)
|
7
src/client/component/gsc/script_error.hpp
Normal file
7
src/client/component/gsc/script_error.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
|
||||
}
|
471
src/client/component/gsc/script_extension.cpp
Normal file
471
src/client/component/gsc/script_extension.cpp
Normal file
@ -0,0 +1,471 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <utils/hook.hpp>
|
||||
|
||||
#include "component/command.hpp"
|
||||
#include "component/console.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
#include "component/logfile.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
|
||||
#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<std::uint32_t, script_function> functions;
|
||||
std::unordered_map<std::uint32_t, script_method> methods;
|
||||
|
||||
bool force_error_print = false;
|
||||
std::optional<std::string> gsc_error_msg;
|
||||
game::scr_entref_t saved_ent_ref;
|
||||
|
||||
function_args get_arguments()
|
||||
{
|
||||
std::vector<scripting::script_value> args;
|
||||
|
||||
for (auto i = 0; static_cast<std::uint32_t>(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<std::uint16_t*>(
|
||||
reinterpret_cast<size_t>(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<std::string> 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<void>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
console::warn("*********** script runtime error *************\n");
|
||||
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(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<void*>(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<std::uint16_t>(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<std::uint16_t>(id));
|
||||
methods[id] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function_args::function_args(std::vector<scripting::script_value> values)
|
||||
: values_(values)
|
||||
{
|
||||
}
|
||||
|
||||
std::uint32_t function_args::size() const
|
||||
{
|
||||
return static_cast<std::uint32_t>(this->values_.size());
|
||||
}
|
||||
|
||||
std::vector<scripting::script_value> 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<uint32_t>(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&func_table))));
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&func_table))));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BDC1E_b, 0x504C4E_b), sizeof(func_table));
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b((&meth_table))));
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4,
|
||||
static_cast<uint32_t>(reverse_b(&meth_table)));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table);
|
||||
utils::hook::set<uint32_t>(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<int>();
|
||||
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<int>();
|
||||
if (!expr)
|
||||
{
|
||||
const auto error = args[1].as<std::string>();
|
||||
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<std::string>();
|
||||
const auto function = args[1].as<std::string>();
|
||||
|
||||
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<std::string>();
|
||||
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<std::string>();
|
||||
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<std::string>();
|
||||
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)
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
388
src/client/component/gsc/script_loading.cpp
Normal file
388
src/client/component/gsc/script_loading.cpp
Normal file
@ -0,0 +1,388 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <xsk/gsc/types.hpp>
|
||||
#include <xsk/gsc/interfaces/compiler.hpp>
|
||||
#include <xsk/gsc/interfaces/decompiler.hpp>
|
||||
#include <xsk/gsc/interfaces/assembler.hpp>
|
||||
#include <xsk/gsc/interfaces/disassembler.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <interface.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
auto compiler = ::gsc::compiler();
|
||||
auto decompiler = ::gsc::decompiler();
|
||||
auto assembler = ::gsc::assembler();
|
||||
auto disassembler = ::gsc::disassembler();
|
||||
|
||||
std::unordered_map<std::string, std::uint32_t> main_handles;
|
||||
std::unordered_map<std::string, std::uint32_t> init_handles;
|
||||
|
||||
std::unordered_map<std::string, game::ScriptFile*> 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<char*>(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<std::uint8_t> 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<game::ScriptFile*>(allocate_buffer(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
const auto stack = assembler->output_stack();
|
||||
script_file_ptr->len = static_cast<int>(stack.size());
|
||||
|
||||
const auto script = assembler->output_script();
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(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<char*>(&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<std::uint8_t> 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<std::uint8_t> stack{script_file->buffer, script_file->buffer + script_file->len};
|
||||
std::vector<std::uint8_t> bytecode{script_file->bytecode, script_file->bytecode + script_file->bytecodeLen};
|
||||
|
||||
auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast<std::uint32_t>(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<void>(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<void>(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::uint16_t>(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<std::uint8_t> 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)
|
@ -1,10 +1,10 @@
|
||||
#include <std_include.hpp>
|
||||
#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"
|
||||
|
@ -1,9 +1,12 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <json.hpp>
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace json
|
||||
{
|
||||
std::string gsc_to_string(const scripting::script_value& _value);
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
#include <std_include.hpp>
|
||||
#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<scripting::function>();
|
||||
say_callbacks.push_back(function);
|
||||
return {};
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
scripting::on_shutdown([](int)
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include <std_include.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include "../../component/console.hpp"
|
||||
#include "../../component/gsc.hpp"
|
||||
#include "component/console.hpp"
|
||||
#include "component/gsc/script_extension.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
|
@ -1354,7 +1354,7 @@ namespace game
|
||||
int compressedLen;
|
||||
int len;
|
||||
int bytecodeLen;
|
||||
const char* buffer;
|
||||
char* buffer;
|
||||
char* bytecode;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user