diff --git a/.gitmodules b/.gitmodules index 0166e43..e91c1b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -36,4 +36,7 @@ [submodule "deps/gsc-tool"] path = deps/gsc-tool url = https://github.com/xensik/gsc-tool.git - branch = xlabs + branch = dev +[submodule "deps/fmt"] + path = deps/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/deps/extra/gsc-tool/gsc_interface.cpp b/deps/extra/gsc-tool/gsc_interface.cpp new file mode 100644 index 0000000..ecfe7a2 --- /dev/null +++ b/deps/extra/gsc-tool/gsc_interface.cpp @@ -0,0 +1,6 @@ +#include "gsc_interface.hpp" + +namespace gsc +{ + std::unique_ptr cxt; +} diff --git a/deps/extra/gsc-tool/gsc_interface.hpp b/deps/extra/gsc-tool/gsc_interface.hpp new file mode 100644 index 0000000..7074ab4 --- /dev/null +++ b/deps/extra/gsc-tool/gsc_interface.hpp @@ -0,0 +1,15 @@ +#pragma once +#undef ERROR +#undef IN +#undef TRUE +#undef FALSE + +#undef far + +#include +#include + +namespace gsc +{ + extern std::unique_ptr cxt; +} diff --git a/deps/extra/gsc-tool/interface.cpp b/deps/extra/gsc-tool/interface.cpp deleted file mode 100644 index fa95b94..0000000 --- a/deps/extra/gsc-tool/interface.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include -#include "interface.hpp" - -namespace gsc -{ - std::unique_ptr compiler() - { - auto compiler = std::make_unique(); - compiler->mode(xsk::gsc::build::prod); - return compiler; - } - - std::unique_ptr assembler() - { - return std::make_unique(); - } -} diff --git a/deps/extra/gsc-tool/interface.hpp b/deps/extra/gsc-tool/interface.hpp deleted file mode 100644 index 4d98ad3..0000000 --- a/deps/extra/gsc-tool/interface.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -namespace gsc -{ - std::unique_ptr compiler(); - std::unique_ptr assembler(); -} diff --git a/deps/fmt b/deps/fmt new file mode 160000 index 0000000..6e6eb63 --- /dev/null +++ b/deps/fmt @@ -0,0 +1 @@ +Subproject commit 6e6eb63770a8f69bba48d079fb0f43f036d6b543 diff --git a/deps/gsc-tool b/deps/gsc-tool index 7d37402..b3ac7d2 160000 --- a/deps/gsc-tool +++ b/deps/gsc-tool @@ -1 +1 @@ -Subproject commit 7d374025b7675bada64c247ebe9378dd335a33da +Subproject commit b3ac7d2562e41a0cf821e39baa96b80c02b6ba9c diff --git a/deps/premake/fmt.lua b/deps/premake/fmt.lua new file mode 100644 index 0000000..905bbf7 --- /dev/null +++ b/deps/premake/fmt.lua @@ -0,0 +1,34 @@ +fmt = { + source = path.join(dependencies.basePath, "fmt"), +} + +function fmt.import() + links { "fmt" } + + fmt.includes() +end + +function fmt.includes() + includedirs { + path.join(fmt.source, "include"), + } +end + +function fmt.project() + project "fmt" + kind "StaticLib" + language "C++" + + fmt.includes() + + files { + path.join(fmt.source, "include/fmt/*.h"), + path.join(fmt.source, "src/*.cc") + } + + removefiles { + path.join(fmt.source, "src/fmt.cc") + } +end + +table.insert(dependencies, fmt) diff --git a/deps/premake/gsc-tool.lua b/deps/premake/gsc-tool.lua index a83f07e..be50dfd 100644 --- a/deps/premake/gsc-tool.lua +++ b/deps/premake/gsc-tool.lua @@ -11,6 +11,9 @@ function gsc_tool.includes() includedirs { path.join(gsc_tool.source, "iw5"), path.join(gsc_tool.source, "utils"), + path.join(gsc_tool.source, "gsc"), + gsc_tool.source, + path.join(dependencies.basePath, "extra/gsc-tool"), } end @@ -20,9 +23,6 @@ function gsc_tool.project() kind "StaticLib" language "C++" - pchheader "stdafx.hpp" - pchsource (path.join(gsc_tool.source, "utils/stdafx.cpp")) - files { path.join(gsc_tool.source, "utils/**.hpp"), path.join(gsc_tool.source, "utils/**.cpp"), @@ -34,25 +34,30 @@ function gsc_tool.project() } zlib.includes() + fmt.includes() project "xsk-gsc-iw5" kind "StaticLib" - language "C++" - cppdialect "C++20" - filter "toolset:msc*" - buildoptions "/bigobj" + filter "action:vs*" buildoptions "/Zc:__cplusplus" filter {} - pchheader "stdafx.hpp" - pchsource (path.join(gsc_tool.source, "iw5/stdafx.cpp")) - files { - path.join(gsc_tool.source, "iw5/**.hpp"), - path.join(gsc_tool.source, "iw5/**.cpp"), - path.join(dependencies.basePath, "extra/gsc-tool/interface.cpp"), + path.join(gsc_tool.source, "iw5/iw5_pc.hpp"), + path.join(gsc_tool.source, "iw5/iw5_pc.cpp"), + path.join(gsc_tool.source, "iw5/iw5_pc_code.cpp"), + path.join(gsc_tool.source, "iw5/iw5_pc_func.cpp"), + path.join(gsc_tool.source, "iw5/iw5_pc_meth.cpp"), + path.join(gsc_tool.source, "iw5/iw5_pc_token.cpp"), + + path.join(gsc_tool.source, "gsc/misc/*.hpp"), + path.join(gsc_tool.source, "gsc/misc/*.cpp"), + path.join(gsc_tool.source, "gsc/*.hpp"), + path.join(gsc_tool.source, "gsc/*.cpp"), + + path.join(dependencies.basePath, "extra/gsc-tool/gsc_interface.cpp"), } includedirs { @@ -60,6 +65,8 @@ function gsc_tool.project() gsc_tool.source, path.join(dependencies.basePath, "extra/gsc-tool"), } + + fmt.includes() end table.insert(dependencies, gsc_tool) diff --git a/src/game/scripting/functions.cpp b/src/game/scripting/functions.cpp index 74d04bb..a06c626 100644 --- a/src/game/scripting/functions.cpp +++ b/src/game/scripting/functions.cpp @@ -5,31 +5,24 @@ #include -#include -#include +#include namespace scripting { - int find_function_index(const std::string& name, const bool prefer_global) + int find_function_index(const std::string& name, [[maybe_unused]] const bool prefer_global) { const auto target = utils::string::to_lower(name); - auto first = xsk::gsc::iw5::resolver::function_id; - auto second = xsk::gsc::iw5::resolver::method_id; - if (!prefer_global) + auto const& first = gsc::cxt->func_map(); + auto const& second = gsc::cxt->meth_map(); + + if (const auto itr = first.find(name); itr != first.end()) { - std::swap(first, second); + return static_cast(itr->second); } - const auto first_res = first(target); - if (first_res) + if (const auto itr = second.find(name); itr != second.end()) { - return first_res; - } - - const auto second_res = second(target); - if (second_res) - { - return second_res; + return static_cast(itr->second); } return -1; @@ -47,17 +40,17 @@ namespace scripting std::string find_token(std::uint32_t id) { - return xsk::gsc::iw5::resolver::token_name(static_cast(id)); + return gsc::cxt->token_name(id); } std::string find_token_single(std::uint32_t id) { - return xsk::gsc::iw5::resolver::token_name(static_cast(id)); + return gsc::cxt->token_name(id); } unsigned int find_token_id(const std::string& name) { - const auto id = xsk::gsc::iw5::resolver::token_id(name); + const auto id = gsc::cxt->token_id(name); if (id) { return id; diff --git a/src/module/gsc/script_error.cpp b/src/module/gsc/script_error.cpp index 9c049d6..219eeaa 100644 --- a/src/module/gsc/script_error.cpp +++ b/src/module/gsc/script_error.cpp @@ -11,8 +11,7 @@ #include #include -#include -#include +#include using namespace utils::string; @@ -125,7 +124,8 @@ namespace gsc { try { - return {xsk::gsc::iw5::resolver::opcode_name(opcode)}; + auto index = gsc::cxt->opcode_enum(opcode); + return {xsk::gsc::opcode_name(index)}; } catch (...) { @@ -140,11 +140,11 @@ namespace gsc if (function_id > (scr_func_max_id - 1)) { - console::error("in call to builtin method \"%s\"%s\n", xsk::gsc::iw5::resolver::method_name(function_id).data(), error.data()); + console::error("in call to builtin method \"%s\"%s\n", gsc::cxt->meth_name(function_id).data(), error.data()); } else { - console::error("in call to builtin function \"%s\"%s\n", xsk::gsc::iw5::resolver::function_name(function_id).data(), error.data()); + console::error("in call to builtin function \"%s\"%s\n", gsc::cxt->func_name(function_id).data(), error.data()); } } diff --git a/src/module/gsc/script_loading.cpp b/src/module/gsc/script_loading.cpp index e959aad..d5ad412 100644 --- a/src/module/gsc/script_loading.cpp +++ b/src/module/gsc/script_loading.cpp @@ -8,15 +8,11 @@ #include "module/file_system.hpp" #include "module/scripting.hpp" +#include #include #include -#include -#include -#include -#include -#include -#include +#include namespace gsc { @@ -24,26 +20,29 @@ namespace gsc namespace { - auto compiler = ::gsc::compiler(); - auto assembler = ::gsc::assembler(); - utils::memory::allocator script_file_allocator; + std::unordered_map included_scripts; std::unordered_map loaded_scripts; std::unordered_map main_handles; std::unordered_map init_handles; + const game::native::dvar_t* developer_script; + void clear() { - loaded_scripts.clear(); script_file_allocator.clear(); + included_scripts.clear(); + loaded_scripts.clear(); main_handles.clear(); init_handles.clear(); } - bool read_script_file(const std::string& name, std::string* data) + bool read_raw_script_file(const std::string& name, std::string* data) { + assert(data->empty()); + char* buffer{}; const auto file_len = game::native::FS_ReadFile(name.data(), &buffer); if (file_len > 0 && buffer) @@ -56,6 +55,30 @@ namespace gsc return false; } + std::pair read_compiled_script_file(const std::string& name, const std::string& real_name) + { + const auto* script_file = game::native::DB_FindXAssetHeader(game::native::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()); + + if (const auto itr = included_scripts.find(name); itr != included_scripts.end()) + { + return {{script_file->bytecode, static_cast(script_file->bytecodeLen)}, {reinterpret_cast(itr->second.data()), itr->second.size()}}; + } + + const std::string stack{script_file->buffer, static_cast(script_file->len)}; + + const auto decompressed_stack = utils::compression::zlib::decompress(stack); + const auto result = included_scripts.emplace(std::make_pair(name, decompressed_stack)); + const auto& itr = result.first; + + return {{script_file->bytecode, static_cast(script_file->bytecodeLen)}, {reinterpret_cast(itr->second.data()), itr->second.size()}}; + } + game::native::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()) @@ -63,18 +86,44 @@ namespace gsc return itr->second; } - std::string source_buffer; - if (!read_script_file(real_name + ".gsc", &source_buffer)) - { - return nullptr; - } - - std::vector data; - data.assign(source_buffer.begin(), source_buffer.end()); - try { - compiler->compile(real_name, data); + auto& compiler = gsc::cxt->compiler(); + auto& assembler = gsc::cxt->assembler(); + + std::string source_buffer; + if (!read_raw_script_file(real_name + ".gsc", &source_buffer)) + { + return nullptr; + } + + std::vector 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(script_file_allocator.allocate(sizeof(game::native::ScriptFile))); + script_file_ptr->name = file_name; + + const auto compressed_stack = utils::compression::zlib::compress({reinterpret_cast(output_script.second.data), output_script.second.size}); + const auto byte_code_size = output_script.first.size + 1; + + script_file_ptr->len = static_cast(output_script.second.size); + script_file_ptr->bytecodeLen = static_cast(output_script.first.size); + + script_file_ptr->buffer = static_cast(script_file_allocator.allocate(compressed_stack.size())); + std::memcpy(const_cast(script_file_ptr->buffer), compressed_stack.data(), compressed_stack.size()); + + script_file_ptr->bytecode = static_cast(game::native::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 0, game::native::PMEM_SOURCE_SCRIPT)); + std::memcpy(script_file_ptr->bytecode, output_script.first.data, output_script.first.size); + + script_file_ptr->compressedLen = static_cast(compressed_stack.size()); + + loaded_scripts[real_name] = script_file_ptr; + + return script_file_ptr; } catch (const std::exception& ex) { @@ -83,50 +132,11 @@ namespace gsc console::error("**********************************************\n"); return nullptr; } - - auto assembly = compiler->output(); - - try - { - assembler->assemble(real_name, assembly); - } - catch (const std::exception& ex) - { - console::error("*********** script compile error *************\n"); - console::error("failed to assemble '%s':\n%s", real_name.data(), ex.what()); - console::error("**********************************************\n"); - return nullptr; - } - - const auto script_file_ptr = static_cast(script_file_allocator.allocate(sizeof(game::native::ScriptFile))); - script_file_ptr->name = file_name; - - const auto stack = assembler->output_stack(); - script_file_ptr->len = static_cast(stack.size()); - - const auto script = assembler->output_script(); - script_file_ptr->bytecodeLen = static_cast(script.size()); - - const auto compressed = xsk::utils::zlib::compress(stack); - const auto stack_size = compressed.size(); - const auto byte_code_size = script.size() + 1; - - script_file_ptr->buffer = static_cast(game::native::Hunk_AllocateTempMemoryHighInternal(stack_size)); - std::memcpy(const_cast(script_file_ptr->buffer), compressed.data(), compressed.size()); - - script_file_ptr->bytecode = static_cast(game::native::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 0, game::native::PMEM_SOURCE_SCRIPT)); - std::memcpy(script_file_ptr->bytecode, script.data(), script.size()); - - script_file_ptr->compressedLen = static_cast(compressed.size()); - - loaded_scripts[real_name] = script_file_ptr; - - return script_file_ptr; } std::string get_script_file_name(const std::string& name) { - const auto id = xsk::gsc::iw5::resolver::token_id(name); + const auto id = gsc::cxt->token_id(name); if (!id) { return name; @@ -135,6 +145,16 @@ namespace gsc return std::to_string(id); } + std::string get_raw_script_file_name(const std::string& name) + { + if (name.ends_with(".gsh")) + { + return name; + } + + return name + ".gsc"; + } + int db_is_x_asset_default(game::native::XAssetType type, const char* name) { if (loaded_scripts.contains(name)) @@ -170,14 +190,14 @@ namespace gsc console::info("Script %s.gsc loaded successfully\n", path); - const auto main_handle = game::native::Scr_GetFunctionHandle(path, xsk::gsc::iw5::resolver::token_id("main")); + const auto main_handle = game::native::Scr_GetFunctionHandle(path, static_cast(gsc::cxt->token_id("main"))); if (main_handle) { console::info("Loaded '%s::main'\n", path); main_handles[path] = main_handle; } - const auto init_handle = game::native::Scr_GetFunctionHandle(path, xsk::gsc::iw5::resolver::token_id("init")); + const auto init_handle = game::native::Scr_GetFunctionHandle(path, static_cast(gsc::cxt->token_id("init"))); if (init_handle) { console::info("Loaded '%s::init'\n", path); @@ -206,6 +226,42 @@ namespace gsc game::native::Scr_FreeThread(static_cast(id)); } } + + void scr_begin_load_scripts_stub() + { + const auto comp_mode = developer_script->current.enabled ? + xsk::gsc::build::dev : + xsk::gsc::build::prod; + + gsc::cxt->init(comp_mode, [](const std::string& include_name) -> std::pair + { + const auto real_name = get_raw_script_file_name(include_name); + + std::string file_buffer; + if (!read_raw_script_file(real_name, &file_buffer) || file_buffer.empty()) + { + const auto name = get_script_file_name(include_name); + if (game::native::DB_XAssetExists(game::native::ASSET_TYPE_SCRIPTFILE, name.data())) + { + return read_compiled_script_file(name, real_name); + } + + throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name)); + } + + return {xsk::gsc::buffer(reinterpret_cast(file_buffer.data()), file_buffer.size()), {}}; + }); + + utils::hook::invoke(SELECT_VALUE(0x4B4EE0, 0x561E80)); + } + + void scr_end_load_scripts_stub() + { + // Cleanup the compiler + gsc::cxt->cleanup(); + + utils::hook::invoke(SELECT_VALUE(0x5DF010, 0x561D00)); + } } game::native::ScriptFile* find_script(game::native::XAssetType type, const char* name, int allow_create_default) @@ -214,7 +270,7 @@ namespace gsc const auto id = static_cast(std::strtol(name, nullptr, 10)); if (id) { - real_name = xsk::gsc::iw5::resolver::token_name(id); + real_name = gsc::cxt->token_name(id); } auto* script = load_custom_script(name, real_name); @@ -229,6 +285,11 @@ namespace gsc class script_loading final : public module { public: + void post_start() override + { + gsc::cxt = std::make_unique(); + } + void post_load() override { if (game::is_mp()) this->patch_mp(); @@ -237,28 +298,15 @@ namespace gsc utils::hook(SELECT_VALUE(0x44685E, 0x56B13E), find_script, HOOK_CALL).install()->quick(); utils::hook(SELECT_VALUE(0x446868, 0x56B148), db_is_x_asset_default, HOOK_CALL).install()->quick(); - // Allow custom scripts to include other custom scripts - xsk::gsc::iw5::resolver::init([](const auto& include_name) -> std::vector - { - const auto real_name = include_name + ".gsc"; + utils::hook(SELECT_VALUE(0x4FDD65, 0x523E03), scr_begin_load_scripts_stub, HOOK_CALL).install()->quick(); + utils::hook(SELECT_VALUE(0x4FDECF, 0x523F4D), scr_end_load_scripts_stub, HOOK_CALL).install()->quick(); // GScr_LoadScripts - std::string file_buffer; - if (!read_script_file(real_name, &file_buffer) || file_buffer.empty()) - { - throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name)); - } - - std::vector result; - result.assign(file_buffer.begin(), file_buffer.end()); - - return result; - }); + developer_script = game::native::Dvar_RegisterBool("developer_script", false, game::native::DVAR_NONE, "Enable developer script comments"); scripting::on_shutdown([](int free_scripts) -> void { if (free_scripts) { - xsk::gsc::iw5::resolver::cleanup(); clear(); } }); @@ -267,7 +315,6 @@ namespace gsc static void patch_mp() { utils::hook(0x523F3E, g_scr_load_scripts_stub, HOOK_CALL).install()->quick(); - utils::hook(0x50D4ED, scr_load_level_stub, HOOK_CALL).install()->quick(); } }; diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp index 830241c..c025dc0 100644 --- a/src/utils/memory.cpp +++ b/src/utils/memory.cpp @@ -14,7 +14,7 @@ namespace utils { std::lock_guard _(this->mutex_); - for (auto& data : this->pool_) + for (const auto& data : this->pool_) { memory::free(data); } @@ -43,7 +43,7 @@ namespace utils { std::lock_guard _(this->mutex_); - const auto data = memory::allocate(length); + auto* data = memory::allocate(length); this->pool_.push_back(data); return data; } @@ -57,21 +57,21 @@ namespace utils { std::lock_guard _(this->mutex_); - const auto data = memory::duplicate_string(string); + auto* data = memory::duplicate_string(string); this->pool_.push_back(data); return data; } void* memory::allocate(const size_t length) { - const auto data = std::calloc(length, 1); + auto* data = std::calloc(length, 1); assert(data != nullptr); return data; } char* memory::duplicate_string(const std::string& string) { - const auto new_string = allocate_array(string.size() + 1); + auto* new_string = allocate_array(string.size() + 1); std::memcpy(new_string, string.data(), string.size()); return new_string; } @@ -88,7 +88,7 @@ namespace utils bool memory::is_set(const void* mem, const char chr, const size_t length) { - const auto mem_arr = static_cast(mem); + const auto* mem_arr = static_cast(mem); for (size_t i = 0; i < length; ++i) {