From 2dcaf2c77bc51234e3c4bc1cb4c11d67139d8f34 Mon Sep 17 00:00:00 2001 From: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:36:42 +0100 Subject: [PATCH] fix: Various achievements issues (Edit the Hex, ROM Hacks) (#1985) ### Problem description As described in #1846: - the `Edit the Hex` achievement doesn't unlock when it should - the `ROM Hacks` achievement is not using event-driven architecture (the functions call `unlockAchievement` themselves) ### Implementation description Firstly, for the `Edit the Hex` achievement: - replaced the old event listener on `EventPatchCreated` with a listener on `EventProviderDataModified`, which picks up bytes changes - ensured the provider data change comes from a File provider, else unlocking the achievement wouldn't make sense - *Note*: a discovered side effect is that the "Fill" function modifies the provider byte per byte (with a for loop) - there is no use in testing the size of the data change, as it is always 1 byte - the Fill function could probably be reworked to fill in whole regions at a time? About the `ROM Hacks` achievement: - implemented the new, still unused `EventPatchCreated` event. - signal signature is `const unsigned char *, u64, const IPSKind`: buffer pointer, buffer size, and IPS kind (IPS/IPS32) - make use of the `::post` and `::subscribe` methods on said event to unlock the achievement - **WARNING::behaviour change**: the event's `post` signal has been moved in the success branch of the IPS generation condition, meaning that achievement will only unlock if IPS patch export has worked. I felt it would make more sense than unlocking an achievement on an error, if there was any to raise. --------- Signed-off-by: BioTheWolff <47079795+BioTheWolff@users.noreply.github.com> --- lib/libimhex/include/hex/api/event_manager.hpp | 8 +++++++- lib/libimhex/include/hex/helpers/patches.hpp | 5 +++++ plugins/builtin/source/content/achievements.cpp | 10 +++++++++- plugins/builtin/source/content/main_menu_items.cpp | 12 ++++++------ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/libimhex/include/hex/api/event_manager.hpp b/lib/libimhex/include/hex/api/event_manager.hpp index 43da0a12b..bb6474d4e 100644 --- a/lib/libimhex/include/hex/api/event_manager.hpp +++ b/lib/libimhex/include/hex/api/event_manager.hpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -253,7 +254,12 @@ namespace hex { EVENT_DEF(EventWindowInitialized); EVENT_DEF(EventWindowDeinitializing, GLFWwindow *); EVENT_DEF(EventBookmarkCreated, ImHexApi::Bookmarks::Entry&); - EVENT_DEF(EventPatchCreated, u64, u8, u8); + + /** + * @brief Called upon creation of an IPS patch. + * As for now, the event only serves a purpose for the achievement unlock. + */ + EVENT_DEF(EventPatchCreated, const u8*, u64, const PatchKind); EVENT_DEF(EventPatternEvaluating); EVENT_DEF(EventPatternExecuted, const std::string&); EVENT_DEF(EventPatternEditorChanged, const std::string&); diff --git a/lib/libimhex/include/hex/helpers/patches.hpp b/lib/libimhex/include/hex/helpers/patches.hpp index 8fe97341b..ffef8932b 100644 --- a/lib/libimhex/include/hex/helpers/patches.hpp +++ b/lib/libimhex/include/hex/helpers/patches.hpp @@ -21,6 +21,11 @@ namespace hex { MissingEOF }; + enum class PatchKind { + IPS, + IPS32 + }; + class Patches { public: Patches() = default; diff --git a/plugins/builtin/source/content/achievements.cpp b/plugins/builtin/source/content/achievements.cpp index 473eb24bc..e8d34bd76 100644 --- a/plugins/builtin/source/content/achievements.cpp +++ b/plugins/builtin/source/content/achievements.cpp @@ -1,8 +1,10 @@ +#include #include #include #include #include +#include #include #include @@ -192,10 +194,16 @@ namespace hex::plugin::builtin { AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_bookmark.name"); }); - EventPatchCreated::subscribe([](u64, u8, u8) { + EventProviderDataModified::subscribe([](const prv::Provider *, u64, const u64, const u8*) { + // Warning: overlaps with the "Flood fill" achievement, since "Fill" works by writing to bytes one-by-one. + // Thus, we do not check for size, that will always be equal to 1 even during a fill operation. AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.modify_byte.name"); }); + EventPatchCreated::subscribe([](const u8 *, u8, PatchKind) { + AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_patch.name"); + }); + EventImHexStartupFinished::subscribe(AchievementManager::loadProgress); EventAchievementUnlocked::subscribe([](const Achievement &) { diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index 67c02913f..129b91b75 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -297,12 +297,12 @@ namespace hex::plugin::builtin { } if (data.has_value()) { - file.writeVector(data.value()); + const auto& bytes = data.value(); + file.writeVector(bytes); + EventPatchCreated::post(bytes.data(), bytes.size(), PatchKind::IPS); } else { handleIPSError(data.error()); } - - AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_patch.name"); }); }); }); @@ -336,12 +336,12 @@ namespace hex::plugin::builtin { } if (data.has_value()) { - file.writeVector(data.value()); + const std::vector& bytes = data.value(); + file.writeVector(bytes); + EventPatchCreated::post(bytes.data(), bytes.size(), PatchKind::IPS32); } else { handleIPSError(data.error()); } - - AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_patch.name"); }); }); });