scripting additions, changes, and cleanup (#330)

This commit is contained in:
m 2022-10-18 05:38:06 -05:00 committed by GitHub
parent 3fb1024e67
commit 8cdb4690c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 643 additions and 134 deletions

6
.gitmodules vendored
View File

@ -48,6 +48,10 @@
[submodule "deps/curl"]
path = deps/curl
url = https://github.com/curl/curl.git
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json.git
[submodule "deps/gsc-tool"]
path = deps/gsc-tool
url = https://github.com/h1-mod/gsc-tool.git
url = https://github.com/xensik/gsc-tool.git
branch = xlabs

2
deps/gsc-tool vendored

@ -1 +1 @@
Subproject commit 378c0d5cd5ac18140bb676eb371d105405dc03fc
Subproject commit 842f168a67878f18ea6a42b9d3ce56ff9fafff0d

1
deps/json vendored Submodule

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

17
deps/premake/json.lua vendored Normal file
View File

@ -0,0 +1,17 @@
json = {
source = path.join(dependencies.basePath, "json")
}
function json.import()
json.includes()
end
function json.includes()
includedirs {path.join(json.source, "single_include/*")}
end
function json.project()
end
table.insert(dependencies, json)

View File

@ -130,12 +130,12 @@ namespace dedicated
void sys_error_stub(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);

View File

@ -9,7 +9,6 @@
#include "scripting.hpp"
#include "game/dvars.hpp"
#include "game/scripting/functions.hpp"
#include <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
@ -72,14 +71,6 @@ namespace gsc
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) &&
@ -107,6 +98,22 @@ namespace gsc
return loaded_scripts[real_name];
}
/*
without this check, gsc rawfiles that a map contains will be compiled. however, these aren't the correct files.
each rawfile has a scriptfile counterpart in asset pool that is meant to be used instead.
the gsc rawfiles are just leftover from creating the maps.
(if you are creating a custom map, you can safely have gsc rawfiles without having scriptfile counterparts)
*/
if (real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart")
|| (real_name.starts_with("maps/mp") && real_name.ends_with("_fx.gsc")))
{
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, real_name.data()))
{
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, real_name.data(), false).scriptfile;
}
}
std::string source_buffer{};
if (!read_scriptfile(real_name + ".gsc", &source_buffer))
{
@ -792,7 +799,7 @@ namespace gsc
for (auto i = 0u; i < args.size(); ++i)
{
const auto str = args[i].as<std::string>();
const auto str = args[i].to_string();
buffer.append(str);
buffer.append("\t");
}
@ -865,6 +872,19 @@ namespace gsc
return scripting::script_value{};
});
function::add("getfunction", [](const function_args& args) -> scripting::script_value
{
const auto filename = args[0].as<std::string>();
const auto function = args[1].as<std::string>();
if (scripting::script_function_table[filename].find(function) != scripting::script_function_table[filename].end())
{
return scripting::function{scripting::script_function_table[filename][function]};
}
return {};
});
scripting::on_shutdown([](int free_scripts)
{
if (free_scripts)

View File

@ -1,6 +1,8 @@
#pragma once
#include "game/scripting/script_value.hpp"
#include "game/scripting/array.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/function.hpp"
namespace gsc
{

View File

@ -50,6 +50,18 @@ namespace io
throw std::runtime_error("fs_game is not properly defined");
}
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);
}
}
class component final : public component_interface
@ -60,7 +72,7 @@ namespace io
use_root_folder = utils::flags::has_flag("io_game_dir");
if (use_root_folder)
{
console::warn("WARNING: GSC has access to your game folder. To prevent possible malicious code, remove this flag.");
console::warn("GSC has access to your game folder. To prevent possible malicious code, remove the '-io_game_dir' launch flag.");
}
gsc::function::add("fileexists", [](const gsc::function_args& args)
@ -141,6 +153,19 @@ namespace io
const auto path = convert_path(args[0].as<std::string>());
return utils::io::remove_file(path);
});
gsc::function::add("va", [](const gsc::function_args& args)
{
auto fmt = args[0].as<std::string>();
for (auto i = 1u; i < args.size(); i++)
{
const auto arg = args[i].to_string();
replace(fmt, "%s", arg);
}
return fmt;
});
}
};
}

View File

@ -0,0 +1,210 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "gsc.hpp"
#include "json.hpp"
#include <json.hpp>
namespace json
{
namespace
{
nlohmann::json gsc_to_json(scripting::script_value _value);
nlohmann::json entity_to_array(unsigned int id)
{
scripting::array array(id);
nlohmann::json obj;
auto string_indexed = -1;
const auto keys = array.get_keys();
for (auto i = 0; i < keys.size(); i++)
{
const auto is_int = keys[i].is<int>();
const auto is_string = keys[i].is<std::string>();
if (string_indexed == -1)
{
string_indexed = is_string;
}
if (!string_indexed && is_int)
{
const auto index = keys[i].as<int>();
obj[index] = gsc_to_json(array[index]);
}
else if (string_indexed && is_string)
{
const auto key = keys[i].as<std::string>();
obj.emplace(key, gsc_to_json(array[key]));
}
}
return obj;
}
nlohmann::json vector_to_array(const float* value)
{
nlohmann::json obj;
obj.push_back(value[0]);
obj.push_back(value[1]);
obj.push_back(value[2]);
return obj;
}
nlohmann::json gsc_to_json(scripting::script_value _value)
{
const auto variable = _value.get_raw();
const auto value = variable.u;
const auto type = variable.type;
switch (type)
{
case (game::SCRIPT_NONE):
return {};
case (game::SCRIPT_INTEGER):
return value.intValue;
case (game::SCRIPT_FLOAT):
return value.floatValue;
case (game::SCRIPT_STRING):
case (game::SCRIPT_ISTRING):
return game::SL_ConvertToString(static_cast<game::scr_string_t>(value.stringValue));
case (game::SCRIPT_VECTOR):
return vector_to_array(value.vectorValue);
case (game::SCRIPT_OBJECT):
{
const auto object_type = game::scr_VarGlob->objectVariableValue[value.uintValue].w.type;
switch (object_type)
{
case (game::SCRIPT_STRUCT):
return "[struct]";
case (game::SCRIPT_ARRAY):
return entity_to_array(value.uintValue);
default:
return "[entity]";
}
}
case (game::SCRIPT_FUNCTION):
return _value.as<scripting::function>().get_name();
default:
return "[unknown type]";
};
}
scripting::script_value json_to_gsc(nlohmann::json obj)
{
const auto type = obj.type();
switch (type)
{
case (nlohmann::detail::value_t::number_integer):
case (nlohmann::detail::value_t::number_unsigned):
return obj.get<int>();
case (nlohmann::detail::value_t::number_float):
return obj.get<float>();
case (nlohmann::detail::value_t::string):
return obj.get<std::string>();
case (nlohmann::detail::value_t::array):
{
scripting::array array;
for (const auto& [key, value] : obj.items())
{
array.push(json_to_gsc(value));
}
return array.get_raw();
}
case (nlohmann::detail::value_t::object):
{
scripting::array array;
for (const auto& [key, value] : obj.items())
{
array[key] = json_to_gsc(value);
}
return array.get_raw();
}
}
return {};
}
}
std::string gsc_to_string(const scripting::script_value& value)
{
return gsc_to_json(value).dump();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
gsc::function::add("array", [](const gsc::function_args& args)
{
scripting::array array(args.get_raw());
return array.get_raw();
});
gsc::function::add("map", [](const gsc::function_args& args)
{
scripting::array array;
for (auto i = 0u; i < args.size(); i += 2)
{
if (i >= args.size() - 1)
{
continue;
}
const auto key = args[i].as<std::string>();
array[key] = args[i + 1];
}
return array;
});
gsc::function::add("jsonparse", [](const gsc::function_args& args)
{
const auto json = args[0].as<std::string>();
const auto obj = nlohmann::json::parse(json);
return json_to_gsc(obj);
});
gsc::function::add("jsonserialize", [](const gsc::function_args& args)
{
const auto value = args[0];
auto indent = -1;
if (args.size() > 1)
{
indent = args[1].as<int>();
}
return gsc_to_json(value).dump(indent);
});
gsc::function::add("jsonprint", [](const gsc::function_args& args) -> scripting::script_value
{
std::string buffer;
for (const auto arg : args.get_raw())
{
buffer.append(gsc_to_string(arg));
buffer.append("\t");
}
printf("%s\n", buffer.data());
return {};
});
}
};
}
REGISTER_COMPONENT(json::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace json
{
std::string gsc_to_string(const scripting::script_value& _value);
}

View File

@ -1,7 +1,9 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "gsc.hpp"
#include "logfile.hpp"
#include "scripting.hpp"
#include "scheduler.hpp"
#include "game/dvars.hpp"
@ -34,6 +36,8 @@ namespace logfile
std::vector<sol::protected_function> player_killed_callbacks;
std::vector<sol::protected_function> player_damage_callbacks;
std::vector<scripting::function> say_callbacks;
game::dvar_t* logfile;
game::dvar_t* g_log;
@ -302,13 +306,22 @@ namespace logfile
game::SV_Cmd_ArgvBuffer(0, cmd, 1024);
auto hidden = false;
if (cmd == "say"s || cmd == "say_team"s)
{
auto hidden = false;
std::string message(game::ConcatArgs(1));
message.erase(0, 1);
hidden = message[1] == '/';
message.erase(0, hidden ? 2 : 1);
for (const auto& callback : say_callbacks)
{
const auto entity_id = game::Scr_GetEntityId(client_num, 0);
const auto result = callback(entity_id, {message, cmd == "say_team"s});
if (result.is<int>() && !hidden)
{
hidden = result.as<int>() == 0;
}
}
scheduler::once([cmd, message, self, hidden]()
{
@ -383,6 +396,18 @@ namespace logfile
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);
gsc::function::add("onplayersay", [](const gsc::function_args& args) -> scripting::script_value
{
const auto function = args[0].as<scripting::function>();
say_callbacks.push_back(function);
return {};
});
scripting::on_shutdown([](int)
{
say_callbacks.clear();
});
}
};
}

View File

@ -14,7 +14,7 @@ namespace logger
{
utils::hook::detour com_error_hook;
const game::dvar_t* logger_dev = nullptr;
game::dvar_t* logger_dev = nullptr;
void print_error(const char* msg, ...)
{

View File

@ -1,33 +1,27 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game/scripting/entity.hpp"
#include "game/scripting/functions.hpp"
#include "game/scripting/event.hpp"
#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 "game/game.hpp"
#include "game/scripting/event.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/functions.hpp"
#include "game/scripting/lua/engine.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.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;
@ -49,7 +43,7 @@ namespace scripting
utils::hook::detour db_find_xasset_header_hook;
std::string current_scriptfile;
std::string current_script_file;
unsigned int current_file_id{};
game::dvar_t* g_dump_scripts;
@ -96,14 +90,11 @@ namespace scripting
{
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();
}
@ -114,7 +105,6 @@ namespace scripting
{
if (!game::VirtualLobby_Loaded())
{
// execute init handles
gsc::load_init_handles();
}
@ -141,7 +131,7 @@ namespace scripting
game::G_LogPrintf("ShutdownGame:\n");
game::G_LogPrintf("------------------------------------------------------------\n");
return g_shutdown_game_hook.invoke<void>(free_scripts);
g_shutdown_game_hook.invoke<void>(free_scripts);
}
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset)
@ -158,7 +148,7 @@ namespace scripting
void process_script_stub(const char* filename)
{
current_scriptfile = filename;
current_script_file = filename;
const auto file_id = atoi(filename);
if (file_id)
@ -182,9 +172,9 @@ namespace scripting
filename = scripting::get_token(current_file_id);
}
if (script_function_table_sort.find(filename) == script_function_table_sort.end())
if (!script_function_table_sort.contains(filename))
{
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_scriptfile.data(), false);
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file.data(), false);
if (script)
{
const auto end = &script->bytecode[script->bytecodeLen];
@ -201,6 +191,7 @@ namespace scripting
{
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)
@ -275,7 +266,7 @@ namespace scripting
g_shutdown_game_hook.create(SELECT_VALUE(0x2A5130_b, 0x422F30_b), g_shutdown_game_stub);
scheduler::loop([]()
scheduler::loop([]
{
lua::engine::run_frame();
}, scheduler::pipeline::server);

View File

@ -8,6 +8,8 @@ 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 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;

View File

@ -48,16 +48,10 @@ namespace ui_scripting
const auto lui_updater = utils::nt::load_resource(LUI_UPDATER);
const auto lua_json = utils::nt::load_resource(LUA_JSON);
struct script
{
std::string name;
std::string root;
};
struct globals_t
{
std::string in_require_script;
std::vector<script> loaded_scripts;
std::unordered_map<std::string, std::string> loaded_scripts;
bool load_raw_script{};
std::string raw_script_name{};
};
@ -66,28 +60,13 @@ namespace ui_scripting
bool is_loaded_script(const std::string& name)
{
for (auto i = globals.loaded_scripts.begin(); i != globals.loaded_scripts.end(); ++i)
{
if (i->name == name)
{
return true;
}
}
return false;
return globals.loaded_scripts.contains(name);
}
std::string get_root_script(const std::string& name)
{
for (auto i = globals.loaded_scripts.begin(); i != globals.loaded_scripts.end(); ++i)
{
if (i->name == name)
{
return i->root;
}
}
return {};
const auto itr = globals.loaded_scripts.find(name);
return itr == globals.loaded_scripts.end() ? std::string() : itr->second;
}
table get_globals()
@ -133,7 +112,7 @@ namespace ui_scripting
void load_script(const std::string& name, const std::string& data)
{
globals.loaded_scripts.push_back({name, name});
globals.loaded_scripts[name] = name;
const auto lua = get_globals();
const auto load_results = lua["loadstring"](data, name);
@ -451,8 +430,10 @@ namespace ui_scripting
return hks_package_require_hook.invoke<void*>(state);
}
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
game::XAssetHeader db_find_x_asset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
{
game::XAssetHeader header{.luaFile = nullptr};
if (!is_loaded_script(globals.in_require_script))
{
return game::DB_FindXAssetHeader(type, name, allow_create_default);
@ -466,14 +447,14 @@ namespace ui_scripting
{
globals.load_raw_script = true;
globals.raw_script_name = target_script;
return static_cast<game::XAssetHeader>(reinterpret_cast<game::LuaFile*>(1));
header.luaFile = reinterpret_cast<game::LuaFile*>(1);
}
else if (name_.starts_with("ui/LUI/"))
{
return game::DB_FindXAssetHeader(type, name, allow_create_default);
}
return static_cast<game::XAssetHeader>(nullptr);
return header;
}
int hks_load_stub(game::hks::lua_State* state, void* compiler_options,
@ -482,14 +463,12 @@ namespace ui_scripting
if (globals.load_raw_script)
{
globals.load_raw_script = false;
globals.loaded_scripts.push_back({globals.raw_script_name, globals.in_require_script});
globals.loaded_scripts[globals.raw_script_name] = globals.in_require_script;
return load_buffer(globals.raw_script_name, utils::io::read_file(globals.raw_script_name));
}
else
{
return hks_load_hook.invoke<int>(state, compiler_options, reader,
reader_data, chunk_name);
}
return hks_load_hook.invoke<int>(state, compiler_options, reader,
reader_data, chunk_name);
}
std::string current_error;
@ -506,7 +485,7 @@ namespace ui_scripting
}
const auto closure = value.v.cClosure;
if (converted_functions.find(closure) == converted_functions.end())
if (!converted_functions.contains(closure))
{
return 0;
}
@ -563,8 +542,8 @@ namespace ui_scripting
return;
}
utils::hook::call(SELECT_VALUE(0xE7419_b, 0x25E809_b), db_find_xasset_header_stub);
utils::hook::call(SELECT_VALUE(0xE72CB_b, 0x25E6BB_b), db_find_xasset_header_stub);
utils::hook::call(SELECT_VALUE(0xE7419_b, 0x25E809_b), db_find_x_asset_header_stub);
utils::hook::call(SELECT_VALUE(0xE72CB_b, 0x25E6BB_b), db_find_x_asset_header_stub);
hks_load_hook.create(SELECT_VALUE(0xB46F0_b, 0x22C180_b), hks_load_stub);
@ -572,7 +551,7 @@ namespace ui_scripting
hks_start_hook.create(SELECT_VALUE(0x103C50_b, 0x27A790_b), hks_start_stub);
hks_shutdown_hook.create(SELECT_VALUE(0xFB370_b, 0x2707C0_b), hks_shutdown_stub);
command::add("lui_restart", []()
command::add("lui_restart", []
{
utils::hook::invoke<void>(SELECT_VALUE(0x1052C0_b, 0x27BEC0_b));
});

View File

@ -116,7 +116,7 @@ namespace scripting
game::AddRefToObject(id);
const auto local_id = game::AllocThread(id);
const auto result = game::VM_Execute(local_id, pos, (unsigned int)arguments.size());
const auto result = game::VM_Execute(local_id, pos, static_cast<std::uint32_t>(arguments.size()));
game::RemoveRefToObject(result);
return get_return_value();
@ -124,13 +124,13 @@ namespace scripting
const char* get_function_pos(const std::string& filename, const std::string& function)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
if (!script_function_table.contains(filename))
{
throw std::runtime_error("File '" + filename + "' not found");
};
}
const auto functions = scripting::script_function_table[filename];
if (functions.find(function) == functions.end())
const auto& functions = script_function_table[filename];
if (!functions.contains(function))
{
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
}
@ -184,7 +184,7 @@ namespace scripting
throw std::runtime_error("Failed to get value for field '" + field + "'");
}
const auto __ = gsl::finally([value]()
const auto _0 = gsl::finally([value]
{
game::RemoveRefToValue(value.type, value.u);
});

View File

@ -1,7 +1,10 @@
#pragma once
#include "game/game.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "function.hpp"
#include "script_value.hpp"
namespace scripting

View File

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

View File

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

View File

@ -6,7 +6,6 @@
#include <xsk/gsc/types.hpp>
#include <xsk/resolver.hpp>
#include <xsk/utils/compression.hpp>
#include <utils/string.hpp>

View File

@ -3,18 +3,16 @@
#include "error.hpp"
#include "value_conversion.hpp"
#include "../execution.hpp"
#include "../functions.hpp"
#include "game/scripting/execution.hpp"
#include "../../../component/command.hpp"
#include "../../../component/logfile.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/fastfiles.hpp"
#include "../../../component/scheduler.hpp"
#include "component/command.hpp"
#include "component/logfile.hpp"
#include "component/scripting.hpp"
#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>
@ -221,9 +219,8 @@ namespace scripting::lua
for (const auto& func : xsk::gsc::h1::resolver::get_methods())
{
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)
const auto name = std::string(func.first);
entity_type[name] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
@ -311,13 +308,13 @@ namespace scripting::lua
entity_type["getstruct"] = [](const entity& entity, const sol::this_state s)
{
const auto id = entity.get_entity_id();
return scripting::lua::entity_to_struct(s, id);
return entity_to_struct(s, id);
};
entity_type["struct"] = sol::property([](const entity& entity, const sol::this_state s)
{
const auto id = entity.get_entity_id();
return scripting::lua::entity_to_struct(s, id);
return entity_to_struct(s, id);
});
entity_type["scriptcall"] = [](const entity& entity, const sol::this_state s, const std::string& filename,
@ -341,8 +338,7 @@ namespace scripting::lua
for (const auto& func : xsk::gsc::h1::resolver::get_functions())
{
const auto func_name = std::string(func.first);
const auto name = utils::string::to_lower(func_name);
const auto name = std::string(func.first);
game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
@ -409,7 +405,7 @@ namespace scripting::lua
game_type["getfunctions"] = [entity_type](const game&, const sol::this_state s, const std::string& filename)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
if (!script_function_table.contains(filename))
{
throw std::runtime_error("File '" + filename + "' not found");
}
@ -428,7 +424,7 @@ namespace scripting::lua
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
const auto _0 = gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(entity, filename, function.first, arguments));
@ -442,7 +438,7 @@ namespace scripting::lua
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
const auto _0 = gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function.first, arguments));
@ -463,7 +459,7 @@ namespace scripting::lua
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
const auto _0 = gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function, arguments));
@ -497,7 +493,7 @@ namespace scripting::lua
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
const auto _0 = gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(entity, filename, function_name, arguments));
@ -511,7 +507,7 @@ namespace scripting::lua
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
const auto _0 = gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function_name, arguments));

View File

@ -1,8 +1,8 @@
#include <std_include.hpp>
#include "value_conversion.hpp"
#include "../functions.hpp"
#include "../execution.hpp"
#include ".../../component/logfile.hpp"
#include "game/scripting/functions.hpp"
#include "game/scripting/execution.hpp"
#include "component/logfile.hpp"
namespace scripting::lua
{
@ -275,7 +275,7 @@ namespace scripting::lua
return entity_to_array(state, value.get_raw().u.uintValue);
}
if (value.is<std::function<void()>>())
if (value.is<function>())
{
return convert_function(state, value.get_raw().u.codePosValue);
}

View File

@ -2,7 +2,7 @@
#include "script_value.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "functions.hpp"
#include "function.hpp"
namespace scripting
{
@ -20,7 +20,6 @@ namespace scripting
{
}
script_value::script_value(const int value)
{
game::VariableValue variable{};
@ -95,6 +94,15 @@ namespace scripting
this->value_ = variable;
}
script_value::script_value(const function& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_FUNCTION;
variable.u.codePosValue = value.get_pos();
this->value_ = variable;
}
script_value::script_value(const vector& value)
{
game::VariableValue variable{};
@ -252,11 +260,17 @@ namespace scripting
**************************************************************/
template <>
bool script_value::is<std::function<void()>>() const
bool script_value::is<function>() const
{
return this->get_raw().type == game::SCRIPT_FUNCTION;
}
template <>
function script_value::get() const
{
return function(this->get_raw().u.codePosValue);
}
/***************************************************************
* Entity
**************************************************************/
@ -331,9 +345,10 @@ namespace scripting
);
}
if (this->is<std::function<void()>>())
if (this->is<function>())
{
return utils::string::va("[[ function ]]");
const auto func = this->as<function>();
return utils::string::va("[[ %s ]]", func.get_name().data());
}
return this->type_name();

View File

@ -9,8 +9,95 @@ namespace scripting
{
class entity;
class array;
class function;
class value_wrap;
namespace
{
std::unordered_map<int, std::string> typenames =
{
{0, "undefined"},
{1, "object"},
{2, "string"},
{3, "localized string"},
{4, "vector"},
{5, "float"},
{6, "int"},
{7, "codepos"},
{8, "precodepos"},
{9, "function"},
{10, "builtin function"},
{11, "builtin method"},
{12, "stack"},
{13, "animation"},
{14, "developer codepos"}, // this exists on H1 but not IW6
{15, "pre animation"},
{16, "thread"},
{17, "notify thread"},
{18, "time thread"},
{19, "child thread"},
{20, "struct"},
{21, "removed entity"},
{22, "entity"},
{23, "array"},
{24, "removed thread"},
{25, "<free>"}, // VAR_COUNT is 25 on H1, but 24 on IW6
{26, "thread list"},
{27, "endon list"},
};
std::string get_typename(const game::VariableValue& value)
{
if (value.type == game::SCRIPT_OBJECT)
{
const auto type = game::scr_VarGlob->objectVariableValue[value.u.uintValue].w.type;
return typenames[type];
}
else
{
return typenames[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:
@ -31,25 +118,28 @@ namespace scripting
script_value(const entity& value);
script_value(const array& value);
script_value(const function& value);
script_value(const vector& value);
template <typename T>
bool is() const;
// was gonna do this but no clue if this is the same on H1 so just return string (https://github.com/fedddddd/t6-gsc-utils/blob/main/src/game/scripting/script_value.hpp#L18)
std::string type_name() const
{
return utils::string::va("%s", this->get_raw().type);
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>())
{
throw std::runtime_error("Invalid type");
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>();
@ -58,7 +148,6 @@ namespace scripting
template <typename T, typename I = int>
T* as_ptr()
{
const auto value = this->as<I>();
if (!value)
@ -72,11 +161,9 @@ namespace scripting
const game::VariableValue& get_raw() const;
variable_value value_{};
private:
template <typename T>
T get() const;
};
class value_wrap
@ -84,6 +171,16 @@ namespace scripting
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
{

View File

@ -37,6 +37,43 @@ namespace game
SCRIPT_ARRAY = 22
};
// recreated from IW6 pdb and H1 elf (no structs :/)
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_DEVELOPER_CODEPOS = 0xE,
VAR_PRE_ANIMATION = 0xF,
VAR_THREAD = 0x10,
VAR_NOTIFY_THREAD = 0x11,
VAR_TIME_THREAD = 0x12,
VAR_CHILD_THREAD = 0x13,
VAR_OBJECT = 0x14,
VAR_DEAD_ENTITY = 0x15,
VAR_ENTITY = 0x16,
VAR_ARRAY = 0x17,
VAR_DEAD_THREAD = 0x18,
VAR_COUNT = 0x19,
VAR_FREE = 0x19,
VAR_THREAD_LIST = 0x1A,
VAR_ENDON_LIST = 0x1B,
VAR_TOTAL_COUNT = 0x1C,
};
struct VariableStackBuffer
{
const char* pos;

View File

@ -346,6 +346,6 @@ namespace game
WEAK symbol<int(lua_State* s, int level, lua_Debug* ar)> hksi_lua_getstack{0xB87A0, 0x2302B0};
WEAK symbol<void(lua_State* s, const char* fmt, ...)> hksi_luaL_error{0xBF120, 0x22F930};
WEAK symbol<void(lua_State* s, int what, int data)> hksi_lua_gc{0, 0x236EF0};
WEAK symbol<const char*> typenames{0x98CD20, 0x10AD750};
WEAK symbol<const char*> s_compilerTypeName{0x98CD20, 0x10AD750};
}
}

View File

@ -70,7 +70,7 @@ namespace ui_scripting
values.push_back(v);
}
if (values.size() == 0)
if (values.empty())
{
values.push_back({});
}
@ -90,7 +90,7 @@ namespace ui_scripting
values.push_back(v);
}
if (values.size() == 0)
if (values.empty())
{
values.push_back({});
}

View File

@ -167,7 +167,7 @@ namespace ui_scripting
{
if (!this->is<T>())
{
const auto hks_typename = game::hks::typenames[this->get_raw().t + 2];
const auto hks_typename = game::hks::s_compilerTypeName[this->get_raw().t + 2];
const auto typename_ = get_typename<T>();
throw std::runtime_error(utils::string::va("%s expected, got %s",

View File

@ -88,6 +88,7 @@
#include <udis86.h>
#include <MinHook.h>
#include <tomcrypt.h>
#include <json.hpp>
#define RAPIDJSON_NOEXCEPT
#define RAPIDJSON_ASSERT(cond) if(cond); else throw std::runtime_error("rapidjson assert fail");