1
0
mirror of synced 2024-11-24 15:50:16 +01:00

feat: Added Achievements (#1230)

This PR adds Achievements to ImHex that serve as both a guide and a fun
way to learn more about ImHex and reverse engineering
This commit is contained in:
Nik 2023-08-06 21:33:15 +02:00 committed by GitHub
parent 64a0c3f6e2
commit e77f138514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1427 additions and 22 deletions

View File

@ -614,7 +614,7 @@ jobs:
# Fedora cmake build (in imhex.spec) # Fedora cmake build (in imhex.spec)
- name: 📦 Build RPM - name: 📦 Build RPM
run: | run: |
fedpkg --path $GITHUB_WORKSPACE --release ${{ matrix.mock_release }} mockbuild --enable-network -N --root $GITHUB_WORKSPACE/mock.cfg fedpkg --path $GITHUB_WORKSPACE --release ${{ matrix.mock_release }} mockbuild --enable-network -N --root $GITHUB_WORKSPACE/mock.cfg extra_args -- -v
- name: 🟩 Move and rename finished RPM - name: 🟩 Move and rename finished RPM
run: | run: |

View File

@ -15,6 +15,7 @@ set(LIBIMHEX_SOURCES
source/api/project_file_manager.cpp source/api/project_file_manager.cpp
source/api/theme_manager.cpp source/api/theme_manager.cpp
source/api/layout_manager.cpp source/api/layout_manager.cpp
source/api/achievement_manager.cpp
source/data_processor/attribute.cpp source/data_processor/attribute.cpp
source/data_processor/link.cpp source/data_processor/link.cpp

View File

@ -0,0 +1,257 @@
#pragma once
#include <string>
#include <unordered_map>
#include <utility>
#include <memory>
#include <vector>
#include <set>
#include <span>
#include <hex/api/event.hpp>
#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>
namespace hex {
class AchievementManager;
class Achievement {
public:
explicit Achievement(std::string unlocalizedCategory, std::string unlocalizedName) : m_unlocalizedCategory(std::move(unlocalizedCategory)), m_unlocalizedName(std::move(unlocalizedName)) { }
[[nodiscard]] const std::string &getUnlocalizedName() const {
return this->m_unlocalizedName;
}
[[nodiscard]] const std::string &getUnlocalizedCategory() const {
return this->m_unlocalizedCategory;
}
[[nodiscard]] bool isUnlocked() const {
return this->m_progress == this->m_maxProgress;
}
Achievement& setDescription(std::string description) {
this->m_unlocalizedDescription = std::move(description);
return *this;
}
Achievement& addRequirement(std::string requirement) {
this->m_requirements.emplace_back(std::move(requirement));
return *this;
}
Achievement& addVisibilityRequirement(std::string requirement) {
this->m_visibilityRequirements.emplace_back(std::move(requirement));
return *this;
}
Achievement& setBlacked() {
this->m_blacked = true;
return *this;
}
Achievement& setInvisible() {
this->m_invisible = true;
return *this;
}
[[nodiscard]] bool isBlacked() const {
return this->m_blacked;
}
[[nodiscard]] bool isInvisible() const {
return this->m_invisible;
}
[[nodiscard]] const std::vector<std::string> &getRequirements() const {
return this->m_requirements;
}
[[nodiscard]] const std::vector<std::string> &getVisibilityRequirements() const {
return this->m_visibilityRequirements;
}
[[nodiscard]] const std::string &getUnlocalizedDescription() const {
return this->m_unlocalizedDescription;
}
[[nodiscard]] const ImGui::Texture &getIcon() const {
if (this->m_iconData.empty())
return this->m_icon;
if (this->m_icon.isValid())
return m_icon;
this->m_icon = ImGui::Texture(reinterpret_cast<const u8*>(this->m_iconData.data()), this->m_iconData.size());
return this->m_icon;
}
Achievement& setIcon(std::span<const std::byte> data) {
this->m_iconData.reserve(data.size());
for (auto &byte : data)
this->m_iconData.emplace_back(static_cast<u8>(byte));
return *this;
}
Achievement& setIcon(std::span<const u8> data) {
this->m_iconData.assign(data.begin(), data.end());
return *this;
}
Achievement& setIcon(std::vector<u8> data) {
this->m_iconData = std::move(data);
return *this;
}
Achievement& setIcon(std::vector<std::byte> data) {
this->m_iconData.reserve(data.size());
for (auto &byte : data)
this->m_iconData.emplace_back(static_cast<u8>(byte));
return *this;
}
Achievement& setRequiredProgress(u32 progress) {
this->m_maxProgress = progress;
return *this;
}
[[nodiscard]] u32 getRequiredProgress() const {
return this->m_maxProgress;
}
[[nodiscard]] u32 getProgress() const {
return this->m_progress;
}
void setClickCallback(const std::function<void(Achievement &)> &callback) {
this->m_clickCallback = callback;
}
[[nodiscard]] const std::function<void(Achievement &)> &getClickCallback() const {
return this->m_clickCallback;
}
[[nodiscard]] bool isTemporary() const {
return this->m_temporary;
}
void setUnlocked(bool unlocked) {
if (unlocked) {
if (this->m_progress < this->m_maxProgress)
this->m_progress++;
} else {
this->m_progress = 0;
}
}
protected:
void setProgress(u32 progress) {
this->m_progress = progress;
}
private:
std::string m_unlocalizedCategory, m_unlocalizedName;
std::string m_unlocalizedDescription;
bool m_blacked = false;
bool m_invisible = false;
std::vector<std::string> m_requirements, m_visibilityRequirements;
std::function<void(Achievement &)> m_clickCallback;
std::vector<u8> m_iconData;
mutable ImGui::Texture m_icon;
u32 m_progress = 0;
u32 m_maxProgress = 1;
bool m_temporary = false;
friend class AchievementManager;
};
class AchievementManager {
public:
AchievementManager() = delete;
struct AchievementNode {
Achievement *achievement;
std::vector<AchievementNode*> children, parents;
std::vector<AchievementNode*> visibilityParents;
ImVec2 position;
[[nodiscard]] bool hasParents() const {
return !this->parents.empty();
}
[[nodiscard]] bool isUnlockable() const {
return std::all_of(this->parents.begin(), this->parents.end(), [](auto &parent) { return parent->achievement->isUnlocked(); });
}
[[nodiscard]] bool isVisible() const {
return std::all_of(this->visibilityParents.begin(), this->visibilityParents.end(), [](auto &parent) { return parent->achievement->isUnlocked(); });
}
[[nodiscard]] bool isUnlocked() const {
return this->achievement->isUnlocked();
}
};
template<std::derived_from<Achievement> T = Achievement>
static Achievement& addAchievement(auto && ... args) {
auto newAchievement = std::make_unique<T>(std::forward<decltype(args)>(args)...);
const auto &category = newAchievement->getUnlocalizedCategory();
const auto &name = newAchievement->getUnlocalizedName();
auto [categoryIter, categoryInserted] = getAchievements().insert({ category, std::unordered_map<std::string, std::unique_ptr<Achievement>>{} });
auto &[categoryKey, achievements] = *categoryIter;
auto [achievementIter, achievementInserted] = achievements.insert({ name, std::move(newAchievement) });
auto &[achievementKey, achievement] = *achievementIter;
achievementAdded();
return *achievement;
}
template<std::derived_from<Achievement> T = Achievement>
static Achievement& addTemporaryAchievement(auto && ... args) {
auto &achievement = addAchievement(std::forward<decltype(args)>(args)...);
achievement.m_temporary = true;
return achievement;
}
static void unlockAchievement(const std::string &unlocalizedCategory, const std::string &unlocalizedName);
static std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>>& getAchievements();
static std::unordered_map<std::string, std::vector<AchievementNode*>>& getAchievementStartNodes(bool rebuild = true);
static std::unordered_map<std::string, std::list<AchievementNode>>& getAchievementNodes(bool rebuild = true);
static void loadProgress();
static void storeProgress();
static void clear();
static void clearTemporary();
private:
static void achievementAdded();
};
}

View File

@ -23,7 +23,11 @@
#define EVENT_DEF(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, true, __VA_ARGS__) #define EVENT_DEF(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, true, __VA_ARGS__)
#define EVENT_DEF_NO_LOG(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, false, __VA_ARGS__) #define EVENT_DEF_NO_LOG(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, false, __VA_ARGS__)
/* Forward declarations */
struct GLFWwindow; struct GLFWwindow;
namespace hex { class Achievement; }
namespace hex { namespace hex {
@ -202,6 +206,7 @@ namespace hex {
EVENT_DEF(EventStoreContentDownloaded, const std::fs::path&); EVENT_DEF(EventStoreContentDownloaded, const std::fs::path&);
EVENT_DEF(EventStoreContentRemoved, const std::fs::path&); EVENT_DEF(EventStoreContentRemoved, const std::fs::path&);
EVENT_DEF(EventImHexClosing); EVENT_DEF(EventImHexClosing);
EVENT_DEF(EventAchievementUnlocked, const Achievement&);
/** /**
* @brief Called when a project has been loaded * @brief Called when a project has been loaded

View File

@ -0,0 +1,223 @@
#include <hex/api/achievement_manager.hpp>
#include <nlohmann/json.hpp>
namespace hex {
std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>> &AchievementManager::getAchievements() {
static std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>> achievements;
return achievements;
}
std::unordered_map<std::string, std::list<AchievementManager::AchievementNode>>& AchievementManager::getAchievementNodes(bool rebuild) {
static std::unordered_map<std::string, std::list<AchievementNode>> nodeCategoryStorage;
if (!nodeCategoryStorage.empty() || !rebuild)
return nodeCategoryStorage;
nodeCategoryStorage.clear();
// Add all achievements to the node storage
for (auto &[categoryName, achievements] : getAchievements()) {
auto &nodes = nodeCategoryStorage[categoryName];
for (auto &[achievementName, achievement] : achievements) {
nodes.emplace_back(achievement.get());
}
}
return nodeCategoryStorage;
}
std::unordered_map<std::string, std::vector<AchievementManager::AchievementNode*>>& AchievementManager::getAchievementStartNodes(bool rebuild) {
static std::unordered_map<std::string, std::vector<AchievementNode*>> startNodes;
if (!startNodes.empty() || !rebuild)
return startNodes;
auto &nodeCategoryStorage = getAchievementNodes();
startNodes.clear();
// Add all parents and children to the nodes
for (auto &[categoryName, achievements] : nodeCategoryStorage) {
for (auto &achievementNode : achievements) {
for (auto &requirement : achievementNode.achievement->getRequirements()) {
for (auto &[requirementCategoryName, requirementAchievements] : nodeCategoryStorage) {
auto iter = std::find_if(requirementAchievements.begin(), requirementAchievements.end(), [&requirement](auto &node) {
return node.achievement->getUnlocalizedName() == requirement;
});
if (iter != requirementAchievements.end()) {
achievementNode.parents.emplace_back(&*iter);
iter->children.emplace_back(&achievementNode);
}
}
}
for (auto &requirement : achievementNode.achievement->getVisibilityRequirements()) {
for (auto &[requirementCategoryName, requirementAchievements] : nodeCategoryStorage) {
auto iter = std::find_if(requirementAchievements.begin(), requirementAchievements.end(), [&requirement](auto &node) {
return node.achievement->getUnlocalizedName() == requirement;
});
if (iter != requirementAchievements.end()) {
achievementNode.visibilityParents.emplace_back(&*iter);
}
}
}
}
}
for (auto &[categoryName, achievements] : nodeCategoryStorage) {
for (auto &achievementNode : achievements) {
if (!achievementNode.hasParents()) {
startNodes[categoryName].emplace_back(&achievementNode);
}
for (const auto &parent : achievementNode.parents) {
if (parent->achievement->getUnlocalizedCategory() != achievementNode.achievement->getUnlocalizedCategory())
startNodes[categoryName].emplace_back(&achievementNode);
}
}
}
return startNodes;
}
void AchievementManager::unlockAchievement(const std::string &unlocalizedCategory, const std::string &unlocalizedName) {
auto &categories = getAchievements();
auto categoryIter = categories.find(unlocalizedCategory);
if (categoryIter == categories.end()) {
return;
}
auto &[categoryName, achievements] = *categoryIter;
auto achievementIter = achievements.find(unlocalizedName);
if (achievementIter == achievements.end()) {
return;
}
auto &nodes = getAchievementNodes()[categoryName];
for (const auto &node : nodes) {
auto &achievement = node.achievement;
if (achievement->getUnlocalizedCategory() != unlocalizedCategory) {
continue;
}
if (achievement->getUnlocalizedName() != unlocalizedName) {
continue;
}
if (node.achievement->isUnlocked()) {
return;
}
for (const auto &requirement : node.parents) {
if (!requirement->achievement->isUnlocked()) {
return;
}
}
achievement->setUnlocked(true);
if (achievement->isUnlocked())
EventManager::post<EventAchievementUnlocked>(*achievement);
}
}
void AchievementManager::clear() {
getAchievements().clear();
getAchievementStartNodes(false).clear();
getAchievementNodes(false).clear();
}
void AchievementManager::clearTemporary() {
auto &categories = getAchievements();
for (auto &[categoryName, achievements] : categories) {
std::erase_if(achievements, [](auto &data) {
auto &[achievementName, achievement] = data;
return achievement->isTemporary();
});
}
std::erase_if(categories, [](auto &data) {
auto &[categoryName, achievements] = data;
return achievements.empty();
});
getAchievementStartNodes(false).clear();
getAchievementNodes(false).clear();
}
void AchievementManager::achievementAdded() {
getAchievementStartNodes(false).clear();
getAchievementNodes(false).clear();
}
constexpr static auto AchievementsFile = "achievements.json";
void AchievementManager::loadProgress() {
for (const auto &directory : fs::getDefaultPaths(fs::ImHexPath::Config)) {
auto path = directory / AchievementsFile;
if (!wolv::io::fs::exists(path)) {
continue;
}
wolv::io::File file(path, wolv::io::File::Mode::Read);
if (!file.isValid()) {
continue;
}
try {
auto json = nlohmann::json::parse(file.readString());
for (const auto &[categoryName, achievements] : getAchievements()) {
for (const auto &[achievementName, achievement] : achievements) {
try {
achievement->setProgress(json[categoryName][achievementName]);
} catch (const std::exception &e) {
log::warn("Failed to load achievement progress for '{}::{}': {}", categoryName, achievementName, e.what());
}
}
}
} catch (const std::exception &e) {
log::error("Failed to load achievements: {}", e.what());
}
}
}
void AchievementManager::storeProgress() {
for (const auto &directory : fs::getDefaultPaths(fs::ImHexPath::Config)) {
auto path = directory / AchievementsFile;
wolv::io::File file(path, wolv::io::File::Mode::Create);
if (!file.isValid()) {
continue;
}
nlohmann::json json;
for (const auto &[categoryName, achievements] : getAchievements()) {
json[categoryName] = nlohmann::json::object();
for (const auto &[achievementName, achievement] : achievements) {
json[categoryName][achievementName] = achievement->getProgress();
}
}
file.writeString(json.dump(4));
break;
}
}
}

View File

@ -93,8 +93,10 @@ namespace hex {
this->m_initializePluginFunction(); this->m_initializePluginFunction();
} catch (const std::exception &e) { } catch (const std::exception &e) {
log::error("Plugin '{}' threw an exception on init: {}", pluginName, e.what()); log::error("Plugin '{}' threw an exception on init: {}", pluginName, e.what());
return false;
} catch (...) { } catch (...) {
log::error("Plugin '{}' threw an exception on init", pluginName); log::error("Plugin '{}' threw an exception on init", pluginName);
return false;
} }
} else { } else {
return false; return false;

View File

@ -88,7 +88,13 @@ namespace hex {
bool Tar::contains(const std::fs::path &path) { bool Tar::contains(const std::fs::path &path) {
mtar_header_t header; mtar_header_t header;
return mtar_find(&this->m_ctx, path.string().c_str(), &header) == MTAR_ESUCCESS;
auto fixedPath = path.string();
#if defined(OS_WINDOWS)
std::replace(fixedPath.begin(), fixedPath.end(), '\\', '/');
#endif
return mtar_find(&this->m_ctx, fixedPath.c_str(), &header) == MTAR_ESUCCESS;
} }
std::string Tar::getOpenErrorString(){ std::string Tar::getOpenErrorString(){

View File

@ -35,8 +35,8 @@ namespace ImGui {
glGenTextures(1, &texture); glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture); glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#if defined(GL_UNPACK_ROW_LENGTH) #if defined(GL_UNPACK_ROW_LENGTH)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

View File

@ -13,6 +13,7 @@
#include <hex/api/theme_manager.hpp> #include <hex/api/theme_manager.hpp>
#include <hex/api/plugin_manager.hpp> #include <hex/api/plugin_manager.hpp>
#include <hex/api/layout_manager.hpp> #include <hex/api/layout_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/ui/view.hpp> #include <hex/ui/view.hpp>
#include <hex/ui/popup.hpp> #include <hex/ui/popup.hpp>
@ -402,6 +403,8 @@ namespace hex::init {
ThemeManager::reset(); ThemeManager::reset();
AchievementManager::getAchievements().clear();
ProjectFile::getHandlers().clear(); ProjectFile::getHandlers().clear();
ProjectFile::getProviderHandlers().clear(); ProjectFile::getProviderHandlers().clear();
ProjectFile::setProjectFunctions(nullptr, nullptr); ProjectFile::setProjectFunctions(nullptr, nullptr);
@ -454,13 +457,14 @@ namespace hex::init {
} }
// Make sure there's only one built-in plugin // Make sure there's only one built-in plugin
builtinPlugins++;
if (builtinPlugins > 1) continue; if (builtinPlugins > 1) continue;
// Initialize the plugin // Initialize the plugin
if (!plugin.initializePlugin()) { if (!plugin.initializePlugin()) {
log::error("Failed to initialize plugin {}", wolv::util::toUTF8String(plugin.getPath().filename())); log::error("Failed to initialize plugin {}", wolv::util::toUTF8String(plugin.getPath().filename()));
loadErrors++; loadErrors++;
} else {
builtinPlugins++;
} }
} }

View File

@ -34,6 +34,7 @@ add_imhex_plugin(
source/content/recent.cpp source/content/recent.cpp
source/content/file_handlers.cpp source/content/file_handlers.cpp
source/content/project.cpp source/content/project.cpp
source/content/achievements.cpp
source/content/providers/file_provider.cpp source/content/providers/file_provider.cpp
source/content/providers/gdb_provider.cpp source/content/providers/gdb_provider.cpp
@ -64,6 +65,7 @@ add_imhex_plugin(
source/content/views/view_find.cpp source/content/views/view_find.cpp
source/content/views/view_theme_manager.cpp source/content/views/view_theme_manager.cpp
source/content/views/view_logs.cpp source/content/views/view_logs.cpp
source/content/views/view_achievements.cpp
source/content/helpers/math_evaluator.cpp source/content/helpers/math_evaluator.cpp
source/content/helpers/notification.cpp source/content/helpers/notification.cpp

View File

@ -5,6 +5,8 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <fonts/codicons_font.h>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
class PopupTextInput : public Popup<PopupTextInput> { class PopupTextInput : public Popup<PopupTextInput> {

View File

@ -0,0 +1,43 @@
#pragma once
#include <hex.hpp>
#include <hex/ui/view.hpp>
#include <hex/api/achievement_manager.hpp>
namespace hex::plugin::builtin {
class ViewAchievements : public View {
public:
ViewAchievements();
~ViewAchievements() override;
void drawContent() override;
void drawAlwaysVisible() override;
[[nodiscard]] bool isAvailable() const override { return true; }
[[nodiscard]] bool hasViewMenuItemEntry() const override { return false; }
[[nodiscard]] ImVec2 getMinSize() const override {
return scaled({ 800, 600 });
}
[[nodiscard]] ImVec2 getMaxSize() const override {
return scaled({ 1600, 1200 });
}
private:
ImVec2 drawAchievementTree(ImDrawList *drawList, const AchievementManager::AchievementNode * prevNode, const std::vector<AchievementManager::AchievementNode*> &nodes, ImVec2 position);
private:
bool m_viewOpen = false;
std::list<const Achievement*> m_achievementUnlockQueue;
const Achievement *m_currAchievement = nullptr;
const Achievement *m_achievementToGoto = nullptr;
float m_achievementUnlockQueueTimer = -1;
bool m_showPopup = true;
ImVec2 m_offset;
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -4,6 +4,61 @@
"country": "United States", "country": "United States",
"fallback": true, "fallback": true,
"translations": { "translations": {
"hex.builtin.achievement.starting_out": "Starting out",
"hex.builtin.achievement.starting_out.docs.name": "RTFM",
"hex.builtin.achievement.starting_out.docs.desc": "Open the documentation by selecting Help -> Documentation from the menu bar.",
"hex.builtin.achievement.starting_out.open_file.name": "The inner workings",
"hex.builtin.achievement.starting_out.open_file.desc": "Open a file by dragging it onto ImHex or by selecting File -> Open from the menu bar.",
"hex.builtin.achievement.starting_out.save_project.name": "Keeping this",
"hex.builtin.achievement.starting_out.save_project.desc": "Save a project by selecting File -> Save Project from the menu bar.",
"hex.builtin.achievement.hex_editor": "Hex Editor",
"hex.builtin.achievement.hex_editor.select_byte.name": "You and you and you",
"hex.builtin.achievement.hex_editor.select_byte.desc": "Select multiple bytes in the Hex Editor by clicking and dragging over them.",
"hex.builtin.achievement.hex_editor.create_bookmark.name": "Building a library",
"hex.builtin.achievement.hex_editor.create_bookmark.desc": "Create a bookmark by right-clicking on a byte and selecting Bookmark from the context menu.",
"hex.builtin.achievement.hex_editor.open_new_view.name": "Seeing double",
"hex.builtin.achievement.hex_editor.open_new_view.desc": "Open a new view by clicking on the 'Open new view' button in a bookmark.",
"hex.builtin.achievement.hex_editor.modify_byte.name": "Edit the hex",
"hex.builtin.achievement.hex_editor.modify_byte.desc": "Modify a byte by double-clicking on it and then entering the new value.",
"hex.builtin.achievement.hex_editor.copy_as.name": "Copy that",
"hex.builtin.achievement.hex_editor.copy_as.desc": "Copy bytes as a C++ array by selecting Copy As -> C++ Array from the context menu.",
"hex.builtin.achievement.hex_editor.create_patch.name": "ROM Hacks",
"hex.builtin.achievement.hex_editor.create_patch.desc": "Create a IPS patch for the use in other tools by selecting the Export option in the File menu.",
"hex.builtin.achievement.hex_editor.fill.name": "Flood fill",
"hex.builtin.achievement.hex_editor.fill.desc": "Fill a region with a byte by selecting Fill from the context menu.",
"hex.builtin.achievement.patterns": "Patterns",
"hex.builtin.achievement.patterns.place_menu.name": "Easy Patterns",
"hex.builtin.achievement.patterns.place_menu.desc": "Place a pattern of any built-in type in your data by right-clicking on a byte and using the 'Place pattern' option.",
"hex.builtin.achievement.patterns.load_existing.name": "Hey, I know this one",
"hex.builtin.achievement.patterns.load_existing.desc": "Load a pattern that has been created by someone else by using the 'File -> Import' menu.",
"hex.builtin.achievement.patterns.modify_data.name": "Edit the pattern",
"hex.builtin.achievement.patterns.modify_data.desc": "Modify the underlying bytes of a pattern by double-clicking its value in the pattern data view and entering a new value.",
"hex.builtin.achievement.patterns.data_inspector.name": "Inspector Gadget",
"hex.builtin.achievement.patterns.data_inspector.desc": "Create a custom data inspector entry using the pattern language. You can find how to do that in the documentation.",
"hex.builtin.achievement.find": "Finding",
"hex.builtin.achievement.find.find_strings.name": "One Ring to find them",
"hex.builtin.achievement.find.find_strings.desc": "Locate all strings in your file by using the Find view in 'Strings' mode.",
"hex.builtin.achievement.find.find_specific_string.name": "Too Many Items",
"hex.builtin.achievement.find.find_specific_string.desc": "Refine your search by searching for occurrences of a specific string by using the 'Sequences' mode.",
"hex.builtin.achievement.find.find_numeric.name": "About ... that much",
"hex.builtin.achievement.find.find_numeric.desc": "Search for numeric values between 250 and 1000 by using the 'Numeric Value' mode.",
"hex.builtin.achievement.data_processor": "Data Processor",
"hex.builtin.achievement.data_processor.place_node.name": "Look at all these nodes",
"hex.builtin.achievement.data_processor.place_node.desc": "Place any built-in node in the data processor by right-clicking on the workspace and selecting a node from the context menu.",
"hex.builtin.achievement.data_processor.create_connection.name": "I feel a connection here",
"hex.builtin.achievement.data_processor.create_connection.desc": "Connect two nodes by dragging a connection from one node to another.",
"hex.builtin.achievement.data_processor.modify_data.name": "Decode this",
"hex.builtin.achievement.data_processor.modify_data.desc": "Preprocess the displayed bytes by using the built-in Read and Write Data Access nodes.",
"hex.builtin.achievement.data_processor.custom_node.name": "Building my own!",
"hex.builtin.achievement.data_processor.custom_node.desc": "Create a custom node by selecting 'Custom -> New Node' from the context menu and simplify your existing pattern by moving nodes into it.",
"hex.builtin.achievement.misc": "Miscellaneous",
"hex.builtin.achievement.misc.analyze_file.name": "owo wat dis?",
"hex.builtin.achievement.misc.analyze_file.desc": "Analyze the bytes of your data by using the 'Analyze' option in the Data Information view.",
"hex.builtin.achievement.misc.download_from_store.name": "There's an app for that",
"hex.builtin.achievement.misc.download_from_store.desc": "Download any item from the Content Store",
"hex.builtin.achievement.misc.create_hash.name": "Hash browns",
"hex.builtin.achievement.misc.create_hash.desc": "Create a new hash function in the Hash view by selecting the type, giving it a name and clicking on the Plus button next to it.",
"hex.builtin.command.calc.desc": "Calculator", "hex.builtin.command.calc.desc": "Calculator",
"hex.builtin.command.cmd.desc": "Command", "hex.builtin.command.cmd.desc": "Command",
"hex.builtin.command.cmd.result": "Run command '{0}'", "hex.builtin.command.cmd.result": "Run command '{0}'",
@ -603,6 +658,10 @@
"hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!", "hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!",
"hex.builtin.tools.wiki_explain.results": "Results", "hex.builtin.tools.wiki_explain.results": "Results",
"hex.builtin.tools.wiki_explain.search": "Search", "hex.builtin.tools.wiki_explain.search": "Search",
"hex.builtin.view.achievements.name": "Achievements",
"hex.builtin.view.achievements.unlocked": "Achievement Unlocked!",
"hex.builtin.view.achievements.unlocked_count": "Unlocked",
"hex.builtin.view.achievements.click": "Click here",
"hex.builtin.view.bookmarks.address": "0x{0:02X} - 0x{1:02X}", "hex.builtin.view.bookmarks.address": "0x{0:02X} - 0x{1:02X}",
"hex.builtin.view.bookmarks.button.jump": "Jump to", "hex.builtin.view.bookmarks.button.jump": "Jump to",
"hex.builtin.view.bookmarks.button.remove": "Remove", "hex.builtin.view.bookmarks.button.remove": "Remove",
@ -961,9 +1020,12 @@
"hex.builtin.welcome.learn.latest.desc": "Read ImHex's current changelog", "hex.builtin.welcome.learn.latest.desc": "Read ImHex's current changelog",
"hex.builtin.welcome.learn.latest.link": "https://github.com/WerWolv/ImHex/releases/latest", "hex.builtin.welcome.learn.latest.link": "https://github.com/WerWolv/ImHex/releases/latest",
"hex.builtin.welcome.learn.latest.title": "Latest Release", "hex.builtin.welcome.learn.latest.title": "Latest Release",
"hex.builtin.welcome.learn.pattern.desc": "Learn how to write ImHex patterns with our extensive documentation", "hex.builtin.welcome.learn.pattern.desc": "Learn how to write ImHex patterns",
"hex.builtin.welcome.learn.pattern.link": "https://imhex.werwolv.net/docs", "hex.builtin.welcome.learn.pattern.link": "https://docs.werwolv.net/pattern-language/",
"hex.builtin.welcome.learn.pattern.title": "Pattern Language Documentation", "hex.builtin.welcome.learn.pattern.title": "Pattern Language Documentation",
"hex.builtin.welcome.learn.imhex.desc": "Learn the basics of ImHex with our extensive documentation",
"hex.builtin.welcome.learn.imhex.link": "https://docs.werwolv.net/imhex/",
"hex.builtin.welcome.learn.imhex.title": "ImHex Documentation",
"hex.builtin.welcome.learn.plugins.desc": "Extend ImHex with additional features using plugins", "hex.builtin.welcome.learn.plugins.desc": "Extend ImHex with additional features using plugins",
"hex.builtin.welcome.learn.plugins.link": "https://github.com/WerWolv/ImHex/wiki/Plugins-Development-Guide", "hex.builtin.welcome.learn.plugins.link": "https://github.com/WerWolv/ImHex/wiki/Plugins-Development-Guide",
"hex.builtin.welcome.learn.plugins.title": "Plugins API", "hex.builtin.welcome.learn.plugins.title": "Plugins API",

View File

@ -0,0 +1,348 @@
#include <hex/api/achievement_manager.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/helpers/crypto.hpp>
#include <content/popups/popup_notification.hpp>
#include <content/popups/popup_text_input.hpp>
#include <nlohmann/json.hpp>
#include <romfs/romfs.hpp>
namespace hex::plugin::builtin {
namespace {
class AchievementStartingOut : public Achievement {
public:
explicit AchievementStartingOut(std::string unlocalizedName) : Achievement("hex.builtin.achievement.starting_out", std::move(unlocalizedName)) { }
};
class AchievementHexEditor : public Achievement {
public:
explicit AchievementHexEditor(std::string unlocalizedName) : Achievement("hex.builtin.achievement.hex_editor", std::move(unlocalizedName)) { }
};
class AchievementPatterns : public Achievement {
public:
explicit AchievementPatterns(std::string unlocalizedName) : Achievement("hex.builtin.achievement.patterns", std::move(unlocalizedName)) { }
};
class AchievementDataProcessor : public Achievement {
public:
explicit AchievementDataProcessor(std::string unlocalizedName) : Achievement("hex.builtin.achievement.data_processor", std::move(unlocalizedName)) { }
};
class AchievementFind : public Achievement {
public:
explicit AchievementFind(std::string unlocalizedName) : Achievement("hex.builtin.achievement.find", std::move(unlocalizedName)) { }
};
class AchievementMisc : public Achievement {
public:
explicit AchievementMisc(std::string unlocalizedName) : Achievement("hex.builtin.achievement.misc", std::move(unlocalizedName)) { }
};
void registerGettingStartedAchievements() {
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.docs.name")
.setDescription("hex.builtin.achievement.starting_out.docs.desc")
.setIcon(romfs::get("assets/achievements/open-book.png").span());
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.open_file.name")
.setDescription("hex.builtin.achievement.starting_out.open_file.desc")
.setIcon(romfs::get("assets/achievements/page-facing-up.png").span());
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.save_project.name")
.setDescription("hex.builtin.achievement.starting_out.save_project.desc")
.setIcon(romfs::get("assets/achievements/card-index-dividers.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.crash.name")
.setDescription("hex.builtin.achievement.starting_out.crash.desc")
.setIcon(romfs::get("assets/achievements/collision-symbol.png").span())
.setInvisible();
}
void registerHexEditorAchievements() {
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.select_byte.name")
.setDescription("hex.builtin.achievement.hex_editor.select_byte.desc")
.setIcon(romfs::get("assets/achievements/bookmark-tabs.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.open_new_view.name")
.setDescription("hex.builtin.achievement.hex_editor.open_new_view.desc")
.setIcon(romfs::get("assets/achievements/frame-with-picture.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.create_bookmark.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.modify_byte.name")
.setDescription("hex.builtin.achievement.hex_editor.modify_byte.desc")
.setIcon(romfs::get("assets/achievements/pencil.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.copy_as.name")
.setDescription("hex.builtin.achievement.hex_editor.copy_as.desc")
.setIcon(romfs::get("assets/achievements/copy.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.modify_byte.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.create_patch.name")
.setDescription("hex.builtin.achievement.hex_editor.create_patch.desc")
.setIcon(romfs::get("assets/achievements/adhesive-bandage.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.modify_byte.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.fill.name")
.setDescription("hex.builtin.achievement.hex_editor.fill.desc")
.setIcon(romfs::get("assets/achievements/water-wave.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.create_bookmark.name")
.setDescription("hex.builtin.achievement.hex_editor.create_bookmark.desc")
.setIcon(romfs::get("assets/achievements/bookmark.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
}
void registerPatternsAchievements() {
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.place_menu.name")
.setDescription("hex.builtin.achievement.patterns.place_menu.desc")
.setIcon(romfs::get("assets/achievements/clipboard.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.load_existing.name")
.setDescription("hex.builtin.achievement.patterns.load_existing.desc")
.setIcon(romfs::get("assets/achievements/hourglass.png").span())
.addRequirement("hex.builtin.achievement.patterns.place_menu.name");
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.modify_data.name")
.setDescription("hex.builtin.achievement.patterns.modify_data.desc")
.setIcon(romfs::get("assets/achievements/hammer.png").span())
.addRequirement("hex.builtin.achievement.patterns.place_menu.name");
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.data_inspector.name")
.setDescription("hex.builtin.achievement.patterns.data_inspector.desc")
.setIcon(romfs::get("assets/achievements/eye-in-speech-bubble.png").span())
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
}
void registerFindAchievements() {
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_strings.name")
.setDescription("hex.builtin.achievement.find.find_strings.desc")
.setIcon(romfs::get("assets/achievements/ring.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_specific_string.name")
.setDescription("hex.builtin.achievement.find.find_specific_string.desc")
.setIcon(romfs::get("assets/achievements/right-pointing-magnifying-glass.png").span())
.addRequirement("hex.builtin.achievement.find.find_strings.name");
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_numeric.name")
.setDescription("hex.builtin.achievement.find.find_numeric.desc")
.setIcon(romfs::get("assets/achievements/abacus.png").span())
.addRequirement("hex.builtin.achievement.find.find_strings.name");
}
void registerDataProcessorAchievements() {
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.place_node.name")
.setDescription("hex.builtin.achievement.data_processor.place_node.desc")
.setIcon(romfs::get("assets/achievements/cloud.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.create_connection.name")
.setDescription("hex.builtin.achievement.data_processor.create_connection.desc")
.setIcon(romfs::get("assets/achievements/linked-paperclips.png").span())
.addRequirement("hex.builtin.achievement.data_processor.place_node.name");
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.modify_data.name")
.setDescription("hex.builtin.achievement.data_processor.modify_data.desc")
.setIcon(romfs::get("assets/achievements/hammer-and-pick.png").span())
.addRequirement("hex.builtin.achievement.data_processor.create_connection.name");
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.custom_node.name")
.setDescription("hex.builtin.achievement.data_processor.custom_node.desc")
.setIcon(romfs::get("assets/achievements/wrench.png").span())
.addRequirement("hex.builtin.achievement.data_processor.create_connection.name");
}
void registerMiscAchievements() {
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.analyze_file.name")
.setDescription("hex.builtin.achievement.misc.analyze_file.desc")
.setIcon(romfs::get("assets/achievements/brain.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.download_from_store.name")
.setDescription("hex.builtin.achievement.misc.download_from_store.desc")
.setIcon(romfs::get("assets/achievements/package.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.create_hash.name")
.setDescription("hex.builtin.achievement.misc.create_hash.desc")
.setIcon(romfs::get("assets/achievements/fortune-cookie.png").span())
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
}
void registerEvents() {
EventManager::subscribe<EventRegionSelected>([](const auto &region) {
if (region.getSize() > 1)
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.select_byte.name");
});
EventManager::subscribe<EventBookmarkCreated>([](const auto&) {
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_bookmark.name");
});
EventManager::subscribe<EventPatchCreated>([](u64, u8, u8) {
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.modify_byte.name");
});
EventManager::subscribe<EventImHexStartupFinished>(AchievementManager::loadProgress);
EventManager::subscribe<EventImHexClosing>(AchievementManager::storeProgress);
// Clear temporary achievements when last provider is closed
EventManager::subscribe<EventProviderChanged>([](hex::prv::Provider *oldProvider, hex::prv::Provider *newProvider) {
hex::unused(oldProvider);
if (newProvider == nullptr) {
AchievementManager::clearTemporary();
}
});
}
void registerChallengeAchievementHandlers() {
static std::string challengeAchievement;
static std::string challengeDescription;
static std::map<std::string, std::vector<u8>> icons;
ProjectFile::registerHandler({
.basePath = "challenge",
.required = false,
.load = [](const std::fs::path &basePath, Tar &tar) {
if (!tar.contains(basePath / "achievements.json") || !tar.contains(basePath / "description.txt"))
return true;
challengeAchievement = tar.readString(basePath / "achievements.json");
challengeDescription = tar.readString(basePath / "description.txt");
nlohmann::json unlockedJson;
if (tar.contains(basePath / "unlocked.json")) {
unlockedJson = nlohmann::json::parse(tar.readString(basePath / "unlocked.json"));
}
try {
auto json = nlohmann::json::parse(challengeAchievement);
if (json.contains("achievements")) {
for (const auto &achievement : json["achievements"]) {
auto &newAchievement = AchievementManager::addTemporaryAchievement<Achievement>("hex.builtin.achievement.challenge", achievement["name"])
.setDescription(achievement["description"]);
if (achievement.contains("icon")) {
if (const auto &icon = achievement["icon"]; icon.is_string() && !icon.is_null()) {
auto iconPath = icon.get<std::string>();
auto data = tar.readVector(basePath / iconPath);
newAchievement.setIcon(data);
icons[iconPath] = std::move(data);
}
}
if (achievement.contains("requirements")) {
if (const auto &requirements = achievement["requirements"]; requirements.is_array()) {
for (const auto &requirement : requirements) {
newAchievement.addRequirement(requirement.get<std::string>());
}
}
}
if (achievement.contains("visibility_requirements")) {
if (const auto &requirements = achievement["visibility_requirements"]; requirements.is_array()) {
for (const auto &requirement : requirements) {
newAchievement.addVisibilityRequirement(requirement.get<std::string>());
}
}
}
if (achievement.contains("password")) {
if (const auto &password = achievement["password"]; password.is_string() && !password.is_null()) {
newAchievement.setClickCallback([password = password.get<std::string>()](Achievement &achievement) {
if (password.empty())
achievement.setUnlocked(true);
else
PopupTextInput::open("Enter Password", "Enter the password to unlock this achievement", [password, &achievement](const std::string &input) {
if (input == password)
achievement.setUnlocked(true);
else
PopupInfo::open("The password you entered was incorrect.");
});
});
if (unlockedJson.contains("achievements") && unlockedJson["achievements"].is_array()) {
for (const auto &unlockedAchievement : unlockedJson["achievements"]) {
if (unlockedAchievement.is_string() && unlockedAchievement.get<std::string>() == achievement["name"].get<std::string>()) {
newAchievement.setUnlocked(true);
break;
}
}
}
}
}
}
}
} catch (const nlohmann::json::exception &e) {
log::error("Failed to load challenge project: {}", e.what());
return false;
}
PopupInfo::open(challengeDescription);
return true;
},
.store = [](const std::fs::path &basePath, Tar &tar) {
if (!challengeAchievement.empty())
tar.writeString(basePath / "achievements.json", challengeAchievement);
if (!challengeDescription.empty())
tar.writeString(basePath / "description.txt", challengeDescription);
for (const auto &[iconPath, data] : icons) {
tar.writeVector(basePath / iconPath, data);
}
nlohmann::json unlockedJson;
unlockedJson["achievements"] = nlohmann::json::array();
for (const auto &[categoryName, achievements] : AchievementManager::getAchievements()) {
for (const auto &[achievementName, achievement] : achievements) {
if (achievement->isTemporary() && achievement->isUnlocked()) {
unlockedJson["achievements"].push_back(achievementName);
}
}
}
tar.writeString(basePath / "unlocked.json", unlockedJson.dump(4));
return true;
}
});
}
}
void registerAchievements() {
registerGettingStartedAchievements();
registerHexEditorAchievements();
registerPatternsAchievements();
registerFindAchievements();
registerDataProcessorAchievements();
registerMiscAchievements();
registerEvents();
registerChallengeAchievementHandlers();
}
}

View File

@ -1,4 +1,5 @@
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/providers/provider.hpp> #include <hex/providers/provider.hpp>
#include <hex/providers/buffered_reader.hpp> #include <hex/providers/buffered_reader.hpp>
@ -48,6 +49,8 @@ namespace hex::plugin::builtin {
}); });
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.cpp", [](prv::Provider *provider, u64 offset, size_t size) { ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.cpp", [](prv::Provider *provider, u64 offset, size_t size) {
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.copy_as.name");
return formatLanguageArray(provider, offset, size, hex::format("constexpr std::array<uint8_t, {0}> data = {{", size), "0x{0:02X}, ", "};"); return formatLanguageArray(provider, offset, size, hex::format("constexpr std::array<uint8_t, {0}> data = {{", size), "0x{0:02X}, ", "};");
}); });

View File

@ -1,7 +1,9 @@
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/localization.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/data_processor/node.hpp> #include <hex/data_processor/node.hpp>
#include <hex/api/localization.hpp>
#include <hex/helpers/crypto.hpp> #include <hex/helpers/crypto.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
#include <hex/helpers/logger.hpp> #include <hex/helpers/logger.hpp>
@ -467,6 +469,10 @@ namespace hex::plugin::builtin {
const auto &address = this->getIntegerOnInput(0); const auto &address = this->getIntegerOnInput(0);
const auto &data = this->getBufferOnInput(1); const auto &data = this->getBufferOnInput(1);
if (!data.empty()) {
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.modify_data.name");
}
this->setOverlayData(address, data); this->setOverlayData(address, data);
} }
}; };

View File

@ -5,6 +5,8 @@
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/localization.hpp> #include <hex/api/localization.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/providers/provider.hpp> #include <hex/providers/provider.hpp>
#include <hex/helpers/fmt.hpp> #include <hex/helpers/fmt.hpp>
@ -160,6 +162,9 @@ namespace hex::plugin::builtin {
resetPath.release(); resetPath.release();
} }
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.save_project.name");
return result; return result;
} }

View File

@ -4,6 +4,7 @@
#include <hex/api/localization.hpp> #include <hex/api/localization.hpp>
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
#include <hex/helpers/fmt.hpp> #include <hex/helpers/fmt.hpp>
@ -247,6 +248,8 @@ namespace hex::plugin::builtin {
this->m_file.close(); this->m_file.close();
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.open_file.name");
return true; return true;
} }

View File

@ -20,6 +20,7 @@
#include "content/views/view_find.hpp" #include "content/views/view_find.hpp"
#include "content/views/view_theme_manager.hpp" #include "content/views/view_theme_manager.hpp"
#include "content/views/view_logs.hpp" #include "content/views/view_logs.hpp"
#include "content/views/view_achievements.hpp"
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -46,6 +47,7 @@ namespace hex::plugin::builtin {
ContentRegistry::Views::add<ViewFind>(); ContentRegistry::Views::add<ViewFind>();
ContentRegistry::Views::add<ViewThemeManager>(); ContentRegistry::Views::add<ViewThemeManager>();
ContentRegistry::Views::add<ViewLogs>(); ContentRegistry::Views::add<ViewLogs>();
ContentRegistry::Views::add<ViewAchievements>();
} }
} }

View File

@ -1,6 +1,7 @@
#include "content/views/view_about.hpp" #include "content/views/view_about.hpp"
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/helpers/fmt.hpp> #include <hex/helpers/fmt.hpp>
#include <hex/helpers/fs.hpp> #include <hex/helpers/fs.hpp>
@ -23,7 +24,8 @@ namespace hex::plugin::builtin {
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000); ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000);
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.documentation" }, 3000, Shortcut::None, [] { ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.documentation" }, 3000, Shortcut::None, [] {
hex::openWebpage("https://imhex.werwolv.net/docs"); hex::openWebpage("https://docs.werwolv.net/imhex");
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.docs.name");
}); });
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.menu.help.ask_for_help" }, 4000, CTRLCMD + SHIFT + Keys::D, [] { ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.menu.help.ask_for_help" }, 4000, CTRLCMD + SHIFT + Keys::D, [] {

View File

@ -0,0 +1,378 @@
#include "content/views/view_achievements.hpp"
#include <hex/api/content_registry.hpp>
#include <wolv/utils/guards.hpp>
#include <cmath>
#include <imgui_internal.h>
namespace hex::plugin::builtin {
ViewAchievements::ViewAchievements() : View("hex.builtin.view.achievements.name") {
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.view.achievements.name" }, 2600, Shortcut::None, [&, this] {
this->m_viewOpen = true;
this->getWindowOpenState() = true;
});
EventManager::subscribe<EventAchievementUnlocked>(this, [this](const Achievement &achievement) {
this->m_achievementUnlockQueue.push_back(&achievement);
});
this->m_showPopup = bool(ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", 1));
}
ViewAchievements::~ViewAchievements() {
EventManager::unsubscribe<EventAchievementUnlocked>(this);
}
void drawAchievement(ImDrawList *drawList, const AchievementManager::AchievementNode *node, ImVec2 position) {
const auto achievementSize = scaled({ 50, 50 });
auto &achievement = *node->achievement;
const auto borderColor = [&] {
if (achievement.isUnlocked())
return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow, 1.0F);
else if (node->isUnlockable())
return ImGui::GetColorU32(ImGuiCol_Button, 1.0F);
else
return ImGui::GetColorU32(ImGuiCol_PlotLines, 1.0F);
}();
const auto fillColor = [&] {
if (achievement.isUnlocked())
return ImGui::GetColorU32(ImGuiCol_FrameBg, 1.0F) | 0xFF000000;
else if (node->isUnlockable())
return (u32(ImColor(ImLerp(ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), ImGui::GetStyleColorVec4(ImGuiCol_Text), sinf(ImGui::GetTime() * 6.0F) * 0.5F + 0.5F))) & 0x00FFFFFF) | 0x80000000;
else
return ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5F);
}();
if (achievement.isUnlocked()) {
drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0);
drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled);
} else {
drawList->AddRectFilled(position, position + achievementSize, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000, 5_scaled, 0);
}
if (const auto &icon = achievement.getIcon(); icon.isValid()) {
ImVec2 iconSize;
if (icon.getSize().x > icon.getSize().y) {
iconSize.x = achievementSize.x;
iconSize.y = iconSize.x / icon.getSize().x * icon.getSize().y;
} else {
iconSize.y = achievementSize.y;
iconSize.x = iconSize.y / icon.getSize().y * icon.getSize().x;
}
iconSize *= 0.7F;
ImVec2 margin = (achievementSize - iconSize) / 2.0F;
drawList->AddImage(icon, position + margin, position + margin + iconSize);
}
if (!achievement.isUnlocked()) {
drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0);
drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled);
}
auto tooltipPos = position + ImVec2(achievementSize.x, 0);
auto tooltipSize = achievementSize * ImVec2(4, 0);
if (ImGui::IsMouseHoveringRect(position, position + achievementSize)) {
ImGui::SetNextWindowPos(tooltipPos);
ImGui::SetNextWindowSize(tooltipSize);
if (ImGui::BeginTooltip()) {
if (achievement.isBlacked() && !achievement.isUnlocked()) {
ImGui::TextUnformatted("[ ??? ]");
} else {
ImGui::BeginDisabled(!achievement.isUnlocked());
ImGui::TextUnformatted(LangEntry(achievement.getUnlocalizedName()));
if (auto requiredProgress = achievement.getRequiredProgress(); requiredProgress > 1) {
ImGui::ProgressBar(float(achievement.getProgress()) / float(requiredProgress + 1), ImVec2(achievementSize.x * 4, 5_scaled), "");
}
bool separator = false;
if (achievement.getClickCallback() && !achievement.isUnlocked()) {
ImGui::Separator();
separator = true;
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "[ {} ]", LangEntry("hex.builtin.view.achievements.click"));
}
if (const auto &desc = achievement.getUnlocalizedDescription(); !desc.empty()) {
if (!separator)
ImGui::Separator();
else
ImGui::NewLine();
ImGui::TextFormattedWrapped("{}", LangEntry(desc));
}
ImGui::EndDisabled();
}
ImGui::EndTooltip();
}
if (!achievement.isUnlocked() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (ImGui::GetIO().KeyShift) {
#if defined (DEBUG)
AchievementManager::unlockAchievement(node->achievement->getUnlocalizedCategory(), node->achievement->getUnlocalizedName());
#endif
} else {
if (auto clickCallback = achievement.getClickCallback(); clickCallback)
clickCallback(achievement);
}
}
}
}
void drawOverlay(ImDrawList *drawList, ImVec2 windowMin, ImVec2 windowMax, const std::string &currCategory) {
auto &achievements = AchievementManager::getAchievements()[currCategory];
auto unlockedCount = std::count_if(achievements.begin(), achievements.end(), [](const auto &entry) {
const auto &[name, achievement] = entry;
return achievement->isUnlocked();
});
auto invisibleCount = std::count_if(achievements.begin(), achievements.end(), [](const auto &entry) {
const auto &[name, achievement] = entry;
return achievement->isInvisible();
});
auto unlockedText = hex::format("{}: {} / {}{}", "hex.builtin.view.achievements.unlocked_count"_lang, unlockedCount, achievements.size() - invisibleCount, invisibleCount > 0 ? "+" : " ");
auto &style = ImGui::GetStyle();
auto overlaySize = ImGui::CalcTextSize(unlockedText.c_str()) + style.ItemSpacing + style.WindowPadding * 2.0F;
auto padding = scaled({ 10, 10 });
auto overlayPos = ImVec2(windowMax.x - overlaySize.x - padding.x, windowMin.y + padding.y);
drawList->AddRectFilled(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8F));
drawList->AddRect(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_Border));
ImGui::SetCursorScreenPos(overlayPos + padding);
ImGui::BeginGroup();
ImGui::TextUnformatted(unlockedText.c_str());
ImGui::EndGroup();
}
void drawBackground(ImDrawList *drawList, ImVec2 min, ImVec2 max, ImVec2 offset) {
const auto patternSize = scaled({ 10, 10 });
const auto darkColor = ImGui::GetColorU32(ImGuiCol_TableRowBg);
const auto lightColor = ImGui::GetColorU32(ImGuiCol_TableRowBgAlt);
drawList->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 0.0F, 0, 1.0_scaled);
bool light = false;
bool prevStart = false;
for (float x = min.x + offset.x; x < max.x; x += i32(patternSize.x)) {
if (prevStart == light)
light = !light;
prevStart = light;
for (float y = min.y + offset.y; y < max.y; y += i32(patternSize.y)) {
drawList->AddRectFilled({ x, y }, { x + patternSize.x, y + patternSize.y }, light ? lightColor : darkColor);
light = !light;
}
}
}
ImVec2 ViewAchievements::drawAchievementTree(ImDrawList *drawList, const AchievementManager::AchievementNode * prevNode, const std::vector<AchievementManager::AchievementNode*> &nodes, ImVec2 position) {
ImVec2 maxPos = position;
for (auto &node : nodes) {
if (node->achievement->isInvisible() && !node->achievement->isUnlocked())
continue;
if (!node->visibilityParents.empty()) {
bool visible = true;
for (auto &parent : node->visibilityParents) {
if (!parent->achievement->isUnlocked()) {
visible = false;
break;
}
}
if (!visible)
continue;
}
drawList->ChannelsSetCurrent(1);
if (prevNode != nullptr) {
if (prevNode->achievement->getUnlocalizedCategory() != node->achievement->getUnlocalizedCategory())
continue;
auto start = prevNode->position + scaled({ 25, 25 });
auto end = position + scaled({ 25, 25 });
auto middle = ((start + end) / 2.0F) - scaled({ 50, 0 });
const auto color = [prevNode]{
if (prevNode->achievement->isUnlocked())
return ImGui::GetColorU32(ImGuiCol_Text) | 0xFF000000;
else
return ImGui::GetColorU32(ImGuiCol_TextDisabled) | 0xFF000000;
}();
drawList->AddBezierQuadratic(start, middle, end, color, 2_scaled);
if (this->m_achievementToGoto != nullptr) {
if (this->m_achievementToGoto == node->achievement) {
this->m_offset = position - scaled({ 100, 100 });
}
}
}
drawList->ChannelsSetCurrent(2);
drawAchievement(drawList, node, position);
node->position = position;
auto newMaxPos = drawAchievementTree(drawList, node, node->children, position + scaled({ 150, 0 }));
if (newMaxPos.x > maxPos.x)
maxPos.x = newMaxPos.x;
if (newMaxPos.y > maxPos.y)
maxPos.y = newMaxPos.y;
position.y = maxPos.y + 100_scaled;
}
return maxPos;
}
void ViewAchievements::drawContent() {
if (ImGui::Begin(View::toWindowName("hex.builtin.view.achievements.name").c_str(), &this->m_viewOpen, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
if (ImGui::BeginTabBar("##achievement_categories")) {
auto &startNodes = AchievementManager::getAchievementStartNodes();
std::vector<std::string> categories;
for (const auto &[categoryName, achievements] : startNodes) {
categories.push_back(categoryName);
}
std::reverse(categories.begin(), categories.end());
for (const auto &categoryName : categories) {
const auto &achievements = startNodes[categoryName];
bool visible = false;
for (const auto &achievement : achievements) {
if (achievement->isUnlocked() || achievement->isUnlockable()) {
visible = true;
break;
}
}
if (!visible)
continue;
ImGuiTabItemFlags flags = ImGuiTabItemFlags_None;
if (this->m_achievementToGoto != nullptr) {
if (this->m_achievementToGoto->getUnlocalizedCategory() == categoryName) {
flags |= ImGuiTabItemFlags_SetSelected;
}
}
if (ImGui::BeginTabItem(LangEntry(categoryName), nullptr, flags)) {
auto drawList = ImGui::GetWindowDrawList();
const auto cursorPos = ImGui::GetCursorPos();
const auto windowPos = ImGui::GetWindowPos() + ImVec2(0, cursorPos.y);
const auto windowSize = ImGui::GetWindowSize() - ImVec2(0, cursorPos.y);;
const float borderSize = 20.0_scaled;
const auto windowPadding = ImGui::GetStyle().WindowPadding;
const auto innerWindowPos = windowPos + ImVec2(borderSize, borderSize);
const auto innerWindowSize = windowSize - ImVec2(borderSize * 2, borderSize * 2) - ImVec2(0, ImGui::GetTextLineHeightWithSpacing());
drawList->PushClipRect(innerWindowPos, innerWindowPos + innerWindowSize, true);
drawList->ChannelsSplit(4);
drawList->ChannelsSetCurrent(0);
drawBackground(drawList, innerWindowPos, innerWindowPos + innerWindowSize, this->m_offset);
auto maxPos = drawAchievementTree(drawList, nullptr, achievements, innerWindowPos + scaled({ 100, 100 }) + this->m_offset);
drawList->ChannelsSetCurrent(3);
drawOverlay(drawList, innerWindowPos, innerWindowPos + innerWindowSize, categoryName);
drawList->ChannelsMerge();
if (ImGui::IsMouseHoveringRect(innerWindowPos, innerWindowPos + innerWindowSize)) {
auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
this->m_offset += dragDelta;
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
}
this->m_offset = -ImClamp(-this->m_offset, { 0, 0 }, ImMax(maxPos - innerWindowPos - innerWindowSize, { 0, 0 }));
drawList->PopClipRect();
ImGui::SetCursorScreenPos(innerWindowPos + ImVec2(0, innerWindowSize.y + windowPadding.y));
ImGui::BeginGroup();
{
if (ImGui::Checkbox("Show popup", &this->m_showPopup))
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", i64(this->m_showPopup));
}
ImGui::EndGroup();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
}
ImGui::End();
this->getWindowOpenState() = this->m_viewOpen;
this->m_achievementToGoto = nullptr;
}
void ViewAchievements::drawAlwaysVisible() {
if (this->m_achievementUnlockQueueTimer >= 0 && this->m_showPopup) {
this->m_achievementUnlockQueueTimer -= ImGui::GetIO().DeltaTime;
if (this->m_currAchievement != nullptr) {
const ImVec2 windowSize = scaled({ 200, 55 });
ImGui::SetNextWindowPos(ImHexApi::System::getMainWindowPosition() + ImVec2 { ImHexApi::System::getMainWindowSize().x - windowSize.x - 100_scaled, 0 });
ImGui::SetNextWindowSize(windowSize);
if (ImGui::Begin("##achievement_unlocked", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs)) {
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "{}", "hex.builtin.view.achievements.unlocked"_lang);
ImGui::Image(this->m_currAchievement->getIcon(), scaled({ 20, 20 }));
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
ImGui::TextFormattedWrapped("{}", LangEntry(this->m_currAchievement->getUnlocalizedName()));
if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
this->m_viewOpen = true;
this->getWindowOpenState() = this->m_viewOpen;
this->m_achievementToGoto = this->m_currAchievement;
}
}
ImGui::End();
}
} else {
this->m_achievementUnlockQueueTimer = -1.0F;
this->m_currAchievement = nullptr;
if (!this->m_achievementUnlockQueue.empty()) {
this->m_currAchievement = this->m_achievementUnlockQueue.front();
this->m_achievementUnlockQueue.pop_front();
this->m_achievementUnlockQueueTimer = 2.5F;
}
}
}
}

View File

@ -2,6 +2,7 @@
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/api/task.hpp> #include <hex/api/task.hpp>
#include <hex/helpers/fmt.hpp> #include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
@ -289,8 +290,10 @@ namespace hex::plugin::builtin {
auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true); auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true);
if (auto *viewProvider = dynamic_cast<ViewProvider*>(newProvider); viewProvider != nullptr) { if (auto *viewProvider = dynamic_cast<ViewProvider*>(newProvider); viewProvider != nullptr) {
viewProvider->setProvider(region.getStartAddress(), region.getSize(), provider); viewProvider->setProvider(region.getStartAddress(), region.getSize(), provider);
if (viewProvider->open()) if (viewProvider->open()) {
EventManager::post<EventProviderOpened>(viewProvider); EventManager::post<EventProviderOpened>(viewProvider);
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.open_new_view.name");
}
} }
}); });
} }

View File

@ -4,8 +4,7 @@
#include <hex/providers/provider.hpp> #include <hex/providers/provider.hpp>
#include <cstring> #include <hex/api/achievement_manager.hpp>
#include <hex/helpers/logger.hpp> #include <hex/helpers/logger.hpp>
#include <pl/pattern_language.hpp> #include <pl/pattern_language.hpp>
@ -144,6 +143,8 @@ namespace hex::plugin::builtin {
editingFunction, editingFunction,
false false
}); });
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.data_inspector.name");
} catch (const pl::core::err::EvaluatorError::Exception &error) { } catch (const pl::core::err::EvaluatorError::Exception &error) {
log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what()); log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what());
} }

View File

@ -2,12 +2,11 @@
#include "content/popups/popup_notification.hpp" #include "content/popups/popup_notification.hpp"
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/helpers/logger.hpp> #include <hex/api/achievement_manager.hpp>
#include <hex/providers/provider.hpp> #include <hex/providers/provider.hpp>
#include <hex/api/project_file_manager.hpp> #include <hex/helpers/logger.hpp>
#include <imnodes.h> #include <imnodes.h>
#include <imnodes_internal.h> #include <imnodes_internal.h>
@ -166,6 +165,8 @@ namespace hex::plugin::builtin {
editing = ImGui::IsItemActive(); editing = ImGui::IsItemActive();
if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) { if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) {
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.custom_node.name");
this->m_dataProcessor->getWorkspaceStack().push_back(&this->m_workspace); this->m_dataProcessor->getWorkspaceStack().push_back(&this->m_workspace);
this->m_requiresAttributeUpdate = true; this->m_requiresAttributeUpdate = true;
@ -595,6 +596,7 @@ namespace hex::plugin::builtin {
ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords); ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords);
workspace.nodes.push_back(std::move(node)); workspace.nodes.push_back(std::move(node));
ImHexApi::Provider::markDirty(); ImHexApi::Provider::markDirty();
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.place_node.name");
} }
ImGui::EndPopup(); ImGui::EndPopup();
@ -818,6 +820,8 @@ namespace hex::plugin::builtin {
fromAttr->addConnectedAttribute(newLink.getId(), toAttr); fromAttr->addConnectedAttribute(newLink.getId(), toAttr);
toAttr->addConnectedAttribute(newLink.getId(), fromAttr); toAttr->addConnectedAttribute(newLink.getId(), fromAttr);
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.create_connection.name");
} while (false); } while (false);
} }
} }

View File

@ -1,6 +1,8 @@
#include "content/views/view_find.hpp" #include "content/views/view_find.hpp"
#include <hex/api/imhex_api.hpp> #include <hex/api/imhex_api.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/providers/buffered_reader.hpp> #include <hex/providers/buffered_reader.hpp>
#include <array> #include <array>
@ -439,6 +441,16 @@ namespace hex::plugin::builtin {
void ViewFind::runSearch() { void ViewFind::runSearch() {
Region searchRegion = this->m_searchSettings.region; Region searchRegion = this->m_searchSettings.region;
if (this->m_searchSettings.mode == SearchSettings::Mode::Strings)
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_strings.name");
else if (this->m_searchSettings.mode == SearchSettings::Mode::Sequence)
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_specific_string.name");
else if (this->m_searchSettings.mode == SearchSettings::Mode::Value) {
if (this->m_searchSettings.value.inputMin == "250" && this->m_searchSettings.value.inputMax == "1000")
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_specific_string.name");
}
this->m_searchTask = TaskManager::createTask("hex.builtin.view.find.searching", searchRegion.getSize(), [this, settings = this->m_searchSettings, searchRegion](auto &task) { this->m_searchTask = TaskManager::createTask("hex.builtin.view.find.searching", searchRegion.getSize(), [this, settings = this->m_searchSettings, searchRegion](auto &task) {
auto provider = ImHexApi::Provider::get(); auto provider = ImHexApi::Provider::get();

View File

@ -3,6 +3,8 @@
#include "content/providers/memory_file_provider.hpp" #include "content/providers/memory_file_provider.hpp"
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/ui/popup.hpp> #include <hex/ui/popup.hpp>
#include <hex/helpers/crypto.hpp> #include <hex/helpers/crypto.hpp>
@ -176,8 +178,10 @@ namespace hex::plugin::builtin {
ImGui::BeginDisabled(this->m_newHashName.empty() || this->m_selectedHash == nullptr); ImGui::BeginDisabled(this->m_newHashName.empty() || this->m_selectedHash == nullptr);
if (ImGui::IconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { if (ImGui::IconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
if (this->m_selectedHash != nullptr) if (this->m_selectedHash != nullptr) {
this->m_hashFunctions->push_back(this->m_selectedHash->create(this->m_newHashName)); this->m_hashFunctions->push_back(this->m_selectedHash->create(this->m_newHashName));
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.create_hash.name");
}
} }
ImGui::EndDisabled(); ImGui::EndDisabled();

View File

@ -2,10 +2,13 @@
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/keybinding.hpp> #include <hex/api/keybinding.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <hex/helpers/crypto.hpp>
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/crypto.hpp>
#include <hex/providers/buffered_reader.hpp>
#include <content/providers/view_provider.hpp> #include <content/providers/view_provider.hpp>
#include <content/helpers/math_evaluator.hpp> #include <content/helpers/math_evaluator.hpp>
@ -540,6 +543,8 @@ namespace hex::plugin::builtin {
auto remainingSize = std::min<size_t>(size - i, bytes.size()); auto remainingSize = std::min<size_t>(size - i, bytes.size());
provider->write(provider->getBaseAddress() + address + i, bytes.data(), remainingSize); provider->write(provider->getBaseAddress() + address + i, bytes.data(), remainingSize);
} }
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.fill.name");
} }
private: private:

View File

@ -1,6 +1,7 @@
#include "content/views/view_information.hpp" #include "content/views/view_information.hpp"
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/providers/provider.hpp> #include <hex/providers/provider.hpp>
#include <hex/providers/buffered_reader.hpp> #include <hex/providers/buffered_reader.hpp>
@ -64,6 +65,8 @@ namespace hex::plugin::builtin {
} }
void ViewInformation::analyze() { void ViewInformation::analyze() {
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.analyze_file.name");
this->m_analyzerTask = TaskManager::createTask("hex.builtin.view.information.analyzing", 0, [this](auto &task) { this->m_analyzerTask = TaskManager::createTask("hex.builtin.view.information.analyzing", 0, [this](auto &task) {
auto provider = ImHexApi::Provider::get(); auto provider = ImHexApi::Provider::get();

View File

@ -1,6 +1,8 @@
#include "content/views/view_pattern_editor.hpp" #include "content/views/view_pattern_editor.hpp"
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <pl/patterns/pattern.hpp> #include <pl/patterns/pattern.hpp>
#include <pl/core/preprocessor.hpp> #include <pl/core/preprocessor.hpp>
@ -11,7 +13,6 @@
#include <hex/helpers/fs.hpp> #include <hex/helpers/fs.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
#include <hex/api/project_file_manager.hpp>
#include <hex/helpers/magic.hpp> #include <hex/helpers/magic.hpp>
#include <hex/helpers/binary_pattern.hpp> #include <hex/helpers/binary_pattern.hpp>
@ -1086,6 +1087,7 @@ namespace hex::plugin::builtin {
auto selection = ImHexApi::HexEditor::getSelection(); auto selection = ImHexApi::HexEditor::getSelection();
appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress())); appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress()));
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.place_menu.name");
} }
void ViewPatternEditor::appendArray(const std::string &type, size_t size) { void ViewPatternEditor::appendArray(const std::string &type, size_t size) {
@ -1115,6 +1117,7 @@ namespace hex::plugin::builtin {
PopupFileChooser::open(paths, std::vector<nfdfilteritem_t>{ { "Pattern File", "hexpat" } }, false, PopupFileChooser::open(paths, std::vector<nfdfilteritem_t>{ { "Pattern File", "hexpat" } }, false,
[this, provider](const std::fs::path &path) { [this, provider](const std::fs::path &path) {
this->loadPatternFile(path, provider); this->loadPatternFile(path, provider);
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.load_existing.name");
}); });
}, ImHexApi::Provider::isValid); }, ImHexApi::Provider::isValid);

View File

@ -1,5 +1,6 @@
#include "content/views/view_store.hpp" #include "content/views/view_store.hpp"
#include <hex/api/theme_manager.hpp> #include <hex/api/theme_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/api_urls.hpp> #include <hex/api_urls.hpp>
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
@ -93,6 +94,7 @@ namespace hex::plugin::builtin {
} else if (!entry.installed) { } else if (!entry.installed) {
if (ImGui::Button("hex.builtin.view.store.download"_lang, buttonSize)) { if (ImGui::Button("hex.builtin.view.store.download"_lang, buttonSize)) {
entry.downloading = this->download(category.path, entry.fileName, entry.link, false); entry.downloading = this->download(category.path, entry.fileName, entry.link, false);
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.download_from_store.name");
} }
} else { } else {
if (ImGui::Button("hex.builtin.view.store.remove"_lang, buttonSize)) { if (ImGui::Button("hex.builtin.view.store.remove"_lang, buttonSize)) {

View File

@ -6,6 +6,7 @@
#include <hex/api/plugin_manager.hpp> #include <hex/api/plugin_manager.hpp>
#include <hex/api/theme_manager.hpp> #include <hex/api/theme_manager.hpp>
#include <hex/api/layout_manager.hpp> #include <hex/api/layout_manager.hpp>
#include <hex/api/achievement_manager.hpp>
#include <hex/api_urls.hpp> #include <hex/api_urls.hpp>
#include <hex/ui/view.hpp> #include <hex/ui/view.hpp>
#include <hex/helpers/fs.hpp> #include <hex/helpers/fs.hpp>
@ -284,6 +285,10 @@ namespace hex::plugin::builtin {
{ {
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
hex::openWebpage("hex.builtin.welcome.learn.latest.link"_lang); hex::openWebpage("hex.builtin.welcome.learn.latest.link"_lang);
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.imhex.title"_lang, "hex.builtin.welcome.learn.imhex.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) {
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.docs.name");
hex::openWebpage("hex.builtin.welcome.learn.imhex.link"_lang);
}
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
hex::openWebpage("hex.builtin.welcome.learn.pattern.link"_lang); hex::openWebpage("hex.builtin.welcome.learn.pattern.link"_lang);
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
@ -527,6 +532,10 @@ namespace hex::plugin::builtin {
if (showTipOfTheDay) if (showTipOfTheDay)
PopupTipOfTheDay::open(); PopupTipOfTheDay::open();
} }
if (hasCrashed) {
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.crash.name");
}
} }
} }

View File

@ -38,6 +38,7 @@ namespace hex::plugin::builtin {
void registerNetworkEndpoints(); void registerNetworkEndpoints();
void registerFileHandlers(); void registerFileHandlers();
void registerProjectHandlers(); void registerProjectHandlers();
void registerAchievements();
void addFooterItems(); void addFooterItems();
void addTitleBarButtons(); void addTitleBarButtons();
@ -96,6 +97,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
registerFileHandlers(); registerFileHandlers();
registerProjectHandlers(); registerProjectHandlers();
registerCommandForwarders(); registerCommandForwarders();
registerAchievements();
addFooterItems(); addFooterItems();
addTitleBarButtons(); addTitleBarButtons();

View File

@ -21,8 +21,10 @@
#include <hex/api/imhex_api.hpp> #include <hex/api/imhex_api.hpp>
#include <hex/api/content_registry.hpp> #include <hex/api/content_registry.hpp>
#include <hex/helpers/utils.hpp> #include <hex/api/achievement_manager.hpp>
#include <hex/api/localization.hpp> #include <hex/api/localization.hpp>
#include <hex/helpers/utils.hpp>
#include <content/helpers/math_evaluator.hpp> #include <content/helpers/math_evaluator.hpp>
#include <imgui.h> #include <imgui.h>
@ -332,6 +334,7 @@ namespace hex::plugin::builtin::ui {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
this->m_editingPattern = &pattern; this->m_editingPattern = &pattern;
this->m_editingPatternOffset = pattern.getOffset(); this->m_editingPatternOffset = pattern.getOffset();
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.modify_data.name");
} }
ImGui::SameLine(0, 0); ImGui::SameLine(0, 0);