diff --git a/.gitmodules b/.gitmodules index 4112be93..3e52ed56 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/deps/gsc-tool b/deps/gsc-tool index 378c0d5c..842f168a 160000 --- a/deps/gsc-tool +++ b/deps/gsc-tool @@ -1 +1 @@ -Subproject commit 378c0d5cd5ac18140bb676eb371d105405dc03fc +Subproject commit 842f168a67878f18ea6a42b9d3ce56ff9fafff0d diff --git a/deps/json b/deps/json new file mode 160000 index 00000000..a3e6e26d --- /dev/null +++ b/deps/json @@ -0,0 +1 @@ +Subproject commit a3e6e26dc83a726b292f5be0492fcc408663ce55 diff --git a/deps/premake/json.lua b/deps/premake/json.lua new file mode 100644 index 00000000..c060e3a0 --- /dev/null +++ b/deps/premake/json.lua @@ -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) diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index e7761255..aff4669f 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -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); diff --git a/src/client/component/gsc.cpp b/src/client/component/gsc.cpp index da3bad50..37743d55 100644 --- a/src/client/component/gsc.cpp +++ b/src/client/component/gsc.cpp @@ -9,7 +9,6 @@ #include "scripting.hpp" #include "game/dvars.hpp" -#include "game/scripting/functions.hpp" #include #include @@ -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(); + 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(); + const auto function = args[1].as(); + + 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) diff --git a/src/client/component/gsc.hpp b/src/client/component/gsc.hpp index a8c4211e..5043efeb 100644 --- a/src/client/component/gsc.hpp +++ b/src/client/component/gsc.hpp @@ -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 { diff --git a/src/client/component/io.cpp b/src/client/component/io.cpp index 28b05131..44f8b961 100644 --- a/src/client/component/io.cpp +++ b/src/client/component/io.cpp @@ -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()); return utils::io::remove_file(path); }); + + gsc::function::add("va", [](const gsc::function_args& args) + { + auto fmt = args[0].as(); + + for (auto i = 1u; i < args.size(); i++) + { + const auto arg = args[i].to_string(); + replace(fmt, "%s", arg); + } + + return fmt; + }); } }; } diff --git a/src/client/component/json.cpp b/src/client/component/json.cpp new file mode 100644 index 00000000..50ebe9af --- /dev/null +++ b/src/client/component/json.cpp @@ -0,0 +1,210 @@ +#include +#include "loader/component_loader.hpp" + +#include "scheduler.hpp" +#include "gsc.hpp" +#include "json.hpp" + +#include + +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(); + const auto is_string = keys[i].is(); + + if (string_indexed == -1) + { + string_indexed = is_string; + } + + if (!string_indexed && is_int) + { + const auto index = keys[i].as(); + obj[index] = gsc_to_json(array[index]); + } + else if (string_indexed && is_string) + { + const auto key = keys[i].as(); + 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(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().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(); + case (nlohmann::detail::value_t::number_float): + return obj.get(); + case (nlohmann::detail::value_t::string): + return obj.get(); + 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(); + array[key] = args[i + 1]; + } + + return array; + }); + + gsc::function::add("jsonparse", [](const gsc::function_args& args) + { + const auto json = args[0].as(); + 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(); + } + + 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) diff --git a/src/client/component/json.hpp b/src/client/component/json.hpp new file mode 100644 index 00000000..2c07f88a --- /dev/null +++ b/src/client/component/json.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace json +{ + std::string gsc_to_string(const scripting::script_value& _value); +} diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp index 5d04bcea..720f404e 100644 --- a/src/client/component/logfile.cpp +++ b/src/client/component/logfile.cpp @@ -1,7 +1,9 @@ #include #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 player_killed_callbacks; std::vector player_damage_callbacks; + std::vector 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() && !hidden) + { + hidden = result.as() == 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(); + say_callbacks.push_back(function); + return {}; + }); + + scripting::on_shutdown([](int) + { + say_callbacks.clear(); + }); } }; } diff --git a/src/client/component/logger.cpp b/src/client/component/logger.cpp index 9f8d7ee7..80c59025 100644 --- a/src/client/component/logger.cpp +++ b/src/client/component/logger.cpp @@ -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, ...) { diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index 448ffd89..2f640ece 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -1,33 +1,27 @@ #include #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 -#include -#include +#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 -#include -#include namespace scripting { std::unordered_map> fields_table; + std::unordered_map> script_function_table; std::unordered_map>> script_function_table_sort; + std::unordered_map> script_function_table_rev; + utils::concurrency::container 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(free_scripts); + g_shutdown_game_hook.invoke(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); diff --git a/src/client/component/scripting.hpp b/src/client/component/scripting.hpp index 1fea9b02..f1795603 100644 --- a/src/client/component/scripting.hpp +++ b/src/client/component/scripting.hpp @@ -8,6 +8,8 @@ namespace scripting extern std::unordered_map> fields_table; extern std::unordered_map> script_function_table; extern std::unordered_map>> script_function_table_sort; + extern std::unordered_map> script_function_table_rev; + extern utils::concurrency::container shared_table; extern std::string current_file; diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 4b1a59b4..bdbdbe5a 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -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