diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index abcd9677f..40b019ab4 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -38,6 +38,7 @@ set(LIBIMHEX_SOURCES source/helpers/debugging.cpp source/helpers/default_paths.cpp source/helpers/imgui_hooks.cpp + source/helpers/semantic_version.cpp source/test/tests.cpp diff --git a/lib/libimhex/include/hex/api/event_manager.hpp b/lib/libimhex/include/hex/api/event_manager.hpp index 1fc3c253b..79aa7ff88 100644 --- a/lib/libimhex/include/hex/api/event_manager.hpp +++ b/lib/libimhex/include/hex/api/event_manager.hpp @@ -217,6 +217,7 @@ namespace hex { EVENT_DEF(EventOSThemeChanged); EVENT_DEF(EventDPIChanged, float, float); EVENT_DEF(EventWindowFocused, bool); + EVENT_DEF(EventImHexUpdated, SemanticVersion, SemanticVersion); /** * @brief Called when the provider is created. diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index 2ecd25c1b..d522c4932 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -618,7 +619,7 @@ namespace hex { * @brief Gets the current ImHex version * @return ImHex version */ - std::string getImHexVersion(bool withBuildType = true); + SemanticVersion getImHexVersion(); /** * @brief Gets the current git commit hash @@ -695,6 +696,13 @@ namespace hex { */ void* getLibImHexModuleHandle(); + /** + * Adds a new migration routine that will be executed when upgrading from a lower version than specified in migrationVersion + * @param migrationVersion Upgrade point version + * @param function Function to run + */ + void addMigrationRoutine(SemanticVersion migrationVersion, std::function function); + } /** diff --git a/lib/libimhex/include/hex/helpers/semantic_version.hpp b/lib/libimhex/include/hex/helpers/semantic_version.hpp new file mode 100644 index 000000000..109489427 --- /dev/null +++ b/lib/libimhex/include/hex/helpers/semantic_version.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include +#include +#include + +namespace hex { + + class SemanticVersion { + public: + SemanticVersion() = default; + SemanticVersion(std::string version); + SemanticVersion(std::string_view version); + SemanticVersion(const char *version); + + std::strong_ordering operator<=>(const SemanticVersion &) const; + bool operator==(const SemanticVersion &other) const; + + u32 major() const; + u32 minor() const; + u32 patch() const; + bool nightly() const; + const std::string& buildType() const; + + bool isValid() const; + + std::string get(bool withBuildType = true) const; + + private: + std::vector m_parts; + std::string m_buildType; + }; + +} \ No newline at end of file diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 6a9e25665..078b38947 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -630,7 +630,7 @@ namespace hex { } runtime.addDefine("__IMHEX__"); - runtime.addDefine("__IMHEX_VERSION__", ImHexApi::System::getImHexVersion()); + runtime.addDefine("__IMHEX_VERSION__", ImHexApi::System::getImHexVersion().get()); } void addPragma(const std::string &name, const pl::api::PragmaHandler &handler) { diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 83611e2a5..530f19a5e 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -645,6 +645,14 @@ namespace hex { return hex::getContainingModule(reinterpret_cast(&getLibImHexModuleHandle)); } + void addMigrationRoutine(SemanticVersion migrationVersion, std::function function) { + EventImHexUpdated::subscribe([migrationVersion, function](const SemanticVersion &oldVersion, const SemanticVersion &newVersion) { + if (oldVersion < migrationVersion && newVersion >= migrationVersion) { + function(); + } + }); + } + const std::map& getInitArguments() { return *impl::s_initArguments; @@ -794,16 +802,11 @@ namespace hex { return { { name, version } }; } - std::string getImHexVersion(bool withBuildType) { + SemanticVersion getImHexVersion() { #if defined IMHEX_VERSION - if (withBuildType) { - return IMHEX_VERSION; - } else { - auto version = std::string(IMHEX_VERSION); - return version.substr(0, version.find('-')); - } + return SemanticVersion(IMHEX_VERSION); #else - return "Unknown"; + return {}; #endif } @@ -837,7 +840,7 @@ namespace hex { } bool isNightlyBuild() { - return getImHexVersion(false).ends_with("WIP"); + return getImHexVersion().nightly(); } bool updateImHex(UpdateType updateType) { diff --git a/lib/libimhex/source/api/plugin_manager.cpp b/lib/libimhex/source/api/plugin_manager.cpp index ecb6fd0bb..d4f0e263c 100644 --- a/lib/libimhex/source/api/plugin_manager.cpp +++ b/lib/libimhex/source/api/plugin_manager.cpp @@ -129,7 +129,7 @@ namespace hex { const auto requestedVersion = getCompatibleVersion(); - const auto imhexVersion = ImHexApi::System::getImHexVersion(); + const auto imhexVersion = ImHexApi::System::getImHexVersion().get(); if (!imhexVersion.starts_with(requestedVersion)) { if (requestedVersion.empty()) { log::warn("Plugin '{}' did not specify a compatible version, assuming it is compatible with the current version of ImHex.", wolv::util::toUTF8String(m_path.filename())); diff --git a/lib/libimhex/source/helpers/semantic_version.cpp b/lib/libimhex/source/helpers/semantic_version.cpp new file mode 100644 index 000000000..688b0208c --- /dev/null +++ b/lib/libimhex/source/helpers/semantic_version.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +namespace hex { + + SemanticVersion::SemanticVersion(const char *version) : SemanticVersion(std::string(version)) { + + } + + SemanticVersion::SemanticVersion(std::string_view version) : SemanticVersion(std::string(version.begin(), version.end())) { + + } + + SemanticVersion::SemanticVersion(std::string version) { + if (version.empty()) + return; + + if (version.starts_with("v")) + version = version.substr(1); + + m_parts = wolv::util::splitString(version, "."); + + if (m_parts.size() != 3 && m_parts.size() != 4) { + m_parts.clear(); + return; + } + + if (m_parts.back().contains("-")) { + auto buildTypeParts = wolv::util::splitString(m_parts.back(), "-"); + if (buildTypeParts.size() != 2) { + m_parts.clear(); + return; + } + + m_parts.back() = buildTypeParts[0]; + m_buildType = buildTypeParts[1]; + } + } + + u32 SemanticVersion::major() const { + return std::stoul(m_parts[0]); + } + + u32 SemanticVersion::minor() const { + return std::stoul(m_parts[1]); + } + + u32 SemanticVersion::patch() const { + return std::stoul(m_parts[2]); + } + + bool SemanticVersion::nightly() const { + return m_parts.size() == 4 && m_parts[3] == "WIP"; + } + + const std::string& SemanticVersion::buildType() const { + return m_buildType; + } + + + bool SemanticVersion::isValid() const { + return !m_parts.empty(); + } + + bool SemanticVersion::operator==(const SemanticVersion& other) const { + return this->m_parts == other.m_parts; + } + + std::strong_ordering SemanticVersion::operator<=>(const SemanticVersion &other) const { + if (*this == other) + return std::strong_ordering::equivalent; + + if (this->major() > other.major()) + return std::strong_ordering::greater; + if (this->minor() > other.minor()) + return std::strong_ordering::greater; + if (this->patch() > other.patch()) + return std::strong_ordering::greater; + if (!this->nightly() && other.nightly()) + return std::strong_ordering::greater; + + return std::strong_ordering::less; + } + + std::string SemanticVersion::get(bool withBuildType) const { + auto result = wolv::util::combineStrings(m_parts, "."); + + if (withBuildType && !m_buildType.empty()) + result += hex::format("-{}", m_buildType); + + return result; + } + + +} diff --git a/main/gui/source/init/splash_window.cpp b/main/gui/source/init/splash_window.cpp index fe706d451..93868fa02 100644 --- a/main/gui/source/init/splash_window.cpp +++ b/main/gui/source/init/splash_window.cpp @@ -331,9 +331,9 @@ namespace hex::init { // Draw version information // In debug builds, also display the current commit hash and branch #if defined(DEBUG) - const static auto VersionInfo = hex::format("{0} : {1}@{2}", ImHexApi::System::getImHexVersion(), ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); + const static auto VersionInfo = hex::format("{0} : {1}@{2}", ImHexApi::System::getImHexVersion().get(), ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); #else - const static auto VersionInfo = hex::format("{0}", ImHexApi::System::getImHexVersion()); + const static auto VersionInfo = hex::format("{0}", ImHexApi::System::getImHexVersion().get()); #endif drawList->AddText(ImVec2((this->m_splashBackgroundTexture.getSize().x - ImGui::CalcTextSize(VersionInfo.c_str()).x) / 2, 105), ImColor(0xFF, 0xFF, 0xFF, 0xFF), VersionInfo.c_str()); diff --git a/main/gui/source/main.cpp b/main/gui/source/main.cpp index 2f932fb3e..2d981d771 100644 --- a/main/gui/source/main.cpp +++ b/main/gui/source/main.cpp @@ -40,7 +40,7 @@ int main(int argc, char **argv) { } // Log some system information to aid debugging when users share their logs - log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion()); + log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion().get()); log::info("Compiled using commit {}@{}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); log::info("Running on {} {} ({})", ImHexApi::System::getOSName(), ImHexApi::System::getOSVersion(), ImHexApi::System::getArchitecture()); #if defined(OS_LINUX) diff --git a/plugins/builtin/source/content/command_line_interface.cpp b/plugins/builtin/source/content/command_line_interface.cpp index 4ce83f106..45f925d5f 100644 --- a/plugins/builtin/source/content/command_line_interface.cpp +++ b/plugins/builtin/source/content/command_line_interface.cpp @@ -31,7 +31,7 @@ namespace hex::plugin::builtin { std::ignore = args; hex::log::print(std::string(romfs::get("logo.ans").string()), - ImHexApi::System::getImHexVersion(), + ImHexApi::System::getImHexVersion().get(), ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash(), __DATE__, __TIME__, ImHexApi::System::isPortableVersion() ? "Portable" : "Installed"); diff --git a/plugins/builtin/source/content/communication_interface.cpp b/plugins/builtin/source/content/communication_interface.cpp index f719f8e9d..653801bcf 100644 --- a/plugins/builtin/source/content/communication_interface.cpp +++ b/plugins/builtin/source/content/communication_interface.cpp @@ -18,7 +18,7 @@ namespace hex::plugin::builtin { nlohmann::json result; result["build"] = { - { "version", ImHexApi::System::getImHexVersion() }, + { "version", ImHexApi::System::getImHexVersion().get() }, { "commit", ImHexApi::System::getCommitHash(true) }, { "branch", ImHexApi::System::getCommitBranch() } }; diff --git a/plugins/builtin/source/content/events.cpp b/plugins/builtin/source/content/events.cpp index 2eb78045f..0e3d204b4 100644 --- a/plugins/builtin/source/content/events.cpp +++ b/plugins/builtin/source/content/events.cpp @@ -226,11 +226,15 @@ namespace hex::plugin::builtin { }); EventWindowInitialized::subscribe([] { - if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", "") == "") { + const auto currVersion = ImHexApi::System::getImHexVersion(); + const auto prevLaunchVersion = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ""); + if (prevLaunchVersion == "") { EventFirstLaunch::post(); } - ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ImHexApi::System::getImHexVersion()); + EventImHexUpdated::post(SemanticVersion(prevLaunchVersion), currVersion); + + ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", currVersion.get(false)); }); EventWindowDeinitializing::subscribe([](GLFWwindow *window) { diff --git a/plugins/builtin/source/content/init_tasks.cpp b/plugins/builtin/source/content/init_tasks.cpp index 40b621603..5992c3e97 100644 --- a/plugins/builtin/source/content/init_tasks.cpp +++ b/plugins/builtin/source/content/init_tasks.cpp @@ -39,9 +39,7 @@ namespace hex::plugin::builtin { return false; // Convert the current version string to a format that can be compared to the latest release - auto versionString = ImHexApi::System::getImHexVersion(); - size_t versionLength = std::min(versionString.find_first_of('-'), versionString.length()); - auto currVersion = "v" + versionString.substr(0, versionLength); + auto currVersion = "v" + ImHexApi::System::getImHexVersion().get(false); // Get the latest release version string auto latestVersion = releases["tag_name"].get(); @@ -59,7 +57,7 @@ namespace hex::plugin::builtin { ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", uuid); } - TaskManager::createBackgroundTask("hex.builtin.task.sending_statistics"_lang, [uuid, versionString](auto&) { + TaskManager::createBackgroundTask("hex.builtin.task.sending_statistics"_lang, [uuid](auto&) { // To avoid potentially flooding our database with lots of dead users // from people just visiting the website, don't send telemetry data from // the web version @@ -71,7 +69,7 @@ namespace hex::plugin::builtin { nlohmann::json telemetry = { { "uuid", uuid }, { "format_version", "1" }, - { "imhex_version", versionString }, + { "imhex_version", ImHexApi::System::getImHexVersion().get(false) }, { "imhex_commit", fmt::format("{}@{}", ImHexApi::System::getCommitHash(true), ImHexApi::System::getCommitBranch()) }, { "install_type", ImHexApi::System::isPortableVersion() ? "Portable" : "Installed" }, { "os", ImHexApi::System::getOSName() }, diff --git a/plugins/builtin/source/content/out_of_box_experience.cpp b/plugins/builtin/source/content/out_of_box_experience.cpp index fdbc94808..4c74aa6bb 100644 --- a/plugins/builtin/source/content/out_of_box_experience.cpp +++ b/plugins/builtin/source/content/out_of_box_experience.cpp @@ -298,7 +298,7 @@ namespace hex::plugin::builtin { ImGui::TextUnformatted("hex.builtin.oobe.server_contact.data_collected.version"_lang); ImGui::TableNextColumn(); ImGuiExt::TextFormattedWrapped("{}\n{}@{}\n{}", - ImHexApi::System::getImHexVersion(), + ImHexApi::System::getImHexVersion().get(), ImHexApi::System::getCommitHash(true), ImHexApi::System::getCommitBranch(), ImHexApi::System::isPortableVersion() ? "Portable" : "Installed" diff --git a/plugins/builtin/source/content/project.cpp b/plugins/builtin/source/content/project.cpp index 237de3754..de67ec005 100644 --- a/plugins/builtin/source/content/project.cpp +++ b/plugins/builtin/source/content/project.cpp @@ -158,7 +158,7 @@ namespace hex::plugin::builtin { } { - const auto metadataContent = hex::format("{}\n{}", MetadataHeaderMagic, ImHexApi::System::getImHexVersion()); + const auto metadataContent = hex::format("{}\n{}", MetadataHeaderMagic, ImHexApi::System::getImHexVersion().get(false)); tar.writeString(MetadataPath, metadataContent); } diff --git a/plugins/builtin/source/content/views/view_about.cpp b/plugins/builtin/source/content/views/view_about.cpp index 9a7f48a19..ee43711b5 100644 --- a/plugins/builtin/source/content/views/view_about.cpp +++ b/plugins/builtin/source/content/views/view_about.cpp @@ -193,7 +193,7 @@ namespace hex::plugin::builtin { ImGui::TableNextColumn(); { // Draw basic information about ImHex and its version - ImGuiExt::TextFormatted("ImHex Hex Editor v{} by WerWolv", ImHexApi::System::getImHexVersion()); + ImGuiExt::TextFormatted("ImHex Hex Editor v{} by WerWolv", ImHexApi::System::getImHexVersion().get()); ImGui::Indent(25_scaled); ImGuiExt::TextFormatted("Powered by Dear ImGui v{}", ImGui::GetVersion()); ImGui::Unindent(25_scaled); @@ -582,9 +582,9 @@ namespace hex::plugin::builtin { static ReleaseNotes notes; // Set up the request to get the release notes the first time the page is opened - const static auto ImHexVersionString = ImHexApi::System::getImHexVersion(false); + const static auto ImHexVersion = ImHexApi::System::getImHexVersion(); AT_FIRST_TIME { - static HttpRequest request("GET", GitHubApiURL + std::string("/releases/") + (ImHexVersionString.ends_with(".WIP") ? "latest" : ( "tags/v" + ImHexVersionString))); + static HttpRequest request("GET", GitHubApiURL + std::string("/releases/") + (ImHexVersion.nightly() ? "latest" : ( "tags/v" + ImHexVersion.get(false)))); m_releaseNoteRequest = request.execute(); }; diff --git a/plugins/builtin/source/content/views/view_store.cpp b/plugins/builtin/source/content/views/view_store.cpp index 15a306f20..5a9363e2e 100644 --- a/plugins/builtin/source/content/views/view_store.cpp +++ b/plugins/builtin/source/content/views/view_store.cpp @@ -57,7 +57,7 @@ namespace hex::plugin::builtin { // Force update all installed items after an update so that there's no old and incompatible versions around anymore { const auto prevUpdateVersion = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ""); - if (prevUpdateVersion != ImHexApi::System::getImHexVersion()) { + if (SemanticVersion(prevUpdateVersion) != ImHexApi::System::getImHexVersion()) { updateAll(); } } @@ -359,7 +359,7 @@ namespace hex::plugin::builtin { } TaskManager::doLater([] { - ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ImHexApi::System::getImHexVersion()); + ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ImHexApi::System::getImHexVersion().get(false)); }); }); }