This commit is contained in:
2024-01-24 10:45:25 +01:00
commit bcdbe48523
267 changed files with 39510 additions and 0 deletions
+319
View File
@@ -0,0 +1,319 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "script_extension.hpp"
#include "script_error.hpp"
#include "component/scripting.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
using namespace utils::string;
namespace gsc
{
namespace
{
std::array<const char*, 27> var_typename =
{
"undefined",
"object",
"string",
"localized string",
"vector",
"float",
"int",
"codepos",
"precodepos",
"function",
"builtin function",
"builtin method",
"stack",
"animation",
"pre animation",
"thread",
"thread",
"thread",
"thread",
"struct",
"removed entity",
"entity",
"array",
"removed thread",
"<free>",
"thread list",
"endon list",
};
utils::hook::detour scr_emit_function_hook;
unsigned int current_filename = 0;
std::string unknown_function_error;
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);
}
std::string get_filename_name()
{
const auto filename_str = game::SL_ConvertToString(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(unsigned int 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 compile_error_stub(const char* code_pos, [[maybe_unused]] const char* msg)
{
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;
}
}
unsigned int scr_get_object(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_POINTER)
{
return value->u.pointerValue;
}
scr_error(va("Type %s is not an object", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
unsigned int scr_get_const_string(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (game::Scr_CastString(value))
{
assert(value->type == game::VAR_STRING);
return value->u.stringValue;
}
game::Scr_ErrorInternal();
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
unsigned int scr_get_const_istring(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_ISTRING)
{
return value->u.stringValue;
}
scr_error(va("Type %s is not a localized string", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
void scr_get_vector(unsigned int index, float* vector_value)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_VECTOR)
{
std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
return;
}
scr_error(va("Type %s is not a vector", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
}
int scr_get_int(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_INTEGER)
{
return value->u.intValue;
}
scr_error(va("Type %s is not an int", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
float scr_get_float(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
auto* value = game::scr_VmPub->top - index;
if (value->type == game::VAR_FLOAT)
{
return value->u.floatValue;
}
if (value->type == game::VAR_INTEGER)
{
return static_cast<float>(value->u.intValue);
}
scr_error(va("Type %s is not a float", var_typename[value->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0.0f;
}
int scr_get_pointer_type(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
{
return game::GetObjectType((game::scr_VmPub->top - index)->u.intValue);
}
scr_error(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
int scr_get_type(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
return (game::scr_VmPub->top - index)->type;
}
scr_error(va("Parameter %u does not exist", index + 1));
return 0;
}
const char* scr_get_type_name(unsigned int index)
{
if (index < game::scr_VmPub->outparamcount)
{
return var_typename[(game::scr_VmPub->top - index)->type];
}
scr_error(va("Parameter %u does not exist", index + 1));
return nullptr;
}
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(0x14042E150, &scr_emit_function_stub);
utils::hook::call(0x14042E0E4, compile_error_stub); // LinkFile
utils::hook::call(0x14042E138, compile_error_stub); // LinkFile
utils::hook::call(0x14042E22B, find_variable_stub); // Scr_EmitFunction
// Restore basic error messages to scr functions
utils::hook::jump(0x140438ED0, scr_get_object);
utils::hook::jump(0x140438AD0, scr_get_const_string);
utils::hook::jump(0x1404388B0, scr_get_const_istring);
utils::hook::jump(0x1404393D0, scr_get_vector);
utils::hook::jump(0x140438E10, scr_get_int);
utils::hook::jump(0x140438D60, scr_get_float);
utils::hook::jump(0x1404390B0, scr_get_pointer_type);
utils::hook::jump(0x140439280, scr_get_type);
utils::hook::jump(0x1404392F0, scr_get_type_name);
}
void pre_destroy() override
{
scr_emit_function_hook.clear();
}
};
}
REGISTER_COMPONENT(gsc::error)
+16
View File
@@ -0,0 +1,16 @@
#pragma once
namespace gsc
{
unsigned int scr_get_object(unsigned int index);
unsigned int scr_get_const_string(unsigned int index);
unsigned int scr_get_const_istring(unsigned int index);
void scr_get_vector(unsigned int index, float* vector_value);
int scr_get_int(unsigned int index);
float scr_get_float(unsigned int index);
int scr_get_pointer_type(unsigned int index);
int scr_get_type(unsigned int index);
const char* scr_get_type_name(unsigned int index);
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
}
@@ -0,0 +1,324 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/scripting/functions.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "component/console.hpp"
#include "component/scripting.hpp"
#include "component/notifies.hpp"
#include "component/command.hpp"
#include "script_extension.hpp"
#include "script_error.hpp"
#include "script_loading.hpp"
namespace gsc
{
std::uint16_t function_id_start = 0x25D;
std::uint16_t method_id_start = 0x8429;
void* func_table[0x1000];
const game::dvar_t* developer_script = nullptr;
namespace
{
#define RVA(ptr) static_cast<std::uint32_t>(reinterpret_cast<std::size_t>(ptr) - 0x140000000)
struct gsc_error : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
std::unordered_map<std::uint16_t, game::BuiltinFunction> functions;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
unsigned int scr_get_function_stub(const char** p_name, int* type)
{
const auto result = utils::hook::invoke<unsigned int>(0x1403CD9F0, p_name, type);
for (const auto& [name, func] : functions)
{
game::Scr_RegisterFunction(func, 0, name);
}
return result;
}
void execute_custom_function(game::BuiltinFunction function)
{
auto error = false;
try
{
function();
}
catch (const std::exception& ex)
{
error = true;
force_error_print = true;
gsc_error_msg = ex.what();
}
if (error)
{
game::Scr_ErrorInternal();
}
}
void vm_call_builtin_function(const std::uint32_t index)
{
const auto func = reinterpret_cast<game::BuiltinFunction>(scripting::get_function_by_index(index));
const auto custom = functions.contains(static_cast<std::uint16_t>(index));
if (!custom)
{
func();
}
else
{
execute_custom_function(func);
}
}
void builtin_call_error(const std::string& error)
{
const auto pos = game::scr_function_stack->pos;
const auto function_id = *reinterpret_cast<std::uint16_t*>(reinterpret_cast<std::size_t>(pos - 2));
if (function_id > 0x1000)
{
console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
}
else
{
console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
}
}
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
{
try
{
const auto index = gsc_ctx->opcode_enum(opcode);
return {gsc_ctx->opcode_name(index)};
}
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>(0x1404E4D00, mark_pos);
return;
}
console::warn("******* script runtime error ********\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x144D57840);
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA8 && opcode_id <= 0xAE))
{
builtin_call_error(error);
}
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.data());
}
else
{
console::warn("while processing instruction 0x%X%s\n", opcode_id, error.data());
}
}
force_error_print = false;
gsc_error_msg = {};
print_callstack();
console::warn("************************************\n");
utils::hook::invoke<void>(0x1404E4D00, mark_pos);
}
void inc_in_param()
{
game::Scr_ClearOutParams();
if (game::scr_VmPub->top == game::scr_VmPub->maxstack)
{
game::Sys_Error("Internal script stack overflow");
}
++game::scr_VmPub->top;
++game::scr_VmPub->inparamcount;
}
void add_code_pos(const char* pos)
{
inc_in_param();
game::scr_VmPub->top->type = game::VAR_FUNCTION;
game::scr_VmPub->top->u.codePosValue = pos;
}
void scr_print()
{
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
{
console::info("%s", game::Scr_GetString(i));
}
}
void scr_print_ln()
{
for (auto i = 0u; i < game::Scr_GetNumParam(); ++i)
{
console::info("%s", game::Scr_GetString(i));
}
console::info("\n");
}
void assert_cmd()
{
if (!game::Scr_GetInt(0))
{
scr_error("Assert fail");
}
}
void assert_ex_cmd()
{
if (!game::Scr_GetInt(0))
{
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(1)));
}
}
void assert_msg_cmd()
{
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0)));
}
const char* get_code_pos(const int index)
{
if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount)
{
scr_error("Scr_GetCodePos: index is out of range");
return "";
}
const auto* value = &game::scr_VmPub->top[-index];
if (value->type != game::VAR_FUNCTION)
{
scr_error("Scr_GetCodePos requires a function as parameter");
return "";
}
return value->u.codePosValue;
}
}
void add_function(const std::string& name, game::BuiltinFunction function)
{
++function_id_start;
functions[function_id_start] = function;
gsc_ctx->func_add(name, function_id_start);
}
void scr_error(const char* error)
{
force_error_print = true;
gsc_error_msg = error;
game::Scr_ErrorInternal();
}
class extension final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print);
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln);
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D353C, 0x14042E33C), 0x1000); // Scr_RegisterFunction
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D3542 + 4, 0x14042E342 + 4), RVA(&func_table)); // Scr_RegisterFunction
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0
utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts
if (game::environment::is_sp())
{
return;
}
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
utils::hook::nop(0x14043BBBE + 5, 2);
utils::hook::call(0x14043BBBE, vm_call_builtin_function);
utils::hook::call(0x14043CEB1, vm_error_stub);
utils::hook::call(0x14042E76F, scr_get_function_stub);
utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd);
utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd);
add_function("replacefunc", []
{
if (scr_get_type(0) != game::VAR_FUNCTION || scr_get_type(1) != game::VAR_FUNCTION)
{
throw gsc_error("Parameter must be a function");
}
notifies::set_gsc_hook(get_code_pos(0), get_code_pos(1));
});
add_function("executecommand", []
{
const auto* cmd = game::Scr_GetString(0);
command::execute(cmd);
});
add_function("isdedicated", []
{
game::Scr_AddInt(game::environment::is_dedi());
});
}
};
}
REGISTER_COMPONENT(gsc::extension)
@@ -0,0 +1,12 @@
#pragma once
namespace gsc
{
extern void* func_table[0x1000];
extern const game::dvar_t* developer_script;
void add_function(const std::string& name, game::BuiltinFunction function);
void scr_error(const char* error);
}
+369
View File
@@ -0,0 +1,369 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/io.hpp>
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <utils/compression.hpp>
#include "component/filesystem.hpp"
#include "component/console.hpp"
#include "component/scripting.hpp"
#include "component/fastfiles.hpp"
#include "script_loading.hpp"
namespace gsc
{
std::unique_ptr<xsk::gsc::iw6_pc::context> gsc_ctx;
namespace
{
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;
utils::memory::allocator script_allocator;
const game::dvar_t* developer_script;
void clear()
{
main_handles.clear();
init_handles.clear();
loaded_scripts.clear();
script_allocator.clear();
}
bool read_raw_script_file(const std::string& name, std::string* data)
{
if (filesystem::read_file(name, data))
{
return true;
}
// This will prevent 'fake' GSC raw files from being compiled.
// They are parsed by the game's own parser later as they are special files.
if (name.starts_with("maps/createfx") || name.starts_with("maps/createart") ||
(name.starts_with("maps/mp") && name.ends_with("_fx.gsc")))
{
return false;
}
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.data(), 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 (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end())
{
return itr->second;
}
try
{
auto& compiler = gsc_ctx->compiler();
auto& assembler = gsc_ctx->assembler();
std::string source_buffer{};
if (!read_raw_script_file(real_name + ".gsc", &source_buffer))
{
return nullptr;
}
console::info("Compiling script '%s'\n", real_name.data());
std::vector<std::uint8_t> data;
data.assign(source_buffer.begin(), source_buffer.end());
const auto assembly_ptr = compiler.compile(real_name, data);
// Pair of two buffers. First is the byte code and second is the stack
const auto output_script = assembler.assemble(*assembly_ptr);
const auto script_file_ptr = static_cast<game::ScriptFile*>(script_allocator.allocate(sizeof(game::ScriptFile)));
script_file_ptr->name = file_name;
script_file_ptr->bytecodeLen = static_cast<int>(std::get<0>(output_script).size);
script_file_ptr->len = static_cast<int>(std::get<1>(output_script).size);
const auto byte_code_size = static_cast<std::uint32_t>(std::get<0>(output_script).size + 1);
const auto stack_size = static_cast<std::uint32_t>(std::get<1>(output_script).size + 1);
script_file_ptr->buffer = static_cast<char*>(script_allocator.allocate(stack_size));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), std::get<1>(output_script).data, std::get<1>(output_script).size);
script_file_ptr->bytecode = static_cast<std::uint8_t*>(game::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 1, game::PMEM_SOURCE_SCRIPT));
std::memcpy(script_file_ptr->bytecode, std::get<0>(output_script).data, std::get<0>(output_script).size);
script_file_ptr->compressedLen = 0;
loaded_scripts[real_name] = script_file_ptr;
return script_file_ptr;
}
catch (const std::exception& ex)
{
console::error("*********** script compile error *************\n");
console::error("failed to compile '%s':\n%s", real_name.data(), ex.what());
console::error("**********************************************\n");
return nullptr;
}
}
std::string get_script_file_name(const std::string& name)
{
const auto id = gsc_ctx->token_id(name);
if (!id)
{
return name;
}
return std::to_string(id);
}
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> read_compiled_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());
const auto len = script_file->compressedLen;
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(len)};
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
std::vector<std::uint8_t> stack_data;
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
return {{script_file->bytecode, static_cast<std::uint32_t>(script_file->bytecodeLen)}, stack_data};
}
void load_script(const std::string& name)
{
if (!game::Scr_LoadScript(name.data()))
{
return;
}
const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->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 = root_dir / "scripts";
if (!utils::io::directory_exists(script_dir.generic_string()))
{
return;
}
const auto scripts = utils::io::list_files(script_dir.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_game_type_script_stub()
{
utils::hook::invoke<void>(0x1403CCB10);
clear();
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path);
}
}
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;
}
game::DB_GetRawBuffer(rawfile, buf, size);
}
void g_load_structs_stub()
{
for (auto& function_handle : main_handles)
{
console::info("Executing '%s::main'\n", function_handle.first.data());
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
game::RemoveRefToObject(thread);
}
utils::hook::invoke<void>(0x1403D2CA0);
}
void scr_load_level_stub()
{
utils::hook::invoke<void>(0x1403CDC60);
for (auto& function_handle : init_handles)
{
console::info("Executing '%s::init'\n", function_handle.first.data());
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
game::RemoveRefToObject(thread);
}
}
void scr_begin_load_scripts_stub()
{
const auto comp_mode = developer_script->current.enabled ?
xsk::gsc::build::dev :
xsk::gsc::build::prod;
gsc_ctx->init(comp_mode, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
{
const auto script_name = std::filesystem::path(included_path).replace_extension().string();
std::string file_buffer;
if (!read_raw_script_file(included_path, &file_buffer) || file_buffer.empty())
{
const auto name = get_script_file_name(script_name);
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
{
return read_compiled_script_file(name, script_name);
}
throw std::runtime_error(std::format("Could not load gsc file '{}'", script_name));
}
std::vector<std::uint8_t> script_data;
script_data.assign(file_buffer.begin(), file_buffer.end());
return {{}, script_data};
});
utils::hook::invoke<void>(SELECT_VALUE(0x1403D3860, 0x14042E6B0));
}
void scr_end_load_scripts_stub()
{
gsc_ctx->cleanup();
utils::hook::invoke<void>(SELECT_VALUE(0x140603C00, 0x14042E7E0));
}
}
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::strtol(name, nullptr, 10));
if (id)
{
real_name = gsc_ctx->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:
loading()
{
gsc_ctx = std::make_unique<xsk::gsc::iw6_pc::context>();
}
void post_unpack() override
{
// Load our scripts with an uncompressed stack
utils::hook::call(SELECT_VALUE(0x1403DC8F0, 0x140437940), db_get_raw_buffer_stub);
utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts
utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts
developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
if (game::environment::is_sp())
{
return;
}
// ProcessScript
utils::hook::call(0x1404378D7, find_script);
utils::hook::call(0x1404378E7, db_is_x_asset_default);
// GScr_LoadScripts
utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub);
// Exec script handles
utils::hook::call(0x14039F64E, g_load_structs_stub);
utils::hook::call(0x14039F653, scr_load_level_stub);
scripting::on_shutdown([](int free_scripts)
{
if (free_scripts)
{
clear();
}
});
}
};
}
REGISTER_COMPONENT(gsc::loading)
@@ -0,0 +1,9 @@
#pragma once
#include <xsk/gsc/engine/iw6_pc.hpp>
namespace gsc
{
extern std::unique_ptr<xsk::gsc::iw6_pc::context> gsc_ctx;
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
}