#include #include "loader/component_loader.hpp" #include "game/scripting/event.hpp" #include "game/scripting/execution.hpp" #include "game/scripting/array.hpp" #include "gsc.hpp" #include "json.hpp" #include "scripting.hpp" #include namespace json { namespace { constexpr const auto max_str_len = 0x5000u; struct dump_context_t { std::unordered_set visited_objects; }; nlohmann::json gsc_to_json(const scripting::script_value& value, bool print_id, dump_context_t& ctx); nlohmann::json array_to_json(const scripting::array& array, bool print_id, dump_context_t& ctx) { if (array.size() == 0) { return nlohmann::json::array(); } nlohmann::json obj; auto string_indexed = -1; const auto keys = array.get_keys(); for (const auto& key : keys) { const auto is_int = key.is(); const auto is_string = key.is(); if (string_indexed == -1) { string_indexed = is_string; } if (!string_indexed && is_int) { const auto index = key.as(); obj[index] = gsc_to_json(array[index], print_id, ctx); } else if (string_indexed && is_string) { const auto key_str = key.as(); obj.emplace(key_str, gsc_to_json(array[key_str], print_id, ctx)); } } return obj; } nlohmann::json object_to_json(const scripting::object& object, bool print_id, dump_context_t& ctx) { const auto id = object.get_entity_id(); if (ctx.visited_objects.find(id) != ctx.visited_objects.end()) { return utils::string::va("[struct reference %i]", id); } ctx.visited_objects.insert(id); auto obj = nlohmann::json::object(); if (print_id) { obj["__id"] = id; } const auto keys = object.get_keys(); for (const auto& key : keys) { if (key == "__id") { continue; } obj.emplace(key, gsc_to_json(object[key], print_id, ctx)); } 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(const scripting::script_value& value, bool print_id, dump_context_t& ctx) { const auto& variable = value.get_raw(); if (value.is()) { return value.as(); } if (value.is()) { return value.as(); } if (value.is()) { return value.as(); } if (value.is()) { return vector_to_array(variable.u.vectorValue); } if (value.is()) { return object_to_json(variable.u.uintValue, print_id, ctx); } if (value.is()) { return array_to_json(variable.u.uintValue, print_id, ctx); } if (value.is()) { return object_to_json(variable.u.uintValue, print_id, ctx); } if (value.is()) { const auto function = value.as(); return utils::string::va("[[ %s ]]", function.get_name().data()); } if (variable.type == game::SCRIPT_NONE) { return {}; } if (variable.type == game::SCRIPT_CODEPOS) { const auto function = scripting::find_function(variable.u.codePosValue); return utils::string::va("[codepos: %s]", function.data()); } if (variable.type == game::SCRIPT_END) { const auto function = scripting::find_function(variable.u.codePosValue); return utils::string::va("[precodepos: %s]", function.data()); } return utils::string::va("[%s]", value.type_name().data()); } scripting::script_value json_to_gsc(nlohmann::json obj) { const auto type = obj.type(); switch (type) { case (nlohmann::detail::value_t::boolean): return obj.get(); 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.emplace_back(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) { dump_context_t ctx; return gsc_to_json(value, false, ctx).dump(); } class component final : public component_interface { public: void on_startup([[maybe_unused]] plugin::plugin* plugin) override { gsc::function::add_multiple([](const scripting::variadic_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; }, "createmap", "json::create_map"); gsc::function::add_multiple([](const std::string& json) { const auto obj = nlohmann::json::parse(json); return json_to_gsc(obj); }, "jsonparse", "json::parse"); gsc::function::add_multiple([](const scripting::script_value& value, const scripting::variadic_args& args) { auto indent = -1; auto print_id = false; if (args.size() > 0) { indent = args[0].as(); } if (args.size() > 1) { print_id = args[1].as(); } dump_context_t ctx; const auto json = gsc_to_json(value, print_id, ctx); auto dump = json.dump(indent); if (dump.size() > max_str_len) { printf("^3WARNING: json dump longer than %i bytes, truncating!", max_str_len); dump.resize(max_str_len); } return dump; }, "jsonserialize", "json::serialize"); gsc::function::add_multiple([](const std::string& file, const scripting::script_value& value, const scripting::variadic_args& va) { std::string file_name = file; auto indent = -1; auto print_id = false; if (!file_name.ends_with(".json")) { file_name.append(".json"); } if (va.size() > 0) { indent = va[0].as(); } if (va.size() > 1) { print_id = va[1].as(); } dump_context_t ctx; const auto json = gsc_to_json(value, print_id, ctx); const auto dump = json.dump(indent); return utils::io::write_file(file_name, dump); }, "jsondump", "json::dump"); gsc::function::add_multiple([](const scripting::variadic_args& args) { std::string buffer; for (const auto& arg : args) { buffer.append(gsc_to_string(arg)); buffer.append("\t"); } printf("%s\n", buffer.data()); }, "jsonprint", "json::print"); } }; } REGISTER_COMPONENT(json::component)