Port new gsc interface from s1x
This commit is contained in:
parent
3776b199e2
commit
ddffc38063
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -43,9 +43,9 @@
|
||||
[submodule "deps/curl"]
|
||||
path = deps/curl
|
||||
url = https://github.com/curl/curl.git
|
||||
[submodule "deps/gsc-tool-h2"]
|
||||
path = deps/gsc-tool-h2
|
||||
url = https://github.com/fedddddd/gsc-tool-h2.git
|
||||
[submodule "deps/json"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "deps/gsc-tool"]
|
||||
path = deps/gsc-tool
|
||||
url = https://github.com/xensik/gsc-tool.git
|
||||
|
1
deps/gsc-tool
vendored
Submodule
1
deps/gsc-tool
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e05d853ba6448212166b18847b430768d99d39e3
|
1
deps/gsc-tool-h2
vendored
1
deps/gsc-tool-h2
vendored
@ -1 +0,0 @@
|
||||
Subproject commit c86b0b53921e91a300359f0f4da38e103641ef5a
|
54
deps/premake/gsc-tool.lua
vendored
54
deps/premake/gsc-tool.lua
vendored
@ -1,38 +1,30 @@
|
||||
gsc_tool = {
|
||||
source = path.join(dependencies.basePath, "gsc-tool-h2/src")
|
||||
source = path.join(dependencies.basePath, "gsc-tool"),
|
||||
}
|
||||
|
||||
function gsc_tool.import()
|
||||
links {"xsk-gsc-h2", "xsk-gsc-utils"}
|
||||
links { "xsk-gsc-h2", "xsk-gsc-utils" }
|
||||
gsc_tool.includes()
|
||||
end
|
||||
|
||||
function gsc_tool.includes()
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "utils"),
|
||||
path.join(gsc_tool.source, "h2"),
|
||||
path.join(dependencies.basePath, "extra/gsc-tool") -- https://github.com/GEEKiDoS/open-teknomw3/blob/master/deps/extra/gsc-tool
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
-- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L95
|
||||
function gsc_tool.project()
|
||||
project "xsk-gsc-utils"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
pchheader "stdafx.hpp"
|
||||
pchsource(path.join(gsc_tool.source, "utils/stdafx.cpp"))
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "utils/**.h"),
|
||||
path.join(gsc_tool.source, "utils/**.hpp"),
|
||||
path.join(gsc_tool.source, "utils/**.cpp")
|
||||
path.join(gsc_tool.source, "include/xsk/utils/*.hpp"),
|
||||
path.join(gsc_tool.source, "src/utils/*.cpp"),
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "utils"),
|
||||
gsc_tool.source
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
|
||||
zlib.includes()
|
||||
@ -41,28 +33,30 @@ function gsc_tool.project()
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
pchheader "stdafx.hpp"
|
||||
pchsource(path.join(gsc_tool.source, "h2/stdafx.cpp"))
|
||||
filter "action:vs*"
|
||||
buildoptions "/Zc:__cplusplus"
|
||||
filter {}
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "h2/**.h"),
|
||||
path.join(gsc_tool.source, "h2/**.hpp"),
|
||||
path.join(gsc_tool.source, "h2/**.cpp"),
|
||||
path.join(dependencies.basePath, "extra/gsc-tool/interface.cpp")
|
||||
path.join(gsc_tool.source, "include/xsk/stdinc.hpp"),
|
||||
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/engine/h2.hpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/h2.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/engine/h2_code.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/h2_func.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/h2_meth.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/h2_token.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/*.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/common/*.cpp"),
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/common/*.hpp"),
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "h2"),
|
||||
gsc_tool.source,
|
||||
path.join(dependencies.basePath, "extra/gsc-tool")
|
||||
path.join(gsc_tool.source, "include"),
|
||||
}
|
||||
|
||||
-- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L25
|
||||
-- adding these build options fixes a bunch of parser stuff
|
||||
filter "action:vs*"
|
||||
buildoptions "/bigobj"
|
||||
buildoptions "/Zc:__cplusplus"
|
||||
filter {}
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsc_tool)
|
@ -249,8 +249,6 @@ namespace fastfiles
|
||||
return reallocate_asset_pool<Type, pool_size * Multiplier>();
|
||||
}
|
||||
|
||||
#define RVA(ptr) static_cast<uint32_t>(reinterpret_cast<size_t>(ptr) - 0x140000000)
|
||||
|
||||
void reallocate_xmodel_pool()
|
||||
{
|
||||
// array used for DB_GetAllXAssetOfType, not big enough if many assets are added
|
||||
|
@ -1,789 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "gsc.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
#include "game/scripting/lua/error.hpp"
|
||||
|
||||
#include "notifies.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/gsc/interfaces/compiler.hpp>
|
||||
#include <xsk/gsc/interfaces/decompiler.hpp>
|
||||
#include <xsk/gsc/interfaces/assembler.hpp>
|
||||
#include <xsk/gsc/interfaces/disassembler.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <interface.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
void* func_table[0x1000]{};
|
||||
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* developer_script = nullptr;
|
||||
|
||||
auto compiler = ::gsc::compiler();
|
||||
auto decompiler = ::gsc::decompiler();
|
||||
auto assembler = ::gsc::assembler();
|
||||
auto disassembler = ::gsc::disassembler();
|
||||
|
||||
std::unordered_map<std::string, unsigned int> main_handles;
|
||||
std::unordered_map<std::string, unsigned int> init_handles;
|
||||
|
||||
std::unordered_map<scripting::script_function, unsigned int> functions;
|
||||
std::optional<std::string> gsc_error;
|
||||
|
||||
utils::memory::allocator scriptfile_allocator;
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
|
||||
struct
|
||||
{
|
||||
char* buf = nullptr;
|
||||
char* pos = nullptr;
|
||||
unsigned int size = 0x1000000;
|
||||
} script_memory;
|
||||
|
||||
char* allocate_buffer(size_t size)
|
||||
{
|
||||
if (script_memory.buf == nullptr)
|
||||
{
|
||||
script_memory.buf = game::PMem_AllocFromSource_NoDebug(script_memory.size, 4, 1, game::PMEM_SOURCE_SCRIPT);
|
||||
script_memory.pos = script_memory.buf;
|
||||
}
|
||||
|
||||
if (script_memory.pos + size > script_memory.buf + script_memory.size)
|
||||
{
|
||||
game::Com_Error(game::ERR_FATAL, "Out of custom script memory");
|
||||
}
|
||||
|
||||
const auto pos = script_memory.pos;
|
||||
script_memory.pos += size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void free_script_memory()
|
||||
{
|
||||
game::PMem_PopFromSource_NoDebug(script_memory.buf, script_memory.size, 4, 1, game::PMEM_SOURCE_SCRIPT);
|
||||
script_memory.buf = nullptr;
|
||||
script_memory.pos = nullptr;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
main_handles.clear();
|
||||
init_handles.clear();
|
||||
loaded_scripts.clear();
|
||||
scriptfile_allocator.clear();
|
||||
free_script_memory();
|
||||
}
|
||||
|
||||
bool read_scriptfile(const std::string& name, std::string* data)
|
||||
{
|
||||
if (filesystem::read_file(name, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto name_str = name.data();
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
|
||||
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
|
||||
{
|
||||
const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name.data(), false);
|
||||
const auto len = game::DB_GetRawFileLen(asset.rawfile);
|
||||
data->resize(len);
|
||||
game::DB_GetRawBuffer(asset.rawfile, data->data(), len);
|
||||
if (len > 0)
|
||||
{
|
||||
data->pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
|
||||
{
|
||||
if (loaded_scripts.find(real_name) != loaded_scripts.end())
|
||||
{
|
||||
return loaded_scripts[real_name];
|
||||
}
|
||||
|
||||
std::string source_buffer{};
|
||||
if (!read_scriptfile(real_name + ".gsc", &source_buffer))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data = std::vector<uint8_t>{source_buffer.begin(), source_buffer.end()};
|
||||
|
||||
try
|
||||
{
|
||||
compiler->compile(real_name, data);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to compile '%s':\n%s", real_name.data(), e.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto assembly = compiler->output();
|
||||
|
||||
try
|
||||
{
|
||||
assembler->assemble(real_name, assembly);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to assemble '%s':\n%s", real_name.data(), e.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto script_file_ptr = scriptfile_allocator.allocate<game::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());
|
||||
|
||||
script_file_ptr->buffer = scriptfile_allocator.allocate_array<char>(stack.size() + 1);
|
||||
std::memcpy(script_file_ptr->buffer, stack.data(), stack.size());
|
||||
|
||||
script_file_ptr->bytecode = allocate_buffer(script.size() + 1);
|
||||
std::memcpy(script_file_ptr->bytecode, script.data(), script.size());
|
||||
|
||||
script_file_ptr->compressedLen = 0;
|
||||
|
||||
loaded_scripts[real_name] = script_file_ptr;
|
||||
|
||||
return script_file_ptr;
|
||||
}
|
||||
|
||||
void load_script(const std::string& name)
|
||||
{
|
||||
if (!game::Scr_LoadScript(name.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto main_handle = game::Scr_GetFunctionHandle(name.data(),
|
||||
xsk::gsc::h2::resolver::token_id("main"));
|
||||
const auto init_handle = game::Scr_GetFunctionHandle(name.data(),
|
||||
xsk::gsc::h2::resolver::token_id("init"));
|
||||
|
||||
if (main_handle)
|
||||
{
|
||||
console::info("Loaded '%s::main'\n", name.data());
|
||||
main_handles[name] = main_handle;
|
||||
}
|
||||
|
||||
if (init_handle)
|
||||
{
|
||||
console::info("Loaded '%s::init'\n", name.data());
|
||||
init_handles[name] = init_handle;
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::filesystem::path& root_dir, const std::string& subfolder)
|
||||
{
|
||||
std::filesystem::path script_dir = root_dir / subfolder;
|
||||
if (!utils::io::directory_exists(script_dir.generic_string()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (!script.ends_with(".gsc"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path path(script);
|
||||
const auto relative = path.lexically_relative(root_dir).generic_string();
|
||||
const auto base_name = relative.substr(0, relative.size() - 4);
|
||||
|
||||
load_script(base_name);
|
||||
}
|
||||
}
|
||||
|
||||
void load_gametype_script_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(0x1404E1400, a1, a2);
|
||||
|
||||
fastfiles::enum_assets(game::ASSET_TYPE_RAWFILE, [](game::XAssetHeader header)
|
||||
{
|
||||
std::string name = header.rawfile->name;
|
||||
|
||||
if (name.ends_with(".gsc") && name.starts_with("scripts/"))
|
||||
{
|
||||
const auto base_name = name.substr(0, name.size() - 4);
|
||||
load_script(base_name);
|
||||
}
|
||||
}, true);
|
||||
|
||||
const auto mapname = game::Dvar_FindVar("mapname");
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path, "scripts");
|
||||
load_scripts(path, "scripts/"s + mapname->current.string);
|
||||
}
|
||||
}
|
||||
|
||||
void g_load_structs_stub()
|
||||
{
|
||||
for (auto& function_handle : main_handles)
|
||||
{
|
||||
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(function_handle.second, 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x140510B40);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x1404FD130);
|
||||
|
||||
for (auto& function_handle : init_handles)
|
||||
{
|
||||
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(function_handle.second, 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
int db_is_xasset_default(int type, const char* name)
|
||||
{
|
||||
if (loaded_scripts.find(name) != loaded_scripts.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return utils::hook::invoke<int>(0x1404143C0, type, name);
|
||||
}
|
||||
|
||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||
{
|
||||
if (rawfile->len > 0 && rawfile->compressedLen == 0)
|
||||
{
|
||||
std::memset(buf, 0, size);
|
||||
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x140413C40, rawfile, buf, size);
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
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\"",
|
||||
function.value().first.data(), function.value().second.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("\tat unknown location %p", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
|
||||
{
|
||||
try
|
||||
{
|
||||
return {xsk::gsc::h2::resolver::opcode_name(opcode)};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void builtin_call_error(const std::string& error)
|
||||
{
|
||||
const auto pos = game::scr_function_stack->pos;
|
||||
const auto function_id = *reinterpret_cast<std::uint16_t*>(
|
||||
reinterpret_cast<size_t>(pos - 2));
|
||||
|
||||
if (function_id > 0x1000)
|
||||
{
|
||||
console::warn("in call to builtin method \"%s\"%s",
|
||||
xsk::gsc::h2::resolver::method_name(function_id).data(), error.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("in call to builtin function \"%s\"%s",
|
||||
xsk::gsc::h2::resolver::function_name(function_id).data(), error.data());
|
||||
}
|
||||
}
|
||||
|
||||
bool force_error_print = false;
|
||||
void* vm_error_stub(void* a1)
|
||||
{
|
||||
if (!developer_script->current.enabled && !force_error_print)
|
||||
{
|
||||
return utils::hook::invoke<void*>(0x140614670, a1);
|
||||
}
|
||||
|
||||
console::warn("*********** script runtime error *************\n");
|
||||
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x14BAA93E8);
|
||||
const std::string error = gsc_error.has_value()
|
||||
? utils::string::va(": %s", gsc_error.value().data())
|
||||
: "";
|
||||
|
||||
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF))
|
||||
{
|
||||
builtin_call_error(error);
|
||||
}
|
||||
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.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("while processing instruction 0x%X%s\n",
|
||||
opcode_id, error.data());
|
||||
}
|
||||
}
|
||||
|
||||
force_error_print = false;
|
||||
gsc_error = {};
|
||||
|
||||
print_callstack();
|
||||
console::warn("**********************************************\n");
|
||||
return utils::hook::invoke<void*>(0x140614670, a1);
|
||||
}
|
||||
|
||||
std::string unknown_function_error{};
|
||||
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 = utils::string::va(
|
||||
"while processing function '%s' in script '%s':\nunknown script '%s'",
|
||||
pos.first.data(), pos.second.data(), scripting::current_file.data()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown_function_error = utils::string::va(
|
||||
"unknown script '%s'",
|
||||
scripting::current_file.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int current_filename{};
|
||||
std::string get_filename_name()
|
||||
{
|
||||
const auto filename_str = game::SL_ConvertToString(
|
||||
static_cast<game::scr_string_t>(current_filename));
|
||||
const auto id = std::atoi(filename_str);
|
||||
if (id == 0)
|
||||
{
|
||||
return filename_str;
|
||||
}
|
||||
|
||||
return scripting::get_token_single(id);
|
||||
}
|
||||
|
||||
|
||||
void get_unknown_function_error(unsigned int thread_name)
|
||||
{
|
||||
const auto filename = get_filename_name();
|
||||
const auto name = scripting::get_token_single(thread_name);
|
||||
|
||||
unknown_function_error = utils::string::va(
|
||||
"while processing script '%s':\nunknown function '%s::%s'",
|
||||
scripting::current_file.data(), filename.data(), name.data()
|
||||
);
|
||||
}
|
||||
|
||||
void unknown_function_stub(const char* code_pos)
|
||||
{
|
||||
get_unknown_function_error(code_pos);
|
||||
game::Com_Error(game::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 = game::FindVariable(parent_id, thread_name);
|
||||
if (!res)
|
||||
{
|
||||
get_unknown_function_error(thread_name);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s",
|
||||
unknown_function_error.data());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void register_gsc_functions_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(0x140509F20, a1, a2);
|
||||
for (const auto& func : functions)
|
||||
{
|
||||
game::Scr_RegisterFunction(func.first, 0, func.second);
|
||||
}
|
||||
}
|
||||
|
||||
scripting::script_value get_argument(int index)
|
||||
{
|
||||
if (index >= static_cast<int>(game::scr_VmPub->outparamcount))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return game::scr_VmPub->top[-index];
|
||||
}
|
||||
|
||||
auto function_id_start = 0x320;
|
||||
void add_function(const std::string& name, scripting::script_function function)
|
||||
{
|
||||
if (xsk::gsc::h2::resolver::find_function(name))
|
||||
{
|
||||
const auto id = xsk::gsc::h2::resolver::function_id(name);
|
||||
functions[function] = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++function_id_start;
|
||||
xsk::gsc::h2::resolver::add_function(name, static_cast<std::uint16_t>(id));
|
||||
functions[function] = id;
|
||||
}
|
||||
}
|
||||
|
||||
void execute_custom_function(scripting::script_function function)
|
||||
{
|
||||
auto error = false;
|
||||
|
||||
try
|
||||
{
|
||||
function({});
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error = true;
|
||||
force_error_print = true;
|
||||
gsc_error = e.what();
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void vm_call_builtin_stub(scripting::script_function function)
|
||||
{
|
||||
auto custom = false;
|
||||
{
|
||||
custom = functions.find(function) != functions.end();
|
||||
}
|
||||
|
||||
if (!custom)
|
||||
{
|
||||
function({});
|
||||
}
|
||||
else
|
||||
{
|
||||
execute_custom_function(function);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
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_script_file_name(const std::string& name)
|
||||
{
|
||||
const auto id = xsk::gsc::h2::resolver::token_id(name);
|
||||
if (id == 0)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::to_string(id);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> decompile_scriptfile(const std::string& name, const std::string& real_name)
|
||||
{
|
||||
const auto* scriptfile = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
|
||||
if (scriptfile == nullptr)
|
||||
{
|
||||
throw std::runtime_error(std::format("couldn't load scriptfile '{}'", real_name));
|
||||
}
|
||||
|
||||
console::info("Decompiling scriptfile '%s'\n", real_name.data());
|
||||
|
||||
std::vector<std::uint8_t> stack{scriptfile->buffer, scriptfile->buffer + scriptfile->len};
|
||||
std::vector<std::uint8_t> bytecode{scriptfile->bytecode, scriptfile->bytecode + scriptfile->bytecodeLen};
|
||||
|
||||
auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast<std::uint32_t>(stack.size()));
|
||||
|
||||
disassembler->disassemble(name, bytecode, decompressed_stack);
|
||||
auto output = disassembler->output();
|
||||
|
||||
decompiler->decompile(name, output);
|
||||
|
||||
return decompiler->output();
|
||||
}
|
||||
|
||||
void pmem_init_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x14061EC80);
|
||||
|
||||
const auto type_0 = &game::g_scriptmem[0];
|
||||
const auto type_1 = &game::g_scriptmem[1];
|
||||
|
||||
const auto size_0 = 0x200000; // default size
|
||||
const auto size_1 = 0x200000 + script_memory.size;
|
||||
|
||||
const auto block = reinterpret_cast<char*>(VirtualAlloc(NULL, size_0 + size_1, MEM_RESERVE, PAGE_READWRITE));
|
||||
|
||||
type_0->buf = block;
|
||||
type_0->size = size_0;
|
||||
|
||||
type_1->buf = block + size_0;
|
||||
type_1->size = size_1;
|
||||
|
||||
utils::hook::set<uint32_t>(0x14061EC72, size_0 + size_1);
|
||||
}
|
||||
}
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/)
|
||||
{
|
||||
std::string real_name = name;
|
||||
const auto id = static_cast<std::uint16_t>(std::atoi(name));
|
||||
if (id)
|
||||
{
|
||||
real_name = xsk::gsc::h2::resolver::token_name(id);
|
||||
}
|
||||
|
||||
const auto script = load_custom_script(name, real_name);
|
||||
if (script)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
developer_script = dvars::register_bool("developer_script", false, 0, "Print GSC errors");
|
||||
|
||||
// Allow custom scripts to include other custom scripts
|
||||
xsk::gsc::h2::resolver::init([](const auto& include_name)
|
||||
{
|
||||
const auto real_name = include_name + ".gsc";
|
||||
|
||||
std::string file_buffer;
|
||||
if (!read_scriptfile(real_name, &file_buffer) || file_buffer.empty())
|
||||
{
|
||||
const auto name = get_script_file_name(include_name);
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
|
||||
{
|
||||
return decompile_scriptfile(name, real_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(std::format("couldn't load gsc file '{}'", real_name));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> result;
|
||||
result.assign(file_buffer.begin(), file_buffer.end());
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
utils::hook::call(0x1405C6177, find_script);
|
||||
utils::hook::call(0x1405C6187, db_is_xasset_default);
|
||||
|
||||
// Loads scripts with an uncompressed stack
|
||||
utils::hook::call(0x1405C61E0, db_get_raw_buffer_stub);
|
||||
|
||||
// load handles
|
||||
utils::hook::call(0x1404E17B2, load_gametype_script_stub);
|
||||
|
||||
// execute handles
|
||||
utils::hook::call(0x1404C8F71, g_load_structs_stub);
|
||||
utils::hook::call(0x1404C8F80, scr_load_level_stub);
|
||||
|
||||
utils::hook::call(0x1405CB94F, vm_error_stub);
|
||||
|
||||
utils::hook::call(0x1405BC583, unknown_function_stub);
|
||||
utils::hook::call(0x1405BC5CF, unknown_function_stub);
|
||||
utils::hook::call(0x1405BC6BA, find_variable_stub);
|
||||
scr_emit_function_hook.create(0x1405BC5E0, scr_emit_function_stub);
|
||||
|
||||
utils::hook::call(0x1405BCBAB, register_gsc_functions_stub);
|
||||
utils::hook::set<uint32_t>(0x1405BC7BC, 0x1000); // change builtin func count
|
||||
|
||||
#define RVA(ptr) static_cast<uint32_t>(reinterpret_cast<size_t>(ptr) - 0x140000000)
|
||||
utils::hook::set<uint32_t>(0x1405BC7C2 + 4, RVA(&func_table));
|
||||
utils::hook::inject(0x1405BCB78 + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(0x1405CA678 + 4, RVA(&func_table));
|
||||
|
||||
utils::hook::nop(0x1405CA683, 8);
|
||||
utils::hook::call(0x1405CA683, vm_call_builtin_stub);
|
||||
|
||||
// Increase script memory
|
||||
utils::hook::call(0x1405A4798, pmem_init_stub);
|
||||
|
||||
add_function("print", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto num = game::Scr_GetNumParam();
|
||||
std::string buffer{};
|
||||
|
||||
for (auto i = 0; i < num; i++)
|
||||
{
|
||||
const auto str = game::Scr_GetString(i);
|
||||
buffer.append(str);
|
||||
buffer.append("\t");
|
||||
}
|
||||
|
||||
printf("%s\n", buffer.data());
|
||||
});
|
||||
|
||||
add_function("assert", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto expr = get_argument(0).as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
throw std::runtime_error("assert fail");
|
||||
}
|
||||
});
|
||||
|
||||
add_function("assertex", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto expr = get_argument(0).as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
const auto error = get_argument(1).as<std::string>();
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
});
|
||||
|
||||
add_function("replacefunc", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto what = get_argument(0).get_raw();
|
||||
const auto with = get_argument(1).get_raw();
|
||||
|
||||
if (what.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("parameter 1 must be a function");
|
||||
}
|
||||
|
||||
if (with.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("parameter 2 must be a function");
|
||||
}
|
||||
|
||||
notifies::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
|
||||
});
|
||||
|
||||
add_function("getsoundlength", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto name = get_argument(0);
|
||||
if (!name.is<std::string>())
|
||||
{
|
||||
throw std::runtime_error("parameter 1 must be a string");
|
||||
}
|
||||
|
||||
const auto name_str = name.as<std::string>();
|
||||
const auto sound = game::DB_FindXAssetHeader(game::ASSET_TYPE_SOUND, name_str.data(), false).sound;
|
||||
if (!sound || !sound->count || !sound->head->soundFile || sound->head->soundFile->type != game::SAT_STREAMED)
|
||||
{
|
||||
return game::Scr_AddInt(-1);
|
||||
}
|
||||
|
||||
return game::Scr_AddInt(sound->head->soundFile->u.streamSnd.totalMsec);
|
||||
});
|
||||
|
||||
add_function("executecommand", [](const game::scr_entref_t ref)
|
||||
{
|
||||
const auto cmd = get_argument(0).as<std::string>();
|
||||
command::execute(cmd);
|
||||
});
|
||||
|
||||
scripting::on_shutdown([](bool free_scripts, bool post_shutdown)
|
||||
{
|
||||
if (free_scripts && post_shutdown)
|
||||
{
|
||||
xsk::gsc::h2::resolver::cleanup();
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern void* func_table[0x1000];
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/);
|
||||
}
|
326
src/client/component/gsc/script_error.cpp
Normal file
326
src/client/component/gsc/script_error.cpp
Normal file
@ -0,0 +1,326 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_extension.hpp"
|
||||
#include "script_error.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
|
||||
unsigned int current_filename = 0;
|
||||
|
||||
std::string unknown_function_error;
|
||||
|
||||
std::array<const char*, 27> 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",
|
||||
};
|
||||
|
||||
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::SL_ConvertToString(static_cast<game::scr_string_t>(current_filename));
|
||||
const auto id = std::atoi(filename_str);
|
||||
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 '{}'", 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::Com_Error(game::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 = game::FindVariable(parent_id, thread_name);
|
||||
if (!res)
|
||||
{
|
||||
get_unknown_function_error(thread_name);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s", unknown_function_error.data());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned int scr_get_object(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_POINTER)
|
||||
{
|
||||
return value->u.pointerValue;
|
||||
}
|
||||
|
||||
scr_error(false, "Type %s is not an object", var_typename[value->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_string(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (game::Scr_CastString(value))
|
||||
{
|
||||
assert(value->type == game::VAR_STRING);
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_istring(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_ISTRING)
|
||||
{
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
scr_error(false, "type %s is not a localized string", var_typename[value->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scr_validate_localized_string_ref(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(false, "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::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_VECTOR)
|
||||
{
|
||||
std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
|
||||
return;
|
||||
}
|
||||
|
||||
scr_error(false, "type %s is not a vector", var_typename[value->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
}
|
||||
|
||||
int scr_get_int(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return value->u.intValue;
|
||||
}
|
||||
|
||||
scr_error(false, "type %s is not an int", var_typename[value->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
float scr_get_float(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_FLOAT)
|
||||
{
|
||||
return value->u.floatValue;
|
||||
}
|
||||
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return static_cast<float>(value->u.intValue);
|
||||
}
|
||||
|
||||
scr_error(false, "type %s is not a float", var_typename[value->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int scr_get_pointer_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
|
||||
{
|
||||
return static_cast<int>(game::GetObjectType((game::scr_VmPub->top - index)->u.uintValue));
|
||||
}
|
||||
|
||||
scr_error(false, "type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]);
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scr_get_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return (game::scr_VmPub->top - index)->type;
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* scr_get_type_name(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return var_typename[(game::scr_VmPub->top - index)->type];
|
||||
}
|
||||
|
||||
scr_error(false, "parameter %u does not exist", index + 1);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
class error final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scr_emit_function_hook.create(0x1405BC5E0, scr_emit_function_stub);
|
||||
|
||||
utils::hook::call(0x1405BC583, compile_error_stub); // LinkFile
|
||||
utils::hook::call(0x1405BC5CF, compile_error_stub); // LinkFile
|
||||
utils::hook::call(0x1405BC6BA, find_variable_stub); // Scr_EmitFunction
|
||||
|
||||
// Restore basic error messages for commonly used scr functions
|
||||
utils::hook::jump(0x1405C7950, scr_get_object);
|
||||
utils::hook::jump(0x1405C7420, scr_get_const_string);
|
||||
utils::hook::jump(0x1405C71E0, scr_get_const_istring);
|
||||
utils::hook::jump(0x1404FEED0, scr_validate_localized_string_ref);
|
||||
utils::hook::jump(0x1405C7E90, scr_get_vector);
|
||||
utils::hook::jump(0x1405C7890, scr_get_int);
|
||||
utils::hook::jump(0x1405C7730, scr_get_float);
|
||||
|
||||
utils::hook::jump(0x1405C7B70, scr_get_pointer_type);
|
||||
utils::hook::jump(0x1405C7D40, scr_get_type);
|
||||
utils::hook::jump(0x1405C7DB0, scr_get_type_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::error)
|
6
src/client/component/gsc/script_error.hpp
Normal file
6
src/client/component/gsc/script_error.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
|
||||
}
|
328
src/client/component/gsc/script_extension.cpp
Normal file
328
src/client/component/gsc/script_extension.cpp
Normal file
@ -0,0 +1,328 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_error.hpp"
|
||||
#include "script_extension.hpp"
|
||||
#include "script_loading.hpp"
|
||||
|
||||
#include "component/console.hpp"
|
||||
#include "component/command.hpp"
|
||||
#include "component/notifies.hpp"
|
||||
|
||||
#include "game/scripting/script_value.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::uint16_t function_id_start = 0x320;
|
||||
void* func_table[FUNC_TABLE_SIZE];
|
||||
|
||||
namespace
|
||||
{
|
||||
struct gsc_error : public std::runtime_error
|
||||
{
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
std::unordered_map<game::BuiltinFunction, std::uint16_t> functions;
|
||||
|
||||
bool force_error_print = false;
|
||||
std::optional<std::string> gsc_error_msg;
|
||||
|
||||
utils::hook::detour scr_register_function_hook;
|
||||
|
||||
unsigned int scr_get_function_stub(const char** p_name, int* type)
|
||||
{
|
||||
const auto result = utils::hook::invoke<unsigned int>(0x140509F20, p_name, type);
|
||||
|
||||
for (const auto& [func, id] : functions)
|
||||
{
|
||||
game::Scr_RegisterFunction(func, 0, id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void execute_custom_function(game::BuiltinFunction function)
|
||||
{
|
||||
auto error = false;
|
||||
|
||||
try
|
||||
{
|
||||
function();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error = true;
|
||||
force_error_print = true;
|
||||
gsc_error_msg = e.what();
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_in_game_memory_range(uintptr_t ptr)
|
||||
{
|
||||
return ptr <= BASE_ADDRESS + BINARY_PAYLOAD_SIZE;
|
||||
}
|
||||
|
||||
bool is_in_game_memory_range(void* ptr)
|
||||
{
|
||||
return is_in_game_memory_range(reinterpret_cast<uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
void vm_call_builtin_function(const game::BuiltinFunction function)
|
||||
{
|
||||
const auto custom = !is_in_game_memory_range(function);
|
||||
if (!custom)
|
||||
{
|
||||
function();
|
||||
}
|
||||
else
|
||||
{
|
||||
execute_custom_function(function);
|
||||
}
|
||||
}
|
||||
|
||||
void builtin_call_error(const std::string& error)
|
||||
{
|
||||
const auto pos = game::scr_function_stack->pos;
|
||||
const auto function_id = *reinterpret_cast<std::uint16_t*>(reinterpret_cast<std::size_t>(pos - 2));
|
||||
|
||||
if (function_id > FUNC_TABLE_SIZE)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_call_opcode(const std::uint8_t opcode_id)
|
||||
{
|
||||
return (opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF);
|
||||
}
|
||||
|
||||
void vm_error_stub(int mark_pos)
|
||||
{
|
||||
if (!developer_script->current.enabled && !force_error_print)
|
||||
{
|
||||
utils::hook::invoke<void>(0x140614670, mark_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
console::warn("******* script runtime error ********\n");
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x14BAA93E8);
|
||||
|
||||
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
|
||||
|
||||
if (is_call_opcode(opcode_id))
|
||||
{
|
||||
builtin_call_error(error);
|
||||
}
|
||||
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.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("while processing instruction 0x%X%s\n", opcode_id, error.data());
|
||||
}
|
||||
}
|
||||
|
||||
force_error_print = false;
|
||||
gsc_error_msg = {};
|
||||
|
||||
print_callstack();
|
||||
console::warn("************************************\n");
|
||||
utils::hook::invoke<void>(0x140614670, mark_pos);
|
||||
}
|
||||
|
||||
void scr_print()
|
||||
{
|
||||
const auto num = game::Scr_GetNumParam();
|
||||
std::string buffer{};
|
||||
|
||||
for (auto i = 0u; i < num; i++)
|
||||
{
|
||||
const auto str = game::Scr_GetString(i);
|
||||
buffer.append(str);
|
||||
buffer.append("\t");
|
||||
}
|
||||
|
||||
console::info("%s\n", buffer.data());
|
||||
}
|
||||
|
||||
void assert_cmd()
|
||||
{
|
||||
if (!game::Scr_GetInt(0))
|
||||
{
|
||||
scr_error(true, "assert fail");
|
||||
}
|
||||
}
|
||||
|
||||
void assert_ex_cmd()
|
||||
{
|
||||
if (!game::Scr_GetInt(0))
|
||||
{
|
||||
scr_error(true, "assert fail: %s", game::Scr_GetString(1));
|
||||
}
|
||||
}
|
||||
|
||||
void assert_msg_cmd()
|
||||
{
|
||||
scr_error(true, "assert fail: %s", game::Scr_GetString(0));
|
||||
}
|
||||
|
||||
scripting::script_value get_argument(int index)
|
||||
{
|
||||
if (index >= static_cast<int>(game::scr_VmPub->outparamcount))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return game::scr_VmPub->top[-index];
|
||||
}
|
||||
}
|
||||
|
||||
void scr_error(bool force_print, const char* fmt, ...)
|
||||
{
|
||||
{
|
||||
char buffer[2048]{};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
force_error_print = force_print;
|
||||
gsc_error_msg = buffer;
|
||||
}
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
void add_function(const std::string& name, game::BuiltinFunction function)
|
||||
{
|
||||
if (gsc_ctx->func_exists(name))
|
||||
{
|
||||
const auto id = gsc_ctx->func_id(name);
|
||||
functions[function] = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++function_id_start;
|
||||
functions[function] = id;
|
||||
gsc_ctx->func_add(name, id);
|
||||
}
|
||||
}
|
||||
|
||||
class extension final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::set<uint32_t>(0x1405BC7BC, FUNC_TABLE_SIZE); // change builtin func count
|
||||
|
||||
utils::hook::set<uint32_t>(0x1405BC7C2 + 4, RVA(&func_table));
|
||||
utils::hook::inject(0x1405BCB78 + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(0x1405CA678 + 4, RVA(&func_table));
|
||||
|
||||
utils::hook::nop(0x1405CA683, 8);
|
||||
utils::hook::call(0x1405CA683, vm_call_builtin_function);
|
||||
|
||||
utils::hook::call(0x1405CB94F, vm_error_stub); // LargeLocalResetToMark
|
||||
|
||||
utils::hook::call(0x1405BCBAB, scr_get_function_stub);
|
||||
|
||||
add_function("print", scr_print);
|
||||
add_function("println", scr_print);
|
||||
|
||||
add_function("assert", assert_cmd);
|
||||
add_function("assertex", assert_ex_cmd);
|
||||
add_function("assertmsg", assert_msg_cmd);
|
||||
|
||||
add_function("replacefunc", []()
|
||||
{
|
||||
const auto what = get_argument(0).get_raw();
|
||||
const auto with = get_argument(1).get_raw();
|
||||
|
||||
if (what.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("parameter 1 must be a function");
|
||||
}
|
||||
|
||||
if (with.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("parameter 2 must be a function");
|
||||
}
|
||||
|
||||
notifies::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
|
||||
});
|
||||
|
||||
add_function("getsoundlength", []()
|
||||
{
|
||||
const auto name = game::Scr_GetString(0);
|
||||
const auto sound = game::DB_FindXAssetHeader(game::ASSET_TYPE_SOUND, name, false).sound;
|
||||
if (!sound || !sound->count || !sound->head->soundFile || sound->head->soundFile->type != game::SAT_STREAMED)
|
||||
{
|
||||
return game::Scr_AddInt(-1);
|
||||
}
|
||||
|
||||
return game::Scr_AddInt(sound->head->soundFile->u.streamSnd.totalMsec);
|
||||
});
|
||||
|
||||
add_function("executecommand", []()
|
||||
{
|
||||
const auto cmd = game::Scr_GetString(0);
|
||||
command::execute(cmd);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::extension)
|
11
src/client/component/gsc/script_extension.hpp
Normal file
11
src/client/component/gsc/script_extension.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#define FUNC_TABLE_SIZE 0x1000
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern void* func_table[FUNC_TABLE_SIZE];
|
||||
|
||||
void scr_error(bool force_print, const char* fmt, ...);
|
||||
void add_function(const std::string& name, game::BuiltinFunction function);
|
||||
}
|
431
src/client/component/gsc/script_loading.cpp
Normal file
431
src/client/component/gsc/script_loading.cpp
Normal file
@ -0,0 +1,431 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "component/filesystem.hpp"
|
||||
#include "component/console.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
#include "component/fastfiles.hpp"
|
||||
|
||||
#include "script_loading.hpp"
|
||||
|
||||
#include <utils/compression.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unique_ptr<xsk::gsc::h2::context> gsc_ctx;
|
||||
game::dvar_t* developer_script = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned int> main_handles;
|
||||
std::unordered_map<std::string, unsigned int> init_handles;
|
||||
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
utils::memory::allocator script_allocator;
|
||||
|
||||
struct
|
||||
{
|
||||
char* buf = nullptr;
|
||||
char* pos = nullptr;
|
||||
unsigned int size = 0x1000000;
|
||||
} script_memory;
|
||||
|
||||
char* allocate_buffer(size_t size)
|
||||
{
|
||||
if (script_memory.buf == nullptr)
|
||||
{
|
||||
script_memory.buf = game::PMem_AllocFromSource_NoDebug(script_memory.size, 4, 1, game::PMEM_SOURCE_SCRIPT);
|
||||
script_memory.pos = script_memory.buf;
|
||||
}
|
||||
|
||||
if (script_memory.pos + size > script_memory.buf + script_memory.size)
|
||||
{
|
||||
game::Com_Error(game::ERR_FATAL, "Out of custom script memory");
|
||||
}
|
||||
|
||||
const auto pos = script_memory.pos;
|
||||
script_memory.pos += size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void free_script_memory()
|
||||
{
|
||||
game::PMem_PopFromSource_NoDebug(script_memory.buf, script_memory.size, 4, 1, game::PMEM_SOURCE_SCRIPT);
|
||||
script_memory.buf = nullptr;
|
||||
script_memory.pos = nullptr;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
main_handles.clear();
|
||||
init_handles.clear();
|
||||
loaded_scripts.clear();
|
||||
script_allocator.clear();
|
||||
free_script_memory();
|
||||
}
|
||||
|
||||
bool read_raw_script_file(const std::string& name, std::string* data)
|
||||
{
|
||||
if (filesystem::read_file(name, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto name_str = name.data();
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
|
||||
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
|
||||
{
|
||||
const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name.data(), false);
|
||||
const auto len = game::DB_GetRawFileLen(asset.rawfile);
|
||||
data->resize(len);
|
||||
game::DB_GetRawBuffer(asset.rawfile, data->data(), len);
|
||||
if (len > 0)
|
||||
{
|
||||
data->pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
game::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())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto& compiler = gsc_ctx->compiler();
|
||||
auto& assembler = gsc_ctx->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);
|
||||
const auto output_script = assembler.assemble(*assembly_ptr);
|
||||
|
||||
const auto script_file_ptr = static_cast<game::ScriptFile*>(script_allocator.allocate(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
script_file_ptr->len = static_cast<int>(output_script.second.size);
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(output_script.first.size);
|
||||
|
||||
const auto stack_size = static_cast<std::uint32_t>(output_script.second.size + 1);
|
||||
const auto byte_code_size = static_cast<std::uint32_t>(output_script.first.size + 1);
|
||||
|
||||
script_file_ptr->buffer = static_cast<char*>(script_allocator.allocate(stack_size));
|
||||
std::memcpy(const_cast<char*>(script_file_ptr->buffer), output_script.second.data, output_script.second.size);
|
||||
|
||||
script_file_ptr->bytecode = allocate_buffer(byte_code_size);
|
||||
std::memcpy(script_file_ptr->bytecode, output_script.first.data, output_script.first.size);
|
||||
|
||||
script_file_ptr->compressedLen = 0;
|
||||
|
||||
loaded_scripts[real_name] = script_file_ptr;
|
||||
|
||||
return script_file_ptr;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to compile '%s':\n%s", real_name.data(), ex.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_raw_script_file_name(const std::string& name)
|
||||
{
|
||||
if (name.ends_with(".gsh"))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return name + ".gsc";
|
||||
}
|
||||
|
||||
std::string get_script_file_name(const std::string& name)
|
||||
{
|
||||
const auto id = gsc_ctx->token_id(name);
|
||||
if (!id)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::to_string(id);
|
||||
}
|
||||
|
||||
auto read_compiled_script_file(const std::string& name, const std::string& real_name)
|
||||
{
|
||||
const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
|
||||
if (!script_file)
|
||||
{
|
||||
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
|
||||
}
|
||||
|
||||
const auto len = script_file->compressedLen;
|
||||
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(len)};
|
||||
|
||||
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
|
||||
|
||||
std::vector<std::uint8_t> stack_data;
|
||||
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
|
||||
|
||||
const xsk::gsc::buffer buffer{reinterpret_cast<uint8_t*>(script_file->bytecode), static_cast<std::size_t>(script_file->bytecodeLen)};
|
||||
|
||||
return std::make_pair(buffer, stack_data);
|
||||
}
|
||||
|
||||
void load_script(const std::string& name)
|
||||
{
|
||||
if (!game::Scr_LoadScript(name.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
|
||||
const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("init"));
|
||||
|
||||
if (main_handle)
|
||||
{
|
||||
console::info("Loaded '%s::main'\n", name.data());
|
||||
main_handles[name] = main_handle;
|
||||
}
|
||||
|
||||
if (init_handle)
|
||||
{
|
||||
console::info("Loaded '%s::init'\n", name.data());
|
||||
init_handles[name] = init_handle;
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::filesystem::path& root_dir, const std::string& subfolder)
|
||||
{
|
||||
std::filesystem::path script_dir = root_dir / subfolder;
|
||||
if (!utils::io::directory_exists(script_dir.generic_string()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (!script.ends_with(".gsc"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path path(script);
|
||||
const auto relative = path.lexically_relative(root_dir).generic_string();
|
||||
const auto base_name = relative.substr(0, relative.size() - 4);
|
||||
|
||||
load_script(base_name);
|
||||
}
|
||||
}
|
||||
|
||||
int db_is_x_asset_default(game::XAssetType type, const char* name)
|
||||
{
|
||||
if (loaded_scripts.contains(name))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return game::DB_IsXAssetDefault(type, name);
|
||||
}
|
||||
|
||||
void load_gametype_script_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(0x1404E1400, a1, a2);
|
||||
|
||||
fastfiles::enum_assets(game::ASSET_TYPE_RAWFILE, [](game::XAssetHeader header)
|
||||
{
|
||||
std::string name = header.rawfile->name;
|
||||
|
||||
if (name.ends_with(".gsc") && name.starts_with("scripts/"))
|
||||
{
|
||||
const auto base_name = name.substr(0, name.size() - 4);
|
||||
load_script(base_name);
|
||||
}
|
||||
}, true);
|
||||
|
||||
const auto mapname = game::Dvar_FindVar("mapname");
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path, "scripts");
|
||||
load_scripts(path, "scripts/"s + mapname->current.string);
|
||||
}
|
||||
}
|
||||
|
||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||
{
|
||||
if (rawfile->len > 0 && rawfile->compressedLen == 0)
|
||||
{
|
||||
std::memset(buf, 0, size);
|
||||
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
|
||||
return;
|
||||
}
|
||||
|
||||
game::DB_GetRawBuffer(rawfile, buf, size);
|
||||
}
|
||||
|
||||
void g_load_structs_stub()
|
||||
{
|
||||
for (auto& function_handle : main_handles)
|
||||
{
|
||||
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(function_handle.second, 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x140510B40);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x1404FD130);
|
||||
|
||||
for (auto& function_handle : init_handles)
|
||||
{
|
||||
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||
const auto thread = game::Scr_ExecThread(function_handle.second, 0);
|
||||
game::RemoveRefToObject(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void scr_begin_load_scripts_stub()
|
||||
{
|
||||
const auto comp_mode = developer_script->current.enabled
|
||||
? xsk::gsc::build::dev
|
||||
: xsk::gsc::build::prod;
|
||||
|
||||
gsc_ctx->init(comp_mode, [](const std::string& include_name)
|
||||
-> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
|
||||
{
|
||||
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::DB_XAssetExists(game::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));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> script_data;
|
||||
script_data.assign(file_buffer.begin(), file_buffer.end());
|
||||
|
||||
return {{}, script_data};
|
||||
});
|
||||
|
||||
utils::hook::invoke<void>(0x1405BCAE0);
|
||||
}
|
||||
|
||||
void sl_end_load_scripts_stub()
|
||||
{
|
||||
gsc_ctx->cleanup();
|
||||
utils::hook::invoke<void>(0x1405BFBF0);
|
||||
}
|
||||
|
||||
void pmem_init_stub()
|
||||
{
|
||||
utils::hook::invoke<void>(0x14061EC80);
|
||||
|
||||
const auto type_0 = &game::g_scriptmem[0];
|
||||
const auto type_1 = &game::g_scriptmem[1];
|
||||
|
||||
const auto size_0 = 0x200000; // default size
|
||||
const auto size_1 = 0x200000 + script_memory.size;
|
||||
|
||||
const auto block = reinterpret_cast<char*>(VirtualAlloc(NULL, size_0 + size_1, MEM_RESERVE, PAGE_READWRITE));
|
||||
|
||||
type_0->buf = block;
|
||||
type_0->size = size_0;
|
||||
|
||||
type_1->buf = block + size_0;
|
||||
type_1->size = size_1;
|
||||
|
||||
utils::hook::set<uint32_t>(0x14061EC72, size_0 + size_1);
|
||||
}
|
||||
}
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
std::string real_name = name;
|
||||
const auto id = static_cast<std::uint16_t>(std::strtol(name, nullptr, 10));
|
||||
if (id)
|
||||
{
|
||||
real_name = gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
auto* script = load_custom_script(name, real_name);
|
||||
if (script)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile;
|
||||
}
|
||||
|
||||
class loading final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
gsc_ctx = std::make_unique<xsk::gsc::h2::context>();
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::call(0x1404E1627, scr_begin_load_scripts_stub);
|
||||
utils::hook::call(0x1405BCC14, sl_end_load_scripts_stub);
|
||||
|
||||
developer_script = dvars::register_bool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
|
||||
|
||||
utils::hook::call(0x1405C6177, find_script);
|
||||
utils::hook::call(0x1405C6187, db_is_x_asset_default);
|
||||
|
||||
// Loads scripts with an uncompressed stack
|
||||
utils::hook::call(0x1405C61E0, db_get_raw_buffer_stub);
|
||||
|
||||
// load handles
|
||||
utils::hook::call(0x1404E17B2, load_gametype_script_stub);
|
||||
|
||||
// execute handles
|
||||
utils::hook::call(0x1404C8F71, g_load_structs_stub);
|
||||
utils::hook::call(0x1404C8F80, scr_load_level_stub);
|
||||
|
||||
// increase script memory
|
||||
utils::hook::call(0x1405A4798, pmem_init_stub);
|
||||
|
||||
scripting::on_shutdown([](bool free_scripts, bool post_shutdown)
|
||||
{
|
||||
if (free_scripts && post_shutdown)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::loading)
|
12
src/client/component/gsc/script_loading.hpp
Normal file
12
src/client/component/gsc/script_loading.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/scripting/functions.hpp"
|
||||
#include <xsk/gsc/engine/h2.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern std::unique_ptr<xsk::gsc::h2::context> gsc_ctx;
|
||||
extern game::dvar_t* developer_script;
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
|
||||
}
|
@ -8,9 +8,6 @@
|
||||
#include "command.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
@ -50,7 +47,7 @@ namespace mapents
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto token = xsk::gsc::h2::resolver::token_name(static_cast<std::uint16_t>(id));
|
||||
const auto token = scripting::find_token_single(static_cast<std::uint16_t>(id));
|
||||
const auto key = "\"" + token + "\"";
|
||||
|
||||
const auto new_line = key + line.substr(first_space);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/scripting/lua/error.hpp"
|
||||
|
||||
namespace notifies
|
||||
{
|
||||
extern bool hook_enabled;
|
||||
|
@ -162,9 +162,9 @@ namespace scripting
|
||||
{
|
||||
auto result = scripting::find_token(id);
|
||||
|
||||
if (canonical_string_table.find(id) != canonical_string_table.end())
|
||||
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
|
||||
{
|
||||
result.push_back(canonical_string_table[id]);
|
||||
result.push_back(itr->second);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -323,12 +323,22 @@ namespace scripting
|
||||
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id)
|
||||
{
|
||||
if (canonical_string_table.find(id) == canonical_string_table.end())
|
||||
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return {canonical_string_table[id]};
|
||||
std::string get_token(unsigned int id)
|
||||
{
|
||||
if (const auto itr = canonical_string_table.find(id); itr != canonical_string_table.end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
return find_token_single(id);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
|
@ -17,4 +17,5 @@ namespace scripting
|
||||
void on_shutdown(const std::function<void(bool, bool)>& callback);
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id);
|
||||
std::string get_token_single(unsigned int id);
|
||||
std::string get_token(unsigned int id);
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
|
||||
#include "../../component/gsc.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include "component/gsc/script_extension.hpp"
|
||||
#include "component/gsc/script_loading.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
@ -15,23 +15,17 @@ namespace scripting
|
||||
int find_function_index(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
auto first = xsk::gsc::h2::resolver::function_id;
|
||||
auto second = xsk::gsc::h2::resolver::method_id;
|
||||
if (!prefer_global)
|
||||
auto const& first = gsc::gsc_ctx->func_map();
|
||||
auto const& second = gsc::gsc_ctx->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;
|
||||
@ -67,20 +61,20 @@ namespace scripting
|
||||
|
||||
results.push_back(utils::string::va("_ID%i", id));
|
||||
results.push_back(utils::string::va("_id_%04X", id));
|
||||
results.push_back(xsk::gsc::h2::resolver::token_name(static_cast<std::uint16_t>(id)));
|
||||
results.push_back(gsc::gsc_ctx->token_name(id));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::string find_token_single(unsigned int id)
|
||||
{
|
||||
return xsk::gsc::h2::resolver::token_name(static_cast<std::uint16_t>(id));
|
||||
return gsc::gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
unsigned int find_token_id(const std::string& name)
|
||||
{
|
||||
const auto id = xsk::gsc::h2::resolver::token_id(name);
|
||||
if (id != 0)
|
||||
const auto id = gsc::gsc_ctx->token_id(name);
|
||||
if (id)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
@ -13,13 +13,10 @@
|
||||
#include "component/scheduler.hpp"
|
||||
#include "component/filesystem.hpp"
|
||||
|
||||
#include "component/gsc/script_loading.hpp"
|
||||
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
|
||||
#include "lualib.h"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
@ -359,7 +356,7 @@ namespace scripting::lua
|
||||
|
||||
auto entity_type = state.new_usertype<entity>("entity");
|
||||
|
||||
for (const auto& func : xsk::gsc::h2::resolver::get_methods())
|
||||
for (const auto& func : gsc::gsc_ctx->meth_map())
|
||||
{
|
||||
const auto name = std::string(func.first);
|
||||
entity_type[name.data()] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va)
|
||||
@ -490,7 +487,7 @@ namespace scripting::lua
|
||||
auto game_type = state.new_usertype<game>("game_");
|
||||
state["game"] = game();
|
||||
|
||||
for (const auto& func : xsk::gsc::h2::resolver::get_functions())
|
||||
for (const auto& func : gsc::gsc_ctx->func_map())
|
||||
{
|
||||
const auto name = std::string(func.first);
|
||||
game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va)
|
||||
|
@ -1179,6 +1179,44 @@ namespace game
|
||||
unsigned short classnum;
|
||||
};
|
||||
|
||||
typedef void(*BuiltinMethod)(scr_entref_t);
|
||||
typedef void(*BuiltinFunction)();
|
||||
|
||||
enum
|
||||
{
|
||||
VAR_UNDEFINED = 0x0,
|
||||
VAR_BEGIN_REF = 0x1,
|
||||
VAR_POINTER = 0x1,
|
||||
VAR_STRING = 0x2,
|
||||
VAR_ISTRING = 0x3,
|
||||
VAR_VECTOR = 0x4,
|
||||
VAR_END_REF = 0x5,
|
||||
VAR_FLOAT = 0x5,
|
||||
VAR_INTEGER = 0x6,
|
||||
VAR_CODEPOS = 0x7,
|
||||
VAR_PRECODEPOS = 0x8,
|
||||
VAR_FUNCTION = 0x9,
|
||||
VAR_BUILTIN_FUNCTION = 0xA,
|
||||
VAR_BUILTIN_METHOD = 0xB,
|
||||
VAR_STACK = 0xC,
|
||||
VAR_ANIMATION = 0xD,
|
||||
VAR_PRE_ANIMATION = 0xE,
|
||||
VAR_THREAD = 0xF,
|
||||
VAR_NOTIFY_THREAD = 0x10,
|
||||
VAR_TIME_THREAD = 0x11,
|
||||
VAR_CHILD_THREAD = 0x12,
|
||||
VAR_OBJECT = 0x13,
|
||||
VAR_DEAD_ENTITY = 0x14,
|
||||
VAR_ENTITY = 0x15,
|
||||
VAR_ARRAY = 0x16,
|
||||
VAR_DEAD_THREAD = 0x17,
|
||||
VAR_COUNT = 0x18,
|
||||
VAR_FREE = 0x18,
|
||||
VAR_THREAD_LIST = 0x19,
|
||||
VAR_ENDON_LIST = 0x1A,
|
||||
VAR_TOTAL_COUNT = 0x1B,
|
||||
};
|
||||
|
||||
enum scriptType_e
|
||||
{
|
||||
SCRIPT_NONE = 0,
|
||||
|
@ -86,6 +86,7 @@ namespace game
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{0x1405C2130};
|
||||
WEAK symbol<void(unsigned int parentId, unsigned int id, VariableValue* value)> SetNewVariableValue{0x1405C5EA0};
|
||||
WEAK symbol<void(unsigned int parentId, unsigned int index)> RemoveVariableValue{0x1405C2A50};
|
||||
WEAK symbol<unsigned int(unsigned int)> GetObjectType{0x1405C25D0};
|
||||
|
||||
WEAK symbol<unsigned int(const char* name)> G_GetWeaponForName{0x14051B260};
|
||||
WEAK symbol<int(void* ps, unsigned int weapon, int a3, int a4, __int64 a5, int a6)>
|
||||
@ -136,10 +137,11 @@ namespace game
|
||||
WEAK symbol<void(int value)> Scr_AddInt{0x1405C69A0};
|
||||
WEAK symbol<void(const char* value)> Scr_AddString{0x1405C6A80};
|
||||
WEAK symbol<unsigned int(const char* name)> Scr_LoadScript{0x1405BCEC0};
|
||||
WEAK symbol<int()> Scr_GetNumParam{0x1405C7940};
|
||||
WEAK symbol<unsigned int()> Scr_GetNumParam{0x1405C7940};
|
||||
WEAK symbol<unsigned int(const char* script, unsigned int name)> Scr_GetFunctionHandle{0x1405BCD50};
|
||||
WEAK symbol<unsigned int(int handle, unsigned int paramcount)> Scr_ExecThread{0x1405C6F40};
|
||||
WEAK symbol<unsigned int(void* func, int type, unsigned int name)> Scr_RegisterFunction{0x1405BC7B0};
|
||||
WEAK symbol<bool(VariableValue* value)> Scr_CastString{0x1405C33A0};
|
||||
|
||||
WEAK symbol<char*(unsigned int size, unsigned int alignment,
|
||||
unsigned int type, int source)> PMem_AllocFromSource_NoDebug{0x14061E680};
|
||||
|
@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#define BINARY_PAYLOAD_SIZE 0x12000000
|
||||
#define BASE_ADDRESS 0x140000000
|
||||
#define INJECT_HOST_AS_LIB
|
||||
|
||||
#define RVA(ptr) static_cast<std::uint32_t>(reinterpret_cast<std::size_t>(ptr) - BASE_ADDRESS)
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100)
|
||||
#pragma warning(disable: 4127)
|
||||
|
Loading…
Reference in New Issue
Block a user