From 089fd82be97209b69fa7f129b846dbf1860989d8 Mon Sep 17 00:00:00 2001 From: quaK Date: Sat, 29 Jun 2024 00:19:02 +0300 Subject: [PATCH] update gsc --- src/client/component/gsc/error_info_map.hpp | 26 +++---- src/client/component/gsc/script_error.cpp | 71 +++++++++++++++++-- src/client/component/gsc/script_extension.cpp | 29 +++++--- src/client/component/gsc/script_loading.cpp | 54 +++++++++++--- src/client/component/gsc/script_loading.hpp | 9 +++ 5 files changed, 151 insertions(+), 38 deletions(-) diff --git a/src/client/component/gsc/error_info_map.hpp b/src/client/component/gsc/error_info_map.hpp index 0c074d25..ff8f4dfd 100644 --- a/src/client/component/gsc/error_info_map.hpp +++ b/src/client/component/gsc/error_info_map.hpp @@ -16,24 +16,24 @@ namespace gsc //sub_1400B2BE0 {0x1400b2ced,""}, //sub_1400F4F90 - {0x1400f5142,""}, - {0x1400f5246,""}, - {0x1400f52b2,""}, - {0x1400f53e2,""}, - {0x1400f544f,""}, + {0x1400f5142,"Invalid match data definition specified."}, + {0x1400f5246,"Invalid match data definition specified."}, + {0x1400f52b2,"Invalid match data definition specified."}, + {0x1400f53e2,"Invalid match data definition specified."}, + {0x1400f544f,"Invalid match data definition specified."}, //sub_1400F5AC0 - {0x1400f5c4e,""}, - {0x1400f5cbb,""}, + {0x1400f5c4e,"Invalid match data definition specified."}, + {0x1400f5cbb,"Invalid match data definition specified."}, //sub_1400F5EF0 - {0x1400f60ca,""}, - {0x1400f6139,""}, + {0x1400f60ca,"Invalid match data definition specified."}, + {0x1400f6139,"Invalid match data definition specified."}, //sub_1400F6440 - {0x1400f64b7,""}, + {0x1400f64b7,"Invalid match data definition specified. lifeCount must be a short"}, //sub_1400F6540 - {0x1400f65e6,""}, - {0x1400f660e,""}, + {0x1400f65e6,"Invalid match data definition specified. The lives field must be an indexed array of life structures"}, + {0x1400f660e,"Invalid match data definition specified. The players field must be an indexed array of player structures"}, //sub_140117AB0 - {0x140117b38,""}, + {0x140117b38,"ERROR: G_RagdollConstraintEntity_Spawn(), Will not spawn."}, //sub_1403D9B70 {0x1403d9bac,""}, //sub_1403D9D80 diff --git a/src/client/component/gsc/script_error.cpp b/src/client/component/gsc/script_error.cpp index 382b1d17..86715329 100644 --- a/src/client/component/gsc/script_error.cpp +++ b/src/client/component/gsc/script_error.cpp @@ -306,6 +306,19 @@ namespace gsc } } + std::uint32_t get_opcode_size(const std::uint8_t opcode) + { + try + { + const auto index = gsc_ctx->opcode_enum(opcode); + return gsc_ctx->opcode_size(index); + } + catch (...) + { + return 0; + } + } + void builtin_call_error(const std::string& error) { const auto function_id = get_function_id(); @@ -320,21 +333,62 @@ namespace gsc } } - void print_callstack() + void print_callstack(uint8_t opcode_id) { + bool first = true; + 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_stack = game::scr_function_stack; + const auto pos = frame == game::scr_VmPub->function_frame ? 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()); + auto& file_name = function.value().second; + + if (gsc::source_pos_map.contains(file_name)) + { + const auto script = gsc::loaded_scripts[file_name]; + assert(script); + + const auto& pos_map = gsc::source_pos_map[file_name]; + std::uint32_t opcode_size = 0; + if (first) + { + opcode_size = get_opcode_size(opcode_id); + } + if (!opcode_size) + { + opcode_size = 3; // function call + } + auto position = static_cast(pos - script->bytecode); + position = position + opcode_size; + + if (pos_map.contains(position)) + { + const auto& info = pos_map.at(position); + + console::warn("\tat function \"%s\" in file \"%s.gsc\" (line %d, col %d)\n", + function.value().first.data(), file_name.data(), info.line, info.column); + } + else + { + goto NO_DEVMAP; + } + } + else + { + NO_DEVMAP: + console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), file_name.data()); + } } else { console::warn("\tat unknown location %p\n", pos); } + + first = false; } } @@ -375,13 +429,20 @@ namespace gsc force_error_print = false; gsc_error_msg = {}; - print_callstack(); + print_callstack(opcode_id); console::warn("**********************************************\n"); } void vm_error_stub(__int64 mark_pos) { - vm_error_internal(); + try + { + vm_error_internal(); + } + catch (xsk::gsc::error& err) + { + console::error("vm_error: %s\n", err.what()); + } utils::hook::invoke(0x510C80_b, mark_pos); } diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp index 99e71140..02080ae8 100644 --- a/src/client/component/gsc/script_extension.cpp +++ b/src/client/component/gsc/script_extension.cpp @@ -114,9 +114,10 @@ namespace gsc } } - void vm_call_builtin_method_internal(game::scr_entref_t ent_ref, int function_id) + int meth_function_id = 0; + void vm_call_builtin_method_internal(game::scr_entref_t ent_ref) { - const auto custom_function_id = static_cast(function_id); // cast for gsc-tool & our custom method map + const auto custom_function_id = static_cast(meth_function_id); // cast for gsc-tool & our custom method map const auto custom = methods.contains(custom_function_id); if (custom) { @@ -124,7 +125,7 @@ namespace gsc return; } - builtin_method meth = meth_table[function_id - 0x8000]; + builtin_method meth = meth_table[meth_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); @@ -134,19 +135,24 @@ namespace gsc meth(ent_ref); } + void save_meth_func_id([[maybe_unused]] game::scr_entref_t ent_ref, int function_id) + { + meth_function_id = function_id; + } + void vm_call_builtin_method_stub(utils::hook::assembler& a) { - //a.pushad64(); + 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.call_aligned(save_meth_func_id); a.pop(rdx); a.pop(ecx); - //a.popad64(); + a.popad64(); - a.jmp(0xC0E8F9_b); + a.jmp(0xC0E8F2_b); } void print(const function_args& args) @@ -283,7 +289,11 @@ namespace gsc public: void post_unpack() override { - developer_script = game::Dvar_RegisterBool("developer_script", true, 0, "Enable developer script comments"); // enable by default for now +#ifdef DEBUG + developer_script = game::Dvar_RegisterBool("developer_script", true, 0, "Enable developer script comments"); +#else + developer_script = game::Dvar_RegisterBool("developer_script", false, 0, "Enable developer script comments"); +#endif utils::hook::set(0xBFD16B_b + 1, func_table_count); // change builtin func count utils::hook::set(0xBFD172_b + 4, static_cast(reverse_b((&func_table)))); @@ -296,7 +306,8 @@ namespace gsc utils::hook::inject(0xBFD5AF_b + 3, &meth_table); utils::hook::set(0xBFD5B6_b + 2, sizeof(meth_table)); 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)); + utils::hook::call(0xC0E8F2_b, vm_call_builtin_method_internal); utils::hook::jump(0xC0D0A4_b, utils::hook::assemble(vm_execute_stub), true); diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp index fa6b8435..a3189f8c 100644 --- a/src/client/component/gsc/script_loading.cpp +++ b/src/client/component/gsc/script_loading.cpp @@ -19,7 +19,9 @@ namespace gsc { - std::unique_ptr gsc_ctx = std::make_unique();; + std::unique_ptr gsc_ctx = std::make_unique(); + std::unordered_map loaded_scripts; + std::unordered_map source_pos_map; namespace { @@ -30,7 +32,6 @@ namespace gsc std::unordered_map init_handles; utils::memory::allocator scriptfile_allocator; - std::unordered_map loaded_scripts; char* script_mem_buf = nullptr; std::uint64_t script_mem_buf_size = 0; @@ -105,6 +106,35 @@ namespace gsc return false; } + void parse_gsc_map(const std::string& name, const xsk::gsc::buffer& devmap) + { + auto data = devmap.data; + auto offset = 0; + + auto count = *reinterpret_cast(data + offset); + offset += sizeof(uint32_t); + + console::debug("parsing gscmap for \"%s\" (%d)\n", name.data(), count); + + code_pos_map pos_map{}; + + for (auto i = 0; i < count; ++i) + { + auto pos = *reinterpret_cast(data + offset); + offset += sizeof(uint32_t); + + auto line = *reinterpret_cast(data + offset); + offset += sizeof(uint16_t); + + auto column = *reinterpret_cast(data + offset); + offset += sizeof(uint16_t); + + pos_map[pos] = { line, column }; + } + + source_pos_map[name] = pos_map; + } + game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) { if (const auto itr = loaded_scripts.find(file_name); itr != loaded_scripts.end()) @@ -149,8 +179,11 @@ namespace gsc const auto assembly_ptr = compiler.compile(real_name, data); const auto output_script = assembler.assemble(*assembly_ptr); - const auto bytecode = output_script.first; - const auto stack = output_script.second; + const auto bytecode = std::get<0>(output_script); + const auto stack = std::get<1>(output_script); + const auto devmap = std::get<2>(output_script); + + parse_gsc_map(real_name, devmap); const auto script_file_ptr = static_cast(scriptfile_allocator.allocate(sizeof(game::ScriptFile))); script_file_ptr->name = file_name; @@ -293,21 +326,20 @@ namespace gsc xsk::gsc::build::dev : xsk::gsc::build::prod; - gsc_ctx->init(comp_mode, [](const std::string& include_name) - -> std::pair> + gsc_ctx->init(comp_mode, []([[maybe_unused]] auto const* ctx, const auto& included_path) -> std::pair> { - const auto real_name = get_raw_script_file_name(include_name); + const auto script_name = std::filesystem::path(included_path).replace_extension().string(); std::string file_buffer; - if (!read_raw_script_file(real_name, &file_buffer) || file_buffer.empty()) + if (!read_raw_script_file(script_name, &file_buffer) || file_buffer.empty()) { - const auto name = get_script_file_name(include_name); + 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, real_name); + return read_compiled_script_file(name, script_name); } - throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name)); + throw std::runtime_error(std::format("Could not load gsc file '{}'", script_name)); } std::vector script_data; diff --git a/src/client/component/gsc/script_loading.hpp b/src/client/component/gsc/script_loading.hpp index 0ab9ae94..e8c737c8 100644 --- a/src/client/component/gsc/script_loading.hpp +++ b/src/client/component/gsc/script_loading.hpp @@ -4,6 +4,15 @@ namespace gsc { extern std::unique_ptr gsc_ctx; + extern std::unordered_map loaded_scripts; + + struct source_pos_info + { + xsk::u16 line; + xsk::u16 column; + }; + using code_pos_map = std::map; + extern std::unordered_map source_pos_map; game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default); }