Merge branch 'develop' of https://github.com/h1-mod/h1-mod into develop

This commit is contained in:
fed 2022-10-14 20:16:56 +02:00
commit c2a0b667bd
13 changed files with 328 additions and 167 deletions

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 849f7ceaf7876286aa663b4f0157b4542090fe90
Subproject commit 991fa6682e819590c695f00c6b880548e55fa914

2
deps/curl vendored

@ -1 +1 @@
Subproject commit b82eb72d8097c93313329a4aecd41317a293f506
Subproject commit 68fa9bf3f5d7b4fcbb57619f70cb4aabb79a51f6

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit e10c62ec18a8b7689adcc3076e40c5a9ef1de3b6
Subproject commit e8e678e1018615536f5b9cd07654d36449a28ac2

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 7c896ce8194619f2f9625bd115086f97b1cf7129
Subproject commit 57786d126249b5ed4f42b579047941805e742949

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 19f8551627d2a89b910d961fc9c7f626f3af7b21
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc

View File

@ -609,10 +609,12 @@ namespace command
add_commands_generic();
gsc::function::add("executecommand", []()
gsc::function::add("executecommand", [](const gsc::function_args& args)
{
const auto cmd = gsc::get_argument(0).as<std::string>();
const auto cmd = args[0].as<std::string>();
command::execute(cmd, true);
return scripting::script_value{};
});
}

View File

@ -225,9 +225,9 @@ namespace dedicated
}), true);
// return 0 so the game doesn't override the cfg
gsc::function::add("isusingmatchrulesdata", []()
gsc::function::add("isusingmatchrulesdata", [](const gsc::function_args& args)
{
game::Scr_AddInt(0);
return 0;
});
// delay console commands until the initialization is done // COULDN'T FOUND

View File

@ -26,11 +26,13 @@
namespace gsc
{
void* func_table[0x1000]{};
void* meth_table[0x1000]{};
builtin_function func_table[0x1000]{};
builtin_method meth_table[0x1000]{};
namespace
{
utils::hook::detour scr_emit_function_hook;
game::dvar_t* developer_script = nullptr;
auto compiler = ::gsc::compiler();
@ -42,8 +44,8 @@ namespace gsc
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::unordered_map<unsigned int, script_function> functions;
std::unordered_map<unsigned int, script_method> methods;
std::optional<std::string> gsc_error;
@ -52,6 +54,8 @@ namespace gsc
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{};
@ -349,11 +353,16 @@ namespace gsc
}
}
void builtin_call_error(const std::string& error_str)
std::uint16_t get_function_id()
{
const auto pos = game::scr_function_stack->pos;
const auto function_id = *reinterpret_cast<std::uint16_t*>(
return *reinterpret_cast<std::uint16_t*>(
reinterpret_cast<size_t>(pos - 2));
}
void builtin_call_error(const std::string& error_str)
{
const auto function_id = get_function_id();
if (function_id > 0x1000)
{
@ -367,11 +376,12 @@ namespace gsc
}
}
void* vm_error_stub(void* a1)
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);
utils::hook::invoke<void>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
return;
}
console::warn("*********** script runtime error *************\n");
@ -405,7 +415,7 @@ namespace gsc
print_callstack();
console::warn("**********************************************\n");
return utils::hook::invoke<void*>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
utils::hook::invoke<void*>(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), a1);
}
void get_unknown_function_error(const char* code_pos)
@ -471,31 +481,49 @@ namespace gsc
return res;
}
void register_gsc_functions_stub(void* a1, void* a2)
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
{
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);
}
current_filename = filename;
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
}
void register_gsc_methods_stub(void* a1, void* a2)
function_args get_arguments()
{
utils::hook::invoke<void>(SELECT_VALUE(0x2E0FB0_b, 0x1CE120_b), a1, a2);
for (const auto& method : methods)
std::vector<scripting::script_value> args;
for (auto i = 0; static_cast<unsigned int>(i) < game::scr_VmPub->outparamcount; ++i)
{
game::Scr_RegisterFunction(method.first, 0, method.second);
const auto value = game::scr_VmPub->top[-i];
args.push_back(value);
}
return args;
}
void execute_custom_function(builtin_function function)
void return_value(const scripting::script_value& value)
{
if (game::scr_VmPub->outparamcount)
{
game::Scr_ClearOutParams();
}
scripting::push_value(value);
}
void call_custom_function(const uint16_t id)
{
auto error = false;
try
{
function();
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& e)
{
@ -510,13 +538,20 @@ namespace gsc
}
}
void execute_custom_method(scripting::script_function method, game::scr_entref_t ent_ref)
void call_custom_method(const uint16_t id)
{
auto error = false;
try
{
method(ent_ref);
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& e)
{
@ -533,10 +568,8 @@ namespace gsc
void vm_call_builtin_function_stub(builtin_function function)
{
auto is_custom_function = false;
{
is_custom_function = functions.find(function) != functions.end();
}
const auto function_id = get_function_id();
const auto is_custom_function = functions.find(function_id) != functions.end();
if (!is_custom_function)
{
@ -544,15 +577,30 @@ namespace gsc
}
else
{
execute_custom_function(function);
call_custom_function(function_id);
}
}
utils::hook::detour scr_emit_function_hook;
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
game::scr_entref_t get_entity_id_stub(unsigned int ent_id)
{
current_filename = filename;
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
const auto ref = game::Scr_GetEntityIdRef(ent_id);
saved_ent_ref = ref;
return ref;
}
void vm_call_builtin_method_stub(builtin_method method)
{
const auto function_id = get_function_id();
const auto is_custom_function = methods.find(function_id) != methods.end();
if (!is_custom_function)
{
method(saved_ent_ref);
}
else
{
call_custom_method(function_id);
}
}
}
@ -597,52 +645,67 @@ namespace gsc
}
}
scripting::script_value get_argument(int index)
{
if (index >= static_cast<int>(game::scr_VmPub->outparamcount))
{
return {};
}
return game::scr_VmPub->top[-index];
}
namespace function
{
void add(const std::string& name, builtin_function function)
void add(const std::string& name, script_function function)
{
if (xsk::gsc::h1::resolver::find_function(name))
{
const auto id = xsk::gsc::h1::resolver::function_id(name);
functions[function] = id;
functions[id] = function;
}
else
{
const auto id = ++function_id_start;
xsk::gsc::h1::resolver::add_function(name, static_cast<std::uint16_t>(id));
functions[function] = id;
functions[id] = function;
}
}
}
namespace method
{
void add(const std::string& name, builtin_method method)
void add(const std::string& name, script_method method)
{
if (xsk::gsc::h1::resolver::find_method(name))
{
const auto id = xsk::gsc::h1::resolver::method_id(name);
methods[method] = id;
methods[id] = method;
}
else
{
const auto id = ++method_id_start;
xsk::gsc::h1::resolver::add_method(name, static_cast<std::uint16_t>(id));
methods[method] = id;
methods[id] = method;
}
}
}
function_args::function_args(std::vector<scripting::script_value> values)
: values_(values)
{
}
unsigned int function_args::size() const
{
return static_cast<unsigned int>(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 component final : public component_interface
{
public:
@ -693,9 +756,6 @@ namespace gsc
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
utils::hook::set<uint32_t>(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4,
@ -712,47 +772,62 @@ namespace gsc
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));
/*
trying to do a jump hook to push the ent reference (if it exists) and the builtin function/methods works, but
if longjmp is called because of a runtime error in our custom functions/methods, then the game just kinda dies
or gets incredibly slow but will eventually load. for functions, the workaround is easy but for methods, we still
have to remember the entity that called the method so the workaround is just hooking the Scr_GetEntityIdRef call
*/
utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8);
utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub);
function::add("print", []()
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 function_args& args)
{
const auto num = game::Scr_GetNumParam();
std::string buffer{};
for (auto i = 0; i < num; i++)
for (auto i = 0u; i < args.size(); ++i)
{
const auto str = game::Scr_GetString(i);
const auto str = args[i].as<std::string>();
buffer.append(str);
buffer.append("\t");
}
console::info("%s\n", buffer.data());
return scripting::script_value{};
});
function::add("assert", []()
function::add("assert", [](const function_args& args)
{
const auto expr = get_argument(0).as<int>();
const auto expr = args[0].as<int>();
if (!expr)
{
throw std::runtime_error("assert fail");
}
return scripting::script_value{};
});
function::add("assertex", []()
function::add("assertex", [](const function_args& args)
{
const auto expr = get_argument(0).as<int>();
const auto expr = args[0].as<int>();
if (!expr)
{
const auto error = get_argument(1).as<std::string>();
const auto error = args[1].as<std::string>();
throw std::runtime_error(error);
}
return scripting::script_value{};
});
function::add("replacefunc", []()
function::add("replacefunc", [](const function_args& args)
{
const auto what = get_argument(0).get_raw();
const auto with = get_argument(1).get_raw();
const auto what = args[0].get_raw();
const auto with = args[1].get_raw();
if (what.type != game::SCRIPT_FUNCTION)
{
@ -765,26 +840,29 @@ namespace gsc
}
logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
return scripting::script_value{};
});
function::add("toupper", []()
function::add("toupper", [](const function_args& args)
{
const auto string = get_argument(0).as<std::string>();
game::Scr_AddString(utils::string::to_upper(string).data());
const auto string = args[0].as<std::string>();
return utils::string::to_upper(string);
});
function::add("logprint", []()
function::add("logprint", [](const function_args& args)
{
std::string buffer{};
const auto params = game::Scr_GetNumParam();
for (auto i = 0; i < params; i++)
for (auto i = 0u; i < args.size(); ++i)
{
const auto string = game::Scr_GetString(i);
const auto string = args[i].as<std::string>();
buffer.append(string);
}
game::G_LogPrintf("%s", buffer.data());
return scripting::script_value{};
});
scripting::on_shutdown([](int free_scripts)

View File

@ -4,26 +4,44 @@
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);
extern void* func_table[0x1000];
extern void* meth_table[0x1000];
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];
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/);
void load_main_handles();
void load_init_handles();
scripting::script_value get_argument(int index);
namespace function
{
void add(const std::string& name, builtin_function function);
void add(const std::string& name, script_function function);
}
namespace method
{
void add(const std::string& name, builtin_method function);
void add(const std::string& name, script_method function);
}
}

View File

@ -23,7 +23,7 @@ namespace io
{
if (path.generic_string().find("..") != std::string::npos)
{
throw std::runtime_error("io: directory traversal is not allowed");
throw std::runtime_error("directory traversal is not allowed");
}
}
@ -38,7 +38,7 @@ namespace io
return (fs_game_path / path).generic_string();
}
throw std::runtime_error("io: fs_game is not properly defined");
throw std::runtime_error("fs_game is not properly defined");
}
}
@ -47,60 +47,59 @@ namespace io
public:
void post_unpack() override
{
gsc::function::add("fileexists", []()
gsc::function::add("fileexists", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddBool(utils::io::file_exists(path));
const auto path = convert_path(args[0].as<std::string>());
return utils::io::file_exists(path);
});
gsc::function::add("writefile", []()
gsc::function::add("writefile", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
const auto data = gsc::get_argument(1).as<std::string>();
const auto params = game::Scr_GetNumParam();
const auto path = convert_path(args[0].as<std::string>());
const auto data = args[1].as<std::string>();
auto append = false;
if (params > 2)
if (args.size() > 2u)
{
append = gsc::get_argument(2).as<bool>();
append = args[2].as<bool>();
}
game::Scr_AddBool(utils::io::write_file(path, data, append));
return utils::io::write_file(path, data, append);
});
gsc::function::add("readfile", []()
gsc::function::add("readfile", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddString(utils::io::read_file(path).data());
const auto path = convert_path(args[0].as<std::string>());
return utils::io::read_file(path);
});
gsc::function::add("filesize", []()
gsc::function::add("filesize", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddInt(static_cast<uint32_t>(utils::io::file_size(path)));
const auto path = convert_path(args[0].as<std::string>());
return static_cast<uint32_t>(utils::io::file_size(path));
});
gsc::function::add("createdirectory", []()
gsc::function::add("createdirectory", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddBool(utils::io::create_directory(path));
const auto path = convert_path(args[0].as<std::string>());
return utils::io::create_directory(path);
});
gsc::function::add("directoryexists", []()
gsc::function::add("directoryexists", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddBool(utils::io::directory_exists(path));
const auto path = convert_path(args[0].as<std::string>());
return utils::io::directory_exists(path);
});
gsc::function::add("directoryisempty", []()
gsc::function::add("directoryisempty", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddBool(utils::io::directory_is_empty(path));
const auto path = convert_path(args[0].as<std::string>());
return utils::io::directory_is_empty(path);
});
gsc::function::add("listfiles", []()
gsc::function::add("listfiles", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
const auto path = convert_path(args[0].as<std::string>());
const auto files = utils::io::list_files(path);
scripting::array array{};
@ -109,20 +108,22 @@ namespace io
array.push(file);
}
scripting::push_value(array);
return array;
});
gsc::function::add("copyfolder", []()
gsc::function::add("copyfolder", [](const gsc::function_args& args)
{
const auto source = convert_path(gsc::get_argument(0).as<std::string>());
const auto target = convert_path(gsc::get_argument(1).as<std::string>());
const auto source = convert_path(args[0].as<std::string>());
const auto target = convert_path(args[1].as<std::string>());
utils::io::copy_folder(source, target);
return scripting::script_value{};
});
gsc::function::add("removefile", []()
gsc::function::add("removefile", [](const gsc::function_args& args)
{
const auto path = convert_path(gsc::get_argument(0).as<std::string>());
game::Scr_AddBool(utils::io::remove_file(path));
const auto path = convert_path(args[0].as<std::string>());
return utils::io::remove_file(path);
});
}
};

View File

@ -1,12 +1,11 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "party.hpp"
#include "console.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace logger
@ -15,48 +14,42 @@ namespace logger
{
utils::hook::detour com_error_hook;
const game::dvar_t* logger_dev = nullptr;
void print_error(const char* msg, ...)
{
char buffer[2048];
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::error(buffer);
console::error("%s", buffer);
}
void print_com_error(int, const char* msg, ...)
{
char buffer[2048];
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::error(buffer);
console::error("%s", buffer);
}
void com_error_stub(const int error, const char* msg, ...)
{
char buffer[2048];
char buffer[2048]{};
va_list ap;
{
va_list ap;
va_start(ap, msg);
va_start(ap, msg);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
va_end(ap);
console::error("Error: %s\n", buffer);
}
console::error("Error: %s\n", buffer);
party::clear_sv_motd(); // clear sv_motd on error if it exists
@ -65,50 +58,43 @@ namespace logger
void print_warning(const char* msg, ...)
{
char buffer[2048];
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::warn(buffer);
console::warn("%s", buffer);
}
void print(const char* msg, ...)
{
char buffer[2048];
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::info(buffer);
console::info("%s", buffer);
}
void print_dev(const char* msg, ...)
{
static auto* enabled = dvars::register_bool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff");
if (!enabled->current.enabled)
if (!logger_dev->current.enabled)
{
return;
}
char buffer[2048];
char buffer[2048]{};
va_list ap;
va_start(ap, msg);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
vsnprintf_s(buffer, _TRUNCATE, msg, ap);
va_end(ap);
console::info(buffer);
console::info("%s", buffer);
}
}
@ -126,6 +112,8 @@ namespace logger
}
com_error_hook.create(game::Com_Error, com_error_stub);
logger_dev = dvars::register_bool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff");
}
};
}

View File

@ -15,6 +15,12 @@ namespace scripting
{
}
script_value::script_value(const value_wrap& value)
: value_(value.get_raw())
{
}
script_value::script_value(const int value)
{
game::VariableValue variable{};
@ -292,6 +298,12 @@ namespace scripting
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>())

View File

@ -9,12 +9,14 @@ namespace scripting
{
class entity;
class array;
class value_wrap;
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);
@ -53,6 +55,20 @@ namespace scripting
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_{};
@ -62,4 +78,50 @@ namespace scripting
T get() const;
};
class value_wrap
{
public:
value_wrap(const scripting::script_value& value, int argument_index);
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_;
};
}