feature: experimental so_survival fix

This commit is contained in:
FutureRave 2023-02-12 12:43:29 +00:00
parent b07ab0b2ca
commit 3ee2d1ec42
No known key found for this signature in database
GPG Key ID: 22F9079C86CFAB31
13 changed files with 232 additions and 151 deletions

5
.gitmodules vendored
View File

@ -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

6
deps/extra/gsc-tool/gsc_interface.cpp vendored Normal file
View File

@ -0,0 +1,6 @@
#include "gsc_interface.hpp"
namespace gsc
{
std::unique_ptr<xsk::gsc::iw5_pc::context> cxt;
}

15
deps/extra/gsc-tool/gsc_interface.hpp vendored Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#undef ERROR
#undef IN
#undef TRUE
#undef FALSE
#undef far
#include <stdinc.hpp>
#include <iw5/iw5_pc.hpp>
namespace gsc
{
extern std::unique_ptr<xsk::gsc::iw5_pc::context> cxt;
}

View File

@ -1,18 +0,0 @@
#include <stdafx.hpp>
#include <xsk/iw5.hpp>
#include "interface.hpp"
namespace gsc
{
std::unique_ptr<xsk::gsc::compiler> compiler()
{
auto compiler = std::make_unique<xsk::gsc::iw5::compiler>();
compiler->mode(xsk::gsc::build::prod);
return compiler;
}
std::unique_ptr<xsk::gsc::assembler> assembler()
{
return std::make_unique<xsk::gsc::iw5::assembler>();
}
}

View File

@ -1,7 +0,0 @@
#pragma once
namespace gsc
{
std::unique_ptr<xsk::gsc::compiler> compiler();
std::unique_ptr<xsk::gsc::assembler> assembler();
}

1
deps/fmt vendored Submodule

@ -0,0 +1 @@
Subproject commit 6e6eb63770a8f69bba48d079fb0f43f036d6b543

2
deps/gsc-tool vendored

@ -1 +1 @@
Subproject commit 7d374025b7675bada64c247ebe9378dd335a33da
Subproject commit b3ac7d2562e41a0cf821e39baa96b80c02b6ba9c

34
deps/premake/fmt.lua vendored Normal file
View File

@ -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)

View File

@ -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)

View File

@ -5,31 +5,24 @@
#include <utils/string.hpp>
#include <xsk/gsc/types.hpp>
#include <xsk/resolver.hpp>
#include <gsc_interface.hpp>
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<int>(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<int>(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<std::uint16_t>(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<std::uint16_t>(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;

View File

@ -11,8 +11,7 @@
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <xsk/gsc/types.hpp>
#include <xsk/resolver.hpp>
#include <gsc_interface.hpp>
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());
}
}

View File

@ -8,15 +8,11 @@
#include "module/file_system.hpp"
#include "module/scripting.hpp"
#include <utils/compression.hpp>
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
#include <xsk/gsc/interfaces/assembler.hpp>
#include <xsk/utils/compression.hpp>
#include <xsk/resolver.hpp>
#include <interface.hpp>
#include <gsc_interface.hpp>
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<std::string, std::string> included_scripts;
std::unordered_map<std::string, game::native::ScriptFile*> loaded_scripts;
std::unordered_map<std::string, int> main_handles;
std::unordered_map<std::string, int> 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<xsk::gsc::buffer, xsk::gsc::buffer> 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<std::uint32_t>(script_file->bytecodeLen)}, {reinterpret_cast<std::uint8_t*>(itr->second.data()), itr->second.size()}};
}
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(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<std::uint32_t>(script_file->bytecodeLen)}, {reinterpret_cast<std::uint8_t*>(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<std::uint8_t> 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<std::uint8_t> 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<game::native::ScriptFile*>(script_file_allocator.allocate(sizeof(game::native::ScriptFile)));
script_file_ptr->name = file_name;
const auto compressed_stack = utils::compression::zlib::compress({reinterpret_cast<const char*>(output_script.second.data), output_script.second.size});
const auto byte_code_size = output_script.first.size + 1;
script_file_ptr->len = static_cast<int>(output_script.second.size);
script_file_ptr->bytecodeLen = static_cast<int>(output_script.first.size);
script_file_ptr->buffer = static_cast<char*>(script_file_allocator.allocate(compressed_stack.size()));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), compressed_stack.data(), compressed_stack.size());
script_file_ptr->bytecode = static_cast<std::uint8_t*>(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<int>(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<game::native::ScriptFile*>(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<int>(stack.size());
const auto script = assembler->output_script();
script_file_ptr->bytecodeLen = static_cast<int>(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<char*>(game::native::Hunk_AllocateTempMemoryHighInternal(stack_size));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), compressed.data(), compressed.size());
script_file_ptr->bytecode = static_cast<std::uint8_t*>(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<int>(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<std::uint16_t>(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<std::uint16_t>(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<std::uint16_t>(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<xsk::gsc::buffer, xsk::gsc::buffer>
{
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<std::uint8_t*>(file_buffer.data()), file_buffer.size()), {}};
});
utils::hook::invoke<void>(SELECT_VALUE(0x4B4EE0, 0x561E80));
}
void scr_end_load_scripts_stub()
{
// Cleanup the compiler
gsc::cxt->cleanup();
utils::hook::invoke<void>(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::uint16_t>(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<xsk::gsc::iw5_pc::context>();
}
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<std::uint8_t>
{
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<std::uint8_t> 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();
}
};

View File

@ -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<char>(string.size() + 1);
auto* new_string = allocate_array<char>(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<const char*>(mem);
const auto* mem_arr = static_cast<const char*>(mem);
for (size_t i = 0; i < length; ++i)
{