very early gsc not working

TODO:
- add script_extension.cpp for scripting.cpp
This commit is contained in:
m 2023-12-28 01:59:00 -06:00
parent 6b4dc872c1
commit 3c09055d21
37 changed files with 4141 additions and 36 deletions

3
.gitmodules vendored
View File

@ -31,3 +31,6 @@
[submodule "deps/rapidjson"] [submodule "deps/rapidjson"]
path = deps/rapidjson path = deps/rapidjson
url = https://github.com/Tencent/rapidjson.git url = https://github.com/Tencent/rapidjson.git
[submodule "deps/gsc-tool"]
path = deps/gsc-tool
url = https://github.com/xensik/gsc-tool.git

1
deps/gsc-tool vendored Submodule

@ -0,0 +1 @@
Subproject commit cbfcce1dd67534c2115331e41d4cb6893e96196c

62
deps/premake/gsc-tool.lua vendored Normal file
View File

@ -0,0 +1,62 @@
gsc_tool = {
source = path.join(dependencies.basePath, "gsc-tool")
}
function gsc_tool.import()
links {"xsk-gsc-iw7", "xsk-gsc-utils"}
gsc_tool.includes()
end
function gsc_tool.includes()
includedirs {
path.join(gsc_tool.source, "include")
}
end
function gsc_tool.project()
project "xsk-gsc-utils"
kind "StaticLib"
language "C++"
warnings "Off"
files {
path.join(gsc_tool.source, "include/xsk/utils/*.hpp"),
path.join(gsc_tool.source, "src/utils/*.cpp")
}
includedirs {
path.join(gsc_tool.source, "include")
}
zlib.includes()
project "xsk-gsc-iw7"
kind "StaticLib"
language "C++"
warnings "Off"
filter "action:vs*"
buildoptions "/Zc:__cplusplus"
filter {}
files {
path.join(gsc_tool.source, "include/xsk/stdinc.hpp"),
path.join(gsc_tool.source, "include/xsk/gsc/engine/iw7.hpp"),
path.join(gsc_tool.source, "src/gsc/engine/iw7.cpp"),
path.join(gsc_tool.source, "src/gsc/engine/iw7_code.cpp"),
path.join(gsc_tool.source, "src/gsc/engine/iw7_func.cpp"),
path.join(gsc_tool.source, "src/gsc/engine/iw7_meth.cpp"),
path.join(gsc_tool.source, "src/gsc/engine/iw7_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, "include")
}
end
table.insert(dependencies, gsc_tool)

View File

@ -2,42 +2,202 @@
#include "loader/component_loader.hpp" #include "loader/component_loader.hpp"
#include "filesystem.hpp" #include "filesystem.hpp"
#include "game/game.hpp" #include "component/dvars.hpp"
#include "component/console/console.hpp"
#include "dvars.hpp" #include "game/game.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/io.hpp>
namespace game::filesystem namespace filesystem
{ {
file::file(std::string name) namespace
: name_(std::move(name))
{ {
char* buffer{}; utils::hook::detour fs_startup_hook;
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
if (size >= 0 && buffer) bool initialized = false;
std::deque<std::filesystem::path>& get_search_paths_internal()
{ {
this->valid_ = true; static std::deque<std::filesystem::path> search_paths{};
this->buffer_.append(buffer, size); return search_paths;
game::FS_FreeFile(buffer); }
void fs_startup_stub(const char* name)
{
console::info("[FS] Startup\n");
initialized = true;
filesystem::register_path(L".");
filesystem::register_path(L"iw7-mod");
filesystem::register_path(L"devraw");
filesystem::register_path(L"devraw_shared");
filesystem::register_path(L"raw_shared");
filesystem::register_path(L"raw");
filesystem::register_path(L"main");
fs_startup_hook.invoke<void>(name);
}
std::vector<std::filesystem::path> get_paths(const std::filesystem::path& path)
{
std::vector<std::filesystem::path> paths{};
paths.push_back(path);
return paths;
}
bool can_insert_path(const std::filesystem::path& path)
{
for (const auto& path_ : get_search_paths_internal())
{
if (path_ == path)
{
return false;
} }
} }
bool file::exists() const return true;
{
return this->valid_;
} }
const std::string& file::get_buffer() const const char* sys_default_install_path_stub()
{ {
return this->buffer_; static auto current_path = std::filesystem::current_path().string();
return current_path.data();
}
} }
const std::string& file::get_name() const std::string read_file(const std::string& path)
{ {
return this->name_; for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
return utils::io::read_file(path_.generic_string());
}
}
return {};
}
bool read_file(const std::string& path, std::string* data, std::string* real_path)
{
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::read_file(path_.generic_string(), data))
{
if (real_path != nullptr)
{
*real_path = path_.generic_string();
}
return true;
}
}
return false;
}
bool find_file(const std::string& path, std::string* real_path)
{
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
*real_path = path_.generic_string();
return true;
}
}
return false;
}
bool exists(const std::string& path)
{
for (const auto& search_path : get_search_paths_internal())
{
const auto path_ = search_path / path;
if (utils::io::file_exists(path_.generic_string()))
{
return true;
}
}
return false;
}
void register_path(const std::filesystem::path& path)
{
if (!initialized)
{
return;
}
const auto paths = get_paths(path);
for (const auto& path_ : paths)
{
if (can_insert_path(path_))
{
console::info("[FS] Registering path '%s'\n", path_.generic_string().data());
get_search_paths_internal().push_front(path_);
}
}
}
void unregister_path(const std::filesystem::path& path)
{
if (!initialized)
{
return;
}
const auto paths = get_paths(path);
for (const auto& path_ : paths)
{
auto& search_paths = get_search_paths_internal();
for (auto i = search_paths.begin(); i != search_paths.end();)
{
if (*i == path_)
{
console::debug("[FS] Unregistering path '%s'\n", path_.generic_string().data());
i = search_paths.erase(i);
}
else
{
++i;
}
}
}
}
std::vector<std::string> get_search_paths()
{
std::vector<std::string> paths{};
for (const auto& path : get_search_paths_internal())
{
paths.push_back(path.generic_string());
}
return paths;
}
std::vector<std::string> get_search_paths_rev()
{
std::vector<std::string> paths{};
const auto& search_paths = get_search_paths_internal();
for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i)
{
paths.push_back(i->generic_string());
}
return paths;
} }
class component final : public component_interface class component final : public component_interface
@ -45,9 +205,14 @@ namespace game::filesystem
public: public:
void post_unpack() override void post_unpack() override
{ {
dvars::override::register_string("fs_basegame", "iw7-mod", 2048); fs_startup_hook.create(0xCDD800_b, fs_startup_stub);
utils::hook::jump(0xCFE5E0_b, sys_default_install_path_stub);
// fs_game flags
utils::hook::set<uint32_t>(0xCDD415_b, 0);
} }
}; };
} }
REGISTER_COMPONENT(game::filesystem::component) REGISTER_COMPONENT(filesystem::component)

View File

@ -1,19 +1,15 @@
#pragma once #pragma once
namespace game::filesystem namespace filesystem
{ {
class file std::string read_file(const std::string& path);
{ bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr);
public: bool find_file(const std::string& path, std::string* real_path);
file(std::string name); bool exists(const std::string& path);
[[nodiscard]] bool exists() const; void register_path(const std::filesystem::path& path);
[[nodiscard]] const std::string& get_buffer() const; void unregister_path(const std::filesystem::path& path);
[[nodiscard]] const std::string& get_name() const;
private: std::vector<std::string> get_search_paths();
bool valid_ = false; std::vector<std::string> get_search_paths_rev();
std::string name_;
std::string buffer_;
};
} }

View File

@ -0,0 +1,362 @@
#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>
using namespace utils::string;
namespace gsc
{
namespace
{
utils::hook::detour scr_emit_function_hook;
std::uint32_t 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(std::uint32_t filename, std::uint32_t 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, scripting::current_file_id
);
}
else
{
unknown_function_error = std::format("unknown script '{}' ({})",
scripting::current_file, scripting::current_file_id);
}
}
void get_unknown_function_error(std::uint32_t 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_SCRIPT_DROP, "script link error\n%s", unknown_function_error.data());
}
std::uint32_t find_variable_stub(std::uint32_t parent_id, std::uint32_t thread_name)
{
const auto res = game::FindVariable(parent_id, thread_name);
if (!res)
{
get_unknown_function_error(thread_name);
game::Com_Error(game::ERR_SCRIPT_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(va("Type %s is not an object", var_typename[value->type]));
}
scr_error(va("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(va("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(va("Type %s is not a localized string", var_typename[value->type]));
}
scr_error(va("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(va("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(va("Type %s is not a vector", var_typename[value->type]));
}
scr_error(va("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(va("Type %s is not an int", var_typename[value->type]));
}
scr_error(va("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(va("Type %s is not a float", var_typename[value->type]));
}
scr_error(va("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(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
}
scr_error(va("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(va("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(va("Parameter %u does not exist", index + 1));
return nullptr;
}
template <size_t rva>
void safe_func()
{
static utils::hook::detour hook;
static const auto stub = []()
{
__try
{
hook.invoke<void>();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
game::Scr_ErrorInternal();
}
};
const auto ptr = rva + 0_b;
hook.create(reinterpret_cast<void*>(ptr), stub);
}
}
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(SELECT_VALUE(0x3BD680_b, 0x504660_b), &scr_emit_function_stub);
utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), compile_error_stub); // CompileError (LinkFile)
utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), compile_error_stub); // ^
utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub); // Scr_EmitFunction
// Restore basic error messages for commonly used scr functions
utils::hook::jump(SELECT_VALUE(0x3C89F0_b, 0x50F9E0_b), scr_get_object);
utils::hook::jump(SELECT_VALUE(0x3C84C0_b, 0x50F560_b), scr_get_const_string);
utils::hook::jump(SELECT_VALUE(0x3C8280_b, 0x50F320_b), scr_get_const_istring);
utils::hook::jump(SELECT_VALUE(0x2D6950_b, 0x452EF0_b), scr_validate_localized_string_ref);
utils::hook::jump(SELECT_VALUE(0x3C8F30_b, 0x50FF20_b), scr_get_vector);
utils::hook::jump(SELECT_VALUE(0x3C8930_b, 0x50F920_b), scr_get_int);
utils::hook::jump(SELECT_VALUE(0x3C87D0_b, 0x50F870_b), scr_get_float);
utils::hook::jump(SELECT_VALUE(0x3C8C10_b, 0x50FC00_b), scr_get_pointer_type);
utils::hook::jump(SELECT_VALUE(0x3C8DE0_b, 0x50FDD0_b), scr_get_type);
utils::hook::jump(SELECT_VALUE(0x3C8E50_b, 0x50FE40_b), scr_get_type_name);
if (!game::environment::is_sp())
{
safe_func<0xBA7A0>(); // fix vlobby cac crash
}
}
void pre_destroy() override
{
scr_emit_function_hook.clear();
}
};
}
REGISTER_COMPONENT(gsc::error)
*/

View File

@ -0,0 +1,6 @@
#pragma once
namespace gsc
{
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
}

View File

@ -0,0 +1,471 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "component/command.hpp"
#include "component/console/console.hpp"
#include "component/dvars.hpp"
#include "component/scripting.hpp"
#include "script_extension.hpp"
#include "script_loading.hpp"
#include <utils/hook.hpp>
namespace gsc
{
std::uint16_t function_id_start = 0x30A;
std::uint16_t method_id_start = 0x8586;
builtin_function func_table[0x1000];
builtin_method meth_table[0x1000];
const game::dvar_t* developer_script = nullptr;
namespace
{
std::unordered_map<std::uint16_t, script_function> functions;
std::unordered_map<std::uint16_t, script_method> methods;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
game::scr_entref_t saved_ent_ref;
function_args get_arguments()
{
std::vector<scripting::script_value> args;
for (auto i = 0; static_cast<std::uint32_t>(i) < game::scr_VmPub->outparamcount; ++i)
{
const auto value = game::scr_VmPub->top[-i];
args.push_back(value);
}
return args;
}
void return_value(const scripting::script_value& value)
{
if (game::scr_VmPub->outparamcount)
{
game::Scr_ClearOutParams();
}
scripting::push_value(value);
}
std::uint16_t get_function_id()
{
const auto pos = game::scr_function_stack->pos;
return *reinterpret_cast<std::uint16_t*>(
reinterpret_cast<size_t>(pos - 2));
}
game::scr_entref_t get_entity_id_stub(std::uint32_t ent_id)
{
const auto ref = game::Scr_GetEntityIdRef(ent_id);
saved_ent_ref = ref;
return ref;
}
void execute_custom_function(const std::uint16_t id)
{
try
{
const auto& function = functions[id];
const auto result = function(get_arguments());
const auto type = result.get_raw().type;
if (type)
{
return_value(result);
}
}
catch (const std::exception& ex)
{
scr_error(ex.what());
}
}
void vm_call_builtin_function_stub(builtin_function func)
{
const auto function_id = get_function_id();
const auto custom = functions.contains(static_cast<std::uint16_t>(function_id));
if (custom)
{
execute_custom_function(function_id);
return;
}
if (func == nullptr)
{
scr_error(utils::string::va("builtin function \"%s\" doesn't exist", gsc_ctx->func_name(function_id).data()), true);
return;
}
func();
}
void execute_custom_method(const std::uint16_t id)
{
try
{
const auto& method = methods[id];
const auto result = method(saved_ent_ref, get_arguments());
const auto type = result.get_raw().type;
if (type)
{
return_value(result);
}
}
catch (const std::exception& ex)
{
scr_error(ex.what());
}
}
void vm_call_builtin_method_stub(builtin_method meth)
{
const auto method_id = get_function_id();
const auto custom = methods.contains(static_cast<std::uint16_t>(method_id));
if (custom)
{
execute_custom_method(method_id);
return;
}
if (meth == nullptr)
{
scr_error(utils::string::va("builtin method \"%s\" doesn't exist", gsc_ctx->meth_name(method_id).data()), true);
return;
}
meth(saved_ent_ref);
}
void builtin_call_error(const std::string& error)
{
const auto function_id = get_function_id();
if (function_id > 0x1000)
{
console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
}
else
{
console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
}
}
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
{
try
{
const auto index = gsc_ctx->opcode_enum(opcode);
return { gsc_ctx->opcode_name(index) };
}
catch (...)
{
return {};
}
}
void print_callstack()
{
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
{
const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos;
const auto function = find_function(frame->fs.pos);
if (function.has_value())
{
console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data());
}
else
{
console::warn("\tat unknown location %p\n", pos);
}
}
}
void vm_error_stub(int mark_pos)
{
#ifdef FALSE // TODO
const bool dev_script = developer_script ? developer_script->current.enabled : false;
#else
const bool dev_script = true;
#endif
if (!dev_script && !force_error_print)
{
utils::hook::invoke<void>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos);
return;
}
console::warn("*********** script runtime error *************\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(SELECT_VALUE(0xC4015E8_b, 0xB7B8968_b));
const std::string error_str = gsc_error_msg.has_value()
? utils::string::va(": %s", gsc_error_msg.value().data())
: "";
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF))
{
builtin_call_error(error_str);
}
else
{
const auto opcode = get_opcode_name(opcode_id);
if (opcode.has_value())
{
console::warn("while processing instruction %s%s\n", opcode.value().data(), error_str.data());
}
else
{
console::warn("while processing instruction 0x%X%s\n", opcode_id, error_str.data());
}
}
force_error_print = false;
gsc_error_msg = {};
print_callstack();
console::warn("**********************************************\n");
utils::hook::invoke<void*>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos);
}
void print(const function_args& args)
{
std::string buffer{};
for (auto i = 0u; i < args.size(); ++i)
{
const auto str = args[i].to_string();
buffer.append(str);
buffer.append("\t");
}
console::info("%s\n", buffer.data());
}
scripting::script_value typeof(const function_args& args)
{
return args[0].type_name();
}
}
void scr_error(const char* error, const bool force_print)
{
force_error_print = force_print;
gsc_error_msg = error;
game::Scr_ErrorInternal();
}
namespace function
{
void add(const std::string& name, script_function function)
{
if (gsc_ctx->func_exists(name))
{
const auto id = gsc_ctx->func_id(name);
functions[id] = function;
}
else
{
const auto id = ++function_id_start;
gsc_ctx->func_add(name, static_cast<std::uint16_t>(id));
functions[id] = function;
}
}
}
namespace method
{
void add(const std::string& name, script_method method)
{
if (gsc_ctx->meth_exists(name))
{
const auto id = gsc_ctx->meth_id(name);
methods[id] = method;
}
else
{
const auto id = ++method_id_start;
gsc_ctx->meth_add(name, static_cast<std::uint16_t>(id));
methods[id] = method;
}
}
}
function_args::function_args(std::vector<scripting::script_value> values)
: values_(values)
{
}
std::uint32_t function_args::size() const
{
return static_cast<std::uint32_t>(this->values_.size());
}
std::vector<scripting::script_value> function_args::get_raw() const
{
return this->values_;
}
scripting::value_wrap function_args::get(const int index) const
{
if (index >= this->values_.size())
{
throw std::runtime_error(utils::string::va("parameter %d does not exist", index));
}
return {this->values_[index], index};
}
class extension final : public component_interface
{
public:
void post_unpack() override
{
#ifdef FALSE // TODO
developer_script = dvars::register_bool("developer_script", false, 0, "Enable developer script comments");
#endif
utils::hook::set<uint32_t>(0xBFD16C_b, 0x1000); // change builtin func count
utils::hook::set<uint32_t>(0xBFD172_b + 4,
static_cast<uint32_t>(reverse_b((&func_table))));
// TODO
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4,
static_cast<uint32_t>(reverse_b((&func_table))));
utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table);
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BDC1E_b, 0x504C4E_b), sizeof(func_table));
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4,
static_cast<uint32_t>(reverse_b((&meth_table))));
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4,
static_cast<uint32_t>(reverse_b(&meth_table)));
utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table);
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BDC3F_b, 0x504C6F_b), sizeof(meth_table));
utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8);
utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub);
utils::hook::call(SELECT_VALUE(0x3CBA12_b, 0x512A72_b), get_entity_id_stub);
utils::hook::nop(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), 6);
utils::hook::nop(SELECT_VALUE(0x3CBA4E_b, 0x512AAE_b), 2);
utils::hook::call(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), vm_call_builtin_method_stub);
utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub); // LargeLocalResetToMark
if (game::environment::is_dedi())
{
function::add("isusingmatchrulesdata", [](const function_args& args)
{
// return 0 so the game doesn't override the cfg
return 0;
});
}
/*
function::add("print", [](const function_args& args)
{
print(args);
return scripting::script_value{};
});
function::add("println", [](const function_args& args)
{
print(args);
return scripting::script_value{};
});
function::add("assert", [](const function_args& args)
{
const auto expr = args[0].as<int>();
if (!expr)
{
throw std::runtime_error("assert fail");
}
return scripting::script_value{};
});
function::add("assertex", [](const function_args& args)
{
const auto expr = args[0].as<int>();
if (!expr)
{
const auto error = args[1].as<std::string>();
throw std::runtime_error(error);
}
return scripting::script_value{};
});
function::add("getfunction", [](const function_args& args)
{
const auto filename = args[0].as<std::string>();
const auto function = args[1].as<std::string>();
if (!scripting::script_function_table[filename].contains(function))
{
throw std::runtime_error("function not found");
}
return scripting::function{scripting::script_function_table[filename][function]};
});
function::add("replacefunc", [](const function_args& args)
{
const auto what = args[0].get_raw();
const auto with = args[1].get_raw();
if (what.type != game::VAR_FUNCTION || with.type != game::VAR_FUNCTION)
{
throw std::runtime_error("replacefunc: parameter 1 must be a function");
}
logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
return scripting::script_value{};
});
function::add("toupper", [](const function_args& args)
{
const auto string = args[0].as<std::string>();
return utils::string::to_upper(string);
});
function::add("logprint", [](const function_args& args)
{
std::string buffer{};
for (auto i = 0u; i < args.size(); ++i)
{
const auto string = args[i].as<std::string>();
buffer.append(string);
}
game::G_LogPrintf("%s", buffer.data());
return scripting::script_value{};
});
function::add("executecommand", [](const function_args& args)
{
command::execute(args[0].as<std::string>(), false);
return scripting::script_value{};
});
function::add("typeof", typeof);
function::add("type", typeof);
*/
}
};
}
REGISTER_COMPONENT(gsc::extension)

View File

@ -0,0 +1,48 @@
#pragma once
#include "game/scripting/array.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/function.hpp"
namespace gsc
{
class function_args
{
public:
function_args(std::vector<scripting::script_value>);
unsigned int size() const;
std::vector<scripting::script_value> get_raw() const;
scripting::value_wrap get(const int index) const;
scripting::value_wrap operator[](const int index) const
{
return this->get(index);
}
private:
std::vector<scripting::script_value> values_;
};
using builtin_function = void(*)();
using builtin_method = void(*)(game::scr_entref_t);
using script_function = std::function<scripting::script_value(const function_args&)>;
using script_method = std::function<scripting::script_value(const game::scr_entref_t, const function_args&)>;
extern builtin_function func_table[0x1000];
extern builtin_method meth_table[0x1000];
extern const game::dvar_t* developer_script;
void scr_error(const char* error, const bool force_print = false);
namespace function
{
void add(const std::string& name, script_function function);
}
namespace method
{
void add(const std::string& name, script_method function);
}
}

View File

@ -0,0 +1,428 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "component/console/console.hpp"
#include "component/fastfiles.hpp"
#include "component/filesystem.hpp"
#include "component/scripting.hpp"
#include "game/game.hpp"
#include "script_loading.hpp"
#include <utils/compression.hpp>
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace gsc
{
std::unique_ptr<xsk::gsc::iw7::context> gsc_ctx = std::make_unique<xsk::gsc::iw7::context>();;
namespace
{
utils::hook::detour scr_begin_load_scripts_hook;
utils::hook::detour scr_end_load_scripts_hook;
std::unordered_map<std::string, std::uint32_t> main_handles;
std::unordered_map<std::string, std::uint32_t> init_handles;
utils::memory::allocator scriptfile_allocator;
std::unordered_map<const char*, game::ScriptFile*> loaded_scripts;
struct
{
char* buf = nullptr;
char* pos = nullptr;
const unsigned int size = 0x100000i64;
} script_memory;
char* allocate_buffer(size_t size)
{
if (script_memory.buf == nullptr)
{
game::PMem_BeginAlloc("custom_gsc_script", game::PMEM_STACK_GAME);
script_memory.buf = game::PMem_AllocFromSource_NoDebug(script_memory.size, 4, game::PMEM_SOURCE_EXTERNAL); // PMEM_SOURCE_SCRIPT, defined as 0 on IW7??
game::PMem_EndAlloc("custom_gsc_script", game::PMEM_STACK_GAME);
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_Free("custom_gsc_script", game::PMEM_STACK_GAME);
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_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_str, 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(file_name); itr != loaded_scripts.end())
{
return itr->second;
}
if (game::Com_FrontEndScene_IsActive())
{
return nullptr;
}
std::string source_buffer{};
if (!read_raw_script_file(real_name + ".gsc", &source_buffer) || source_buffer.empty())
{
return nullptr;
}
// filter out "GSC rawfiles" that were used for development usage and are not meant for us.
// each "GSC rawfile" has a ScriptFile counterpart to be used instead
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, file_name) &&
!game::DB_IsXAssetDefault(game::ASSET_TYPE_SCRIPTFILE, file_name))
{
if ((real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart") || real_name.starts_with("maps/mp"))
&& (real_name.ends_with("_fx") || real_name.ends_with("_fog") || real_name.ends_with("_hdr")))
{
console::debug("Refusing to compile rawfile '%s'\n", real_name.data());
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, file_name, false).scriptfile;
}
}
console::debug("Loading custom gsc '%s.gsc'", real_name.data());
try
{
auto& compiler = gsc_ctx->compiler();
auto& assembler = gsc_ctx->assembler();
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 bytecode = output_script.first; // formerly named "script"
const auto stack = output_script.second;
const auto script_file_ptr = static_cast<game::ScriptFile*>(scriptfile_allocator.allocate(sizeof(game::ScriptFile)));
script_file_ptr->name = file_name;
script_file_ptr->len = static_cast<int>(stack.size);
script_file_ptr->bytecodeLen = static_cast<int>(bytecode.size);
const auto stack_size = static_cast<std::uint32_t>(stack.size + 1);
const auto byte_code_size = static_cast<std::uint32_t>(bytecode.size + 1);
script_file_ptr->buffer = static_cast<char*>(scriptfile_allocator.allocate(stack_size));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), stack.data, stack.size);
script_file_ptr->bytecode = allocate_buffer(byte_code_size);
std::memcpy(script_file_ptr->bytecode, bytecode.data, bytecode.size);
script_file_ptr->compressedLen = 0;
loaded_scripts[file_name] = script_file_ptr;
return script_file_ptr;
}
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;
}
}
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);
}
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> 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 == nullptr)
{
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
}
console::debug("Decompiling scriptfile '%s'\n", real_name.data());
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());
return {{reinterpret_cast<std::uint8_t*>(script_file->bytecode), static_cast<std::uint32_t>(script_file->bytecodeLen)}, 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::debug("Loaded '%s::main'\n", name.data());
main_handles[name] = main_handle;
}
if (init_handle)
{
console::debug("Loaded '%s::init'\n", name.data());
init_handles[name] = init_handle;
}
}
void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& 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_scripts_stub(void* a1, void* a2)
{
utils::hook::invoke<void>(0xB50670_b, a1, a2);
if (!game::Com_FrontEndScene_IsActive())
{
for (const auto& path : filesystem::get_search_paths())
{
load_scripts(path, "scripts/");
}
}
}
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 scr_begin_load_scripts_stub(bool a1)
{
const bool dev_script = true;
const auto comp_mode = dev_script ?
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};
});
scr_begin_load_scripts_hook.invoke<void>(a1);
}
void scr_end_load_scripts_stub()
{
// cleanup the compiler
gsc_ctx->cleanup();
scr_end_load_scripts_hook.invoke<void>();
}
utils::hook::detour g_load_structs_hook;
void g_load_structs_stub()
{
for (auto& function_handle : main_handles)
{
printf("Executing '%s::main'\n", function_handle.first.data());
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
}
g_load_structs_hook.invoke<void>();
}
utils::hook::detour scr_load_level_hook;
void scr_load_level_stub()
{
for (auto& function_handle : init_handles)
{
printf("Executing '%s::init'\n", function_handle.first.data());
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
}
scr_load_level_hook.invoke<void>();
}
utils::hook::detour g_shutdown_game_hook;
void g_shutdown_game_stub(bool full_clear)
{
clear();
g_shutdown_game_hook.invoke<void>(full_clear);
}
}
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 = 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_unpack() override
{
// Load our scripts with an uncompressed stack
utils::hook::call(0xC09DA7_b, db_get_raw_buffer_stub);
scr_begin_load_scripts_hook.create(0xBFD500_b, scr_begin_load_scripts_stub);
scr_end_load_scripts_hook.create(0xBFD630_b, scr_end_load_scripts_stub);
// ProcessScript: hook xasset functions to return our own custom scripts
utils::hook::call(0xC09D37_b, find_script);
utils::hook::call(0xC09D47_b, db_is_x_asset_default);
// GScr_LoadScripts: initial loading of scripts
utils::hook::call(0xB5CB70_b, load_scripts_stub);
// execute main handle after G_LoadStructs (now called G_Spawn_LoadStructs)
g_load_structs_hook.create(0x409FB0_b, g_load_structs_stub);
// execute init handle
scr_load_level_hook.create(0xB51B40_b, scr_load_level_stub);
// clear memory (G_MainMP_ShutdownGameMemory)
g_shutdown_game_hook.create(0xC56C80_b, g_shutdown_game_stub);
}
void pre_destroy() override
{
scr_begin_load_scripts_hook.clear();
scr_end_load_scripts_hook.clear();
}
};
}
REGISTER_COMPONENT(gsc::loading)

View File

@ -0,0 +1,9 @@
#pragma once
#include <xsk/gsc/engine/iw7.hpp>
namespace gsc
{
extern std::unique_ptr<xsk::gsc::iw7::context> gsc_ctx;
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
}

View File

@ -198,10 +198,16 @@ namespace patches
char* db_read_raw_file_stub(const char* filename, char* buf, const int size) char* db_read_raw_file_stub(const char* filename, char* buf, const int size)
{ {
const auto file = game::filesystem::file(filename); std::string file_name = filename;
if (file.exists()) if (file_name.find(".cfg") == std::string::npos)
{ {
snprintf(buf, size, "%s\n", file.get_buffer().data()); file_name.append(".cfg");
}
std::string buffer{};
if (filesystem::read_file(file_name, &buffer))
{
snprintf(buf, size, "%s\n", buffer.data());
return buf; return buf;
} }

View File

@ -0,0 +1,221 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "component/gsc/script_loading.hpp"
#include "component/scheduler.hpp"
#include "component/scripting.hpp"
#include "component/console/console.hpp"
#include "game/game.hpp"
#include "game/scripting/event.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/functions.hpp"
#include <utils/hook.hpp>
namespace scripting
{
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
utils::concurrency::container<shared_table_t> shared_table;
std::string current_file;
unsigned int current_file_id{};
namespace
{
utils::hook::detour vm_notify_hook;
utils::hook::detour vm_execute_hook;
utils::hook::detour g_load_structs_hook;
utils::hook::detour scr_load_level_hook;
utils::hook::detour g_shutdown_game_hook;
utils::hook::detour scr_add_class_field_hook;
utils::hook::detour scr_set_thread_position_hook;
utils::hook::detour process_script_hook;
utils::hook::detour sl_get_canonical_string_hook;
utils::hook::detour db_find_xasset_header_hook;
const char* current_script_file_name;
game::dvar_t* g_dump_scripts;
std::vector<std::function<void(bool, bool)>> shutdown_callbacks;
std::unordered_map<unsigned int, std::string> canonical_string_table;
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
game::VariableValue* top)
{
if (!game::Com_FrontEndScene_IsActive())
{
const auto* string = game::SL_ConvertToString(string_value);
if (string)
{
event e{};
e.name = string;
e.entity = notify_list_owner_id;
for (auto* value = top; value->type != game::VAR_PRECODEPOS; --value)
{
e.arguments.emplace_back(*value);
}
}
}
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
}
void g_shutdown_game_stub(const int free_scripts)
{
if (free_scripts)
{
script_function_table_sort.clear();
script_function_table.clear();
script_function_table_rev.clear();
canonical_string_table.clear();
}
for (const auto& callback : shutdown_callbacks)
{
callback(free_scripts, false);
}
scripting::notify(*game::levelEntityId, "shutdownGame_called", {1});
g_shutdown_game_hook.invoke<void>();
for (const auto& callback : shutdown_callbacks)
{
callback(free_scripts, true);
}
}
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset)
{
const auto name_str = game::SL_ConvertToString(name);
if (fields_table[classnum].find(name_str) == fields_table[classnum].end())
{
fields_table[classnum][name_str] = offset;
}
scr_add_class_field_hook.invoke<void>(classnum, name, canonical_string, offset);
}
void process_script_stub(const char* filename)
{
current_script_file_name = filename;
const auto file_id = atoi(filename);
if (file_id)
{
current_file_id = static_cast<std::uint16_t>(file_id);
current_file = scripting::get_token(current_file_id);
}
else
{
current_file_id = 0;
current_file = filename;
}
process_script_hook.invoke<void>(filename);
}
void add_function_sort(unsigned int id, const char* pos)
{
std::string filename = current_file;
if (!script_function_table_sort.contains(filename))
{
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file_name, false);
if (script)
{
const auto end = &script->bytecode[script->bytecodeLen];
script_function_table_sort[filename].emplace_back("__end__", end);
}
}
const auto name = scripting::get_token(id);
auto& itr = script_function_table_sort[filename];
itr.insert(itr.end() - 1, {name, pos});
}
void add_function(const std::string& file, unsigned int id, const char* pos)
{
const auto name = get_token(id);
script_function_table[file][name] = pos;
script_function_table_rev[pos] = {file, name};
}
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
{
add_function_sort(thread_name, code_pos);
add_function(current_file, thread_name, code_pos);
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
}
unsigned int sl_get_canonical_string_stub(const char* str)
{
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
canonical_string_table[result] = str;
return result;
}
}
std::string get_token(unsigned int id)
{
if (canonical_string_table.find(id) != canonical_string_table.end())
{
return canonical_string_table[id];
}
return scripting::find_token(id);
}
void on_shutdown(const std::function<void(bool, bool)>& callback)
{
shutdown_callbacks.push_back(callback);
}
std::optional<std::string> get_canonical_string(const unsigned int id)
{
if (canonical_string_table.find(id) == canonical_string_table.end())
{
return {};
}
return {canonical_string_table[id]};
}
class component final : public component_interface
{
public:
void post_unpack() override
{
vm_notify_hook.create(0xC10460_b, vm_notify_stub);
scr_add_class_field_hook.create(0xC061F0_b, scr_add_class_field_stub);
scr_set_thread_position_hook.create(0xBFD190_b, scr_set_thread_position_stub);
process_script_hook.create(0xC09D20_b, process_script_stub);
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub);
// clear memory (SV_GameMP_ShutdownGameVM)
g_shutdown_game_hook.create(0xB21CC0_b, g_shutdown_game_stub);
}
};
}
REGISTER_COMPONENT(scripting::component)

View File

@ -0,0 +1,21 @@
#pragma once
#include <utils/concurrency.hpp>
namespace scripting
{
using shared_table_t = std::unordered_map<std::string, std::string>;
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
extern utils::concurrency::container<shared_table_t> shared_table;
extern std::string current_file;
extern unsigned int current_file_id;
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(unsigned int id);
}

View File

@ -0,0 +1,335 @@
#include <std_include.hpp>
#include "array.hpp"
#include "script_value.hpp"
#include "execution.hpp"
namespace scripting
{
array_value::array_value(unsigned int parent_id, unsigned int id)
: id_(id)
, parent_id_(parent_id)
{
if (!this->id_)
{
return;
}
const auto value = game::scr_VarGlob->childVariableValue[this->id_ + 0xFA00 * (this->parent_id_ & 3)];
game::VariableValue variable;
variable.u = value.u.u;
variable.type = value.type;
this->value_ = variable;
}
void array_value::operator=(const script_value& _value)
{
if (!this->id_)
{
return;
}
const auto value = _value.get_raw();
const auto variable = &game::scr_VarGlob->childVariableValue[this->id_ + 0xFA00 * (this->parent_id_ & 3)];
game::AddRefToValue(value.type, value.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)value.type;
variable->u.u = value.u;
this->value_ = value;
}
array::array(const unsigned int id)
: id_(id)
{
this->add();
}
array::array(const array& other) : array(other.id_)
{
}
array::array(array&& other) noexcept
{
this->id_ = other.id_;
other.id_ = 0;
}
array::array()
{
this->id_ = make_array();
}
array::array(std::vector<script_value> values)
{
this->id_ = make_array();
for (const auto& value : values)
{
this->push(value);
}
}
array::array(std::unordered_map<std::string, script_value> values)
{
this->id_ = make_array();
for (const auto& value : values)
{
this->set(value.first, value.second);
}
}
array::~array()
{
this->release();
}
array& array::operator=(const array& other)
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
this->add();
}
return *this;
}
array& array::operator=(array&& other) noexcept
{
if (&other != this)
{
this->release();
this->id_ = other.id_;
other.id_ = 0;
}
return *this;
}
void array::add() const
{
if (this->id_)
{
game::AddRefToValue(game::VAR_POINTER, {static_cast<int>(this->id_)});
}
}
void array::release() const
{
if (this->id_)
{
game::RemoveRefToValue(game::VAR_POINTER, {static_cast<int>(this->id_)});
}
}
std::vector<script_value> array::get_keys() const
{
std::vector<script_value> result;
const auto offset = 0xFA00 * (this->id_ & 3);
auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild;
for (auto i = offset + current; current; i = offset + current)
{
const auto var = game::scr_VarGlob->childVariableValue[i];
if (var.type == game::VAR_UNDEFINED)
{
current = var.nextSibling;
continue;
}
const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8));
const auto* str = game::SL_ConvertToString(string_value);
script_value key;
if (string_value < 0x40000 && str)
{
key = str;
}
else
{
key = (string_value - 0x800000) & 0xFFFFFF;
}
result.push_back(key);
current = var.nextSibling;
}
return result;
}
int array::size() const
{
return static_cast<int>(game::scr_VarGlob->objectVariableValue[this->id_].u.f.next);
}
unsigned int array::push(script_value value) const
{
this->set(this->size(), value);
return this->size();
}
void array::erase(const unsigned int index) const
{
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
if (variable_id)
{
game::RemoveVariableValue(this->id_, variable_id);
}
}
void array::erase(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0);
const auto variable_id = game::FindVariable(this->id_, string_value);
if (variable_id)
{
game::RemoveVariableValue(this->id_, variable_id);
}
}
script_value array::pop() const
{
const auto value = this->get(this->size() - 1);
this->erase(this->size() - 1);
return value;
}
script_value array::get(const script_value& key) const
{
if (key.is<int>())
{
return this->get(key.as<int>());
}
else
{
return this->get(key.as<std::string>());
}
}
script_value array::get(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0);
const auto variable_id = game::FindVariable(this->id_, string_value);
if (!variable_id)
{
return {};
}
const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
game::VariableValue variable;
variable.u = value.u.u;
variable.type = value.type;
return variable;
}
script_value array::get(const unsigned int index) const
{
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
if (!variable_id)
{
return {};
}
const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
game::VariableValue variable;
variable.u = value.u.u;
variable.type = value.type;
return variable;
}
void array::set(const script_value& key, const script_value& value) const
{
if (key.is<int>())
{
this->set(key.as<int>(), value);
}
else
{
this->set(key.as<std::string>(), value);
}
}
void array::set(const std::string& key, const script_value& _value) const
{
const auto value = _value.get_raw();
const auto variable_id = this->get_value_id(key);
if (!variable_id)
{
return;
}
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
game::AddRefToValue(value.type, value.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)value.type;
variable->u.u = value.u;
}
void array::set(const unsigned int index, const script_value& _value) const
{
const auto value = _value.get_raw();
const auto variable_id = this->get_value_id(index);
if (!variable_id)
{
return;
}
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
game::AddRefToValue(value.type, value.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)value.type;
variable->u.u = value.u;
}
unsigned int array::get_entity_id() const
{
return this->id_;
}
unsigned int array::get_value_id(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0);
const auto variable_id = game::FindVariable(this->id_, string_value);
if (!variable_id)
{
return game::GetNewVariable(this->id_, string_value);
}
return variable_id;
}
unsigned int array::get_value_id(const unsigned int index) const
{
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
if (!variable_id)
{
return game::GetNewArrayVariable(this->id_, index);
}
return variable_id;
}
entity array::get_raw() const
{
return entity(this->id_);
}
}

View File

@ -0,0 +1,89 @@
#pragma once
#include "game/game.hpp"
#include "script_value.hpp"
namespace scripting
{
class array_value : public script_value
{
public:
array_value(unsigned int, unsigned int);
void operator=(const script_value&);
private:
unsigned int id_;
unsigned int parent_id_;
};
class array final
{
public:
array();
array(const unsigned int);
array(std::vector<script_value>);
array(std::unordered_map<std::string, script_value>);
array(const array& other);
array(array&& other) noexcept;
~array();
array& operator=(const array& other);
array& operator=(array&& other) noexcept;
std::vector<script_value> get_keys() const;
int size() const;
unsigned int push(script_value) const;
void erase(const unsigned int) const;
void erase(const std::string&) const;
script_value pop() const;
script_value get(const script_value&) const;
script_value get(const std::string&) const;
script_value get(const unsigned int) const;
void set(const script_value&, const script_value&) const;
void set(const std::string&, const script_value&) const;
void set(const unsigned int, const script_value&) const;
unsigned int get_entity_id() const;
unsigned int get_value_id(const std::string&) const;
unsigned int get_value_id(const unsigned int) const;
entity get_raw() const;
array_value operator[](const int index) const
{
return {this->id_, this->get_value_id(index)};
}
array_value operator[](const std::string& key) const
{
return {this->id_, this->get_value_id(key)};
}
template <typename I = int, typename S = std::string>
array_value operator[](const script_value& key) const
{
if (key.is<I>())
{
return { this->id_, this->get_value_id(key.as<I>()) };
}
if (key.is<S>())
{
return { this->id_, this->get_value_id(key.as<S>()) };
}
throw std::runtime_error("Invalid key type");
}
private:
void add() const;
void release() const;
unsigned int id_;
};
}

View File

@ -0,0 +1,120 @@
#include <std_include.hpp>
#include "entity.hpp"
#include "script_value.hpp"
#include "execution.hpp"
namespace scripting
{
entity::entity()
: entity(0)
{
}
entity::entity(const entity& other) : entity(other.entity_id_)
{
}
entity::entity(entity&& other) noexcept
{
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
entity::entity(const unsigned int entity_id)
: entity_id_(entity_id)
{
this->add();
}
entity::entity(game::scr_entref_t entref)
: entity(game::FindEntityId(entref.entnum, entref.classnum))
{
}
entity::~entity()
{
this->release();
}
entity& entity::operator=(const entity& other)
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
this->add();
}
return *this;
}
entity& entity::operator=(entity&& other) noexcept
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
return *this;
}
unsigned int entity::get_entity_id() const
{
return this->entity_id_;
}
game::scr_entref_t entity::get_entity_reference() const
{
if (!this->entity_id_)
{
const auto not_null = static_cast<uint16_t>(~0ui16);
return game::scr_entref_t{not_null, not_null};
}
return game::Scr_GetEntityIdRef(this->get_entity_id());
}
bool entity::operator==(const entity& other) const noexcept
{
return this->get_entity_id() == other.get_entity_id();
}
bool entity::operator!=(const entity& other) const noexcept
{
return !this->operator==(other);
}
void entity::add() const
{
if (this->entity_id_)
{
game::AddRefToValue(game::VAR_POINTER, {static_cast<int>(this->entity_id_)});
}
}
void entity::release() const
{
if (this->entity_id_)
{
game::RemoveRefToValue(game::VAR_POINTER, {static_cast<int>(this->entity_id_)});
}
}
void entity::set(const std::string& field, const script_value& value) const
{
set_entity_field(*this, field, value);
}
template <>
script_value entity::get<script_value>(const std::string& field) const
{
return get_entity_field(*this, field);
}
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
{
return call_function(name, *this, arguments);
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "game/game.hpp"
#include "script_value.hpp"
namespace scripting
{
class entity final
{
public:
entity();
entity(unsigned int entity_id);
entity(game::scr_entref_t entref);
entity(const entity& other);
entity(entity&& other) noexcept;
~entity();
entity& operator=(const entity& other);
entity& operator=(entity&& other) noexcept;
void set(const std::string& field, const script_value& value) const;
template <typename T = script_value>
T get(const std::string& field) const;
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
unsigned int get_entity_id() const;
game::scr_entref_t get_entity_reference() const;
bool operator ==(const entity& other) const noexcept;
bool operator !=(const entity& other) const noexcept;
private:
unsigned int entity_id_;
void add() const;
void release() const;
};
template <>
script_value entity::get(const std::string& field) const;
template <typename T>
T entity::get(const std::string& field) const
{
return this->get<script_value>(field).as<T>();
}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "script_value.hpp"
#include "entity.hpp"
namespace scripting
{
struct event
{
std::string name;
entity entity{};
std::vector<script_value> arguments;
};
}

View File

@ -0,0 +1,251 @@
#include <std_include.hpp>
#include "execution.hpp"
#include "safe_execution.hpp"
#include "stack_isolation.hpp"
#include "component/scripting.hpp"
namespace scripting
{
namespace
{
game::VariableValue* allocate_argument()
{
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
++game::scr_VmPub->inparamcount;
return value_ptr;
}
int get_field_id(const int classnum, const std::string& field)
{
if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end())
{
return scripting::fields_table[classnum][field];
}
return -1;
}
script_value get_return_value()
{
if (game::scr_VmPub->inparamcount == 0)
{
return {};
}
game::Scr_ClearOutParams();
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
}
}
void push_value(const script_value& value)
{
auto* value_ptr = allocate_argument();
*value_ptr = value.get_raw();
game::AddRefToValue(value_ptr->type, value_ptr->u);
}
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
{
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
const auto event_id = game::SL_GetString(event.data(), 0);
game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
}
script_value call_function(const std::string& name, const entity& entity,
const std::vector<script_value>& arguments)
{
const auto entref = entity.get_entity_reference();
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
const auto function = find_function(name, !is_method_call);
if (function == nullptr)
{
throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'");
}
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
if (!safe_execution::call(function, entref))
{
throw std::runtime_error(
"Error executing "s + (is_method_call ? "method" : "function") + " '" + name + "'");
}
return get_return_value();
}
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
{
return call_function(name, entity(), arguments);
}
template <>
script_value call(const std::string& name, const std::vector<script_value>& arguments)
{
return call_function(name, arguments);
}
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
{
const auto id = entity.get_entity_id();
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
game::AddRefToObject(id);
const auto local_id = game::AllocThread(id);
const auto result = game::VM_Execute(local_id, pos, static_cast<std::uint32_t>(arguments.size()));
game::RemoveRefToObject(result);
return get_return_value();
}
const char* get_function_pos(const std::string& filename, const std::string& function)
{
if (!script_function_table.contains(filename))
{
throw std::runtime_error("File '" + filename + "' not found");
}
const auto& functions = script_function_table[filename];
if (!functions.contains(function))
{
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
}
return functions.at(function);
}
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments)
{
const auto pos = get_function_pos(filename, function);
return exec_ent_thread(entity, pos, arguments);
}
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto entref = entity.get_entity_reference();
const int id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
push_value(value);
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
if (!safe_execution::set_entity_field(entref, id))
{
throw std::runtime_error("Failed to set value for field '" + field + "'");
}
}
else
{
set_object_variable(entity.get_entity_id(), field, value);
}
}
script_value get_entity_field(const entity& entity, const std::string& field)
{
const auto entref = entity.get_entity_reference();
const auto id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
game::VariableValue value{};
if (!safe_execution::get_entity_field(entref, id, &value))
{
throw std::runtime_error("Failed to get value for field '" + field + "'");
}
const auto _0 = gsl::finally([value]
{
game::RemoveRefToValue(value.type, value.u);
});
return value;
}
return get_object_variable(entity.get_entity_id(), field);
}
unsigned int make_array()
{
unsigned int index = 0;
const auto variable = game::AllocVariable(&index);
variable->w.type = game::VAR_ARRAY;
variable->u.f.prev = 0;
variable->u.f.next = 0;
return index;
}
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value)
{
const auto offset = 0xFA00 * (parent_id & 3);
const auto variable_id = game::GetVariable(parent_id, id);
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
const auto& raw_value = value.get_raw();
game::AddRefToValue(raw_value.type, raw_value.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = static_cast<char>(raw_value.type);
variable->u.u = raw_value.u;
}
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value)
{
const auto id = scripting::find_token_id(name);
set_object_variable(parent_id, id, value);
}
script_value get_object_variable(const unsigned int parent_id, const unsigned int id)
{
const auto offset = 0xFA00 * (parent_id & 3);
const auto variable_id = game::FindVariable(parent_id, id);
if (!variable_id)
{
return {};
}
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
game::VariableValue value{};
value.type = static_cast<int>(variable->type);
value.u = variable->u.u;
return value;
}
script_value get_object_variable(const unsigned int parent_id, const std::string& name)
{
const auto id = scripting::find_token_id(name);
return get_object_variable(parent_id, id);
}
}

View File

@ -0,0 +1,47 @@
#pragma once
#include "game/game.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "function.hpp"
#include "script_value.hpp"
namespace scripting
{
void push_value(const script_value& value);
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
script_value call_function(const std::string& name, const entity& entity,
const std::vector<script_value>& arguments);
template <typename T = script_value>
T call(const std::string& name, const std::vector<script_value>& arguments = {});
template <>
script_value call(const std::string& name, const std::vector<script_value>& arguments);
template <typename T>
T call(const std::string& name, const std::vector<script_value>& arguments)
{
return call<script_value>(name, arguments).as<T>();
}
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
const char* get_function_pos(const std::string& filename, const std::string& function);
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments);
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
script_value get_entity_field(const entity& entity, const std::string& field);
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
unsigned int make_array();
script_value get_object_variable(const unsigned int parent_id, const unsigned int id);
script_value get_object_variable(const unsigned int parent_id, const std::string& name);
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value);
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value);
}

View File

@ -0,0 +1,44 @@
#include <std_include.hpp>
#include "game/scripting/function.hpp"
#include "game/scripting/execution.hpp"
#include "component/scripting.hpp"
namespace scripting
{
function::function(const char* pos)
: pos_(pos)
{
}
script_value function::get_raw() const
{
game::VariableValue value;
value.type = game::VAR_FUNCTION;
value.u.codePosValue = this->pos_;
return value;
}
const char* function::get_pos() const
{
return this->pos_;
}
std::string function::get_name() const
{
if (scripting::script_function_table_rev.contains(this->pos_))
{
const auto& func = scripting::script_function_table_rev[this->pos_];
return utils::string::va("%s::%s", func.first.data(), func.second.data());
}
return "unknown function";
}
script_value function::call(const entity& self, std::vector<script_value> arguments) const
{
return exec_ent_thread(self, this->pos_, arguments);
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "entity.hpp"
#include "script_value.hpp"
namespace scripting
{
class function
{
public:
function(const char*);
script_value get_raw() const;
const char* get_pos() const;
std::string get_name() const;
script_value call(const entity& self, std::vector<script_value> arguments) const;
script_value operator()(const entity& self, std::vector<script_value> arguments) const
{
return this->call(self, arguments);
}
script_value operator()(std::vector<script_value> arguments) const
{
return this->call(*game::levelEntityId, arguments);
}
script_value operator()() const
{
return this->call(*game::levelEntityId, {});
}
private:
const char* pos_;
};
}

View File

@ -0,0 +1,107 @@
#include <std_include.hpp>
#include "functions.hpp"
#include <utils/string.hpp>
#include "component/gsc/script_loading.hpp"
namespace scripting
{
namespace
{
int find_function_index(const std::string& name, [[maybe_unused]] const bool prefer_global)
{
const auto target = utils::string::to_lower(name);
auto const& first = gsc::gsc_ctx->func_map();
auto const& second = gsc::gsc_ctx->meth_map();
if (!prefer_global)
{
if (const auto itr = second.find(name); itr != second.end())
{
return static_cast<int>(itr->second);
}
if (const auto itr = first.find(name); itr != first.end())
{
return static_cast<int>(itr->second);
}
}
if (const auto itr = first.find(name); itr != first.end())
{
return static_cast<int>(itr->second);
}
if (const auto itr = second.find(name); itr != second.end())
{
return static_cast<int>(itr->second);
}
return -1;
}
}
std::uint32_t parse_token_id(const std::string& name)
{
if (name.starts_with("_ID"))
{
return static_cast<std::uint32_t>(std::strtol(name.substr(3).data(), nullptr, 10));
}
if (name.starts_with("_id_"))
{
return static_cast<std::uint32_t>(std::strtol(name.substr(4).data(), nullptr, 16));
}
return 0;
}
std::string find_token(std::uint32_t id)
{
return gsc::gsc_ctx->token_name(id);
}
std::string find_token_single(std::uint32_t id)
{
return gsc::gsc_ctx->token_name(id);
}
unsigned int find_token_id(const std::string& name)
{
const auto id = gsc::gsc_ctx->token_id(name);
if (id)
{
return id;
}
const auto parsed_id = parse_token_id(name);
if (parsed_id)
{
return parsed_id;
}
return game::SL_GetCanonicalString(name.data());
}
script_function get_function_by_index(const std::uint32_t index)
{
static const auto function_table = &gsc::func_table;
static const auto method_table = &gsc::meth_table;
if (index < 0x1000)
{
return reinterpret_cast<script_function*>(function_table)[index - 1];
}
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
}
script_function find_function(const std::string& name, const bool prefer_global)
{
const auto index = find_function_index(name, prefer_global);
if (index < 0) return nullptr;
return get_function_by_index(index);
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
using script_function = void(*)(game::scr_entref_t);
std::string find_token(std::uint32_t id);
std::string find_token_single(std::uint32_t id);
unsigned int find_token_id(const std::string& name);
script_function get_function_by_index(std::uint32_t index);
script_function find_function(const std::string& name, const bool prefer_global);
}

View File

@ -0,0 +1,72 @@
#include <std_include.hpp>
#include "safe_execution.hpp"
#pragma warning(push)
#pragma warning(disable: 4611)
namespace scripting::safe_execution
{
namespace
{
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
{
__try
{
function(entref);
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
}
bool call(const script_function function, const game::scr_entref_t& entref)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
*game::g_script_error_level -= 1;
return false;
}
const auto result = execute_with_seh(function, entref);
*game::g_script_error_level -= 1;
return result;
}
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
*game::g_script_error_level -= 1;
return false;
}
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
*game::g_script_error_level -= 1;
return true;
}
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
value->type = game::VAR_UNDEFINED;
value->u.intValue = 0;
*game::g_script_error_level -= 1;
return false;
}
game::GetEntityFieldValue(value, entref.classnum, entref.entnum, offset);
*game::g_script_error_level -= 1;
return true;
}
}
#pragma warning(pop)

View File

@ -0,0 +1,10 @@
#pragma once
#include "functions.hpp"
namespace scripting::safe_execution
{
bool call(script_function function, const game::scr_entref_t& entref);
bool set_entity_field(const game::scr_entref_t& entref, int offset);
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
}

View File

@ -0,0 +1,356 @@
#include <std_include.hpp>
#include "script_value.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "function.hpp"
namespace scripting
{
/***************************************************************
* Constructors
**************************************************************/
script_value::script_value(const game::VariableValue& value)
: value_(value)
{
}
script_value::script_value(const value_wrap& value)
: value_(value.get_raw())
{
}
script_value::script_value(const int value)
{
game::VariableValue variable{};
variable.type = game::VAR_INTEGER;
variable.u.intValue = value;
this->value_ = variable;
}
script_value::script_value(const unsigned int value)
{
game::VariableValue variable{};
variable.type = game::VAR_INTEGER;
variable.u.uintValue = value;
this->value_ = variable;
}
script_value::script_value(const bool value)
: script_value(static_cast<unsigned>(value))
{
}
script_value::script_value(const float value)
{
game::VariableValue variable{};
variable.type = game::VAR_FLOAT;
variable.u.floatValue = value;
this->value_ = variable;
}
script_value::script_value(const double value)
: script_value(static_cast<float>(value))
{
}
script_value::script_value(const char* value)
{
game::VariableValue variable{};
variable.type = game::VAR_STRING;
variable.u.stringValue = game::SL_GetString(value, 0);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(variable.type, variable.u);
});
this->value_ = variable;
}
script_value::script_value(const std::string& value)
: script_value(value.data())
{
}
script_value::script_value(const entity& value)
{
game::VariableValue variable{};
variable.type = game::VAR_POINTER;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const array& value)
{
game::VariableValue variable{};
variable.type = game::VAR_POINTER;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const function& value)
{
game::VariableValue variable{};
variable.type = game::VAR_FUNCTION;
variable.u.codePosValue = value.get_pos();
this->value_ = variable;
}
script_value::script_value(const vector& value)
{
game::VariableValue variable{};
variable.type = game::VAR_VECTOR;
variable.u.vectorValue = game::Scr_AllocVector(value);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(variable.type, variable.u);
});
this->value_ = variable;
}
/***************************************************************
* Integer
**************************************************************/
template <>
bool script_value::is<int>() const
{
return this->get_raw().type == game::VAR_INTEGER;
}
template <>
bool script_value::is<unsigned int>() const
{
return this->is<int>();
}
template <>
bool script_value::is<bool>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
return this->get_raw().u.intValue;
}
template <>
unsigned int script_value::get() const
{
return this->get_raw().u.uintValue;
}
template <>
bool script_value::get() const
{
return this->get_raw().u.uintValue != 0;
}
/***************************************************************
* Float
**************************************************************/
template <>
bool script_value::is<float>() const
{
return this->get_raw().type == game::VAR_FLOAT;
}
template <>
bool script_value::is<double>() const
{
return this->is<float>();
}
template <>
float script_value::get() const
{
return this->get_raw().u.floatValue;
}
template <>
double script_value::get() const
{
return static_cast<double>(this->get_raw().u.floatValue);
}
/***************************************************************
* String
**************************************************************/
template <>
bool script_value::is<const char*>() const
{
return this->get_raw().type == game::VAR_STRING;
}
template <>
bool script_value::is<std::string>() const
{
return this->is<const char*>();
}
template <>
const char* script_value::get() const
{
return game::SL_ConvertToString(static_cast<game::scr_string_t>(this->get_raw().u.stringValue));
}
template <>
std::string script_value::get() const
{
return this->get<const char*>();
}
/***************************************************************
* Array
**************************************************************/
template <>
bool script_value::is<array>() const
{
if (this->get_raw().type != game::VAR_POINTER)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
return type == game::VAR_ARRAY;
}
template <>
array script_value::get() const
{
return array(this->get_raw().u.uintValue);
}
/***************************************************************
* Struct
**************************************************************/
template <>
bool script_value::is<std::map<std::string, script_value>>() const
{
if (this->get_raw().type != game::VAR_POINTER)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
return type == game::VAR_OBJECT;
}
/***************************************************************
* Function
**************************************************************/
template <>
bool script_value::is<function>() const
{
return this->get_raw().type == game::VAR_FUNCTION;
}
template <>
function script_value::get() const
{
return function(this->get_raw().u.codePosValue);
}
/***************************************************************
* Entity
**************************************************************/
template <>
bool script_value::is<entity>() const
{
return this->get_raw().type == game::VAR_POINTER;
}
template <>
entity script_value::get() const
{
return entity(this->get_raw().u.pointerValue);
}
/***************************************************************
* Vector
**************************************************************/
template <>
bool script_value::is<vector>() const
{
return this->get_raw().type == game::VAR_VECTOR;
}
template <>
vector script_value::get() const
{
return this->get_raw().u.vectorValue;
}
/***************************************************************
*
**************************************************************/
const game::VariableValue& script_value::get_raw() const
{
return this->value_.get();
}
value_wrap::value_wrap(const scripting::script_value& value, int argument_index)
: value_(value)
, argument_index_(argument_index)
{
}
std::string script_value::to_string() const
{
if (this->is<int>())
{
return utils::string::va("%i", this->as<int>());
}
if (this->is<float>())
{
return utils::string::va("%f", this->as<float>());
}
if (this->is<std::string>())
{
return this->as<std::string>();
}
if (this->is<vector>())
{
const auto vec = this->as<vector>();
return utils::string::va("(%g, %g, %g)",
vec.get_x(),
vec.get_y(),
vec.get_z()
);
}
if (this->is<function>())
{
const auto func = this->as<function>();
return utils::string::va("[[ %s ]]", func.get_name().data());
}
return this->type_name();
}
}

View File

@ -0,0 +1,223 @@
#pragma once
#include "game/game.hpp"
#include "variable_value.hpp"
#include "vector.hpp"
#include <utils/string.hpp>
namespace scripting
{
class entity;
class array;
class function;
class value_wrap;
namespace
{
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",
};
std::string get_typename(const game::VariableValue& value)
{
if (value.type == game::VAR_POINTER)
{
const auto type = game::scr_VarGlob->objectVariableValue[value.u.uintValue].w.type;
return var_typename[type];
}
else
{
return var_typename[value.type];
}
}
template <typename T, typename A = array>
std::string get_c_typename()
{
auto& info = typeid(T);
if (info == typeid(std::string))
{
return "string";
}
if (info == typeid(const char*))
{
return "string";
}
if (info == typeid(entity))
{
return "entity";
}
if (info == typeid(array))
{
return "array";
}
if (info == typeid(function))
{
return "function";
}
if (info == typeid(vector))
{
return "vector";
}
return info.name();
}
}
class script_value
{
public:
script_value() = default;
script_value(const game::VariableValue& value);
script_value(const value_wrap& value);
script_value(int value);
script_value(unsigned int value);
script_value(bool value);
script_value(float value);
script_value(double value);
script_value(const char* value);
script_value(const std::string& value);
script_value(const entity& value);
script_value(const array& value);
script_value(const function& value);
script_value(const vector& value);
std::string type_name() const
{
return get_typename(this->get_raw());
}
std::string to_string() const;
template <typename T>
bool is() const;
template <typename T>
T as() const
{
if (!this->is<T>())
{
const auto type = get_typename(this->get_raw());
const auto c_type = get_c_typename<T>();
throw std::runtime_error(std::format("has type '{}' but should be '{}'", type, c_type));
}
return get<T>();
}
template <typename T, typename I = int>
T* as_ptr()
{
const auto value = this->as<I>();
if (!value)
{
throw std::runtime_error("is null");
}
return reinterpret_cast<T*>(value);
}
const game::VariableValue& get_raw() const;
variable_value value_{};
private:
template <typename T>
T get() const;
};
class value_wrap
{
public:
value_wrap(const scripting::script_value& value, int argument_index);
std::string to_string() const
{
return this->value_.to_string();
}
std::string type_name() const
{
return this->value_.type_name();
}
template <typename T>
T as() const
{
try
{
return this->value_.as<T>();
}
catch (const std::exception& e)
{
throw std::runtime_error(utils::string::va("parameter %d %s", this->argument_index_, e.what()));
}
}
template <typename T, typename I = int>
T* as_ptr()
{
try
{
return this->value_.as_ptr<T>();
}
catch (const std::exception& e)
{
throw std::runtime_error(utils::string::va("parameter %d %s", this->argument_index_, e.what()));
}
}
template <typename T>
T is() const
{
return this->value_.is<T>();
}
const game::VariableValue& get_raw() const
{
return this->value_.get_raw();
}
int argument_index_{};
scripting::script_value value_;
};
}

View File

@ -0,0 +1,27 @@
#include <std_include.hpp>
#include "stack_isolation.hpp"
namespace scripting
{
stack_isolation::stack_isolation()
{
this->in_param_count_ = game::scr_VmPub->inparamcount;
this->out_param_count_ = game::scr_VmPub->outparamcount;
this->top_ = game::scr_VmPub->top;
this->max_stack_ = game::scr_VmPub->maxstack;
game::scr_VmPub->top = this->stack_;
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
game::scr_VmPub->inparamcount = 0;
game::scr_VmPub->outparamcount = 0;
}
stack_isolation::~stack_isolation()
{
game::Scr_ClearOutParams();
game::scr_VmPub->inparamcount = this->in_param_count_;
game::scr_VmPub->outparamcount = this->out_param_count_;
game::scr_VmPub->top = this->top_;
game::scr_VmPub->maxstack = this->max_stack_;
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class stack_isolation final
{
public:
stack_isolation();
~stack_isolation();
stack_isolation(stack_isolation&&) = delete;
stack_isolation(const stack_isolation&) = delete;
stack_isolation& operator=(stack_isolation&&) = delete;
stack_isolation& operator=(const stack_isolation&) = delete;
private:
game::VariableValue stack_[512]{};
game::VariableValue* max_stack_;
game::VariableValue* top_;
unsigned int in_param_count_;
unsigned int out_param_count_;
};
}

View File

@ -0,0 +1,68 @@
#include <std_include.hpp>
#include "variable_value.hpp"
namespace scripting
{
variable_value::variable_value(const game::VariableValue& value)
{
this->assign(value);
}
variable_value::variable_value(const variable_value& other) noexcept
{
this->operator=(other);
}
variable_value::variable_value(variable_value&& other) noexcept
{
this->operator=(std::move(other));
}
variable_value& variable_value::operator=(const variable_value& other) noexcept
{
if (this != &other)
{
this->release();
this->assign(other.value_);
}
return *this;
}
variable_value& variable_value::operator=(variable_value&& other) noexcept
{
if (this != &other)
{
this->release();
this->value_ = other.value_;
other.value_.type = game::VAR_UNDEFINED;
}
return *this;
}
variable_value::~variable_value()
{
this->release();
}
const game::VariableValue& variable_value::get() const
{
return this->value_;
}
void variable_value::assign(const game::VariableValue& value)
{
this->value_ = value;
game::AddRefToValue(this->value_.type, this->value_.u);
}
void variable_value::release()
{
if (this->value_.type != game::VAR_UNDEFINED)
{
game::RemoveRefToValue(this->value_.type, this->value_.u);
this->value_.type = game::VAR_UNDEFINED;
}
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class variable_value
{
public:
variable_value() = default;
variable_value(const game::VariableValue& value);
variable_value(const variable_value& other) noexcept;
variable_value(variable_value&& other) noexcept;
variable_value& operator=(const variable_value& other) noexcept;
variable_value& operator=(variable_value&& other) noexcept;
~variable_value();
const game::VariableValue& get() const;
private:
void assign(const game::VariableValue& value);
void release();
game::VariableValue value_{{0}, game::VAR_UNDEFINED};
};
}

View File

@ -0,0 +1,85 @@
#include <std_include.hpp>
#include "vector.hpp"
namespace scripting
{
vector::vector(const float* value)
{
for (auto i = 0; i < 3; ++i)
{
this->value_[i] = value[i];
}
}
vector::vector(const game::vec3_t& value)
: vector(&value[0])
{
}
vector::vector(const float x, const float y, const float z)
{
this->value_[0] = x;
this->value_[1] = y;
this->value_[2] = z;
}
vector::operator game::vec3_t&()
{
return this->value_;
}
vector::operator const game::vec3_t&() const
{
return this->value_;
}
game::vec_t& vector::operator[](const size_t i)
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
const game::vec_t& vector::operator[](const size_t i) const
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
float vector::get_x() const
{
return this->operator[](0);
}
float vector::get_y() const
{
return this->operator[](1);
}
float vector::get_z() const
{
return this->operator[](2);
}
void vector::set_x(const float value)
{
this->operator[](0) = value;
}
void vector::set_y(const float value)
{
this->operator[](1) = value;
}
void vector::set_z(const float value)
{
this->operator[](2) = value;
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class vector final
{
public:
vector() = default;
vector(const float* value);
vector(const game::vec3_t& value);
vector(float x, float y, float z);
operator game::vec3_t&();
operator const game::vec3_t&() const;
game::vec_t& operator[](size_t i);
const game::vec_t& operator[](size_t i) const;
float get_x() const;
float get_y() const;
float get_z() const;
void set_x(float value);
void set_y(float value);
void set_z(float value);
private:
game::vec3_t value_{0};
};
}

View File

@ -3221,6 +3221,221 @@ namespace game
BD_NAT_STRICT = 0x3, BD_NAT_STRICT = 0x3,
}; };
struct scr_entref_t
{
unsigned short entnum;
unsigned short classnum;
};
enum PMem_Stack : __int32
{
PMEM_STACK_GAME = 0x0,
PMEM_STACK_RENDER_TARGETS = 0x1,
PMEM_STACK_MEM_VIRTUAL = 0x2,
PMEM_STACK_MEMCARD_LARGE_BUFFER = 0x3,
PMEM_STACK_SOUND = 0x4,
PMEM_STACK_STASHED_MEMORY = 0x5,
PMEM_STACK_CINEMATIC = 0x6,
PMEM_STACK_COUNT = 0x7,
};
enum PMem_Source
{
PMEM_SOURCE_EXTERNAL = 0x0,
PMEM_SOURCE_DATABASE = 0x1,
PMEM_SOURCE_DEFAULT_LOW = 0x2,
PMEM_SOURCE_DEFAULT_HIGH = 0x3,
PMEM_SOURCE_MOVIE = 0x4,
PMEM_SOURCE_SCRIPT = 0x5,
PMEM_SOURCE_UNK5 = 0x5,
PMEM_SOURCE_UNK6 = 0x6,
PMEM_SOURCE_UNK7 = 0x7,
PMEM_SOURCE_UNK8 = 0x8,
PMEM_SOURCE_CUSTOMIZATION = 0x9,
};
enum VariableType
{
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,
};
struct VariableStackBuffer
{
const char* pos;
unsigned __int16 size;
unsigned __int16 bufLen;
unsigned __int16 localId;
char time;
char buf[1];
};
union VariableUnion
{
int intValue;
unsigned int uintValue;
float floatValue;
unsigned int stringValue;
const float* vectorValue;
const char* codePosValue;
unsigned int pointerValue;
VariableStackBuffer* stackValue;
unsigned int entityOffset;
};
struct VariableValue
{
VariableUnion u;
int type;
};
struct function_stack_t
{
const char* pos;
unsigned int localId;
unsigned int localVarCount;
VariableValue* top;
VariableValue* startTop;
};
struct function_frame_t
{
function_stack_t fs;
int topType;
};
struct scrVmPub_t
{
unsigned int* localVars;
VariableValue* maxstack;
int function_count;
function_frame_t* function_frame;
VariableValue* top;
unsigned int inparamcount;
unsigned int outparamcount;
function_frame_t function_frame_start[32];
VariableValue stack[2048];
};
struct ObjectVariableChildren
{
unsigned __int16 firstChild;
unsigned __int16 lastChild;
};
struct ObjectVariableValue_u_f
{
unsigned __int16 prev;
unsigned __int16 next;
};
union ObjectVariableValue_u_o_u
{
unsigned __int16 size;
unsigned __int16 entnum;
unsigned __int16 nextEntId;
unsigned __int16 self;
};
struct ObjectVariableValue_u_o
{
unsigned __int16 refCount;
ObjectVariableValue_u_o_u u;
};
union ObjectVariableValue_w
{
unsigned int type;
unsigned int classnum;
unsigned int notifyName;
unsigned int waitTime;
unsigned int parentLocalId;
};
struct ChildVariableValue_u_f
{
unsigned __int16 prev;
unsigned __int16 next;
};
union ChildVariableValue_u
{
ChildVariableValue_u_f f;
VariableUnion u;
};
struct ChildBucketMatchKeys_keys
{
unsigned __int16 name_hi;
unsigned __int16 parentId;
};
union ChildBucketMatchKeys
{
ChildBucketMatchKeys_keys keys;
unsigned int match;
};
struct ChildVariableValue
{
ChildVariableValue_u u;
unsigned __int16 next;
char type;
char name_lo;
ChildBucketMatchKeys k;
unsigned __int16 nextSibling;
unsigned __int16 prevSibling;
};
union ObjectVariableValue_u
{
ObjectVariableValue_u_f f;
ObjectVariableValue_u_o o;
};
struct ObjectVariableValue
{
ObjectVariableValue_u u;
ObjectVariableValue_w w;
};
struct scrVarGlob_t
{
ObjectVariableValue objectVariableValue[40960];
ObjectVariableChildren objectVariableChildren[40960];
unsigned __int16 childVariableBucket[65536];
ChildVariableValue childVariableValue[384000];
};
#pragma pack(push, 1) #pragma pack(push, 1)
struct bdAuthTicket struct bdAuthTicket
{ {

View File

@ -8,10 +8,22 @@ namespace game
* Functions * Functions
**************************************************************/ **************************************************************/
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{ 0xC04360 };
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{ 0xC05DB0 };
WEAK symbol<void(unsigned int id)> AddRefToObject{ 0xC04350 };
WEAK symbol<void(unsigned int id)> RemoveRefToObject{ 0xC05CA0 };
WEAK symbol<unsigned int(unsigned int id)> AllocThread{ 0xC04580 };
WEAK symbol<ObjectVariableValue* (unsigned int* id)> AllocVariable{ 0xC04650 };
WEAK symbol<unsigned int(const char* filename)> Scr_LoadScript{ 0xBFD900 };
WEAK symbol<unsigned int(const char* filename, unsigned int handle)> Scr_GetFunctionHandle{ 0xBFD780 };
WEAK symbol<unsigned int(int handle, int num_param)> Scr_ExecThread{ 0xC0ACD0 };
WEAK symbol<float()> BG_GetGravity{ 0x68DD0 }; WEAK symbol<float()> BG_GetGravity{ 0x68DD0 };
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{ 0xB8D830 }; WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{ 0xB8D830 };
WEAK symbol<bool()> Com_FrontEnd_IsInFrontEnd{ 0x5AE6C0 };
WEAK symbol<void()> Com_Quit_f{ 0xBADC90 }; WEAK symbol<void()> Com_Quit_f{ 0xBADC90 };
WEAK symbol<bool()> Com_FrontEnd_IsInFrontEnd{ 0x5AE6C0 }; WEAK symbol<bool()> Com_FrontEnd_IsInFrontEnd{ 0x5AE6C0 };
@ -52,12 +64,16 @@ namespace game
WEAK symbol<void(const char* text_in)> Cmd_TokenizeString{ 0xB7D850 }; WEAK symbol<void(const char* text_in)> Cmd_TokenizeString{ 0xB7D850 };
WEAK symbol<void()> Cmd_EndTokenizeString{ 0xB7CC90 }; WEAK symbol<void()> Cmd_EndTokenizeString{ 0xB7CC90 };
WEAK symbol<int(XAssetType type, const char* name)> DB_XAssetExists{ 0xA7C3A0 };
WEAK symbol<int(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{ 0xA77AB0 };
WEAK symbol<const char* (XAssetType type, XAssetHeader header)> DB_GetXAssetHeaderName{ 0x9E5BA0 }; WEAK symbol<const char* (XAssetType type, XAssetHeader header)> DB_GetXAssetHeaderName{ 0x9E5BA0 };
WEAK symbol<bool(std::int32_t, void(__cdecl*)(XAssetHeader, void*), const void*)> DB_EnumXAssets_FastFile{ 0xA76CE0 }; WEAK symbol<bool(std::int32_t, void(__cdecl*)(XAssetHeader, void*), const void*)> DB_EnumXAssets_FastFile{ 0xA76CE0 };
WEAK symbol<bool(XAssetType type, const char* name)> DB_IsXAssetDefault{ 0xA780D0 }; WEAK symbol<bool(XAssetType type, const char* name)> DB_IsXAssetDefault{ 0xA780D0 };
WEAK symbol<XAssetHeader(XAssetType type, const char* name, int allowCreateDefault)> DB_FindXAssetHeader{ 0xA76E00 }; WEAK symbol<XAssetHeader(XAssetType type, const char* name, int allowCreateDefault)> DB_FindXAssetHeader{ 0xA76E00 };
WEAK symbol<bool(const char* zoneName)> DB_IsLocalized{ 0x3BC500 }; WEAK symbol<bool(const char* zoneName)> DB_IsLocalized{ 0x3BC500 };
WEAK symbol<char* (const char* filename, char* buf, int size)> DB_ReadRawFile{ 0xA79E30 }; WEAK symbol<char* (const char* filename, char* buf, int size)> DB_ReadRawFile{ 0xA79E30 };
WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{ 0xF20AF0 };
namespace DDL namespace DDL
{ {
@ -104,6 +120,15 @@ namespace game
WEAK symbol<void(char* buffer)> FS_FreeFile{ 0xCDE1F0 }; WEAK symbol<void(char* buffer)> FS_FreeFile{ 0xCDE1F0 };
WEAK symbol<void(int h, const char* fmt, ...)> FS_Printf{ 0xCDD1C0 }; WEAK symbol<void(int h, const char* fmt, ...)> FS_Printf{ 0xCDD1C0 };
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{ 0xC05A90 };
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewVariable{ 0xC05660 };
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{ 0xC054E0 };
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{ 0xC05100 };
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{ 0xC05000 };
WEAK symbol<void(unsigned int parentId, unsigned int index)> RemoveVariableValue{ 0xC05E90 };
WEAK symbol<void(VariableValue* result, unsigned int classnum,
int entnum, int offset)> GetEntityFieldValue{ 0xC09CC0 };
WEAK symbol<int(int clientNum)> G_MainMP_GetClientScore{ 0xB20550 }; WEAK symbol<int(int clientNum)> G_MainMP_GetClientScore{ 0xB20550 };
WEAK symbol<char* (char* string)> I_CleanStr{ 0xCFACC0 }; WEAK symbol<char* (char* string)> I_CleanStr{ 0xCFACC0 };
@ -154,7 +179,21 @@ namespace game
WEAK symbol<int(int length, void const* data, const netadr_s* to)> Sys_SendPacket{ 0xD57DE0 }; WEAK symbol<int(int length, void const* data, const netadr_s* to)> Sys_SendPacket{ 0xD57DE0 };
WEAK symbol<int(netadr_s* net_from, msg_t* net_message)> Sys_GetPacket{ 0xD57D50 }; WEAK symbol<int(netadr_s* net_from, msg_t* net_message)> Sys_GetPacket{ 0xD57D50 };
WEAK symbol<void(const char* name, PMem_Stack stackIndex)> PMem_BeginAlloc{ 0xCF0E10 };
WEAK symbol<void(const char* name, PMem_Stack stackIndex)> PMem_EndAlloc{ 0xCF1070 };
WEAK symbol<char* (const size_t size, unsigned int alignment, PMem_Source source)> PMem_AllocFromSource_NoDebug{ 0xCF0BB0 };
WEAK symbol<void(const char* name, PMem_Stack stackIndex)> PMem_Free{ 0xCF10D0 };
WEAK symbol<unsigned int(unsigned int localId, const char* pos,
unsigned int paramcount)> VM_Execute{ 0xC0CDB0 };
WEAK symbol<void(unsigned int id, scr_string_t stringValue,
unsigned int paramcount)> Scr_NotifyId{ 0xC0C2B0 };
WEAK symbol<const float* (const float* v)> Scr_AllocVector{ 0xC06960 };
WEAK symbol<void(int)> Scr_AddInt{ 0xC0A580 }; WEAK symbol<void(int)> Scr_AddInt{ 0xC0A580 };
WEAK symbol<void()> Scr_ClearOutParams{ 0xC0ABC0 };
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{ 0xC09050 };
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{ 0x40B6E0 };
WEAK symbol<int()> Scr_GetInt{ 0xC0B950 }; WEAK symbol<int()> Scr_GetInt{ 0xC0B950 };
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{ 0x9E4090 }; WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{ 0x9E4090 };
@ -162,6 +201,10 @@ namespace game
WEAK symbol<const char* (const StringTable* table, const int comparisonColumn, const char* value, const int valueColumn)> StringTable_Lookup{ 0xCE7950 }; WEAK symbol<const char* (const StringTable* table, const int comparisonColumn, const char* value, const int valueColumn)> StringTable_Lookup{ 0xCE7950 };
WEAK symbol<const char* (const StringTable* table, const int row, const int column)> StringTable_GetColumnValueForRow{ 0xCE78E0 }; WEAK symbol<const char* (const StringTable* table, const int row, const int column)> StringTable_GetColumnValueForRow{ 0xCE78E0 };
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{ 0xC037E0 };
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{ 0xC03300 };
WEAK symbol<unsigned int(const char* str)> SL_GetCanonicalString{ 0xBFD340 };
WEAK symbol<void(const char* string)> SV_Cmd_TokenizeString{ 0xB7DD00 }; WEAK symbol<void(const char* string)> SV_Cmd_TokenizeString{ 0xB7DD00 };
WEAK symbol<void()> SV_Cmd_EndTokenizedString{ 0xB7DCC0 }; WEAK symbol<void()> SV_Cmd_EndTokenizedString{ 0xB7DCC0 };
WEAK symbol<void(const char* map, const char* gameType, int clientCount, int agentCount, bool hardcore, WEAK symbol<void(const char* map, const char* gameType, int clientCount, int agentCount, bool hardcore,
@ -181,10 +224,18 @@ namespace game
WEAK symbol<void(int)> SND_StopSounds{ 0xCA06E0 }; WEAK symbol<void(int)> SND_StopSounds{ 0xCA06E0 };
WEAK symbol<void(const char*)> SND_SetMusicState{ 0xC9E110 }; WEAK symbol<void(const char*)> SND_SetMusicState{ 0xC9E110 };
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x12C0758 };
WEAK symbol<int(jmp_buf* Buf)> _setjmp{ 0x1423110 };
/*************************************************************** /***************************************************************
* Variables * Variables
**************************************************************/ **************************************************************/
WEAK symbol<int> g_script_error_level{ 0x6B16298 };
WEAK symbol<jmp_buf> g_script_error{ 0x6B162A0 };
WEAK symbol<unsigned int> levelEntityId{ 0x665A120 };
WEAK symbol<CmdArgs> sv_cmd_args{ 0x5D65C20 }; WEAK symbol<CmdArgs> sv_cmd_args{ 0x5D65C20 };
WEAK symbol<CmdArgs> cmd_args{ 0x5D65B70 }; WEAK symbol<CmdArgs> cmd_args{ 0x5D65B70 };
WEAK symbol<cmd_function_s*> cmd_functions{ 0x5D65CC8 }; WEAK symbol<cmd_function_s*> cmd_functions{ 0x5D65CC8 };
@ -216,4 +267,15 @@ namespace game
WEAK symbol<SOCKET> query_socket{ 0x779FDC8 }; WEAK symbol<SOCKET> query_socket{ 0x779FDC8 };
WEAK symbol<DWORD> threadIds{ 0x602BAB0 }; WEAK symbol<DWORD> threadIds{ 0x602BAB0 };
WEAK symbol<scrVarGlob_t> scr_VarGlob{ 0x6691180 };
WEAK symbol<scrVmPub_t> scr_VmPub{ 0x6B183B0 };
WEAK symbol<function_stack_t> scr_function_stack{ 0x6B22908 };
WEAK game::symbol<unsigned __int64> pmem_size{ 0x7686A28 };
WEAK game::symbol<unsigned char*> pmem_buffer{ 0x7686A20 };
WEAK game::symbol<PhysicalMemory> g_mem{ 0xD5F26E0, 0xC92E1E0 };
WEAK game::symbol<PhysicalMemory> g_scriptmem{ 0xD5F3140, 0xC92EC40 };
WEAK game::symbol<PhysicalMemory> g_physmem{ 0xD5F3BA0, 0xC92F6A0 };
} }