very early gsc not working
TODO: - add script_extension.cpp for scripting.cpp
This commit is contained in:
parent
6b4dc872c1
commit
3c09055d21
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
1
deps/gsc-tool
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit cbfcce1dd67534c2115331e41d4cb6893e96196c
|
62
deps/premake/gsc-tool.lua
vendored
Normal file
62
deps/premake/gsc-tool.lua
vendored
Normal 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)
|
@ -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)
|
||||||
|
@ -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_;
|
|
||||||
};
|
|
||||||
}
|
}
|
362
src/client/component/gsc/script_error.cpp
Normal file
362
src/client/component/gsc/script_error.cpp
Normal 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)
|
||||||
|
*/
|
6
src/client/component/gsc/script_error.hpp
Normal file
6
src/client/component/gsc/script_error.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace gsc
|
||||||
|
{
|
||||||
|
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
|
||||||
|
}
|
471
src/client/component/gsc/script_extension.cpp
Normal file
471
src/client/component/gsc/script_extension.cpp
Normal 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)
|
48
src/client/component/gsc/script_extension.hpp
Normal file
48
src/client/component/gsc/script_extension.hpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
428
src/client/component/gsc/script_loading.cpp
Normal file
428
src/client/component/gsc/script_loading.cpp
Normal 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)
|
9
src/client/component/gsc/script_loading.hpp
Normal file
9
src/client/component/gsc/script_loading.hpp
Normal 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);
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
221
src/client/component/scripting.cpp
Normal file
221
src/client/component/scripting.cpp
Normal 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)
|
21
src/client/component/scripting.hpp
Normal file
21
src/client/component/scripting.hpp
Normal 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);
|
||||||
|
}
|
335
src/client/game/scripting/array.cpp
Normal file
335
src/client/game/scripting/array.cpp
Normal 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_);
|
||||||
|
}
|
||||||
|
}
|
89
src/client/game/scripting/array.hpp
Normal file
89
src/client/game/scripting/array.hpp
Normal 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_;
|
||||||
|
};
|
||||||
|
}
|
120
src/client/game/scripting/entity.cpp
Normal file
120
src/client/game/scripting/entity.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
50
src/client/game/scripting/entity.hpp
Normal file
50
src/client/game/scripting/entity.hpp
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
13
src/client/game/scripting/event.hpp
Normal file
13
src/client/game/scripting/event.hpp
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
251
src/client/game/scripting/execution.cpp
Normal file
251
src/client/game/scripting/execution.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
47
src/client/game/scripting/execution.hpp
Normal file
47
src/client/game/scripting/execution.hpp
Normal 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);
|
||||||
|
}
|
44
src/client/game/scripting/function.cpp
Normal file
44
src/client/game/scripting/function.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
35
src/client/game/scripting/function.hpp
Normal file
35
src/client/game/scripting/function.hpp
Normal 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_;
|
||||||
|
};
|
||||||
|
}
|
107
src/client/game/scripting/functions.cpp
Normal file
107
src/client/game/scripting/functions.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
14
src/client/game/scripting/functions.hpp
Normal file
14
src/client/game/scripting/functions.hpp
Normal 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);
|
||||||
|
}
|
72
src/client/game/scripting/safe_execution.cpp
Normal file
72
src/client/game/scripting/safe_execution.cpp
Normal 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)
|
10
src/client/game/scripting/safe_execution.hpp
Normal file
10
src/client/game/scripting/safe_execution.hpp
Normal 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);
|
||||||
|
}
|
356
src/client/game/scripting/script_value.cpp
Normal file
356
src/client/game/scripting/script_value.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
223
src/client/game/scripting/script_value.hpp
Normal file
223
src/client/game/scripting/script_value.hpp
Normal 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_;
|
||||||
|
};
|
||||||
|
}
|
27
src/client/game/scripting/stack_isolation.cpp
Normal file
27
src/client/game/scripting/stack_isolation.cpp
Normal 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_;
|
||||||
|
}
|
||||||
|
}
|
25
src/client/game/scripting/stack_isolation.hpp
Normal file
25
src/client/game/scripting/stack_isolation.hpp
Normal 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_;
|
||||||
|
};
|
||||||
|
}
|
68
src/client/game/scripting/variable_value.cpp
Normal file
68
src/client/game/scripting/variable_value.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/client/game/scripting/variable_value.hpp
Normal file
27
src/client/game/scripting/variable_value.hpp
Normal 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};
|
||||||
|
};
|
||||||
|
}
|
85
src/client/game/scripting/vector.cpp
Normal file
85
src/client/game/scripting/vector.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
31
src/client/game/scripting/vector.hpp
Normal file
31
src/client/game/scripting/vector.hpp
Normal 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};
|
||||||
|
};
|
||||||
|
}
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user