scripting changes

This commit is contained in:
quaK 2024-05-30 00:59:44 +03:00
parent 74ea8fb55f
commit 9197b8123f
5 changed files with 7152 additions and 146 deletions

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,19 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "game/game.hpp" #include "game/game.hpp"
#include "script_extension.hpp"
#include "script_error.hpp" #include "script_error.hpp"
#include "script_extension.hpp"
#include "script_loading.hpp"
#include "component/scripting.hpp" #include "component/scripting.hpp"
#include "component/console/console.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include "error_info_map.hpp"
using namespace utils::string; using namespace utils::string;
namespace gsc namespace gsc
@ -23,6 +28,9 @@ namespace gsc
std::string unknown_function_error; std::string unknown_function_error;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
std::array<const char*, 27> var_typename = std::array<const char*, 27> var_typename =
{ {
"undefined", "undefined",
@ -281,6 +289,128 @@ namespace gsc
} }
} }
namespace
{
utils::hook::detour scr_error_internal_hook;
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 builtin_call_error(const std::string& error)
{
const auto function_id = get_function_id();
if (function_id > func_table_count)
{
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());
}
}
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_internal()
{
const bool dev_script = developer_script ? developer_script->current.enabled : false;
if (!dev_script && !force_error_print)
{
return;
}
console::warn("*********** script runtime error *************\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x6B22940_b);
const std::string error_str = gsc_error_msg.has_value()
? utils::string::va(": %s", gsc_error_msg.value().data())
: "";
if ((opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin))
|| (opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod)))
{
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");
}
void vm_error_stub(__int64 mark_pos)
{
vm_error_internal();
utils::hook::invoke<void>(0x510C80_b, mark_pos);
}
void scr_error_internal_stub()
{
const auto ret_addr = reinterpret_cast<std::uint64_t>(_ReturnAddress());
if (!gsc_error_msg.has_value())
{
const auto it = scr_error_info_map.find(ret_addr - 5);
if (it != scr_error_info_map.end() && !it->second.empty())
{
gsc_error_msg = it->second;
}
}
scr_error_internal_hook.invoke<void>();
}
}
void scr_error(const char* error, const bool force_print)
{
force_error_print = force_print;
gsc_error_msg = error;
game::Scr_ErrorInternal();
}
std::optional<std::pair<std::string, std::string>> find_function(const char* pos) std::optional<std::pair<std::string, std::string>> find_function(const char* pos)
{ {
for (const auto& file : scripting::script_function_table_sort) for (const auto& file : scripting::script_function_table_sort)
@ -305,6 +435,10 @@ namespace gsc
{ {
scr_emit_function_hook.create(0xBFCF90_b, &scr_emit_function_stub); scr_emit_function_hook.create(0xBFCF90_b, &scr_emit_function_stub);
scr_error_internal_hook.create(game::Scr_ErrorInternal, scr_error_internal_stub);
utils::hook::call(0xC0F8C1_b, vm_error_stub); // LargeLocalResetToMark
utils::hook::call(0xBFCF3A_b, compile_error_stub); // CompileError (LinkFile) utils::hook::call(0xBFCF3A_b, compile_error_stub); // CompileError (LinkFile)
utils::hook::call(0xBFCF86_b, compile_error_stub); // ^ utils::hook::call(0xBFCF86_b, compile_error_stub); // ^
utils::hook::call(0xBFD06F_b, find_variable_stub); // Scr_EmitFunction utils::hook::call(0xBFD06F_b, find_variable_stub); // Scr_EmitFunction

View File

@ -2,5 +2,7 @@
namespace gsc namespace gsc
{ {
void scr_error(const char* error, const bool force_print = false);
std::optional<std::pair<std::string, std::string>> find_function(const char* pos); std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
} }

View File

@ -19,9 +19,6 @@ namespace gsc
std::uint16_t function_id_start = 806; std::uint16_t function_id_start = 806;
std::uint16_t method_id_start = 1483 + 0x8000; std::uint16_t method_id_start = 1483 + 0x8000;
constexpr size_t func_table_count = 0x1000;
constexpr size_t meth_table_count = 0x1000;
builtin_function func_table[func_table_count]{}; builtin_function func_table[func_table_count]{};
builtin_method meth_table[meth_table_count]{}; builtin_method meth_table[meth_table_count]{};
@ -32,9 +29,6 @@ namespace gsc
std::unordered_map<std::uint16_t, script_function> functions; std::unordered_map<std::uint16_t, script_function> functions;
std::unordered_map<std::uint16_t, script_method> methods; std::unordered_map<std::uint16_t, script_method> methods;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
std::unordered_map<const char*, const char*> vm_execute_hooks; std::unordered_map<const char*, const char*> vm_execute_hooks;
const char* target_function = nullptr; const char* target_function = nullptr;
@ -61,16 +55,6 @@ namespace gsc
scripting::push_value(value); 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 nullstub_func() {}
void nullstub_meth(game::scr_entref_t) {}
void execute_custom_function(const std::uint16_t id) void execute_custom_function(const std::uint16_t id)
{ {
try try
@ -130,102 +114,39 @@ namespace gsc
} }
} }
void builtin_call_error(const std::string& error) void vm_call_builtin_method_internal(game::scr_entref_t ent_ref, int function_id)
{ {
const auto function_id = get_function_id(); const auto custom_function_id = static_cast<std::uint16_t>(function_id); // cast for gsc-tool & our custom method map
const auto custom = methods.contains(custom_function_id);
if (function_id > func_table_count) if (custom)
{
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_internal()
{
const bool dev_script = developer_script ? developer_script->current.enabled : false;
if (!dev_script && !force_error_print)
{ {
execute_custom_method(custom_function_id, ent_ref);
return; return;
} }
console::warn("*********** script runtime error *************\n"); builtin_method meth = meth_table[function_id - 0x8000];
if (meth == nullptr)
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x6B22940_b);
const std::string error_str = gsc_error_msg.has_value()
? utils::string::va(": %s", gsc_error_msg.value().data())
: "";
if ((opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin))
|| (opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod)))
{ {
builtin_call_error(error_str); scr_error(utils::string::va("builtin method \"%s\" doesn't exist", gsc_ctx->meth_name(custom_function_id).data()), true);
} return;
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; meth(ent_ref);
gsc_error_msg = {};
print_callstack();
console::warn("**********************************************\n");
} }
void vm_error_stub(__int64 mark_pos) void vm_call_builtin_method_stub(utils::hook::assembler& a)
{ {
vm_error_internal(); //a.pushad64();
a.push(rdx);
a.push(ecx);
a.mov(rdx, rdi); // function id is stored in rdi
a.mov(ecx, ebx); // ent ref is stored in ebx
a.call(vm_call_builtin_method_internal);
a.pop(rdx);
a.pop(ecx);
//a.popad64();
if (!game::CL_IsGameClientActive(0)) a.jmp(0xC0E8F9_b);
{
//return game::Com_Error(game::errorParm::ERR_SCRIPT_DROP, gsc_error_msg.has_value() ? gsc_error_msg.value().data() : "Fatal script error");
}
utils::hook::invoke<void>(0x510C80_b, mark_pos);
} }
void print(const function_args& args) void print(const function_args& args)
@ -289,14 +210,6 @@ namespace gsc
} }
} }
void scr_error(const char* error, const bool force_print)
{
force_error_print = force_print;
gsc_error_msg = error;
game::Scr_ErrorInternal();
}
namespace function namespace function
{ {
void add(const std::string& name, script_function function) void add(const std::string& name, script_function function)
@ -358,39 +271,11 @@ namespace gsc
return {this->values_[index], index}; return {this->values_[index], index};
} }
void vm_call_builtin_method_internal(game::scr_entref_t ent_ref, int function_id) std::uint16_t get_function_id()
{ {
const auto custom_function_id = static_cast<std::uint16_t>(function_id); // cast for gsc-tool & our custom method map const auto pos = game::scr_function_stack->pos;
const auto custom = methods.contains(custom_function_id); return *reinterpret_cast<std::uint16_t*>(
if (custom) reinterpret_cast<size_t>(pos - 2));
{
execute_custom_method(custom_function_id, ent_ref);
return;
}
builtin_method meth = meth_table[function_id - 0x8000];
if (meth == nullptr)
{
scr_error(utils::string::va("builtin method \"%s\" doesn't exist", gsc_ctx->meth_name(custom_function_id).data()), true);
return;
}
meth(ent_ref);
}
void vm_call_builtin_method_stub(utils::hook::assembler& a)
{
//a.pushad64();
a.push(rdx);
a.push(ecx);
a.mov(rdx, rdi); // function id is stored in rdi
a.mov(ecx, ebx); // ent ref is stored in ebx
a.call(vm_call_builtin_method_internal);
a.pop(rdx);
a.pop(ecx);
//a.popad64();
a.jmp(0xC0E8F9_b);
} }
class extension final : public component_interface class extension final : public component_interface
@ -413,8 +298,6 @@ namespace gsc
utils::hook::nop(0xC0E8EB_b, 14); // nop the lea & call at the end of call_builtin_method utils::hook::nop(0xC0E8EB_b, 14); // nop the lea & call at the end of call_builtin_method
utils::hook::jump(0xC0E8EB_b, utils::hook::assemble(vm_call_builtin_method_stub), true); utils::hook::jump(0xC0E8EB_b, utils::hook::assemble(vm_call_builtin_method_stub), true);
utils::hook::call(0xC0F8C1_b, vm_error_stub); // LargeLocalResetToMark
utils::hook::jump(0xC0D0A4_b, utils::hook::assemble(vm_execute_stub), true); utils::hook::jump(0xC0D0A4_b, utils::hook::assemble(vm_execute_stub), true);
/* /*

View File

@ -29,12 +29,15 @@ namespace gsc
using script_function = std::function<scripting::script_value(const function_args&)>; using script_function = std::function<scripting::script_value(const function_args&)>;
using script_method = std::function<scripting::script_value(const game::scr_entref_t, const function_args&)>; using script_method = std::function<scripting::script_value(const game::scr_entref_t, const function_args&)>;
extern builtin_function func_table[0x1000]; constexpr size_t func_table_count = 0x1000;
extern builtin_method meth_table[0x1000]; constexpr size_t meth_table_count = 0x1000;
extern builtin_function func_table[func_table_count];
extern builtin_method meth_table[meth_table_count];
extern const game::dvar_t* developer_script; extern const game::dvar_t* developer_script;
void scr_error(const char* error, const bool force_print = false); std::uint16_t get_function_id();
namespace function namespace function
{ {