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
29 changed files with 643 additions and 134 deletions

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));
});