GSC support (#247)
Co-authored-by: Federico Cecchetto <fedecek3@gmail.com>
This commit is contained in:
parent
b1b6699435
commit
99b3831dd8
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -48,3 +48,6 @@
|
||||
[submodule "deps/curl"]
|
||||
path = deps/curl
|
||||
url = https://github.com/curl/curl.git
|
||||
[submodule "deps/gsc-tool"]
|
||||
path = deps/gsc-tool
|
||||
url = https://github.com/h1-mod/gsc-tool.git
|
||||
|
20
deps/extra/gsc-tool/interface.cpp
vendored
Normal file
20
deps/extra/gsc-tool/interface.cpp
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#include "stdafx.hpp"
|
||||
|
||||
#include <xsk/h1.hpp>
|
||||
|
||||
#include "interface.hpp"
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unique_ptr<xsk::gsc::compiler> compiler()
|
||||
{
|
||||
auto compiler = std::make_unique<xsk::gsc::h1::compiler>();
|
||||
compiler->mode(xsk::gsc::build::prod);
|
||||
return compiler;
|
||||
}
|
||||
|
||||
std::unique_ptr<xsk::gsc::assembler> assembler()
|
||||
{
|
||||
return std::make_unique<xsk::gsc::h1::assembler>();
|
||||
}
|
||||
}
|
7
deps/extra/gsc-tool/interface.hpp
vendored
Normal file
7
deps/extra/gsc-tool/interface.hpp
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unique_ptr<xsk::gsc::compiler> compiler();
|
||||
std::unique_ptr<xsk::gsc::assembler> assembler();
|
||||
}
|
1
deps/gsc-tool
vendored
Submodule
1
deps/gsc-tool
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 74b205271bdd934d85de9b3b06d7008bb7b260c2
|
68
deps/premake/gsc-tool.lua
vendored
Normal file
68
deps/premake/gsc-tool.lua
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
gsc_tool = {
|
||||
source = path.join(dependencies.basePath, "gsc-tool/src")
|
||||
}
|
||||
|
||||
function gsc_tool.import()
|
||||
links {"xsk-gsc-h1", "xsk-gsc-utils"}
|
||||
gsc_tool.includes()
|
||||
end
|
||||
|
||||
function gsc_tool.includes()
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "utils"),
|
||||
path.join(gsc_tool.source, "h1"),
|
||||
path.join(dependencies.basePath, "extra/gsc-tool") -- https://github.com/GEEKiDoS/open-teknomw3/blob/master/deps/extra/gsc-tool
|
||||
}
|
||||
end
|
||||
|
||||
-- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L95
|
||||
function gsc_tool.project()
|
||||
project "xsk-gsc-utils"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
pchheader "stdafx.hpp"
|
||||
pchsource(path.join(gsc_tool.source, "utils/stdafx.cpp"))
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "utils/**.h"),
|
||||
path.join(gsc_tool.source, "utils/**.hpp"),
|
||||
path.join(gsc_tool.source, "utils/**.cpp")
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "utils"),
|
||||
gsc_tool.source
|
||||
}
|
||||
|
||||
zlib.includes()
|
||||
|
||||
project "xsk-gsc-h1"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
|
||||
pchheader "stdafx.hpp"
|
||||
pchsource(path.join(gsc_tool.source, "h1/stdafx.cpp"))
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "h1/**.h"),
|
||||
path.join(gsc_tool.source, "h1/**.hpp"),
|
||||
path.join(gsc_tool.source, "h1/**.cpp"),
|
||||
path.join(dependencies.basePath, "extra/gsc-tool/interface.cpp")
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "h1"),
|
||||
gsc_tool.source,
|
||||
path.join(dependencies.basePath, "extra/gsc-tool")
|
||||
}
|
||||
|
||||
-- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L25
|
||||
-- adding these build options fixes a bunch of parser stuff
|
||||
filter "action:vs*"
|
||||
buildoptions "/bigobj"
|
||||
buildoptions "/Zc:__cplusplus"
|
||||
filter {}
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsc_tool)
|
@ -151,7 +151,7 @@ namespace bots
|
||||
});
|
||||
|
||||
// Clear bot names and reset ID on game shutdown to allow new names to be added without restarting
|
||||
scripting::on_shutdown([]
|
||||
scripting::on_shutdown([](int)
|
||||
{
|
||||
bot_names.clear();
|
||||
bot_id = 0;
|
||||
|
817
src/client/component/gsc.cpp
Normal file
817
src/client/component/gsc.cpp
Normal file
@ -0,0 +1,817 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "gsc.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "logfile.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/gsc/interfaces/compiler.hpp>
|
||||
#include <xsk/gsc/interfaces/assembler.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <interface.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
void* func_table[0x1000]{};
|
||||
void* meth_table[0x1000]{};
|
||||
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* developer_script = nullptr;
|
||||
|
||||
auto compiler = ::gsc::compiler();
|
||||
auto assembler = ::gsc::assembler();
|
||||
|
||||
std::unordered_map<std::string, unsigned int> main_handles;
|
||||
std::unordered_map<std::string, unsigned int> init_handles;
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
|
||||
std::unordered_map<builtin_function, unsigned int> functions;
|
||||
std::unordered_map<builtin_method, unsigned int> methods;
|
||||
|
||||
std::optional<std::string> gsc_error;
|
||||
|
||||
bool force_error_print = false;
|
||||
|
||||
auto function_id_start = 0x30A;
|
||||
auto method_id_start = 0x8586;
|
||||
|
||||
game::scr_entref_t saved_ent_ref;
|
||||
|
||||
std::string unknown_function_error{};
|
||||
unsigned int current_filename{};
|
||||
|
||||
char* allocate_buffer(size_t size)
|
||||
{
|
||||
// PMem_AllocFromSource_NoDebug
|
||||
return utils::hook::invoke<char*>(SELECT_VALUE(0x41FB50_b, 0x5A4DC0_b), size, 4, 1, 5);
|
||||
}
|
||||
|
||||
bool read_scriptfile(const std::string& name, std::string* data)
|
||||
{
|
||||
if (filesystem::read_file(name, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: check back on this to see if there is a property we can distinguish compared to our rawfiles, like compressedLen?
|
||||
// this will filter out the rawfile "gsc" the game zones actually have, this seems to get all of them
|
||||
if (name.starts_with("maps/createfx") || name.starts_with("maps/createart")
|
||||
|| (name.starts_with("maps/mp") && name.ends_with("_fx.gsc")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (loaded_scripts.find(real_name) != loaded_scripts.end())
|
||||
{
|
||||
return loaded_scripts[real_name];
|
||||
}
|
||||
|
||||
std::string source_buffer{};
|
||||
if (!read_scriptfile(real_name + ".gsc", &source_buffer))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data = std::vector<uint8_t>{source_buffer.begin(), source_buffer.end()};
|
||||
|
||||
try
|
||||
{
|
||||
compiler->compile(real_name, data);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to compile '%s':\n%s", real_name.data(), e.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto assembly = compiler->output();
|
||||
|
||||
try
|
||||
{
|
||||
assembler->assemble(real_name, assembly);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to assemble '%s':\n%s", real_name.data(), e.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto script_file_ptr = reinterpret_cast<game::ScriptFile*>(allocate_buffer(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
const auto stack = assembler->output_stack();
|
||||
script_file_ptr->len = static_cast<int>(stack.size());
|
||||
|
||||
const auto script = assembler->output_script();
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(script.size());
|
||||
|
||||
const auto script_size = script.size();
|
||||
const auto buffer_size = script_size + stack.size() + 2;
|
||||
|
||||
const auto buffer = allocate_buffer(buffer_size);
|
||||
std::memcpy(buffer, script.data(), script_size);
|
||||
std::memcpy(&buffer[script_size], stack.data(), stack.size());
|
||||
|
||||
script_file_ptr->bytecode = &buffer[0];
|
||||
script_file_ptr->buffer = &buffer[script.size()];
|
||||
script_file_ptr->compressedLen = 0;
|
||||
|
||||
loaded_scripts[real_name] = script_file_ptr;
|
||||
|
||||
return script_file_ptr;
|
||||
}
|
||||
|
||||
void load_script(const std::string& name)
|
||||
{
|
||||
if (!game::Scr_LoadScript(name.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto main_handle = game::Scr_GetFunctionHandle(name.data(),
|
||||
xsk::gsc::h1::resolver::token_id("main"));
|
||||
const auto init_handle = game::Scr_GetFunctionHandle(name.data(),
|
||||
xsk::gsc::h1::resolver::token_id("init"));
|
||||
|
||||
if (main_handle)
|
||||
{
|
||||
console::info("Loaded '%s::main'\n", name.data());
|
||||
main_handles[name] = main_handle;
|
||||
}
|
||||
|
||||
if (init_handle)
|
||||
{
|
||||
console::info("Loaded '%s::init'\n", name.data());
|
||||
init_handles[name] = init_handle;
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir)
|
||||
{
|
||||
std::filesystem::path script_dir_path = root_dir / script_dir;
|
||||
if (!utils::io::directory_exists(script_dir_path.generic_string()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir_path.generic_string());
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (!script.ends_with(".gsc"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path path(script);
|
||||
const auto relative = path.lexically_relative(root_dir).generic_string();
|
||||
const auto base_name = relative.substr(0, relative.size() - 4);
|
||||
|
||||
load_script(base_name);
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
main_handles.clear();
|
||||
init_handles.clear();
|
||||
loaded_scripts.clear();
|
||||
}
|
||||
|
||||
void load_gametype_script_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x2B9DA0_b, 0x18BC00_b), a1, a2);
|
||||
|
||||
if (game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path, "scripts/");
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
load_scripts(path, "scripts/sp/");
|
||||
}
|
||||
else
|
||||
{
|
||||
load_scripts(path, "scripts/mp/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int db_is_xasset_default(game::XAssetType type, const char* name)
|
||||
{
|
||||
if (loaded_scripts.find(name) != loaded_scripts.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return game::DB_IsXAssetDefault(type, name);
|
||||
}
|
||||
|
||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||
{
|
||||
if (rawfile->len > 0 && rawfile->compressedLen == 0)
|
||||
{
|
||||
std::memset(buf, 0, size);
|
||||
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1F1E00_b, 0x396080_b), rawfile, buf, size);
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos)
|
||||
{
|
||||
for (const auto& file : scripting::script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return {std::make_pair(i->first, file.first)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void print_callstack()
|
||||
{
|
||||
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
|
||||
{
|
||||
const auto pos = frame == game::scr_VmPub->function_frame
|
||||
? game::scr_function_stack->pos
|
||||
: frame->fs.pos;
|
||||
const auto function = find_function(pos);
|
||||
|
||||
if (function.has_value())
|
||||
{
|
||||
console::warn("\tat function \"%s\" in file \"%s.gsc\"",
|
||||
function.value().first.data(), function.value().second.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("\tat unknown location %p", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
|
||||
{
|
||||
try
|
||||
{
|
||||
return {xsk::gsc::h1::resolver::opcode_name(opcode)};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void builtin_call_error()
|
||||
{
|
||||
const auto pos = game::scr_function_stack->pos;
|
||||
const auto function_id = *reinterpret_cast<std::uint16_t*>(
|
||||
reinterpret_cast<size_t>(pos - 2));
|
||||
|
||||
if (function_id > 0x1000)
|
||||
{
|
||||
console::warn("in call to builtin method \"%s\"",
|
||||
xsk::gsc::h1::resolver::method_name(function_id).data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("in call to builtin function \"%s\"",
|
||||
xsk::gsc::h1::resolver::function_name(function_id).data());
|
||||
}
|
||||
}
|
||||
|
||||
void* vm_error_stub(void* a1)
|
||||
{
|
||||
if (!developer_script->current.enabled && !force_error_print)
|
||||
{
|
||||
return utils::hook::invoke<void*>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
|
||||
}
|
||||
|
||||
console::warn("*********** script runtime error *************\n");
|
||||
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(SELECT_VALUE(0xC4015E8_b, 0xB7B8968_b));
|
||||
if ((opcode_id >= 0x1A && opcode_id <= 0x20) || (opcode_id >= 0xA9 && opcode_id <= 0xAF))
|
||||
{
|
||||
builtin_call_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto opcode = get_opcode_name(opcode_id);
|
||||
const std::string error_str = gsc_error.has_value()
|
||||
? utils::string::va(": %s", gsc_error.value().data())
|
||||
: "";
|
||||
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 = {};
|
||||
|
||||
print_callstack();
|
||||
console::warn("**********************************************\n");
|
||||
return utils::hook::invoke<void*>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
|
||||
}
|
||||
|
||||
void get_unknown_function_error(const char* code_pos)
|
||||
{
|
||||
const auto function = find_function(code_pos);
|
||||
if (function.has_value())
|
||||
{
|
||||
const auto& pos = function.value();
|
||||
unknown_function_error = utils::string::va(
|
||||
"while processing function '%s' in script '%s':\nunknown script '%s'",
|
||||
pos.first.data(), pos.second.data(), scripting::current_file.data()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown_function_error = utils::string::va(
|
||||
"unknown script '%s'",
|
||||
scripting::current_file.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_filename_name()
|
||||
{
|
||||
const auto filename_str = game::SL_ConvertToString(
|
||||
static_cast<game::scr_string_t>(current_filename));
|
||||
const auto id = std::atoi(filename_str);
|
||||
if (id == 0)
|
||||
{
|
||||
return filename_str;
|
||||
}
|
||||
|
||||
return scripting::get_token(id);
|
||||
}
|
||||
|
||||
void get_unknown_function_error(unsigned int thread_name)
|
||||
{
|
||||
const auto filename = get_filename_name();
|
||||
const auto name = scripting::get_token(thread_name);
|
||||
|
||||
unknown_function_error = utils::string::va(
|
||||
"while processing script '%s':\nunknown function '%s::%s'",
|
||||
scripting::current_file.data(), filename.data(), name.data()
|
||||
);
|
||||
}
|
||||
|
||||
void unknown_function_stub(const char* code_pos)
|
||||
{
|
||||
get_unknown_function_error(code_pos);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s",
|
||||
unknown_function_error.data());
|
||||
}
|
||||
|
||||
unsigned int find_variable_stub(unsigned int parent_id, unsigned int thread_name)
|
||||
{
|
||||
const auto res = game::FindVariable(parent_id, thread_name);
|
||||
if (!res)
|
||||
{
|
||||
get_unknown_function_error(thread_name);
|
||||
game::Com_Error(game::ERR_DROP, "script link error\n%s",
|
||||
unknown_function_error.data());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void register_gsc_functions_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x2E0F50_b, 0x1CE010_b), a1, a2);
|
||||
for (const auto& function : functions)
|
||||
{
|
||||
game::Scr_RegisterFunction(function.first, 0, function.second);
|
||||
}
|
||||
}
|
||||
|
||||
void register_gsc_methods_stub(void* a1, void* a2)
|
||||
{
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x2E0FB0_b, 0x1CE120_b), a1, a2);
|
||||
for (const auto& method : methods)
|
||||
{
|
||||
game::Scr_RegisterFunction(method.first, 0, method.second);
|
||||
}
|
||||
}
|
||||
|
||||
scripting::script_value get_argument(int index)
|
||||
{
|
||||
if (index >= static_cast<int>(game::scr_VmPub->outparamcount))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return game::scr_VmPub->top[-index];
|
||||
}
|
||||
|
||||
void execute_custom_function(builtin_function function)
|
||||
{
|
||||
auto error = false;
|
||||
|
||||
try
|
||||
{
|
||||
function();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error = true;
|
||||
force_error_print = true;
|
||||
gsc_error = e.what();
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_custom_method(builtin_method method, game::scr_entref_t ent_ref)
|
||||
{
|
||||
auto error = false;
|
||||
|
||||
try
|
||||
{
|
||||
method(ent_ref);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error = true;
|
||||
force_error_print = true;
|
||||
gsc_error = e.what();
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void vm_call_builtin_function_stub(builtin_function function)
|
||||
{
|
||||
auto is_custom_function = false;
|
||||
{
|
||||
is_custom_function = functions.find(function) != functions.end();
|
||||
}
|
||||
|
||||
if (!is_custom_function)
|
||||
{
|
||||
function();
|
||||
}
|
||||
else
|
||||
{
|
||||
execute_custom_function(function);
|
||||
}
|
||||
}
|
||||
|
||||
game::scr_entref_t get_entity_id_stub(unsigned int ent_id)
|
||||
{
|
||||
const auto ref = game::Scr_GetEntityIdRef(ent_id);
|
||||
saved_ent_ref = ref;
|
||||
return ref;
|
||||
}
|
||||
|
||||
void vm_call_builtin_method_stub(builtin_method method)
|
||||
{
|
||||
auto is_custom_function = false;
|
||||
{
|
||||
is_custom_function = methods.find(method) != methods.end();
|
||||
}
|
||||
|
||||
if (!is_custom_function)
|
||||
{
|
||||
method(saved_ent_ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
execute_custom_method(method, saved_ent_ref);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
|
||||
{
|
||||
current_filename = filename;
|
||||
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
|
||||
}
|
||||
|
||||
void replace(std::string& str, const std::string& from, const std::string& to)
|
||||
{
|
||||
const auto start_pos = str.find(from);
|
||||
|
||||
if (start_pos == std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
str.replace(start_pos, from.length(), to);
|
||||
}
|
||||
}
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/)
|
||||
{
|
||||
if (game::VirtualLobby_Loaded())
|
||||
{
|
||||
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile;
|
||||
}
|
||||
|
||||
std::string real_name = name;
|
||||
const auto id = static_cast<std::uint16_t>(std::atoi(name));
|
||||
if (id)
|
||||
{
|
||||
real_name = xsk::gsc::h1::resolver::token_name(id);
|
||||
}
|
||||
|
||||
const auto script = load_custom_script(name, real_name);
|
||||
if (script)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name, 1).scriptfile;
|
||||
}
|
||||
|
||||
void load_main_handles()
|
||||
{
|
||||
for (auto& function_handle : main_handles)
|
||||
{
|
||||
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void load_init_handles()
|
||||
{
|
||||
for (auto& function_handle : init_handles)
|
||||
{
|
||||
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||
}
|
||||
}
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, builtin_function function)
|
||||
{
|
||||
if (xsk::gsc::h1::resolver::find_function(name))
|
||||
{
|
||||
const auto id = xsk::gsc::h1::resolver::function_id(name);
|
||||
functions[function] = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++function_id_start;
|
||||
xsk::gsc::h1::resolver::add_function(name, static_cast<std::uint16_t>(id));
|
||||
functions[function] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace method
|
||||
{
|
||||
void add(const std::string& name, builtin_method method)
|
||||
{
|
||||
if (xsk::gsc::h1::resolver::find_method(name))
|
||||
{
|
||||
const auto id = xsk::gsc::h1::resolver::method_id(name);
|
||||
methods[method] = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++method_id_start;
|
||||
xsk::gsc::h1::resolver::add_method(name, static_cast<std::uint16_t>(id));
|
||||
methods[method] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
developer_script = dvars::register_bool("developer_script", false, 0, "Print GSC errors");
|
||||
|
||||
// Allow custom scripts to include other custom scripts
|
||||
xsk::gsc::h1::resolver::init([](const auto& include_name)
|
||||
{
|
||||
const auto real_name = include_name + ".gsc";
|
||||
|
||||
std::string file_buffer;
|
||||
if (!read_scriptfile(real_name, &file_buffer) || file_buffer.empty())
|
||||
{
|
||||
throw std::runtime_error(std::format("could not load gsc file '{}'", real_name));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> result;
|
||||
result.assign(file_buffer.begin(), file_buffer.end());
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// hook xasset functions to return our own custom scripts
|
||||
utils::hook::call(SELECT_VALUE(0x3C7217_b, 0x50E357_b), find_script);
|
||||
utils::hook::call(SELECT_VALUE(0x3C7227_b, 0x50E367_b), db_is_xasset_default);
|
||||
|
||||
// loads scripts with an uncompressed stack
|
||||
utils::hook::call(SELECT_VALUE(0x3C7280_b, 0x50E3C0_b), db_get_raw_buffer_stub);
|
||||
|
||||
// load script handles
|
||||
utils::hook::call(SELECT_VALUE(0x2BA152_b, 0x18C325_b), load_gametype_script_stub);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub);
|
||||
|
||||
// patch error to drop + give more information
|
||||
utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), unknown_function_stub); // CompileError
|
||||
utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), unknown_function_stub); // ^
|
||||
utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub);
|
||||
scr_emit_function_hook.create(SELECT_VALUE(0x3BD680_b, 0x504660_b), scr_emit_function_stub);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x3BDC4F_b, 0x504C7F_b), register_gsc_functions_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x3BDC5B_b, 0x504C8B_b), register_gsc_methods_stub);
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count
|
||||
|
||||
#define RVA(ptr) static_cast<uint32_t>(reinterpret_cast<size_t>(ptr) - 0x140000000)
|
||||
|
||||
std::memcpy(&func_table, reinterpret_cast<void*>(SELECT_VALUE(0xB8CC510_b, 0xAC83820_b)),
|
||||
sizeof(reinterpret_cast<void*>(SELECT_VALUE(0xB8CC510_b, 0xAC83820_b))));
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4, RVA(&func_table));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC28_b, 0x504C58_b) + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CB718_b, 0x512778_b) + 4, RVA(&func_table));
|
||||
|
||||
std::memcpy(&meth_table, reinterpret_cast<void*>(SELECT_VALUE(0xB8CDD60_b, 0xAC85070_b)),
|
||||
sizeof(reinterpret_cast<void*>(SELECT_VALUE(0xB8CDD60_b, 0xAC85070_b))));
|
||||
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD882_b, 0x504862_b) + 4, RVA(&meth_table));
|
||||
utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table);
|
||||
utils::hook::set<uint32_t>(SELECT_VALUE(0x3CBA3B_b, 0x512A9B_b) + 4, RVA(&meth_table));
|
||||
|
||||
// nop original code and handle calling builtin functions
|
||||
utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8);
|
||||
utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub);
|
||||
|
||||
// same as above, but for builtin methods (different method of doing it, is this overdone?)
|
||||
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);
|
||||
|
||||
function::add("print", []()
|
||||
{
|
||||
const auto num = game::Scr_GetNumParam();
|
||||
std::string buffer{};
|
||||
|
||||
for (auto i = 0; i < num; i++)
|
||||
{
|
||||
const auto str = game::Scr_GetString(i);
|
||||
buffer.append(str);
|
||||
buffer.append("\t");
|
||||
}
|
||||
|
||||
console::info("[SCRIPT] %s\n", buffer.data());
|
||||
});
|
||||
|
||||
function::add("assert", []()
|
||||
{
|
||||
const auto expr = get_argument(0).as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
throw std::runtime_error("assert fail");
|
||||
}
|
||||
});
|
||||
|
||||
function::add("assertex", []()
|
||||
{
|
||||
const auto expr = get_argument(0).as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
const auto error = get_argument(1).as<std::string>();
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
});
|
||||
|
||||
function::add("replacefunc", []()
|
||||
{
|
||||
const auto what = get_argument(0).get_raw();
|
||||
const auto with = get_argument(1).get_raw();
|
||||
|
||||
if (what.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("replaceFunc: parameter 1 must be a function");
|
||||
return;
|
||||
}
|
||||
|
||||
if (with.type != game::SCRIPT_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("replaceFunc: parameter 2 must be a function");
|
||||
return;
|
||||
}
|
||||
|
||||
logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
|
||||
});
|
||||
|
||||
function::add("toupper", []()
|
||||
{
|
||||
const auto string = get_argument(0).as<std::string>();
|
||||
game::Scr_AddString(utils::string::to_upper(string).data());
|
||||
});
|
||||
|
||||
function::add("logprint", []()
|
||||
{
|
||||
std::string buffer{};
|
||||
|
||||
const auto params = game::Scr_GetNumParam();
|
||||
for (auto i = 0; i < params; i++)
|
||||
{
|
||||
const auto string = game::Scr_GetString(i);
|
||||
buffer.append(string);
|
||||
}
|
||||
|
||||
game::G_LogPrintf("%s", buffer.data());
|
||||
});
|
||||
|
||||
function::add("va", []()
|
||||
{
|
||||
auto fmt = get_argument(0).as<std::string>();
|
||||
|
||||
const auto params = game::Scr_GetNumParam();
|
||||
for (auto i = 1; i < params; i++)
|
||||
{
|
||||
const auto arg = get_argument(i).to_string();
|
||||
replace(fmt, "%s", arg);
|
||||
}
|
||||
|
||||
game::Scr_AddString(fmt.data());
|
||||
});
|
||||
|
||||
scripting::on_shutdown([](int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
27
src/client/component/gsc.hpp
Normal file
27
src/client/component/gsc.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
using builtin_function = void(*)();
|
||||
using builtin_method = void(*)(game::scr_entref_t);
|
||||
|
||||
extern void* func_table[0x1000];
|
||||
extern void* meth_table[0x1000];
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/);
|
||||
|
||||
void load_main_handles();
|
||||
void load_init_handles();
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, builtin_function function);
|
||||
}
|
||||
|
||||
namespace method
|
||||
{
|
||||
void add(const std::string& name, builtin_method function);
|
||||
}
|
||||
}
|
@ -1,29 +1,45 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "logfile.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
bool hook_enabled = true;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct gsc_hook_t
|
||||
{
|
||||
bool is_lua_hook{};
|
||||
const char* target_pos{};
|
||||
sol::protected_function lua_function;
|
||||
};
|
||||
|
||||
std::unordered_map<const char*, gsc_hook_t> vm_execute_hooks;
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
utils::hook::detour scr_player_damage_hook;
|
||||
|
||||
utils::hook::detour client_command_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
utils::hook::detour g_log_printf_hook;
|
||||
|
||||
std::vector<sol::protected_function> player_killed_callbacks;
|
||||
std::vector<sol::protected_function> player_damage_callbacks;
|
||||
|
||||
game::dvar_t* logfile;
|
||||
game::dvar_t* g_log;
|
||||
|
||||
utils::hook::detour vm_execute_hook;
|
||||
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
|
||||
bool hook_enabled = true;
|
||||
const char* target_function = nullptr;
|
||||
|
||||
sol::lua_value convert_entity(lua_State* state, const game::mp::gentity_s* ent)
|
||||
{
|
||||
@ -166,21 +182,30 @@ namespace logfile
|
||||
}
|
||||
|
||||
const auto& hook = vm_execute_hooks[pos];
|
||||
const auto state = hook.lua_state();
|
||||
|
||||
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
|
||||
|
||||
std::vector<sol::lua_value> args;
|
||||
|
||||
const auto top = game::scr_function_stack->top;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
if (hook.is_lua_hook)
|
||||
{
|
||||
args.push_back(scripting::lua::convert(state, *value));
|
||||
}
|
||||
const auto& function = hook.lua_function;
|
||||
const auto state = function.lua_state();
|
||||
|
||||
const auto result = hook(self, sol::as_args(args));
|
||||
scripting::lua::handle_error(result);
|
||||
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
|
||||
|
||||
std::vector<sol::lua_value> args;
|
||||
|
||||
const auto top = game::scr_function_stack->top;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
{
|
||||
args.push_back(scripting::lua::convert(state, *value));
|
||||
}
|
||||
|
||||
const auto result = function(self, sol::as_args(args));
|
||||
scripting::lua::handle_error(result);
|
||||
target_function = empty_function;
|
||||
}
|
||||
else
|
||||
{
|
||||
target_function = hook.target_pos;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -212,9 +237,35 @@ namespace logfile
|
||||
a.bind(replace);
|
||||
|
||||
a.popad64();
|
||||
a.mov(r14, reinterpret_cast<char*>(empty_function));
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&target_function)));
|
||||
a.mov(r14, rax);
|
||||
a.jmp(end);
|
||||
}
|
||||
|
||||
void g_log_printf_stub(const char* fmt, ...)
|
||||
{
|
||||
if (!logfile->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char va_buffer[0x400] = {0};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsprintf_s(va_buffer, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
const auto file = g_log->current.string;
|
||||
const auto time = *game::level_time / 1000;
|
||||
|
||||
utils::io::write_file(file, utils::string::va("%3i:%i%i %s",
|
||||
time / 60,
|
||||
time % 60 / 10,
|
||||
time % 60 % 10,
|
||||
va_buffer
|
||||
), true);
|
||||
}
|
||||
}
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback)
|
||||
@ -266,6 +317,13 @@ namespace logfile
|
||||
|
||||
scripting::notify(level, cmd, {player, message, hidden});
|
||||
scripting::notify(player, cmd, {message, hidden});
|
||||
|
||||
game::G_LogPrintf("%s;%s;%i;%s;%s\n",
|
||||
cmd,
|
||||
player.call("getguid").as<const char*>(),
|
||||
player.call("getentitynumber").as<int>(),
|
||||
player.get("name").as<const char*>(),
|
||||
message.data());
|
||||
}, scheduler::pipeline::server);
|
||||
|
||||
if (hidden)
|
||||
@ -277,6 +335,32 @@ namespace logfile
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_lua_hook(const char* pos, const sol::protected_function& callback)
|
||||
{
|
||||
gsc_hook_t hook;
|
||||
hook.is_lua_hook = true;
|
||||
hook.lua_function = callback;
|
||||
vm_execute_hooks[pos] = hook;
|
||||
}
|
||||
|
||||
void set_gsc_hook(const char* source, const char* target)
|
||||
{
|
||||
gsc_hook_t hook;
|
||||
hook.is_lua_hook = false;
|
||||
hook.target_pos = target;
|
||||
vm_execute_hooks[source] = hook;
|
||||
}
|
||||
|
||||
void clear_hook(const char* pos)
|
||||
{
|
||||
vm_execute_hooks.erase(pos);
|
||||
}
|
||||
|
||||
size_t get_hook_count()
|
||||
{
|
||||
return vm_execute_hooks.size();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -291,6 +375,14 @@ namespace logfile
|
||||
|
||||
scr_player_damage_hook.create(0x1CE780_b, scr_player_damage_stub);
|
||||
scr_player_killed_hook.create(0x1CEA60_b, scr_player_killed_stub);
|
||||
|
||||
// Reimplement game log
|
||||
scheduler::once([]()
|
||||
{
|
||||
logfile = dvars::register_bool("logfile", true, game::DVAR_FLAG_NONE, "Enable game logging");
|
||||
g_log = dvars::register_string("g_log", "h1-mod\\logs\\games_mp.log", game::DVAR_FLAG_NONE, "Log file path");
|
||||
}, scheduler::pipeline::main);
|
||||
g_log_printf_hook.create(game::G_LogPrintf, g_log_printf_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -7,7 +7,12 @@
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
extern std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
extern bool hook_enabled;
|
||||
|
||||
void set_lua_hook(const char* pos, const sol::protected_function&);
|
||||
void set_gsc_hook(const char* source, const char* target);
|
||||
void clear_hook(const char* pos);
|
||||
size_t get_hook_count();
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback);
|
||||
void add_player_killed_callback(const sol::protected_function& callback);
|
||||
|
@ -10,9 +10,15 @@
|
||||
#include "game/scripting/lua/engine.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "gsc.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
@ -21,12 +27,16 @@ 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;
|
||||
utils::concurrency::container<shared_table_t> shared_table;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
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;
|
||||
|
||||
@ -39,12 +49,14 @@ namespace scripting
|
||||
|
||||
utils::hook::detour db_find_xasset_header_hook;
|
||||
|
||||
std::string current_file;
|
||||
std::string current_scriptfile;
|
||||
unsigned int current_file_id{};
|
||||
|
||||
game::dvar_t* g_dump_scripts;
|
||||
|
||||
std::vector<std::function<void()>> shutdown_callbacks;
|
||||
std::vector<std::function<void(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)
|
||||
@ -80,29 +92,55 @@ namespace scripting
|
||||
return vm_execute_hook.invoke<unsigned int>();
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
void g_load_structs_stub()
|
||||
{
|
||||
scr_load_level_hook.invoke<void>();
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
// init game in game log
|
||||
game::G_LogPrintf("------------------------------------------------------------\n");
|
||||
game::G_LogPrintf("InitGame\n");
|
||||
|
||||
// start lua engine
|
||||
lua::engine::start();
|
||||
|
||||
// execute main handles
|
||||
gsc::load_main_handles();
|
||||
}
|
||||
|
||||
g_load_structs_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
// execute init handles
|
||||
gsc::load_init_handles();
|
||||
}
|
||||
|
||||
scr_load_level_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
script_function_table_sort.clear();
|
||||
script_function_table.clear();
|
||||
canonical_string_table.clear();
|
||||
}
|
||||
|
||||
for (const auto& callback : shutdown_callbacks)
|
||||
{
|
||||
callback();
|
||||
callback(free_scripts);
|
||||
}
|
||||
|
||||
scripting::notify(*game::levelEntityId, "shutdownGame_called", {1});
|
||||
lua::engine::stop();
|
||||
|
||||
game::G_LogPrintf("ShutdownGame:\n");
|
||||
game::G_LogPrintf("------------------------------------------------------------\n");
|
||||
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
@ -120,10 +158,12 @@ namespace scripting
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
{
|
||||
current_scriptfile = filename;
|
||||
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file_id = file_id;
|
||||
current_file_id = static_cast<std::uint16_t>(file_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -134,24 +174,43 @@ namespace scripting
|
||||
process_script_hook.invoke<void>(filename);
|
||||
}
|
||||
|
||||
void add_function_sort(unsigned int id, const char* pos)
|
||||
{
|
||||
std::string filename = current_file;
|
||||
if (current_file_id)
|
||||
{
|
||||
filename = scripting::get_token(current_file_id);
|
||||
}
|
||||
|
||||
if (script_function_table_sort.find(filename) == script_function_table_sort.end())
|
||||
{
|
||||
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_scriptfile.data(), 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 function_names = scripting::find_token(id);
|
||||
for (const auto& name : function_names)
|
||||
{
|
||||
script_function_table[file][name] = pos;
|
||||
}
|
||||
const auto name = get_token(id);
|
||||
script_function_table[file][name] = pos;
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
|
||||
{
|
||||
add_function_sort(thread_name, code_pos);
|
||||
|
||||
if (current_file_id)
|
||||
{
|
||||
const auto names = scripting::find_token(current_file_id);
|
||||
for (const auto& name : names)
|
||||
{
|
||||
add_function(name, thread_name, code_pos);
|
||||
}
|
||||
const auto name = get_token(current_file_id);
|
||||
add_function(name, thread_name, code_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -164,16 +223,36 @@ namespace scripting
|
||||
unsigned int sl_get_canonical_string_stub(const char* str)
|
||||
{
|
||||
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
|
||||
scripting::token_map[str] = result;
|
||||
canonical_string_table[result] = str;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void on_shutdown(const std::function<void()>& callback)
|
||||
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)>& 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:
|
||||
@ -187,11 +266,9 @@ namespace scripting
|
||||
process_script_hook.create(SELECT_VALUE(0x3C7200_b, 0x50E340_b), process_script_stub);
|
||||
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub);
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
scr_load_level_hook.create(0x450FC0_b, scr_load_level_stub);
|
||||
}
|
||||
else
|
||||
g_load_structs_hook.create(SELECT_VALUE(0x2E7970_b, 0x458520_b), g_load_structs_stub);
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x2D4CD0_b, 0x450FC0_b), scr_load_level_stub);
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
vm_execute_hook.create(0x3CA080_b, vm_execute_stub);
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ namespace scripting
|
||||
|
||||
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 utils::concurrency::container<shared_table_t> shared_table;
|
||||
|
||||
void on_shutdown(const std::function<void()>& callback);
|
||||
extern std::string current_file;
|
||||
|
||||
void on_shutdown(const std::function<void(bool)>& callback);
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id);
|
||||
std::string get_token(unsigned int id);
|
||||
}
|
@ -485,24 +485,27 @@ namespace ui_scripting
|
||||
}
|
||||
}
|
||||
|
||||
std::string current_error;
|
||||
int main_handler(game::hks::lua_State* state)
|
||||
{
|
||||
const auto value = state->m_apistack.base[-1];
|
||||
if (value.t != game::hks::TCFUNCTION)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto closure = value.v.cClosure;
|
||||
if (converted_functions.find(closure) == converted_functions.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& function = converted_functions[closure];
|
||||
bool error = false;
|
||||
|
||||
try
|
||||
{
|
||||
const auto value = state->m_apistack.base[-1];
|
||||
if (value.t != game::hks::TCFUNCTION)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto closure = value.v.cClosure;
|
||||
if (converted_functions.find(closure) == converted_functions.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& function = converted_functions[closure];
|
||||
|
||||
const auto args = get_return_values();
|
||||
const auto results = function(args);
|
||||
|
||||
@ -515,7 +518,13 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
game::hks::hksi_luaL_error(state, e.what());
|
||||
current_error = e.what();
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
game::hks::hksi_luaL_error(state, current_error.data());
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,57 +1,39 @@
|
||||
#include <std_include.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include "../../component/console.hpp"
|
||||
#include "../../component/gsc.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> lowercase_map(
|
||||
const std::unordered_map<std::string, unsigned>& old_map)
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> new_map{};
|
||||
for (auto& entry : old_map)
|
||||
{
|
||||
new_map[utils::string::to_lower(entry.first)] = entry.second;
|
||||
}
|
||||
|
||||
return new_map;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_methods()
|
||||
{
|
||||
static auto methods = lowercase_map(method_map);
|
||||
return methods;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_functions()
|
||||
{
|
||||
static auto function = lowercase_map(function_map);
|
||||
return function;
|
||||
}
|
||||
|
||||
int find_function_index(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
|
||||
const auto& primary_map = prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
const auto& secondary_map = !prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
|
||||
auto function_entry = primary_map.find(target);
|
||||
if (function_entry != primary_map.end())
|
||||
auto first = xsk::gsc::h1::resolver::function_id;
|
||||
auto second = xsk::gsc::h1::resolver::method_id;
|
||||
if (!prefer_global)
|
||||
{
|
||||
return function_entry->second;
|
||||
std::swap(first, second);
|
||||
}
|
||||
|
||||
function_entry = secondary_map.find(target);
|
||||
if (function_entry != secondary_map.end())
|
||||
const auto first_res = first(target);
|
||||
if (first_res)
|
||||
{
|
||||
return function_entry->second;
|
||||
return first_res;
|
||||
}
|
||||
|
||||
const auto second_res = second(target);
|
||||
if (second_res)
|
||||
{
|
||||
return second_res;
|
||||
}
|
||||
|
||||
return -1;
|
||||
@ -59,15 +41,12 @@ namespace scripting
|
||||
|
||||
script_function get_function_by_index(const unsigned index)
|
||||
{
|
||||
static const auto function_table = SELECT_VALUE(0xB8CC510_b, 0xAC83820_b);
|
||||
static const auto method_table = SELECT_VALUE(0xB8CDD60_b, 0xAC85070_b);
|
||||
|
||||
if (index < 0x30A)
|
||||
if (index < 0x1000)
|
||||
{
|
||||
return reinterpret_cast<script_function*>(function_table)[index - 1];
|
||||
return reinterpret_cast<script_function*>(gsc::func_table)[index - 1];
|
||||
}
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
|
||||
return reinterpret_cast<script_function*>(gsc::meth_table)[index - 0x8000];
|
||||
}
|
||||
|
||||
unsigned int parse_token_id(const std::string& name)
|
||||
@ -86,32 +65,17 @@ namespace scripting
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> find_token(unsigned int id)
|
||||
std::string find_token(unsigned int id)
|
||||
{
|
||||
std::vector<std::string> results;
|
||||
|
||||
results.push_back(utils::string::va("_id_%X", id));
|
||||
results.push_back(utils::string::va("_ID%i", id));
|
||||
|
||||
for (const auto& token : token_map)
|
||||
{
|
||||
if (token.second == id)
|
||||
{
|
||||
results.push_back(token.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return xsk::gsc::h1::resolver::token_name(static_cast<std::uint16_t>(id));
|
||||
}
|
||||
|
||||
unsigned int find_token_id(const std::string& name)
|
||||
{
|
||||
const auto result = token_map.find(name);
|
||||
|
||||
if (result != token_map.end())
|
||||
const auto result = xsk::gsc::h1::resolver::token_id(name);
|
||||
if (result)
|
||||
{
|
||||
return result->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto parsed_id = parse_token_id(name);
|
||||
@ -126,7 +90,10 @@ namespace scripting
|
||||
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;
|
||||
if (index < 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return get_function_by_index(index);
|
||||
}
|
||||
|
@ -3,13 +3,9 @@
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, unsigned> method_map;
|
||||
extern std::unordered_map<std::string, unsigned> function_map;
|
||||
extern std::unordered_map<std::string, unsigned> token_map;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
std::vector<std::string> find_token(unsigned int id);
|
||||
std::string find_token(unsigned int id);
|
||||
unsigned int find_token_id(const std::string& name);
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
|
@ -12,6 +12,10 @@
|
||||
#include "../../../component/fastfiles.hpp"
|
||||
#include "../../../component/scheduler.hpp"
|
||||
|
||||
#include <xsk/gsc/types.hpp>
|
||||
#include <xsk/resolver.hpp>
|
||||
#include <xsk/utils/compression.hpp>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/http.hpp>
|
||||
@ -215,9 +219,10 @@ namespace scripting::lua
|
||||
|
||||
auto entity_type = state.new_usertype<entity>("entity");
|
||||
|
||||
for (const auto& func : method_map)
|
||||
for (const auto& func : xsk::gsc::h1::resolver::get_methods())
|
||||
{
|
||||
const auto name = utils::string::to_lower(func.first);
|
||||
const auto func_name = std::string(func.first);
|
||||
const auto name = utils::string::to_lower(func_name);
|
||||
entity_type[name.data()] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
@ -334,9 +339,10 @@ namespace scripting::lua
|
||||
auto game_type = state.new_usertype<game>("game_");
|
||||
state["game"] = game();
|
||||
|
||||
for (const auto& func : function_map)
|
||||
for (const auto& func : xsk::gsc::h1::resolver::get_functions())
|
||||
{
|
||||
const auto name = utils::string::to_lower(func.first);
|
||||
const auto func_name = std::string(func.first);
|
||||
const auto name = utils::string::to_lower(func_name);
|
||||
game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
@ -467,18 +473,18 @@ namespace scripting::lua
|
||||
const std::string function_name, const sol::protected_function& function)
|
||||
{
|
||||
const auto pos = get_function_pos(filename, function_name);
|
||||
logfile::vm_execute_hooks[pos] = function;
|
||||
logfile::set_lua_hook(pos, function);
|
||||
|
||||
auto detour = sol::table::create(function.lua_state());
|
||||
|
||||
detour["disable"] = [pos]()
|
||||
{
|
||||
logfile::vm_execute_hooks.erase(pos);
|
||||
logfile::clear_hook(pos);
|
||||
};
|
||||
|
||||
detour["enable"] = [pos, function]()
|
||||
detour["enable"] = [&]()
|
||||
{
|
||||
logfile::vm_execute_hooks[pos] = function;
|
||||
logfile::set_lua_hook(pos, function);
|
||||
};
|
||||
|
||||
detour["invoke"] = sol::overload(
|
||||
|
@ -120,11 +120,10 @@ namespace scripting::lua
|
||||
game::VariableValue convert_function(sol::lua_value value)
|
||||
{
|
||||
const auto function = value.as<sol::protected_function>();
|
||||
const auto index = reinterpret_cast<char*>(logfile::vm_execute_hooks.size());
|
||||
const auto index = reinterpret_cast<char*>(logfile::get_hook_count() + 1);
|
||||
logfile::set_lua_hook(index, function);
|
||||
|
||||
logfile::vm_execute_hooks[index] = function;
|
||||
|
||||
game::VariableValue func;
|
||||
game::VariableValue func{};
|
||||
func.type = game::SCRIPT_FUNCTION;
|
||||
func.u.codePosValue = index;
|
||||
|
||||
|
@ -108,6 +108,7 @@ namespace game
|
||||
WEAK symbol<int(playerState_s* ps, unsigned int weapon, int dualWield,
|
||||
int startInAltMode, int, int, int, char, ...)> G_GivePlayerWeapon{0x2F24F0, 0x461600};
|
||||
WEAK symbol<void(playerState_s* ps, unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x29D9E0, 0x41C170};
|
||||
WEAK symbol<void(const char* fmt, ...)> G_LogPrintf{0x5FEF0, 0x4215C0};
|
||||
WEAK symbol<void(int clientNum, unsigned int weapon)> G_SelectWeapon{0x2F2EA0, 0x462560};
|
||||
WEAK symbol<int(playerState_s* ps, unsigned int weapon)> G_TakePlayerWeapon{0x2F3050, 0x462770};
|
||||
|
||||
@ -150,6 +151,8 @@ namespace game
|
||||
WEAK symbol<unsigned int(unsigned int localId, const char* pos,
|
||||
unsigned int paramcount)> VM_Execute{0x3C9E50, 0x510EB0};
|
||||
|
||||
WEAK symbol<void(const char* value)> Scr_AddString{0x3C7B20, 0x50EC50};
|
||||
|
||||
WEAK symbol<void(unsigned int id, scr_string_t stringValue,
|
||||
unsigned int paramcount)> Scr_NotifyId{0x3C92E0, 0x510340};
|
||||
WEAK symbol<const float*(const float* v)> Scr_AllocVector{0x3C42D0, 0x50B330};
|
||||
@ -160,6 +163,12 @@ namespace game
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x3C6760, 0x50D8E0};
|
||||
WEAK symbol<unsigned int(int classnum, unsigned int entnum)> Scr_GetEntityId{0x3C66B0, 0x50D830};
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x2E8FC0, 0x459CD0};
|
||||
WEAK symbol<void()> Scr_ErrorInternal{0x3C7F60, 0x50F0D0};
|
||||
|
||||
WEAK symbol<unsigned int(const char* filename)> Scr_LoadScript{0x3BDF70, 0x504FA0};
|
||||
WEAK symbol<unsigned int(const char* filename, unsigned int handle)> Scr_GetFunctionHandle{0x3BDE00, 0x504E30};
|
||||
WEAK symbol<unsigned int(int handle, int num_param)> Scr_ExecThread{0x3C7FE0, 0x50F150};
|
||||
WEAK symbol<unsigned int(void* func, int type, unsigned int name)> Scr_RegisterFunction{0x3BD860, 0x504840};
|
||||
|
||||
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x1BCED0, 0x362840};
|
||||
WEAK symbol<float()> ScrPlace_HiResGetScaleX{0x0, 0x362910};
|
||||
@ -171,6 +180,13 @@ namespace game
|
||||
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x0, 0x0};
|
||||
WEAK symbol<XAssetHeader(XAssetType type, const char* name,
|
||||
int createDefault)> DB_FindXAssetHeader{0x1F1120, 0x3950C0};
|
||||
|
||||
WEAK symbol<int(XAssetType type, const char* name)> DB_IsXAssetDefault{0x1F25A0, 0x3968C0};
|
||||
WEAK symbol<int(XAssetType type, const char* name)> DB_XAssetExists{0x1F6290, 0x39B7B0};
|
||||
|
||||
WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{0x1F1F40, 0x3961B0};
|
||||
WEAK symbol<int(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x1F1E00, 0x396080};
|
||||
|
||||
WEAK symbol<bool(const char* zone, int source)> DB_FileExists{0x1F0D50, 0x394DC0};
|
||||
WEAK symbol<void(XZoneInfo* zoneInfo, unsigned int zoneCount, DBSyncMode syncMode)> DB_LoadXAssets{0x1F31E0, 0x397500};
|
||||
WEAK symbol<bool(const char* zoneName)> DB_IsLocalized{0x1F23C0, 0x396790};
|
||||
@ -279,6 +295,8 @@ namespace game
|
||||
|
||||
WEAK symbol<DWORD> threadIds{0xB896210, 0xAC80740};
|
||||
|
||||
WEAK symbol<int> level_time{0x56DBAA0, 0x7361F9C};
|
||||
|
||||
namespace mp
|
||||
{
|
||||
WEAK symbol<gentity_s> g_entities{0x0, 0x71F19E0};
|
||||
|
@ -36,9 +36,9 @@ namespace utils::string
|
||||
|
||||
std::string to_lower(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
|
||||
{
|
||||
return static_cast<char>(tolower(input));
|
||||
return static_cast<char>(std::tolower(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
@ -46,9 +46,9 @@ namespace utils::string
|
||||
|
||||
std::string to_upper(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input)
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input)
|
||||
{
|
||||
return static_cast<char>(toupper(input));
|
||||
return static_cast<char>(std::toupper(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
@ -131,8 +131,6 @@ namespace utils::string
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100)
|
||||
std::string convert(const std::wstring& wstr)
|
||||
{
|
||||
std::string result;
|
||||
@ -158,7 +156,6 @@ namespace utils::string
|
||||
|
||||
return result;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
std::string replace(std::string str, const std::string& from, const std::string& to)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user