diff --git a/CMakeLists.txt b/CMakeLists.txt index 80f4dd4..cc7d96c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ find_package(SDL2 CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(cxxopts CONFIG REQUIRED) find_package(ZLIB REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(RapidJSON REQUIRED) find_package(FFMPEG COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE REQUIRED) CPMAddPackage(NAME imgui VERSION 1.90 DOWNLOAD_ONLY YES URL https://github.com/ocornut/imgui/archive/refs/tags/v1.90.zip diff --git a/src/Abyss/CMakeLists.txt b/src/Abyss/CMakeLists.txt index d44abc0..f4c9a4f 100644 --- a/src/Abyss/CMakeLists.txt +++ b/src/Abyss/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(Abyss Common/Animation.h Common/CommandLineOpts.cpp Common/CommandLineOpts.h Common/Configuration.cpp Common/Configuration.h + Common/JSON.cpp Common/JSON.h Common/Logging.h Common/MouseProvider.h Common/MouseState.cpp Common/MouseState.h @@ -81,9 +82,12 @@ target_link_libraries(Abyss stormlib::stormlib absl::flat_hash_map absl::btree + nlohmann_json::nlohmann_json ${FFMPEG_LIBRARIES} ${OSX_VIDEOTOOLBOX} ${OSX_COREMEDIA} ${OSX_SECURITY} + PRIVATE + rapidjson ) diff --git a/src/Abyss/Common/JSON.cpp b/src/Abyss/Common/JSON.cpp new file mode 100644 index 0000000..c29faed --- /dev/null +++ b/src/Abyss/Common/JSON.cpp @@ -0,0 +1,25 @@ +#include "JSON.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Abyss::Common { + +nlohmann::json parseJson(std::string_view json) { + rapidjson::Document d; + d.Parse(json.data(), json.length()); + if (d.HasParseError()) { + throw std::runtime_error( + std::format("JSON parse error ({}) at offset {}", rapidjson::GetParseError_En(d.GetParseError()), d.GetErrorOffset())); + } + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + d.Accept(writer); + return nlohmann::json::parse(sb.GetString()); +} + +} // namespace Abyss::Common diff --git a/src/Abyss/Common/JSON.h b/src/Abyss/Common/JSON.h new file mode 100644 index 0000000..e039477 --- /dev/null +++ b/src/Abyss/Common/JSON.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace Abyss::Common { + +// Like normal nlohmann, but supports comments and trailing commas +nlohmann::json parseJson(std::string_view json); + +} // namespace Abyss::Common diff --git a/src/OD2/CMakeLists.txt b/src/OD2/CMakeLists.txt index b76409b..3fe7b0d 100644 --- a/src/OD2/CMakeLists.txt +++ b/src/OD2/CMakeLists.txt @@ -22,6 +22,9 @@ target_sources(OpenDiablo2 # Scenes # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Layouts/Layout.cpp Layouts/Layout.h + Layouts/Profile.cpp Layouts/Profile.h + # Main Menu Scenes/MainMenu/MainMenu.cpp Scenes/MainMenu/MainMenu.h Scenes/MainMenu/Logo.cpp Scenes/MainMenu/Logo.h diff --git a/src/OD2/Layouts/Layout.cpp b/src/OD2/Layouts/Layout.cpp new file mode 100644 index 0000000..494808e --- /dev/null +++ b/src/OD2/Layouts/Layout.cpp @@ -0,0 +1,54 @@ +#include "Layout.h" +#include "Abyss/Common/JSON.h" +#include "Abyss/Singletons.h" +#include +#include + +namespace OD2::Layouts { + +namespace { + +void mergeFromParent(nlohmann::json &object, nlohmann::json &parent) { + if (parent.contains("fields")) { + nlohmann::json fields = std::move(parent["fields"]); + if (object.contains("fields")) { + fields.merge_patch(object["fields"]); + } + object["fields"] = std::move(fields); + } + if (object.contains("children") && parent.contains("children")) { + absl::flat_hash_map brothers; + for (auto &c : parent["children"]) { + if (c.contains("name")) { + brothers[c["name"].get()] = &c; + } + } + for (auto &c : object["children"]) { + if (c.contains("name")) { + auto it = brothers.find(c["name"].get()); + if (it != brothers.end()) { + nlohmann::json &brother = *it->second; + mergeFromParent(c, brother); + } + } + } + } +} + +nlohmann::json readMergedLayout(std::string_view name) { + nlohmann::json me = Abyss::Common::parseJson(Abyss::Singletons::getFileProvider().loadString(absl::StrCat("/data/global/ui/layouts/", name))); + if (me.contains("basedOn")) { + nlohmann::json parent = readMergedLayout(me["basedOn"].get()); + mergeFromParent(me, parent); + } + return me; +} + +} // namespace + +Layout::Layout(std::string_view name, const Profile &profile) { + _data = readMergedLayout(name); + profile.resolveReferences(_data); +} + +} // namespace OD2::Layouts diff --git a/src/OD2/Layouts/Layout.h b/src/OD2/Layouts/Layout.h new file mode 100644 index 0000000..1233fb8 --- /dev/null +++ b/src/OD2/Layouts/Layout.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Profile.h" +#include +#include + +namespace OD2::Layouts { + +class Layout { + nlohmann::json _data; + + public: + // Reads from /data/global/ui/layouts/{name} (name should include .json extension) + explicit Layout(std::string_view name, const Profile &profile); +}; + +} // namespace OD2::Layouts diff --git a/src/OD2/Layouts/Profile.cpp b/src/OD2/Layouts/Profile.cpp new file mode 100644 index 0000000..5e49c89 --- /dev/null +++ b/src/OD2/Layouts/Profile.cpp @@ -0,0 +1,46 @@ +#include "Profile.h" +#include "Abyss/Common/JSON.h" +#include "Abyss/Singletons.h" +#include +#include +#include + +namespace OD2::Layouts { + +namespace { + +nlohmann::json readMergedProfile(std::string_view name) { + nlohmann::json me = + Abyss::Common::parseJson(Abyss::Singletons::getFileProvider().loadString(std::format("/data/global/ui/layouts/_profile{}.json", name))); + if (me.contains("basedOn")) { + nlohmann::json base = readMergedProfile(me["basedOn"].get()); + base.merge_patch(me); + return base; + } + return me; +} + +bool resolveDataReferences(nlohmann::json &object, const nlohmann::json &profile) { + bool again = false; + for (auto &[k, v] : object.items()) { + if (v.is_structured()) { + again = resolveDataReferences(v, profile) || again; + } else if (v.is_string() && v.get().starts_with('$')) { + v = profile.at(v.get().substr(1)); + again = true; + } + } + return again; +} + +} // namespace + +Profile::Profile(std::string_view name) { + _data = readMergedProfile(name); + while (resolveDataReferences(_data, _data)) { + } +} + +void Profile::resolveReferences(nlohmann::json &object) const { resolveDataReferences(object, _data); } + +} // namespace OD2::Layouts diff --git a/src/OD2/Layouts/Profile.h b/src/OD2/Layouts/Profile.h new file mode 100644 index 0000000..47271ee --- /dev/null +++ b/src/OD2/Layouts/Profile.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace OD2::Layouts { + +class Profile { + nlohmann::json _data; + + public: + // Reads from /data/global/ui/layouts/_profile{name}.json + explicit Profile(std::string_view name); + void resolveReferences(nlohmann::json &object) const; +}; + +} // namespace OD2::Layouts diff --git a/vcpkg.json b/vcpkg.json index 92936d8..c18cbc4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,7 +4,7 @@ "version": "0.0.1", "dependencies": [ "cxxopts", - { + { "name": "sdl2", "features": [ "alsa" @@ -15,6 +15,8 @@ "name": "sdl2", "platform": "!linux" }, + "nlohmann-json", + "rapidjson", "spdlog", "zlib", {