#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(char* code_pos) { if (const auto function = find_function(code_pos); 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(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 { scr_emit_function_hook.create(SELECT_VALUE(0x1403D3350, 0x14042E150), &scr_emit_function_stub); utils::hook::call(SELECT_VALUE(0x1403D32E4, 0x14042E0E4), compile_error_stub); // LinkFile utils::hook::call(SELECT_VALUE(0x1403D3338, 0x14042E138), compile_error_stub); // LinkFile utils::hook::call(SELECT_VALUE(0x1403D342A, 0x14042E22B), find_variable_stub); // Scr_EmitFunction // Restore basic error messages to scr functions utils::hook::jump(game::Scr_GetObject, scr_get_object); utils::hook::jump(game::Scr_GetConstString, scr_get_const_string); utils::hook::jump(game::Scr_GetConstIString, scr_get_const_istring); utils::hook::jump(game::Scr_GetVector, scr_get_vector); utils::hook::jump(game::Scr_GetInt, scr_get_int); utils::hook::jump(game::Scr_GetFloat, scr_get_float); utils::hook::jump(SELECT_VALUE(0x1403DE150, 0x1404390B0), scr_get_pointer_type); utils::hook::jump(SELECT_VALUE(0x1403DE320, 0x140439280), scr_get_type); utils::hook::jump(SELECT_VALUE(0x1403DE390, 0x1404392F0), scr_get_type_name); } void pre_destroy() override { scr_emit_function_hook.clear(); } }; } REGISTER_COMPONENT(gsc::error)