493 lines
13 KiB
C++
493 lines
13 KiB
C++
#include <std_include.hpp>
|
|
#include <loader/module_loader.hpp>
|
|
#include "game/game.hpp"
|
|
|
|
#include "script_error.hpp"
|
|
#include "script_loading.hpp"
|
|
|
|
#include "module/log_file.hpp"
|
|
#include "module/scripting.hpp"
|
|
|
|
#include <utils/hook.hpp>
|
|
#include <utils/string.hpp>
|
|
|
|
#include <xsk/gsc/types.hpp>
|
|
#include <xsk/resolver.hpp>
|
|
|
|
using namespace utils::string;
|
|
|
|
namespace gsc
|
|
{
|
|
namespace
|
|
{
|
|
// Array count confirmed at TODO
|
|
std::array<const char*, game::native::VAR_TOTAL_COUNT> 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;
|
|
|
|
char gsc_error_msg[1024];
|
|
|
|
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::native::SL_ConvertToString(current_filename);
|
|
const auto id = std::strtol(filename_str, nullptr, 10);
|
|
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 '{}'.gsc", 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::native::Com_Error(game::native::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 = utils::hook::invoke<unsigned int>(SELECT_VALUE(0x4C4E70, 0x5651F0), parent_id, thread_name);
|
|
if (!res)
|
|
{
|
|
get_unknown_function_error(thread_name);
|
|
game::native::Com_Error(game::native::ERR_DROP, "script link error\n%s", unknown_function_error.data());
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
|
|
{
|
|
try
|
|
{
|
|
return {xsk::gsc::iw5::resolver::opcode_name(opcode)};
|
|
}
|
|
catch (...)
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
void builtin_call_error(const std::string& error)
|
|
{
|
|
const auto pos = game::native::scr_function_stack->pos;
|
|
const auto function_id = *reinterpret_cast<std::uint16_t*>(reinterpret_cast<std::size_t>(pos - 2));
|
|
|
|
if (function_id > (scr_func_max_id - 1))
|
|
{
|
|
log_file::info("in call to builtin method \"%s\"%s\n", xsk::gsc::iw5::resolver::method_name(function_id).data(), error.data());
|
|
}
|
|
else
|
|
{
|
|
log_file::info("in call to builtin function \"%s\"%s\n", xsk::gsc::iw5::resolver::function_name(function_id).data(), error.data());
|
|
}
|
|
}
|
|
|
|
void vm_error_stub(int mark_pos)
|
|
{
|
|
log_file::info("******* script runtime error ********\n");
|
|
|
|
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(SELECT_VALUE(0x1BF6928, 0x20B8E28));
|
|
const auto error = (*gsc_error_msg) ? std::format(": {}\n", gsc_error_msg) : std::string();
|
|
|
|
if ((opcode_id >= 0x84 && opcode_id <= 0x8A) || (opcode_id >= 0x8B && opcode_id <= 0x91))
|
|
{
|
|
builtin_call_error(error);
|
|
}
|
|
else
|
|
{
|
|
const auto opcode = get_opcode_name(opcode_id);
|
|
if (opcode.has_value())
|
|
{
|
|
log_file::info("while processing instruction %s%s\n", opcode.value().data(), error.data());
|
|
}
|
|
else
|
|
{
|
|
log_file::info("while processing instruction 0x%X%s\n", opcode_id, error.data());
|
|
}
|
|
}
|
|
|
|
ZeroMemory(gsc_error_msg, sizeof(gsc_error_msg));
|
|
|
|
log_file::info("************************************\n");
|
|
|
|
game::native::LargeLocalResetToMark(mark_pos);
|
|
}
|
|
|
|
void scr_fx_param_error([[maybe_unused]] int param_index, const char* error_string, int fx_id)
|
|
{
|
|
assert(error_string);
|
|
|
|
char fx_name[0x400]{};
|
|
|
|
if (fx_id)
|
|
{
|
|
const auto index = SELECT_VALUE(game::native::sp::CS_EFFECT_NAMES, game::native::mp::CS_EFFECT_NAMES);
|
|
game::native::SV_GetConfigstring(fx_id + index, fx_name, 1024);
|
|
}
|
|
else
|
|
{
|
|
strncpy_s(fx_name, "not successfully loaded", _TRUNCATE);
|
|
}
|
|
|
|
scr_error(va("%s (effect = %s)\n", error_string, fx_name));
|
|
}
|
|
|
|
void gscr_cast_int()
|
|
{
|
|
switch (scr_get_type(0))
|
|
{
|
|
case game::native::VAR_STRING:
|
|
game::native::Scr_AddInt(std::atoi(game::native::Scr_GetString(0)));
|
|
break;
|
|
case game::native::VAR_FLOAT:
|
|
game::native::Scr_AddInt(static_cast<int>(scr_get_float(0)));
|
|
break;
|
|
case game::native::VAR_INTEGER:
|
|
game::native::Scr_AddInt(scr_get_int(0));
|
|
break;
|
|
default:
|
|
scr_error(va("cannot cast %s to int", scr_get_type_name(0)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gscr_cast_float()
|
|
{
|
|
switch (scr_get_type(0))
|
|
{
|
|
case game::native::VAR_STRING:
|
|
game::native::Scr_AddFloat(static_cast<float>(std::atof(game::native::Scr_GetString(0))));
|
|
break;
|
|
case game::native::VAR_FLOAT:
|
|
game::native::Scr_AddFloat(scr_get_float(0));
|
|
break;
|
|
case game::native::VAR_INTEGER:
|
|
game::native::Scr_AddFloat(static_cast<float>(scr_get_int(0)));
|
|
break;
|
|
default:
|
|
scr_error(va("cannot cast %s to float", scr_get_type_name(0)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void assert_cmd()
|
|
{
|
|
if (!scr_get_int(0))
|
|
{
|
|
scr_error("Assert fail");
|
|
}
|
|
}
|
|
|
|
void assert_ex_cmd()
|
|
{
|
|
if (!scr_get_int(0))
|
|
{
|
|
scr_error(utils::string::va("Assert fail: %s", game::native::Scr_GetString(1)));
|
|
}
|
|
}
|
|
|
|
void assert_msg_cmd()
|
|
{
|
|
scr_error(utils::string::va("Assert fail: %s", game::native::Scr_GetString(0)));
|
|
}
|
|
}
|
|
|
|
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 {};
|
|
}
|
|
|
|
unsigned int scr_get_object(unsigned int index)
|
|
{
|
|
if (index < game::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (value->type == game::native::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::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (game::native::Scr_CastString(value))
|
|
{
|
|
assert(value->type == game::native::VAR_STRING);
|
|
return value->u.stringValue;
|
|
}
|
|
|
|
game::native::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::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (value->type == game::native::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_validate_localized_string_ref([[maybe_unused]] int parm_index, const char* token, int token_len)
|
|
{
|
|
assert(token);
|
|
assert(token_len >= 0);
|
|
|
|
if (token_len < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto char_iter = 0; char_iter < token_len; ++char_iter)
|
|
{
|
|
if (!std::isalnum(static_cast<unsigned char>(token[char_iter])) && token[char_iter] != '_')
|
|
{
|
|
scr_error(va("Illegal localized string reference: %s must contain only alpha-numeric characters and underscores", token));
|
|
}
|
|
}
|
|
}
|
|
|
|
void scr_get_vector(unsigned int index, float* vector_value)
|
|
{
|
|
if (index < game::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (value->type == game::native::VAR_VECTOR)
|
|
{
|
|
std::memcpy(vector_value, value->u.vectorValue, sizeof(game::native::vec3_t));
|
|
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::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (value->type == game::native::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::native::scr_VmPub->outparamcount)
|
|
{
|
|
auto* value = game::native::scr_VmPub->top - index;
|
|
if (value->type == game::native::VAR_FLOAT)
|
|
{
|
|
return value->u.floatValue;
|
|
}
|
|
|
|
if (value->type == game::native::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::native::scr_VmPub->outparamcount)
|
|
{
|
|
if ((game::native::scr_VmPub->top - index)->type == game::native::VAR_POINTER)
|
|
{
|
|
return static_cast<int>(game::native::GetObjectType((game::native::scr_VmPub->top - index)->u.pointerValue));
|
|
}
|
|
|
|
scr_error(va("Type %s is not an object", var_typename[(game::native::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::native::scr_VmPub->outparamcount)
|
|
{
|
|
return (game::native::scr_VmPub->top - index)->type;
|
|
}
|
|
|
|
scr_error(va("Parameter %u does not exist", index + 1));
|
|
return 0;
|
|
}
|
|
|
|
void scr_error(const char* error)
|
|
{
|
|
strncpy_s(gsc_error_msg, error, _TRUNCATE);
|
|
game::native::Scr_ErrorInternal();
|
|
}
|
|
|
|
const char* scr_get_type_name(unsigned int index)
|
|
{
|
|
if (index < game::native::scr_VmPub->outparamcount)
|
|
{
|
|
return var_typename[(game::native::scr_VmPub->top - index)->type];
|
|
}
|
|
|
|
scr_error(va("Parameter %u does not exist", index + 1));
|
|
return nullptr;
|
|
}
|
|
|
|
class error final : public module
|
|
{
|
|
public:
|
|
void post_load() override
|
|
{
|
|
scr_emit_function_hook.create(SELECT_VALUE(0x40DCB0, 0x561400), &scr_emit_function_stub);
|
|
|
|
utils::hook(SELECT_VALUE(0x60DABA, 0x5615FA), &compile_error_stub, HOOK_CALL).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x60DAD1, 0x561611), &compile_error_stub, HOOK_CALL).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x40DCFA, 0x56144A), &find_variable_stub, HOOK_CALL).install()->quick();
|
|
|
|
utils::hook(SELECT_VALUE(0x612BFC, 0x56D87D), &vm_error_stub, HOOK_CALL).install()->quick(); // LargeLocalResetToMark
|
|
|
|
// Restore basic error messages for commonly used scr functions
|
|
utils::hook(SELECT_VALUE(0x52F730, 0x56A630), &scr_get_object, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x40FDE0, 0x56A200), &scr_get_const_string, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x4FD700, 0x56A420), &scr_get_const_istring, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x536FAC, 0x523FAE), &scr_validate_localized_string_ref, HOOK_CALL).install()->quick(); // Scr_ConstructMessageString
|
|
utils::hook(SELECT_VALUE(0x452E90, 0x56A4D0), &scr_get_vector, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x51B520, 0x56A010), &scr_get_int, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x4D8B50, 0x56A190), &scr_get_float, HOOK_JUMP).install()->quick();
|
|
|
|
utils::hook(SELECT_VALUE(0x4C02D0, 0x51F230), &scr_fx_param_error, HOOK_JUMP).install()->quick();
|
|
|
|
utils::hook(SELECT_VALUE(0x4D6510, 0x56A980), &scr_get_pointer_type, HOOK_JUMP).install()->quick();
|
|
utils::hook(SELECT_VALUE(0x4958D0, 0x56A8C0), &scr_get_type, HOOK_JUMP).install()->quick();
|
|
|
|
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92BB58, 0x8AC040), gscr_cast_int);
|
|
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92BB64, 0x8AC04C), gscr_cast_float);
|
|
|
|
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92B93C, 0x8ABE24), assert_cmd);
|
|
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92B948, 0x8ABE30), assert_ex_cmd);
|
|
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92B954, 0x8ABE3C), assert_msg_cmd);
|
|
}
|
|
|
|
void pre_destroy() override
|
|
{
|
|
scr_emit_function_hook.clear();
|
|
}
|
|
};
|
|
}
|
|
|
|
REGISTER_MODULE(gsc::error)
|