From 4288f876e22488893fe2c3d6ee994af50158ba36 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 29 Aug 2023 12:14:12 +0200 Subject: [PATCH] impr: Added lots of comments and cleaned up many views --- .../content/views/view_data_inspector.hpp | 4 + .../include/content/views/view_diff.hpp | 4 + .../content/views/view_achievements.cpp | 85 ++- .../source/content/views/view_bookmarks.cpp | 47 +- .../content/views/view_command_palette.cpp | 25 +- .../source/content/views/view_constants.cpp | 34 +- .../content/views/view_data_inspector.cpp | 299 ++++++---- .../source/content/views/view_diff.cpp | 209 ++++--- .../content/views/view_disassembler.cpp | 520 ++++++++++-------- .../content/views/view_pattern_data.cpp | 10 +- .../source/content/views/view_settings.cpp | 46 +- .../content/views/view_theme_manager.cpp | 31 +- .../source/content/views/view_tools.cpp | 13 + 13 files changed, 833 insertions(+), 494 deletions(-) diff --git a/plugins/builtin/include/content/views/view_data_inspector.hpp b/plugins/builtin/include/content/views/view_data_inspector.hpp index ea5a4ba07..50d7dce6f 100644 --- a/plugins/builtin/include/content/views/view_data_inspector.hpp +++ b/plugins/builtin/include/content/views/view_data_inspector.hpp @@ -26,6 +26,10 @@ namespace hex::plugin::builtin { bool editing; }; + private: + void updateInspectorRows(); + + private: bool m_shouldInvalidate = true; std::endian m_endian = std::endian::native; diff --git a/plugins/builtin/include/content/views/view_diff.hpp b/plugins/builtin/include/content/views/view_diff.hpp index 1b5db6ef6..35b022289 100644 --- a/plugins/builtin/include/content/views/view_diff.hpp +++ b/plugins/builtin/include/content/views/view_diff.hpp @@ -39,6 +39,10 @@ namespace hex::plugin::builtin { DifferenceType type; }; + private: + std::function(u64, const u8*, size_t)> createCompareFunction(size_t otherIndex); + void analyze(prv::Provider *providerA, prv::Provider *providerB); + private: std::array m_columns; diff --git a/plugins/builtin/source/content/views/view_achievements.cpp b/plugins/builtin/source/content/views/view_achievements.cpp index 1e7592af4..7bd72eefb 100644 --- a/plugins/builtin/source/content/views/view_achievements.cpp +++ b/plugins/builtin/source/content/views/view_achievements.cpp @@ -10,15 +10,18 @@ namespace hex::plugin::builtin { ViewAchievements::ViewAchievements() : View("hex.builtin.view.achievements.name") { + // Add achievements menu item to Extas menu ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.view.achievements.name" }, 2600, Shortcut::None, [&, this] { this->m_viewOpen = true; this->getWindowOpenState() = true; }); + // Add newly unlocked achievements to the display queue EventManager::subscribe(this, [this](const Achievement &achievement) { this->m_achievementUnlockQueue.push_back(&achievement); }); + // Load settings this->m_showPopup = bool(ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", 1)); } @@ -31,6 +34,7 @@ namespace hex::plugin::builtin { auto &achievement = *node->achievement; + // Determine achievement border color based on unlock state const auto borderColor = [&] { if (achievement.isUnlocked()) return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow, 1.0F); @@ -40,15 +44,19 @@ namespace hex::plugin::builtin { return ImGui::GetColorU32(ImGuiCol_PlotLines, 1.0F); }(); + // Determine achievement fill color based on unlock state 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 if (node->isUnlockable()) { + auto cycleProgress = sinf(float(ImGui::GetTime()) * 6.0F) * 0.5F + 0.5F; + return (u32(ImColor(ImLerp(ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), ImGui::GetStyleColorVec4(ImGuiCol_Text), cycleProgress))) & 0x00FFFFFF) | 0x80000000; + } else return ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5F); }(); + // Draw achievement background if (achievement.isUnlocked()) { drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0); drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled); @@ -56,6 +64,7 @@ namespace hex::plugin::builtin { drawList->AddRectFilled(position, position + achievementSize, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000, 5_scaled, 0); } + // Draw achievement icon if available if (const auto &icon = achievement.getIcon(); icon.isValid()) { ImVec2 iconSize; if (icon.getSize().x > icon.getSize().y) { @@ -72,6 +81,7 @@ namespace hex::plugin::builtin { drawList->AddImage(icon, position + margin, position + margin + iconSize); } + // Dim achievement if it is not unlocked if (!achievement.isUnlocked()) { drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0); drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled); @@ -80,22 +90,30 @@ namespace hex::plugin::builtin { auto tooltipPos = position + ImVec2(achievementSize.x, 0); auto tooltipSize = achievementSize * ImVec2(4, 0); + // Draw achievement tooltip when hovering over it if (ImGui::IsMouseHoveringRect(position, position + achievementSize)) { ImGui::SetNextWindowPos(tooltipPos); ImGui::SetNextWindowSize(tooltipSize); if (ImGui::BeginTooltip()) { if (achievement.isBlacked() && !achievement.isUnlocked()) { + // Handle achievements that are blacked out ImGui::TextUnformatted("[ ??? ]"); } else { + // Handle regular achievements + ImGui::BeginDisabled(!achievement.isUnlocked()); + + // Draw achievement name ImGui::TextUnformatted(LangEntry(achievement.getUnlocalizedName())); + // Draw progress bar if achievement has progress if (auto requiredProgress = achievement.getRequiredProgress(); requiredProgress > 1) { ImGui::ProgressBar(float(achievement.getProgress()) / float(requiredProgress + 1), ImVec2(achievementSize.x * 4, 5_scaled), ""); } bool separator = false; + // Draw prompt to click on achievement if it has a click callback if (achievement.getClickCallback() && !achievement.isUnlocked()) { ImGui::Separator(); separator = true; @@ -103,6 +121,7 @@ namespace hex::plugin::builtin { ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "[ {} ]", LangEntry("hex.builtin.view.achievements.click")); } + // Draw achievement description if available if (const auto &desc = achievement.getUnlocalizedDescription(); !desc.empty()) { if (!separator) ImGui::Separator(); @@ -117,12 +136,15 @@ namespace hex::plugin::builtin { ImGui::EndTooltip(); } + // Handle achievement click if (!achievement.isUnlocked() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { if (ImGui::GetIO().KeyShift) { + // Allow achievements to be unlocked in debug mode by shift-clicking them #if defined (DEBUG) AchievementManager::unlockAchievement(node->achievement->getUnlocalizedCategory(), node->achievement->getUnlocalizedName()); #endif } else { + // Trigger achievement click callback if (auto clickCallback = achievement.getClickCallback(); clickCallback) clickCallback(achievement); } @@ -132,27 +154,37 @@ namespace hex::plugin::builtin { 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) { + + // Calculate number of achievements that have been unlocked + const 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) { + // Calculate number of invisible achievements + const 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 ? "+" : " "); + // Calculate number of visible achievements + const auto visibleCount = achievements.size() - invisibleCount; + // Construct number of unlocked achievements text + auto unlockedText = hex::format("{}: {} / {}{}", "hex.builtin.view.achievements.unlocked_count"_lang, unlockedCount, visibleCount, invisibleCount > 0 ? "+" : " "); + + // Calculate overlay size 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); + // Draw overlay background drawList->AddRectFilled(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8F)); drawList->AddRect(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_Border)); + // Draw overlay content ImGui::SetCursorScreenPos(overlayPos + padding); ImGui::BeginGroup(); @@ -167,8 +199,10 @@ namespace hex::plugin::builtin { const auto darkColor = ImGui::GetColorU32(ImGuiCol_TableRowBg); const auto lightColor = ImGui::GetColorU32(ImGuiCol_TableRowBgAlt); + // Draw a border around the entire background drawList->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 0.0F, 0, 1.0_scaled); + // Draw a checkerboard pattern bool light = false; bool prevStart = false; for (float x = min.x + offset.x; x < max.x; x += i32(patternSize.x)) { @@ -185,12 +219,18 @@ namespace hex::plugin::builtin { ImVec2 ViewAchievements::drawAchievementTree(ImDrawList *drawList, const AchievementManager::AchievementNode * prevNode, const std::vector &nodes, ImVec2 position) { ImVec2 maxPos = position; + + // Loop over all available achievement nodes for (auto &node : nodes) { + // If the achievement is invisible and not unlocked yet, don't draw anything if (node->achievement->isInvisible() && !node->achievement->isUnlocked()) continue; + // If the achievement has any visibility requirements, check if they are met if (!node->visibilityParents.empty()) { bool visible = true; + + // Check if all the visibility requirements are unlocked for (auto &parent : node->visibilityParents) { if (!parent->achievement->isUnlocked()) { visible = false; @@ -198,13 +238,16 @@ namespace hex::plugin::builtin { } } + // If any of the visibility requirements are not unlocked, don't draw the achievement if (!visible) continue; } drawList->ChannelsSetCurrent(1); + // Check if the achievement has any parents if (prevNode != nullptr) { + // Check if the parent achievement is in the same category if (prevNode->achievement->getUnlocalizedCategory() != node->achievement->getUnlocalizedCategory()) continue; @@ -219,8 +262,10 @@ namespace hex::plugin::builtin { return ImGui::GetColorU32(ImGuiCol_TextDisabled) | 0xFF000000; }(); + // Draw a bezier curve between the parent and child achievement drawList->AddBezierQuadratic(start, middle, end, color, 2_scaled); + // Handle jumping to an achievement if (this->m_achievementToGoto != nullptr) { if (this->m_achievementToGoto == node->achievement) { this->m_offset = position - scaled({ 100, 100 }); @@ -230,8 +275,10 @@ namespace hex::plugin::builtin { drawList->ChannelsSetCurrent(2); + // Draw the achievement drawAchievement(drawList, node, position); + // Adjust the position for the next achievement and continue drawing the achievement tree node->position = position; auto newMaxPos = drawAchievementTree(drawList, node, node->children, position + scaled({ 150, 0 })); if (newMaxPos.x > maxPos.x) @@ -250,6 +297,7 @@ namespace hex::plugin::builtin { if (ImGui::BeginTabBar("##achievement_categories")) { auto &startNodes = AchievementManager::getAchievementStartNodes(); + // Get all achievement category names std::vector categories; for (const auto &[categoryName, achievements] : startNodes) { categories.push_back(categoryName); @@ -257,9 +305,11 @@ namespace hex::plugin::builtin { std::reverse(categories.begin(), categories.end()); + // Draw each individual achievement category for (const auto &categoryName : categories) { const auto &achievements = startNodes[categoryName]; + // Check if any achievements in the category are unlocked or unlockable bool visible = false; for (const auto &achievement : achievements) { if (achievement->isUnlocked() || achievement->isUnlockable()) { @@ -268,17 +318,20 @@ namespace hex::plugin::builtin { } } + // If all achievements in this category are invisible, don't draw it if (!visible) continue; ImGuiTabItemFlags flags = ImGuiTabItemFlags_None; + // Handle jumping to the category of an achievement if (this->m_achievementToGoto != nullptr) { if (this->m_achievementToGoto->getUnlocalizedCategory() == categoryName) { flags |= ImGuiTabItemFlags_SetSelected; } } + // Draw the achievement category if (ImGui::BeginTabItem(LangEntry(categoryName), nullptr, flags)) { auto drawList = ImGui::GetWindowDrawList(); @@ -290,31 +343,40 @@ namespace hex::plugin::builtin { 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()); + + // Prevent the achievement tree from being drawn outside of the window drawList->PushClipRect(innerWindowPos, innerWindowPos + innerWindowSize, true); drawList->ChannelsSplit(4); drawList->ChannelsSetCurrent(0); + // Draw achievement background drawBackground(drawList, innerWindowPos, innerWindowPos + innerWindowSize, this->m_offset); + + // Draw the achievement tree auto maxPos = drawAchievementTree(drawList, nullptr, achievements, innerWindowPos + scaled({ 100, 100 }) + this->m_offset); drawList->ChannelsSetCurrent(3); + // Draw the achievement overlay drawOverlay(drawList, innerWindowPos, innerWindowPos + innerWindowSize, categoryName); drawList->ChannelsMerge(); + // Handle dragging the achievement tree around if (ImGui::IsMouseHoveringRect(innerWindowPos, innerWindowPos + innerWindowSize)) { auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); this->m_offset += dragDelta; ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left); } + // Clamp the achievement tree to the window this->m_offset = -ImClamp(-this->m_offset, { 0, 0 }, ImMax(maxPos - innerWindowPos - innerWindowSize, { 0, 0 })); drawList->PopClipRect(); + // Draw settings below the window ImGui::SetCursorScreenPos(innerWindowPos + ImVec2(0, innerWindowSize.y + windowPadding.y)); ImGui::BeginGroup(); { @@ -338,24 +400,34 @@ namespace hex::plugin::builtin { } void ViewAchievements::drawAlwaysVisible() { + + // Handle showing the achievement unlock popup if (this->m_achievementUnlockQueueTimer >= 0 && this->m_showPopup) { this->m_achievementUnlockQueueTimer -= ImGui::GetIO().DeltaTime; + // Check if there's an achievement that can be drawn 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)) { + // Draw unlock text ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "{}", "hex.builtin.view.achievements.unlocked"_lang); + // Draw achievement icon ImGui::Image(this->m_currAchievement->getIcon(), scaled({ 20, 20 })); + ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); + + // Draw name of achievement ImGui::TextFormattedWrapped("{}", LangEntry(this->m_currAchievement->getUnlocalizedName())); + // Handle clicking on the popup if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + // Open the achievement window and jump to the achievement this->m_viewOpen = true; this->getWindowOpenState() = this->m_viewOpen; this->m_achievementToGoto = this->m_currAchievement; @@ -364,8 +436,11 @@ namespace hex::plugin::builtin { ImGui::End(); } } else { + // Reset the achievement unlock queue timer this->m_achievementUnlockQueueTimer = -1.0F; this->m_currAchievement = nullptr; + + // If there's more achievements to draw, draw the next one if (!this->m_achievementUnlockQueue.empty()) { this->m_currAchievement = this->m_achievementUnlockQueue.front(); this->m_achievementUnlockQueue.pop_front(); diff --git a/plugins/builtin/source/content/views/view_bookmarks.cpp b/plugins/builtin/source/content/views/view_bookmarks.cpp index 3e6ce29f4..2d262f77d 100644 --- a/plugins/builtin/source/content/views/view_bookmarks.cpp +++ b/plugins/builtin/source/content/views/view_bookmarks.cpp @@ -18,6 +18,8 @@ namespace hex::plugin::builtin { ViewBookmarks::ViewBookmarks() : View("hex.builtin.view.bookmarks.name") { + + // Handle bookmark add requests sent by the API EventManager::subscribe(this, [this](Region region, std::string name, std::string comment, color_t color) { if (name.empty()) { name = hex::format("hex.builtin.view.bookmarks.default_title"_lang, region.address, region.address + region.size - 1); @@ -39,9 +41,11 @@ namespace hex::plugin::builtin { EventManager::post(this->m_bookmarks->back()); }); + // Draw hex editor background highlights for bookmarks ImHexApi::HexEditor::addBackgroundHighlightingProvider([this](u64 address, const u8* data, size_t size, bool) -> std::optional { hex::unused(data); + // Check all bookmarks for potential overlaps with the current address for (const auto &bookmark : *this->m_bookmarks) { if (Region { address, size }.isWithin(bookmark.region)) return bookmark.color; @@ -50,12 +54,17 @@ namespace hex::plugin::builtin { return std::nullopt; }); + // Draw hex editor tooltips for bookmarks ImHexApi::HexEditor::addTooltipProvider([this](u64 address, const u8 *data, size_t size) { hex::unused(data); + + // Loop over all bookmarks for (const auto &bookmark : *this->m_bookmarks) { + // Make sure the bookmark overlaps the currently hovered address if (!Region { address, size }.isWithin(bookmark.region)) continue; + // Draw tooltip ImGui::BeginTooltip(); ImGui::PushID(&bookmark); @@ -64,10 +73,12 @@ namespace hex::plugin::builtin { ImGui::TableNextColumn(); { + // Draw bookmark header ImGui::ColorButton("##color", ImColor(bookmark.color)); ImGui::SameLine(0, 10); ImGui::TextFormatted("{} ", bookmark.name); + // Draw extra information table when holding down shift if (ImGui::GetIO().KeyShift) { ImGui::Indent(); if (ImGui::BeginTable("##extra_info", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { @@ -75,12 +86,14 @@ namespace hex::plugin::builtin { ImGui::TableNextRow(); ImGui::TableNextColumn(); + // Draw region ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{}: ", "hex.builtin.common.region"_lang.get()); ImGui::TableNextColumn(); ImGui::TextFormatted("[ 0x{:08X} - 0x{:08X} ] ", bookmark.region.getStartAddress(), bookmark.region.getEndAddress()); + // Draw comment if it's not empty if (!bookmark.comment.empty() && bookmark.comment[0] != '\x00') { ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -109,6 +122,7 @@ namespace hex::plugin::builtin { } }); + // Handle saving / restoring of bookmarks in projects ProjectFile::registerPerProviderHandler({ .basePath = "bookmarks.json", .required = false, @@ -140,14 +154,15 @@ namespace hex::plugin::builtin { } static void drawColorPopup(ImColor &color) { - static auto Palette = []{ + // Generate color picker palette + static auto Palette = [] { constexpr static auto ColorCount = 36; std::array result = { 0 }; u32 counter = 0; for (auto &color : result) { ImGui::ColorConvertHSVtoRGB(float(counter) / float(ColorCount - 1), 0.8F, 0.8F, color.Value.x, color.Value.y, color.Value.z); - color.Value.w = 0.7f; + color.Value.w = 0.7F; counter++; } @@ -155,15 +170,17 @@ namespace hex::plugin::builtin { return result; }(); + // Draw default color picker ImGui::ColorPicker4("##picker", (float*)&color, ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview); ImGui::Separator(); + // Draw color palette int id = 0; for (const auto &paletteColor : Palette) { ImGui::PushID(id); if ((id % 9) != 0) - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + ImGui::SameLine(0.0F, ImGui::GetStyle().ItemSpacing.y); constexpr static ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoDragDrop; if (ImGui::ColorButton("##palette", paletteColor.Value, flags, ImVec2(20, 20))) { @@ -179,6 +196,7 @@ namespace hex::plugin::builtin { if (ImGui::Begin(View::toWindowName("hex.builtin.view.bookmarks.name").c_str(), &this->getWindowOpenState())) { auto provider = ImHexApi::Provider::get(); + // Draw filter input ImGui::PushItemWidth(-1); ImGui::InputTextIcon("##filter", ICON_VS_FILTER, this->m_currFilter); ImGui::PopItemWidth(); @@ -192,9 +210,12 @@ namespace hex::plugin::builtin { int id = 1; auto bookmarkToRemove = this->m_bookmarks->end(); + + // Draw all bookmarks for (auto iter = this->m_bookmarks->begin(); iter != this->m_bookmarks->end(); iter++) { auto &[region, name, comment, color, locked] = *iter; + // Apply filter if (!this->m_currFilter.empty()) { if (!name.contains(this->m_currFilter) && !comment.contains(this->m_currFilter)) continue; @@ -204,6 +225,7 @@ namespace hex::plugin::builtin { auto hoverColor = ImColor(color); hoverColor.Value.w *= 1.3F; + // Draw bookmark header in the same color as the bookmark was set to ImGui::PushID(id); ImGui::PushStyleColor(ImGuiCol_Header, color); ImGui::PushStyleColor(ImGuiCol_HeaderActive, color); @@ -217,14 +239,19 @@ namespace hex::plugin::builtin { bool open = true; if (!ImGui::CollapsingHeader(hex::format("{}###bookmark", name).c_str(), locked ? nullptr : &open)) { + // Handle dragging bookmarks up and down when they're collapsed + + // Set the currently held bookmark as the one being dragged if (ImGui::IsMouseClicked(0) && ImGui::IsItemActivated() && this->m_dragStartIterator == this->m_bookmarks->end()) this->m_dragStartIterator = iter; + // When the mouse moved away from the current bookmark, swap the dragged bookmark with the current one if (ImGui::IsItemHovered() && this->m_dragStartIterator != this->m_bookmarks->end()) { std::iter_swap(iter, this->m_dragStartIterator); this->m_dragStartIterator = iter; } + // When the mouse is released, reset the dragged bookmark if (!ImGui::IsMouseDown(0)) this->m_dragStartIterator = this->m_bookmarks->end(); } else { @@ -237,10 +264,12 @@ namespace hex::plugin::builtin { ImGui::TableNextRow(ImGuiTableRowFlags_None, rowHeight); ImGui::TableNextColumn(); + // Draw bookmark name ImGui::TextUnformatted("hex.builtin.view.bookmarks.header.name"_lang); ImGui::TableNextColumn(); ImGui::TableNextColumn(); + // Draw lock/unlock button if (locked) { if (ImGui::IconButton(ICON_VS_LOCK, ImGui::GetStyleColorVec4(ImGuiCol_Text))) locked = false; ImGui::InfoTooltip("hex.builtin.view.bookmarks.tooltip.unlock"_lang); @@ -251,12 +280,14 @@ namespace hex::plugin::builtin { ImGui::SameLine(); + // Draw color button if (ImGui::ColorButton("hex.builtin.view.bookmarks.header.color"_lang, headerColor.Value, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoAlpha)) { if (!locked) ImGui::OpenPopup("hex.builtin.view.bookmarks.header.color"_lang); } ImGui::InfoTooltip("hex.builtin.view.bookmarks.header.color"_lang); + // Draw color picker if (ImGui::BeginPopup("hex.builtin.view.bookmarks.header.color"_lang)) { drawColorPopup(headerColor); color = headerColor; @@ -265,6 +296,7 @@ namespace hex::plugin::builtin { ImGui::SameLine(); + // Draw bookmark name if the bookmark is locked or an input text box if it's unlocked if (locked) ImGui::TextUnformatted(name.data()); else { @@ -280,11 +312,14 @@ namespace hex::plugin::builtin { ImGui::TableNextColumn(); ImGui::TableNextColumn(); + // Draw jump to address button if (ImGui::IconButton(ICON_VS_DEBUG_STEP_BACK, ImGui::GetStyleColorVec4(ImGuiCol_Text))) ImHexApi::HexEditor::setSelection(region); ImGui::InfoTooltip("hex.builtin.view.bookmarks.tooltip.jump_to"_lang); ImGui::SameLine(); + + // Draw open in new view button if (ImGui::IconButton(ICON_VS_GO_TO_FILE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { TaskManager::doLater([region, provider]{ auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true); @@ -300,11 +335,14 @@ namespace hex::plugin::builtin { ImGui::InfoTooltip("hex.builtin.view.bookmarks.tooltip.open_in_view"_lang); ImGui::SameLine(); + + // Draw the address of the bookmark ImGui::TextFormatted("hex.builtin.view.bookmarks.address"_lang, region.getStartAddress(), region.getEndAddress()); ImGui::TableNextRow(ImGuiTableRowFlags_None, rowHeight); ImGui::TableNextColumn(); + // Draw size of the bookmark ImGui::TextUnformatted("hex.builtin.common.size"_lang); ImGui::TableNextColumn(); ImGui::TableNextColumn(); @@ -313,6 +351,7 @@ namespace hex::plugin::builtin { ImGui::EndTable(); } + // Draw comment if the bookmark is locked or an input text box if it's unlocked if (locked) { if (!comment.empty()) { ImGui::Header("hex.builtin.view.bookmarks.header.comment"_lang); @@ -327,10 +366,12 @@ namespace hex::plugin::builtin { ImGui::NewLine(); } + // Mark a bookmark for removal when the user clicks the remove button if (!open) bookmarkToRemove = iter; } + // Remove the bookmark that was marked for removal if (bookmarkToRemove != this->m_bookmarks->end()) { this->m_bookmarks->erase(bookmarkToRemove); } diff --git a/plugins/builtin/source/content/views/view_command_palette.cpp b/plugins/builtin/source/content/views/view_command_palette.cpp index 6d0e716cb..119476717 100644 --- a/plugins/builtin/source/content/views/view_command_palette.cpp +++ b/plugins/builtin/source/content/views/view_command_palette.cpp @@ -3,11 +3,10 @@ #include #include -#include - namespace hex::plugin::builtin { ViewCommandPalette::ViewCommandPalette() : View("hex.builtin.view.command_palette.name") { + // Add global shortcut to open the command palette ShortcutManager::addGlobalShortcut(CTRLCMD + SHIFT + Keys::P, [this] { EventManager::post("hex.builtin.view.command_palette.name"_lang); this->m_commandPaletteOpen = true; @@ -16,7 +15,7 @@ namespace hex::plugin::builtin { } void ViewCommandPalette::drawContent() { - + // If the command palette is hidden, don't draw it if (!this->m_commandPaletteOpen) return; auto windowPos = ImHexApi::System::getMainWindowPosition(); @@ -24,10 +23,12 @@ namespace hex::plugin::builtin { ImGui::SetNextWindowPos(ImVec2(windowPos.x + windowSize.x * 0.5F, windowPos.y), ImGuiCond_Always, ImVec2(0.5F, 0.0F)); if (ImGui::BeginPopup("hex.builtin.view.command_palette.name"_lang)) { + // Close the popup if the user presses ESC if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) ImGui::CloseCurrentPopup(); + // Draw the main input text box ImGui::PushItemWidth(-1); if (ImGui::InputText("##command_input", this->m_commandBuffer)) { this->m_lastResults = this->getCommandResults(this->m_commandBuffer); @@ -35,6 +36,7 @@ namespace hex::plugin::builtin { ImGui::PopItemWidth(); ImGui::SetItemDefaultFocus(); + // Handle giving back focus to the input text box if (this->m_focusInputTextBox) { ImGui::SetKeyboardFocusHere(-1); ImGui::ActivateItem(ImGui::GetID("##command_input")); @@ -49,6 +51,7 @@ namespace hex::plugin::builtin { this->m_focusInputTextBox = false; } + // Execute the currently selected command when pressing enter if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false))) { if (!this->m_lastResults.empty()) { auto &[displayResult, matchedCommand, callback] = this->m_lastResults.front(); @@ -57,6 +60,7 @@ namespace hex::plugin::builtin { ImGui::CloseCurrentPopup(); } + // Focus the input text box when the popup is opened if (this->m_justOpened) { focusInputTextBox(); this->m_lastResults = this->getCommandResults(""); @@ -66,11 +70,13 @@ namespace hex::plugin::builtin { ImGui::Separator(); + // Draw the results if (ImGui::BeginChild("##results", ImGui::GetContentRegionAvail(), false, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_NavFlattened)) { for (const auto &[displayResult, matchedCommand, callback] : this->m_lastResults) {; ImGui::PushTabStop(true); ON_SCOPE_EXIT { ImGui::PopTabStop(); }; + // Allow executing a command by clicking on it or selecting it with the keyboard and pressing enter if (ImGui::Selectable(displayResult.c_str(), false, ImGuiSelectableFlags_DontClosePopups)) { callback(matchedCommand); break; @@ -91,6 +97,11 @@ namespace hex::plugin::builtin { std::vector ViewCommandPalette::getCommandResults(const std::string &input) { constexpr static auto MatchCommand = [](const std::string &currCommand, const std::string &commandToMatch) -> std::pair { + // Check if the current input matches a command + // NoMatch means that the input doesn't match the command + // PartialMatch means that the input partially matches the command but is not a perfect match + // PerfectMatch means that the input perfectly matches the command + if (currCommand.empty()) { return { MatchType::InfoMatch, "" }; } else if (currCommand.size() <= commandToMatch.size()) { @@ -108,6 +119,7 @@ namespace hex::plugin::builtin { std::vector results; + // Loop over every registered command and check if the input matches it for (const auto &[type, command, unlocalizedDescription, displayCallback, executeCallback] : ContentRegistry::CommandPaletteCommands::impl::getEntries()) { auto AutoComplete = [this, currCommand = command](auto) { @@ -117,6 +129,9 @@ namespace hex::plugin::builtin { }; if (type == ContentRegistry::CommandPaletteCommands::Type::SymbolCommand) { + // Handle symbol commands + // These commands are used by entering a single symbol and then any input + if (auto [match, value] = MatchCommand(input, command); match != MatchType::NoMatch) { if (match != MatchType::PerfectMatch) results.push_back({ command + " (" + LangEntry(unlocalizedDescription) + ")", "", AutoComplete }); @@ -126,6 +141,9 @@ namespace hex::plugin::builtin { } } } else if (type == ContentRegistry::CommandPaletteCommands::Type::KeywordCommand) { + // Handle keyword commands + // These commands are used by entering a keyword followed by a space and then any input + if (auto [match, value] = MatchCommand(input, command + " "); match != MatchType::NoMatch) { if (match != MatchType::PerfectMatch) results.push_back({ command + " (" + LangEntry(unlocalizedDescription) + ")", "", AutoComplete }); @@ -137,6 +155,7 @@ namespace hex::plugin::builtin { } } + // WHen a command has been identified, show the query results for that command for (const auto &handler : ContentRegistry::CommandPaletteCommands::impl::getHandlers()) { const auto &[type, command, queryCallback, displayCallback] = handler; diff --git a/plugins/builtin/source/content/views/view_constants.cpp b/plugins/builtin/source/content/views/view_constants.cpp index 47facc325..afbfa8b12 100644 --- a/plugins/builtin/source/content/views/view_constants.cpp +++ b/plugins/builtin/source/content/views/view_constants.cpp @@ -14,9 +14,6 @@ namespace hex::plugin::builtin { ViewConstants::ViewConstants() : View("hex.builtin.view.constants.name") { this->reloadConstants(); - - this->m_filter.reserve(0xFFFF); - std::memset(this->m_filter.data(), 0x00, this->m_filter.capacity()); } void ViewConstants::reloadConstants() { @@ -70,24 +67,21 @@ namespace hex::plugin::builtin { if (ImGui::Begin(View::toWindowName("hex.builtin.view.constants.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { ImGui::PushItemWidth(-1); - ImGui::InputText( - "##search", this->m_filter.data(), this->m_filter.capacity(), ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) { - auto &view = *static_cast(data->UserData); - view.m_filter.resize(data->BufTextLen); - view.m_filterIndices.clear(); - for (u64 i = 0; i < view.m_constants.size(); i++) { - auto &constant = view.m_constants[i]; - if (hex::containsIgnoreCase(constant.name, data->Buf) || - hex::containsIgnoreCase(constant.category, data->Buf) || - hex::containsIgnoreCase(constant.description, data->Buf) || - hex::containsIgnoreCase(constant.value, data->Buf)) - view.m_filterIndices.push_back(i); - } + if (ImGui::InputTextIcon("##search", ICON_VS_FILTER, this->m_filter)) { + this->m_filterIndices.clear(); + + // Filter the constants according to the entered value + for (u64 i = 0; i < this->m_constants.size(); i++) { + auto &constant = this->m_constants[i]; + if (hex::containsIgnoreCase(constant.name, this->m_filter) || + hex::containsIgnoreCase(constant.category, this->m_filter) || + hex::containsIgnoreCase(constant.description, this->m_filter) || + hex::containsIgnoreCase(constant.value, this->m_filter)) + this->m_filterIndices.push_back(i); + } + } - return 0; - }, - this); ImGui::PopItemWidth(); if (ImGui::BeginTable("##strings", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { @@ -99,6 +93,7 @@ namespace hex::plugin::builtin { auto sortSpecs = ImGui::TableGetSortSpecs(); + // Handle table sorting if (sortSpecs->SpecsDirty) { std::sort(this->m_constants.begin(), this->m_constants.end(), [&sortSpecs](Constant &left, Constant &right) -> bool { if (sortSpecs->Specs->ColumnUserID == ImGui::GetID("category")) { @@ -134,6 +129,7 @@ namespace hex::plugin::builtin { ImGuiListClipper clipper; clipper.Begin(this->m_filterIndices.size()); + // Draw the constants table while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { auto &constant = this->m_constants[this->m_filterIndices[i]]; diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index 64fedf741..8ab0899aa 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -19,7 +19,10 @@ namespace hex::plugin::builtin { using NumberDisplayStyle = ContentRegistry::DataInspector::NumberDisplayStyle; ViewDataInspector::ViewDataInspector() : View("hex.builtin.view.data_inspector.name") { + // Handle region selection EventManager::subscribe(this, [this](const auto ®ion) { + + // Save current selection if (!ImHexApi::Provider::isValid() || region == Region::Invalid()) { this->m_validBytes = 0; this->m_selectedProvider = nullptr; @@ -29,6 +32,7 @@ namespace hex::plugin::builtin { this->m_selectedProvider = region.getProvider(); } + // Invalidate inspector rows this->m_shouldInvalidate = true; }); @@ -42,6 +46,146 @@ namespace hex::plugin::builtin { EventManager::unsubscribe(this); } + + void ViewDataInspector::updateInspectorRows() { + this->m_updateTask = TaskManager::createBackgroundTask("Update Inspector", [this, validBytes = this->m_validBytes, startAddress = this->m_startAddress, endian = this->m_endian, invert = this->m_invert, numberDisplayStyle = this->m_numberDisplayStyle](auto &) { + this->m_workData.clear(); + + if (this->m_selectedProvider == nullptr) + return; + + // Decode bytes using registered inspectors + for (auto &entry : ContentRegistry::DataInspector::impl::getEntries()) { + if (validBytes < entry.requiredSize) + continue; + + // Try to read as many bytes as requested and possible + std::vector buffer(validBytes > entry.maxSize ? entry.maxSize : validBytes); + this->m_selectedProvider->read(startAddress, buffer.data(), buffer.size()); + + // Handle invert setting + if (invert) { + for (auto &byte : buffer) + byte ^= 0xFF; + } + + // Insert processed data into the inspector list + this->m_workData.push_back({ + entry.unlocalizedName, + entry.generatorFunction(buffer, endian, numberDisplayStyle), + entry.editingFunction, + false + }); + } + + + // Decode bytes using custom inspectors defined using the pattern language + const std::map inVariables = { + { "numberDisplayStyle", u128(numberDisplayStyle) } + }; + + // Setup a new pattern language runtime + ContentRegistry::PatternLanguage::configureRuntime(this->m_runtime, this->m_selectedProvider); + + // Setup the runtime to read from the selected provider + this->m_runtime.setDataSource(this->m_selectedProvider->getBaseAddress(), this->m_selectedProvider->getActualSize(), + [this, invert](u64 offset, u8 *buffer, size_t size) { + // Read bytes from the selected provider + this->m_selectedProvider->read(offset, buffer, size); + + // Handle invert setting + if (invert) { + for (auto &byte : std::span(buffer, size)) + byte ^= 0xFF; + } + }); + + // Prevent dangerous function calls + this->m_runtime.setDangerousFunctionCallHandler([] { return false; }); + + // Set the default endianness based on the endian setting + this->m_runtime.setDefaultEndian(endian); + + // Set start address to the selected address + this->m_runtime.setStartAddress(startAddress); + + // Loop over all files in the inspectors folder and execute them + for (const auto &folderPath : fs::getDefaultPaths(fs::ImHexPath::Inspectors)) { + for (const auto &filePath : std::fs::recursive_directory_iterator(folderPath)) { + + // Skip non-files and files that don't end with .hexpat + if (!filePath.exists() || !filePath.is_regular_file() || filePath.path().extension() != ".hexpat") + continue; + + // Read the inspector file + wolv::io::File file(filePath, wolv::io::File::Mode::Read); + if (file.isValid()) { + auto inspectorCode = file.readString(); + + // Execute the inspector file + if (!inspectorCode.empty()) { + if (this->m_runtime.executeString(inspectorCode, {}, inVariables, true)) { + + // Loop over patterns produced by the runtime + const auto &patterns = this->m_runtime.getPatterns(); + for (const auto &pattern : patterns) { + // Skip hidden patterns + if (pattern->getVisibility() == pl::ptrn::Visibility::Hidden) + continue; + + // Set up the editing function if a write formatter is available + auto formatWriteFunction = pattern->getWriteFormatterFunction(); + std::optional editingFunction; + if (!formatWriteFunction.empty()) { + editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector { + try { + pattern->setValue(value); + } catch (const pl::core::err::EvaluatorError::Exception &error) { + log::error("Failed to set value of pattern '{}' to '{}': {}", pattern->getDisplayName(), value, error.what()); + return { }; + } + + return { }; + }; + } + + try { + // Set up the display function using the pattern's formatter + auto displayFunction = [value = pattern->getFormattedValue()]() { + ImGui::TextUnformatted(value.c_str()); + return value; + }; + + // Insert the inspector into the list + this->m_workData.push_back({ + pattern->getDisplayName(), + displayFunction, + editingFunction, + false + }); + + AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.data_inspector.name"); + } catch (const pl::core::err::EvaluatorError::Exception &error) { + log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what()); + } + } + } else { + const auto& error = this->m_runtime.getError(); + + log::error("Failed to execute custom inspector file '{}'!", wolv::util::toUTF8String(filePath.path())); + if (error.has_value()) + log::error("{}", error.value().what()); + } + } + } + } + } + + this->m_dataValid = true; + + }); + } + void ViewDataInspector::drawContent() { if (this->m_dataValid && !this->m_updateTask.isRunning()) { this->m_dataValid = false; @@ -51,119 +195,7 @@ namespace hex::plugin::builtin { if (this->m_shouldInvalidate && !this->m_updateTask.isRunning()) { this->m_shouldInvalidate = false; - this->m_updateTask = TaskManager::createBackgroundTask("Update Inspector", - [this, validBytes = this->m_validBytes, startAddress = this->m_startAddress, endian = this->m_endian, invert = this->m_invert, numberDisplayStyle = this->m_numberDisplayStyle](auto &) { - this->m_workData.clear(); - - if (this->m_selectedProvider == nullptr) - return; - - // Decode bytes using registered inspectors - for (auto &entry : ContentRegistry::DataInspector::impl::getEntries()) { - if (validBytes < entry.requiredSize) - continue; - - std::vector buffer(validBytes > entry.maxSize ? entry.maxSize : validBytes); - this->m_selectedProvider->read(startAddress, buffer.data(), buffer.size()); - - if (invert) { - for (auto &byte : buffer) - byte ^= 0xFF; - } - - this->m_workData.push_back({ - entry.unlocalizedName, - entry.generatorFunction(buffer, endian, numberDisplayStyle), - entry.editingFunction, - false - }); - } - - - // Decode bytes using custom inspectors defined using the pattern language - const std::map inVariables = { - { "numberDisplayStyle", u128(numberDisplayStyle) } - }; - - ContentRegistry::PatternLanguage::configureRuntime(this->m_runtime, this->m_selectedProvider); - - this->m_runtime.setDataSource(this->m_selectedProvider->getBaseAddress(), this->m_selectedProvider->getActualSize(), - [this, invert](u64 offset, u8 *buffer, size_t size) { - this->m_selectedProvider->read(offset, buffer, size); - - if (invert) { - for (size_t i = 0; i < size; i++) - buffer[i] ^= 0xFF; - } - }); - - this->m_runtime.setDangerousFunctionCallHandler([]{ return false; }); - this->m_runtime.setDefaultEndian(endian); - this->m_runtime.setStartAddress(startAddress); - - for (const auto &folderPath : fs::getDefaultPaths(fs::ImHexPath::Inspectors)) { - for (const auto &filePath : std::fs::recursive_directory_iterator(folderPath)) { - if (!filePath.exists() || !filePath.is_regular_file() || filePath.path().extension() != ".hexpat") - continue; - - wolv::io::File file(filePath, wolv::io::File::Mode::Read); - if (file.isValid()) { - auto inspectorCode = file.readString(); - - if (!inspectorCode.empty()) { - if (this->m_runtime.executeString(inspectorCode, {}, inVariables, true)) { - const auto &patterns = this->m_runtime.getPatterns(); - - for (const auto &pattern : patterns) { - if (pattern->getVisibility() == pl::ptrn::Visibility::Hidden) - continue; - - auto formatWriteFunction = pattern->getWriteFormatterFunction(); - std::optional editingFunction; - if (!formatWriteFunction.empty()) { - editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector { - try { - pattern->setValue(value); - } catch (const pl::core::err::EvaluatorError::Exception &error) { - log::error("Failed to set value of pattern '{}' to '{}': {}", pattern->getDisplayName(), value, error.what()); - return { }; - } - - return { }; - }; - } - - try { - this->m_workData.push_back({ - pattern->getDisplayName(), - [value = pattern->getFormattedValue()]() { - ImGui::TextUnformatted(value.c_str()); - return value; - }, - editingFunction, - false - }); - - AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.data_inspector.name"); - } catch (const pl::core::err::EvaluatorError::Exception &error) { - log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what()); - } - } - } else { - const auto& error = this->m_runtime.getError(); - - log::error("Failed to execute custom inspector file '{}'!", wolv::util::toUTF8String(filePath.path())); - if (error.has_value()) - log::error("{}", error.value().what()); - } - } - } - } - } - - this->m_dataValid = true; - - }); + this->updateInspectorRows(); } if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_inspector.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { @@ -175,41 +207,60 @@ namespace hex::plugin::builtin { ImGui::TableHeadersRow(); - u32 i = 0; + int inspectorRowId = 1; for (auto &[unlocalizedName, displayFunction, editingFunction, editing] : this->m_cachedData) { - ImGui::PushID(i); + ImGui::PushID(inspectorRowId); ImGui::TableNextRow(); ImGui::TableNextColumn(); + + // Render inspector row name ImGui::TextUnformatted(LangEntry(unlocalizedName)); ImGui::TableNextColumn(); if (!editing) { + // Handle regular display case + + // Render inspector row value const auto ©Value = displayFunction(); + ImGui::SameLine(); + // Handle copying the value to the clipboard when clicking the row if (ImGui::Selectable("##InspectorLine", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) { ImGui::SetClipboardText(copyValue.c_str()); } + // Enter editing mode when double-clicking the row if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && editingFunction.has_value()) { editing = true; this->m_editingValue = copyValue; } } else { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::SetNextItemWidth(ImGui::GetColumnWidth()); - ImGui::SetKeyboardFocusHere(); - if (ImGui::InputText("##InspectorLineEditing", this->m_editingValue, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { - auto bytes = (*editingFunction)(this->m_editingValue, this->m_endian); + // Handle editing mode + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::SetNextItemWidth(-1); + ImGui::SetKeyboardFocusHere(); + + // Draw input text box + if (ImGui::InputText("##InspectorLineEditing", this->m_editingValue, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { + // Turn the entered value into bytes + auto bytes = editingFunction.value()(this->m_editingValue, this->m_endian); + + // Write those bytes to the selected provider at the current address this->m_selectedProvider->write(this->m_startAddress, bytes.data(), bytes.size()); + + // Disable editing mode this->m_editingValue.clear(); editing = false; + + // Reload all inspector rows this->m_shouldInvalidate = true; } ImGui::PopStyleVar(); + // Disable editing mode when clicking outside the input text box if (!ImGui::IsItemHovered() && ImGui::IsAnyMouseDown()) { this->m_editingValue.clear(); editing = false; @@ -217,7 +268,7 @@ namespace hex::plugin::builtin { } ImGui::PopID(); - i++; + inspectorRowId++; } ImGui::EndTable(); @@ -227,6 +278,9 @@ namespace hex::plugin::builtin { ImGui::Separator(); ImGui::NewLine(); + // Draw inspector settings + + // Draw endian setting { int selection = [this] { switch (this->m_endian) { @@ -249,6 +303,7 @@ namespace hex::plugin::builtin { } } + // Draw radix setting { int selection = [this] { switch (this->m_numberDisplayStyle) { @@ -272,6 +327,7 @@ namespace hex::plugin::builtin { } } + // Draw invert setting { int selection = this->m_invert ? 1 : 0; std::array options = { "hex.builtin.common.no"_lang, "hex.builtin.common.yes"_lang }; @@ -283,6 +339,7 @@ namespace hex::plugin::builtin { } } } else { + // Draw a message when no bytes are selected std::string text = "hex.builtin.view.data_inspector.no_data"_lang; auto textSize = ImGui::CalcTextSize(text.c_str()); auto availableSpace = ImGui::GetContentRegionAvail(); diff --git a/plugins/builtin/source/content/views/view_diff.cpp b/plugins/builtin/source/content/views/view_diff.cpp index 1479ca295..dfb647d91 100644 --- a/plugins/builtin/source/content/views/view_diff.cpp +++ b/plugins/builtin/source/content/views/view_diff.cpp @@ -3,7 +3,6 @@ #include #include -#include namespace hex::plugin::builtin { @@ -17,6 +16,7 @@ namespace hex::plugin::builtin { ViewDiff::ViewDiff() : View("hex.builtin.view.diff.name") { + // Clear the selected diff providers when a provider is closed EventManager::subscribe(this, [this](prv::Provider *) { for (u8 i = 0; i < 2; i++) { this->m_columns[i].provider = -1; @@ -26,34 +26,9 @@ namespace hex::plugin::builtin { this->m_diffs.clear(); }); - auto compareFunction = [this](int otherIndex) { - return [this, otherIndex](u64 address, const u8 *data, size_t) -> std::optional { - const auto &providers = ImHexApi::Provider::getProviders(); - auto otherId = this->m_columns[otherIndex].provider; - if (otherId < 0 || size_t(otherId) >= providers.size()) - return std::nullopt; - - auto &otherProvider = providers[otherId]; - - if (address > otherProvider->getActualSize()) { - if (otherIndex == 1) - return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarGreen)); - else - return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarRed)); - } - - u8 otherByte = 0x00; - otherProvider->read(address, &otherByte, 1); - - if (otherByte != *data) - return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow)); - - return std::nullopt; - }; - }; - - this->m_columns[0].hexEditor.setBackgroundHighlightCallback(compareFunction(1)); - this->m_columns[1].hexEditor.setBackgroundHighlightCallback(compareFunction(0)); + // Set the background highlight callbacks for the two hex editor columns + this->m_columns[0].hexEditor.setBackgroundHighlightCallback(this->createCompareFunction(1)); + this->m_columns[1].hexEditor.setBackgroundHighlightCallback(this->createCompareFunction(0)); } ViewDiff::~ViewDiff() { @@ -66,10 +41,12 @@ namespace hex::plugin::builtin { bool scrolled = false; ImGui::PushID(&column); + // Draw the hex editor float prevScroll = column.hexEditor.getScrollPosition(); column.hexEditor.draw(height); float currScroll = column.hexEditor.getScrollPosition(); + // Check if the user scrolled the hex editor if (prevScroll != currScroll) { scrolled = true; column.scrollLock = 5; @@ -88,10 +65,12 @@ namespace hex::plugin::builtin { auto &providers = ImHexApi::Provider::getProviders(); auto &providerIndex = column.provider; + // Get the name of the currently selected provider std::string preview; if (ImHexApi::Provider::isValid() && providerIndex >= 0) preview = providers[providerIndex]->getName(); + // Draw combobox with all available providers ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("", preview.c_str())) { @@ -114,6 +93,94 @@ namespace hex::plugin::builtin { } + std::function(u64, const u8*, size_t)> ViewDiff::createCompareFunction(size_t otherIndex) { + // Create a function that will handle highlighting the differences between the two providers + // This is a stupidly simple diffing implementation. It will highlight bytes that are different in yellow + // and if one provider is larger than the other it will highlight the extra bytes in green or red depending on which provider is larger + // TODO: Use an actual binary diffing algorithm that searches for the longest common subsequences + + return [this, otherIndex](u64 address, const u8 *data, size_t) -> std::optional { + const auto &providers = ImHexApi::Provider::getProviders(); + auto otherId = this->m_columns[otherIndex].provider; + + // Check if the other provider is valid + if (otherId < 0 || size_t(otherId) >= providers.size()) + return std::nullopt; + + auto &otherProvider = providers[otherId]; + + // Handle the case where one provider is larger than the other one + if (address > otherProvider->getActualSize()) { + if (otherIndex == 1) + return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarGreen)); + else + return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarRed)); + } + + // Read the current byte from the other provider + u8 otherByte = 0x00; + otherProvider->read(address, &otherByte, 1); + + // Compare the two bytes, highlight both in yellow if they are different + if (otherByte != *data) + return getDiffColor(ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow)); + + // No difference + return std::nullopt; + }; + } + + void ViewDiff::analyze(prv::Provider *providerA, prv::Provider *providerB) { + auto commonSize = std::min(providerA->getActualSize(), providerB->getActualSize()); + this->m_diffTask = TaskManager::createTask("Diffing...", commonSize, [this, providerA, providerB](Task &task) { + std::vector differences; + + // Set up readers for both providers + auto readerA = prv::ProviderReader(providerA); + auto readerB = prv::ProviderReader(providerB); + + // Iterate over both providers and compare the bytes + for (auto itA = readerA.begin(), itB = readerB.begin(); itA < readerA.end() && itB < readerB.end(); itA++, itB++) { + // Stop comparing if the diff task was canceled + if (task.wasInterrupted()) + break; + + // If the bytes are different, find the end of the difference + if (*itA != *itB) { + u64 start = itA.getAddress(); + size_t end = 0; + + while (itA != readerA.end() && itB != readerB.end() && *itA != *itB) { + itA++; + itB++; + end++; + } + + // Add the difference to the list + differences.push_back(Diff { Region{ start, end }, ViewDiff::DifferenceType::Modified }); + } + + // Update the progress bar + task.update(itA.getAddress()); + } + + // If one provider is larger than the other, add the extra bytes to the list + if (providerA->getActualSize() != providerB->getActualSize()) { + auto endA = providerA->getActualSize() + 1; + auto endB = providerB->getActualSize() + 1; + + if (endA > endB) + differences.push_back(Diff { Region{ endB, endA - endB }, ViewDiff::DifferenceType::Added }); + else + differences.push_back(Diff { Region{ endA, endB - endA }, ViewDiff::DifferenceType::Removed }); + } + + // Move the calculated differences over so they can be displayed + this->m_diffs = std::move(differences); + this->m_analyzed = true; + }); + } + void ViewDiff::drawContent() { if (ImGui::Begin(View::toWindowName("hex.builtin.view.diff.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { @@ -125,6 +192,7 @@ namespace hex::plugin::builtin { if (a.scrollLock > 0) a.scrollLock--; if (b.scrollLock > 0) b.scrollLock--; + // Change the hex editor providers if the user selected a new provider { const auto &providers = ImHexApi::Provider::getProviders(); if (a.provider >= 0 && size_t(a.provider) < providers.size()) @@ -138,89 +206,61 @@ namespace hex::plugin::builtin { b.hexEditor.setProvider(nullptr); } + // Analyze the providers if they are valid and the user selected a new provider if (!this->m_analyzed && a.provider != -1 && b.provider != -1 && !this->m_diffTask.isRunning()) { const auto &providers = ImHexApi::Provider::getProviders(); auto providerA = providers[a.provider]; auto providerB = providers[b.provider]; - auto commonSize = std::min(providerA->getActualSize(), providerB->getActualSize()); - this->m_diffTask = TaskManager::createTask("Diffing...", commonSize, [this, providerA, providerB](Task &task) { - std::vector differences; - - auto readerA = prv::ProviderReader(providerA); - auto readerB = prv::ProviderReader(providerB); - - for (auto itA = readerA.begin(), itB = readerB.begin(); itA < readerA.end() && itB < readerB.end(); itA++, itB++) { - if (task.wasInterrupted()) - break; - - if (*itA != *itB) { - u64 start = itA.getAddress(); - size_t end = 0; - - while (itA != readerA.end() && itB != readerB.end() && *itA != *itB) { - itA++; - itB++; - end++; - } - - - differences.push_back(Diff { Region{ start, end }, ViewDiff::DifferenceType::Modified }); - } - - task.update(itA.getAddress()); - } - - if (providerA->getActualSize() != providerB->getActualSize()) { - auto endA = providerA->getActualSize() + 1; - auto endB = providerB->getActualSize() + 1; - - if (endA > endB) - differences.push_back(Diff { Region{ endB, endA - endB }, ViewDiff::DifferenceType::Added }); - else - differences.push_back(Diff { Region{ endA, endB - endA }, ViewDiff::DifferenceType::Removed }); - } - - this->m_diffs = std::move(differences); - this->m_analyzed = true; - }); + this->analyze(providerA, providerB); } const auto height = ImGui::GetContentRegionAvail().y; + // Draw the two hex editor columns side by side if (ImGui::BeginTable("##binary_diff", 2, ImGuiTableFlags_None, ImVec2(0, height - 250_scaled))) { ImGui::TableSetupColumn("hex.builtin.view.diff.provider_a"_lang); ImGui::TableSetupColumn("hex.builtin.view.diff.provider_b"_lang); ImGui::TableHeadersRow(); ImGui::BeginDisabled(this->m_diffTask.isRunning()); - ImGui::TableNextColumn(); - if (drawProviderSelector(a)) this->m_analyzed = false; + { + // Draw first provider selector + ImGui::TableNextColumn(); + if (drawProviderSelector(a)) this->m_analyzed = false; - ImGui::TableNextColumn(); - if (drawProviderSelector(b)) this->m_analyzed = false; + // Draw second provider selector + ImGui::TableNextColumn(); + if (drawProviderSelector(b)) this->m_analyzed = false; + } ImGui::EndDisabled(); ImGui::TableNextRow(); + // Draw first hex editor column ImGui::TableNextColumn(); bool scrollB = drawDiffColumn(a, height - 250_scaled); + // Draw second hex editor column ImGui::TableNextColumn(); bool scrollA = drawDiffColumn(b, height - 250_scaled); - if (scrollA && a.scrollLock == 0) { - a.hexEditor.setScrollPosition(b.hexEditor.getScrollPosition()); - a.hexEditor.forceUpdateScrollPosition(); - } - if (scrollB && b.scrollLock == 0) { - b.hexEditor.setScrollPosition(a.hexEditor.getScrollPosition()); - b.hexEditor.forceUpdateScrollPosition(); + // Sync the scroll positions of the hex editors + { + if (scrollA && a.scrollLock == 0) { + a.hexEditor.setScrollPosition(b.hexEditor.getScrollPosition()); + a.hexEditor.forceUpdateScrollPosition(); + } + if (scrollB && b.scrollLock == 0) { + b.hexEditor.setScrollPosition(a.hexEditor.getScrollPosition()); + b.hexEditor.forceUpdateScrollPosition(); + } } ImGui::EndTable(); } + // Draw the differences table if (ImGui::BeginTable("##differences", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable, ImVec2(0, 200_scaled))) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.common.begin"_lang); @@ -228,6 +268,7 @@ namespace hex::plugin::builtin { ImGui::TableSetupColumn("hex.builtin.common.type"_lang); ImGui::TableHeadersRow(); + // Draw the differences if the providers have been analyzed if (this->m_analyzed) { ImGuiListClipper clipper; clipper.Begin(int(this->m_diffs.size())); @@ -236,6 +277,7 @@ namespace hex::plugin::builtin { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { ImGui::TableNextRow(); + // Prevent the list from trying to access non-existing diffs if (size_t(i) >= this->m_diffs.size()) break; @@ -243,6 +285,9 @@ namespace hex::plugin::builtin { const auto &diff = this->m_diffs[i]; + // Draw a clickable row for each difference that will select the difference in both hex editors + + // Draw start address ImGui::TableNextColumn(); if (ImGui::Selectable(hex::format("0x{:02X}", diff.region.getStartAddress()).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { a.hexEditor.setSelection(diff.region); @@ -251,9 +296,11 @@ namespace hex::plugin::builtin { b.hexEditor.jumpToSelection(); } + // Draw end address ImGui::TableNextColumn(); ImGui::TextUnformatted(hex::format("0x{:02X}", diff.region.getEndAddress()).c_str()); + // Draw difference type ImGui::TableNextColumn(); switch (diff.type) { case DifferenceType::Modified: diff --git a/plugins/builtin/source/content/views/view_disassembler.cpp b/plugins/builtin/source/content/views/view_disassembler.cpp index 1430b6ca8..5d5b36a5d 100644 --- a/plugins/builtin/source/content/views/view_disassembler.cpp +++ b/plugins/builtin/source/content/views/view_disassembler.cpp @@ -31,26 +31,33 @@ namespace hex::plugin::builtin { cs_mode mode = this->m_mode; + // Create a capstone disassembler instance if (cs_open(Disassembler::toCapstoneArchitecture(this->m_architecture), mode, &capstoneHandle) == CS_ERR_OK) { + // Tell capstone to skip data bytes cs_option(capstoneHandle, CS_OPT_SKIPDATA, CS_OPT_ON); auto provider = ImHexApi::Provider::get(); std::vector buffer(2048, 0x00); size_t size = this->m_codeRegion.getSize(); + // Read the data in chunks and disassemble it for (u64 address = 0; address < size; address += 2048) { task.update(address); + // Read a chunk of data size_t bufferSize = std::min(u64(2048), (size - address)); provider->read(this->m_codeRegion.getStartAddress() + address, buffer.data(), bufferSize); + // Ask capstone to disassemble the data size_t instructionCount = cs_disasm(capstoneHandle, buffer.data(), bufferSize, this->m_baseAddress + address, 0, &instructions); if (instructionCount == 0) break; + // Reserve enough space for the disassembly this->m_disassembly.reserve(this->m_disassembly.size() + instructionCount); + // Convert the capstone instructions to our disassembly format u64 usedBytes = 0; for (u32 i = 0; i < instructionCount; i++) { const auto &instr = instructions[i]; @@ -70,9 +77,12 @@ namespace hex::plugin::builtin { usedBytes += instr.size; } + // If capstone couldn't disassemble all bytes in the buffer, we might have cut off an instruction + // Adjust the address,so it's being disassembled when we read the next chunk if (instructionCount < bufferSize) address -= (bufferSize - usedBytes); + // Clean up the capstone instructions cs_free(instructions, instructionCount); } @@ -90,276 +100,285 @@ namespace hex::plugin::builtin { ImGui::TextUnformatted("hex.builtin.view.disassembler.position"_lang); ImGui::Separator(); + // Draw base address input ImGui::InputHexadecimal("hex.builtin.view.disassembler.base"_lang, &this->m_baseAddress, ImGuiInputTextFlags_CharsHexadecimal); + // Draw region selection picker ui::regionSelectionPicker(&this->m_codeRegion, provider, &this->m_range); - ImGui::Header("hex.builtin.common.settings"_lang); + // Draw settings + { + ImGui::Header("hex.builtin.common.settings"_lang); - if (ImGui::Combo("hex.builtin.view.disassembler.arch"_lang, reinterpret_cast(&this->m_architecture), Disassembler::ArchitectureNames.data(), Disassembler::getArchitectureSupportedCount())) - this->m_mode = cs_mode(0); + // Draw architecture selector + if (ImGui::Combo("hex.builtin.view.disassembler.arch"_lang, reinterpret_cast(&this->m_architecture), Disassembler::ArchitectureNames.data(), Disassembler::getArchitectureSupportedCount())) + this->m_mode = cs_mode(0); + // Draw sub-settings for each architecture + if (ImGui::BeginChild("modes", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6), true, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::BeginChild("modes", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6), true, ImGuiWindowFlags_AlwaysAutoResize)) { + // Draw endian radio buttons. This setting is available for all architectures + static int littleEndian = true; + ImGui::RadioButton("hex.builtin.common.little_endian"_lang, &littleEndian, true); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.common.big_endian"_lang, &littleEndian, false); - static int littleEndian = true; - ImGui::RadioButton("hex.builtin.common.little_endian"_lang, &littleEndian, true); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.common.big_endian"_lang, &littleEndian, false); + ImGui::NewLine(); - ImGui::NewLine(); + // Draw architecture specific settings + switch (this->m_architecture) { + case Architecture::ARM: + { + static int mode = CS_MODE_ARM; + ImGui::RadioButton("hex.builtin.view.disassembler.arm.arm"_lang, &mode, CS_MODE_ARM); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.arm.thumb"_lang, &mode, CS_MODE_THUMB); - switch (this->m_architecture) { - case Architecture::ARM: - { - static int mode = CS_MODE_ARM; - ImGui::RadioButton("hex.builtin.view.disassembler.arm.arm"_lang, &mode, CS_MODE_ARM); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.arm.thumb"_lang, &mode, CS_MODE_THUMB); + static int extraMode = 0; + ImGui::RadioButton("hex.builtin.view.disassembler.arm.default"_lang, &extraMode, 0); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.arm.cortex_m"_lang, &extraMode, CS_MODE_MCLASS); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.arm.armv8"_lang, &extraMode, CS_MODE_V8); - static int extraMode = 0; - ImGui::RadioButton("hex.builtin.view.disassembler.arm.default"_lang, &extraMode, 0); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.arm.cortex_m"_lang, &extraMode, CS_MODE_MCLASS); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.arm.armv8"_lang, &extraMode, CS_MODE_V8); - - this->m_mode = cs_mode(mode | extraMode); - } - break; - case Architecture::MIPS: - { - static int mode = CS_MODE_MIPS32; - ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips32"_lang, &mode, CS_MODE_MIPS32); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips64"_lang, &mode, CS_MODE_MIPS64); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips32R6"_lang, &mode, CS_MODE_MIPS32R6); - - ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips2"_lang, &mode, CS_MODE_MIPS2); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips3"_lang, &mode, CS_MODE_MIPS3); - - static bool microMode; - ImGui::Checkbox("hex.builtin.view.disassembler.mips.micro"_lang, µMode); - - this->m_mode = cs_mode(mode | (microMode ? CS_MODE_MICRO : cs_mode(0))); - } - break; - case Architecture::X86: - { - static int mode = CS_MODE_32; - ImGui::RadioButton("hex.builtin.view.disassembler.16bit"_lang, &mode, CS_MODE_16); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_32); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_64); - - this->m_mode = cs_mode(mode); - } - break; - case Architecture::PPC: - { - static int mode = CS_MODE_32; - ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_32); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_64); - - static bool qpx = false; - ImGui::Checkbox("hex.builtin.view.disassembler.ppc.qpx"_lang, &qpx); - - #if CS_API_MAJOR >= 5 - static bool spe = false; - ImGui::Checkbox("hex.builtin.view.disassembler.ppc.spe"_lang, &spe); - static bool booke = false; - ImGui::Checkbox("hex.builtin.view.disassembler.ppc.booke"_lang, &booke); - - this->m_mode = cs_mode(mode | (qpx ? CS_MODE_QPX : cs_mode(0)) | (spe ? CS_MODE_SPE : cs_mode(0)) | (booke ? CS_MODE_BOOKE : cs_mode(0))); - #else - this->m_mode = cs_mode(mode | (qpx ? CS_MODE_QPX : cs_mode(0))); - #endif - } - break; - case Architecture::SPARC: - { - static bool v9Mode = false; - ImGui::Checkbox("hex.builtin.view.disassembler.sparc.v9"_lang, &v9Mode); - - this->m_mode = cs_mode(v9Mode ? CS_MODE_V9 : cs_mode(0)); - } - break; - #if CS_API_MAJOR >= 5 - case Architecture::RISCV: - { - static int mode = CS_MODE_RISCV32; - ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_RISCV32); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_RISCV64); - - static bool compressed = false; - ImGui::Checkbox("hex.builtin.view.disassembler.riscv.compressed"_lang, &compressed); - - this->m_mode = cs_mode(mode | (compressed ? CS_MODE_RISCVC : cs_mode(0))); - } - break; - #endif - case Architecture::M68K: - { - static int selectedMode = 0; - - std::pair modes[] = { - {"hex.builtin.view.disassembler.m68k.000"_lang, CS_MODE_M68K_000}, - { "hex.builtin.view.disassembler.m68k.010"_lang, CS_MODE_M68K_010}, - { "hex.builtin.view.disassembler.m68k.020"_lang, CS_MODE_M68K_020}, - { "hex.builtin.view.disassembler.m68k.030"_lang, CS_MODE_M68K_030}, - { "hex.builtin.view.disassembler.m68k.040"_lang, CS_MODE_M68K_040}, - { "hex.builtin.view.disassembler.m68k.060"_lang, CS_MODE_M68K_060}, - }; - - if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { - for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { - if (ImGui::Selectable(modes[i].first)) - selectedMode = i; - } - ImGui::EndCombo(); + this->m_mode = cs_mode(mode | extraMode); } + break; + case Architecture::MIPS: + { + static int mode = CS_MODE_MIPS32; + ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips32"_lang, &mode, CS_MODE_MIPS32); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips64"_lang, &mode, CS_MODE_MIPS64); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips32R6"_lang, &mode, CS_MODE_MIPS32R6); - this->m_mode = cs_mode(modes[selectedMode].second); - } - break; - case Architecture::M680X: - { - static int selectedMode = 0; + ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips2"_lang, &mode, CS_MODE_MIPS2); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.mips.mips3"_lang, &mode, CS_MODE_MIPS3); - std::pair modes[] = { - {"hex.builtin.view.disassembler.m680x.6301"_lang, CS_MODE_M680X_6301 }, - { "hex.builtin.view.disassembler.m680x.6309"_lang, CS_MODE_M680X_6309 }, - { "hex.builtin.view.disassembler.m680x.6800"_lang, CS_MODE_M680X_6800 }, - { "hex.builtin.view.disassembler.m680x.6801"_lang, CS_MODE_M680X_6801 }, - { "hex.builtin.view.disassembler.m680x.6805"_lang, CS_MODE_M680X_6805 }, - { "hex.builtin.view.disassembler.m680x.6808"_lang, CS_MODE_M680X_6808 }, - { "hex.builtin.view.disassembler.m680x.6809"_lang, CS_MODE_M680X_6809 }, - { "hex.builtin.view.disassembler.m680x.6811"_lang, CS_MODE_M680X_6811 }, - { "hex.builtin.view.disassembler.m680x.cpu12"_lang, CS_MODE_M680X_CPU12}, - { "hex.builtin.view.disassembler.m680x.hcs08"_lang, CS_MODE_M680X_HCS08}, - }; + static bool microMode; + ImGui::Checkbox("hex.builtin.view.disassembler.mips.micro"_lang, µMode); - if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { - for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { - if (ImGui::Selectable(modes[i].first)) - selectedMode = i; - } - ImGui::EndCombo(); + this->m_mode = cs_mode(mode | (microMode ? CS_MODE_MICRO : cs_mode(0))); } + break; + case Architecture::X86: + { + static int mode = CS_MODE_32; + ImGui::RadioButton("hex.builtin.view.disassembler.16bit"_lang, &mode, CS_MODE_16); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_32); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_64); - this->m_mode = cs_mode(modes[selectedMode].second); - } - break; - #if CS_API_MAJOR >= 5 - case Architecture::MOS65XX: - { - static int selectedMode = 0; - - std::pair modes[] = { - {"hex.builtin.view.disassembler.mos65xx.6502"_lang, CS_MODE_MOS65XX_6502 }, - { "hex.builtin.view.disassembler.mos65xx.65c02"_lang, CS_MODE_MOS65XX_65C02 }, - { "hex.builtin.view.disassembler.mos65xx.w65c02"_lang, CS_MODE_MOS65XX_W65C02 }, - { "hex.builtin.view.disassembler.mos65xx.65816"_lang, CS_MODE_MOS65XX_65816 }, - { "hex.builtin.view.disassembler.mos65xx.65816_long_m"_lang, CS_MODE_MOS65XX_65816_LONG_M }, - { "hex.builtin.view.disassembler.mos65xx.65816_long_x"_lang, CS_MODE_MOS65XX_65816_LONG_X }, - { "hex.builtin.view.disassembler.mos65xx.65816_long_mx"_lang, CS_MODE_MOS65XX_65816_LONG_MX}, - }; - - if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { - for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { - if (ImGui::Selectable(modes[i].first)) - selectedMode = i; - } - ImGui::EndCombo(); + this->m_mode = cs_mode(mode); } + break; + case Architecture::PPC: + { + static int mode = CS_MODE_32; + ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_32); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_64); - this->m_mode = cs_mode(modes[selectedMode].second); - } - break; - #endif - #if CS_API_MAJOR >= 5 - case Architecture::BPF: - { - static int mode = CS_MODE_BPF_CLASSIC; - ImGui::RadioButton("hex.builtin.view.disassembler.bpf.classic"_lang, &mode, CS_MODE_BPF_CLASSIC); - ImGui::SameLine(); - ImGui::RadioButton("hex.builtin.view.disassembler.bpf.extended"_lang, &mode, CS_MODE_BPF_EXTENDED); + static bool qpx = false; + ImGui::Checkbox("hex.builtin.view.disassembler.ppc.qpx"_lang, &qpx); - this->m_mode = cs_mode(mode); - } - break; - case Architecture::SH: - { - static u32 selectionMode = 0; - static bool fpu = false; - static bool dsp = false; + #if CS_API_MAJOR >= 5 + static bool spe = false; + ImGui::Checkbox("hex.builtin.view.disassembler.ppc.spe"_lang, &spe); + static bool booke = false; + ImGui::Checkbox("hex.builtin.view.disassembler.ppc.booke"_lang, &booke); - std::pair modes[] = { - { "hex.builtin.view.disassembler.sh.sh2"_lang, CS_MODE_SH2 }, - { "hex.builtin.view.disassembler.sh.sh2a"_lang, CS_MODE_SH2A }, - { "hex.builtin.view.disassembler.sh.sh3"_lang, CS_MODE_SH3 }, - { "hex.builtin.view.disassembler.sh.sh4"_lang, CS_MODE_SH4 }, - { "hex.builtin.view.disassembler.sh.sh4a"_lang, CS_MODE_SH4A }, - }; - - if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectionMode].first)) { - for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { - if (ImGui::Selectable(modes[i].first)) - selectionMode = i; - } - ImGui::EndCombo(); + this->m_mode = cs_mode(mode | (qpx ? CS_MODE_QPX : cs_mode(0)) | (spe ? CS_MODE_SPE : cs_mode(0)) | (booke ? CS_MODE_BOOKE : cs_mode(0))); + #else + this->m_mode = cs_mode(mode | (qpx ? CS_MODE_QPX : cs_mode(0))); + #endif } + break; + case Architecture::SPARC: + { + static bool v9Mode = false; + ImGui::Checkbox("hex.builtin.view.disassembler.sparc.v9"_lang, &v9Mode); - ImGui::Checkbox("hex.builtin.view.disassembler.sh.fpu"_lang, &fpu); - ImGui::SameLine(); - ImGui::Checkbox("hex.builtin.view.disassembler.sh.dsp"_lang, &dsp); - - this->m_mode = cs_mode(modes[selectionMode].second | (fpu ? CS_MODE_SHFPU : cs_mode(0)) | (dsp ? CS_MODE_SHDSP : cs_mode(0))); - } - break; - case Architecture::TRICORE: - { - static u32 selectionMode = 0; - - std::pair modes[] = { - { "hex.builtin.view.disassembler.tricore.110"_lang, CS_MODE_TRICORE_110 }, - { "hex.builtin.view.disassembler.tricore.120"_lang, CS_MODE_TRICORE_120 }, - { "hex.builtin.view.disassembler.tricore.130"_lang, CS_MODE_TRICORE_130 }, - { "hex.builtin.view.disassembler.tricore.131"_lang, CS_MODE_TRICORE_131 }, - { "hex.builtin.view.disassembler.tricore.160"_lang, CS_MODE_TRICORE_160 }, - { "hex.builtin.view.disassembler.tricore.161"_lang, CS_MODE_TRICORE_161 }, - { "hex.builtin.view.disassembler.tricore.162"_lang, CS_MODE_TRICORE_162 }, - }; - - if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectionMode].first)) { - for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { - if (ImGui::Selectable(modes[i].first)) - selectionMode = i; - } - ImGui::EndCombo(); + this->m_mode = cs_mode(v9Mode ? CS_MODE_V9 : cs_mode(0)); } + break; + #if CS_API_MAJOR >= 5 + case Architecture::RISCV: + { + static int mode = CS_MODE_RISCV32; + ImGui::RadioButton("hex.builtin.view.disassembler.32bit"_lang, &mode, CS_MODE_RISCV32); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.64bit"_lang, &mode, CS_MODE_RISCV64); - this->m_mode = cs_mode(modes[selectionMode].second); - } - break; - case Architecture::WASM: - #endif - case Architecture::EVM: - case Architecture::TMS320C64X: - case Architecture::ARM64: - case Architecture::SYSZ: - case Architecture::XCORE: - this->m_mode = cs_mode(0); - break; + static bool compressed = false; + ImGui::Checkbox("hex.builtin.view.disassembler.riscv.compressed"_lang, &compressed); + + this->m_mode = cs_mode(mode | (compressed ? CS_MODE_RISCVC : cs_mode(0))); + } + break; + #endif + case Architecture::M68K: + { + static int selectedMode = 0; + + std::pair modes[] = { + {"hex.builtin.view.disassembler.m68k.000"_lang, CS_MODE_M68K_000}, + { "hex.builtin.view.disassembler.m68k.010"_lang, CS_MODE_M68K_010}, + { "hex.builtin.view.disassembler.m68k.020"_lang, CS_MODE_M68K_020}, + { "hex.builtin.view.disassembler.m68k.030"_lang, CS_MODE_M68K_030}, + { "hex.builtin.view.disassembler.m68k.040"_lang, CS_MODE_M68K_040}, + { "hex.builtin.view.disassembler.m68k.060"_lang, CS_MODE_M68K_060}, + }; + + if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { + for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { + if (ImGui::Selectable(modes[i].first)) + selectedMode = i; + } + ImGui::EndCombo(); + } + + this->m_mode = cs_mode(modes[selectedMode].second); + } + break; + case Architecture::M680X: + { + static int selectedMode = 0; + + std::pair modes[] = { + {"hex.builtin.view.disassembler.m680x.6301"_lang, CS_MODE_M680X_6301 }, + { "hex.builtin.view.disassembler.m680x.6309"_lang, CS_MODE_M680X_6309 }, + { "hex.builtin.view.disassembler.m680x.6800"_lang, CS_MODE_M680X_6800 }, + { "hex.builtin.view.disassembler.m680x.6801"_lang, CS_MODE_M680X_6801 }, + { "hex.builtin.view.disassembler.m680x.6805"_lang, CS_MODE_M680X_6805 }, + { "hex.builtin.view.disassembler.m680x.6808"_lang, CS_MODE_M680X_6808 }, + { "hex.builtin.view.disassembler.m680x.6809"_lang, CS_MODE_M680X_6809 }, + { "hex.builtin.view.disassembler.m680x.6811"_lang, CS_MODE_M680X_6811 }, + { "hex.builtin.view.disassembler.m680x.cpu12"_lang, CS_MODE_M680X_CPU12}, + { "hex.builtin.view.disassembler.m680x.hcs08"_lang, CS_MODE_M680X_HCS08}, + }; + + if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { + for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { + if (ImGui::Selectable(modes[i].first)) + selectedMode = i; + } + ImGui::EndCombo(); + } + + this->m_mode = cs_mode(modes[selectedMode].second); + } + break; + #if CS_API_MAJOR >= 5 + case Architecture::MOS65XX: + { + static int selectedMode = 0; + + std::pair modes[] = { + {"hex.builtin.view.disassembler.mos65xx.6502"_lang, CS_MODE_MOS65XX_6502 }, + { "hex.builtin.view.disassembler.mos65xx.65c02"_lang, CS_MODE_MOS65XX_65C02 }, + { "hex.builtin.view.disassembler.mos65xx.w65c02"_lang, CS_MODE_MOS65XX_W65C02 }, + { "hex.builtin.view.disassembler.mos65xx.65816"_lang, CS_MODE_MOS65XX_65816 }, + { "hex.builtin.view.disassembler.mos65xx.65816_long_m"_lang, CS_MODE_MOS65XX_65816_LONG_M }, + { "hex.builtin.view.disassembler.mos65xx.65816_long_x"_lang, CS_MODE_MOS65XX_65816_LONG_X }, + { "hex.builtin.view.disassembler.mos65xx.65816_long_mx"_lang, CS_MODE_MOS65XX_65816_LONG_MX}, + }; + + if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectedMode].first)) { + for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { + if (ImGui::Selectable(modes[i].first)) + selectedMode = i; + } + ImGui::EndCombo(); + } + + this->m_mode = cs_mode(modes[selectedMode].second); + } + break; + #endif + #if CS_API_MAJOR >= 5 + case Architecture::BPF: + { + static int mode = CS_MODE_BPF_CLASSIC; + ImGui::RadioButton("hex.builtin.view.disassembler.bpf.classic"_lang, &mode, CS_MODE_BPF_CLASSIC); + ImGui::SameLine(); + ImGui::RadioButton("hex.builtin.view.disassembler.bpf.extended"_lang, &mode, CS_MODE_BPF_EXTENDED); + + this->m_mode = cs_mode(mode); + } + break; + case Architecture::SH: + { + static u32 selectionMode = 0; + static bool fpu = false; + static bool dsp = false; + + std::pair modes[] = { + { "hex.builtin.view.disassembler.sh.sh2"_lang, CS_MODE_SH2 }, + { "hex.builtin.view.disassembler.sh.sh2a"_lang, CS_MODE_SH2A }, + { "hex.builtin.view.disassembler.sh.sh3"_lang, CS_MODE_SH3 }, + { "hex.builtin.view.disassembler.sh.sh4"_lang, CS_MODE_SH4 }, + { "hex.builtin.view.disassembler.sh.sh4a"_lang, CS_MODE_SH4A }, + }; + + if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectionMode].first)) { + for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { + if (ImGui::Selectable(modes[i].first)) + selectionMode = i; + } + ImGui::EndCombo(); + } + + ImGui::Checkbox("hex.builtin.view.disassembler.sh.fpu"_lang, &fpu); + ImGui::SameLine(); + ImGui::Checkbox("hex.builtin.view.disassembler.sh.dsp"_lang, &dsp); + + this->m_mode = cs_mode(modes[selectionMode].second | (fpu ? CS_MODE_SHFPU : cs_mode(0)) | (dsp ? CS_MODE_SHDSP : cs_mode(0))); + } + break; + case Architecture::TRICORE: + { + static u32 selectionMode = 0; + + std::pair modes[] = { + { "hex.builtin.view.disassembler.tricore.110"_lang, CS_MODE_TRICORE_110 }, + { "hex.builtin.view.disassembler.tricore.120"_lang, CS_MODE_TRICORE_120 }, + { "hex.builtin.view.disassembler.tricore.130"_lang, CS_MODE_TRICORE_130 }, + { "hex.builtin.view.disassembler.tricore.131"_lang, CS_MODE_TRICORE_131 }, + { "hex.builtin.view.disassembler.tricore.160"_lang, CS_MODE_TRICORE_160 }, + { "hex.builtin.view.disassembler.tricore.161"_lang, CS_MODE_TRICORE_161 }, + { "hex.builtin.view.disassembler.tricore.162"_lang, CS_MODE_TRICORE_162 }, + }; + + if (ImGui::BeginCombo("hex.builtin.view.disassembler.settings.mode"_lang, modes[selectionMode].first)) { + for (u32 i = 0; i < IM_ARRAYSIZE(modes); i++) { + if (ImGui::Selectable(modes[i].first)) + selectionMode = i; + } + ImGui::EndCombo(); + } + + this->m_mode = cs_mode(modes[selectionMode].second); + } + break; + case Architecture::WASM: + #endif + case Architecture::EVM: + case Architecture::TMS320C64X: + case Architecture::ARM64: + case Architecture::SYSZ: + case Architecture::XCORE: + this->m_mode = cs_mode(0); + break; + } } + ImGui::EndChild(); } - ImGui::EndChild(); + // Draw disassemble button ImGui::BeginDisabled(this->m_disassemblerTask.isRunning()); { if (ImGui::Button("hex.builtin.view.disassembler.disassemble"_lang)) @@ -367,6 +386,7 @@ namespace hex::plugin::builtin { } ImGui::EndDisabled(); + // Draw a spinner if the disassembler is running if (this->m_disassemblerTask.isRunning()) { ImGui::SameLine(); ImGui::TextSpinner("hex.builtin.view.disassembler.disassembling"_lang); @@ -377,6 +397,7 @@ namespace hex::plugin::builtin { ImGui::TextUnformatted("hex.builtin.view.disassembler.disassembly.title"_lang); ImGui::Separator(); + // Draw disassembly table if (ImGui::BeginTable("##disassembly", 4, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.view.disassembler.disassembly.address"_lang); @@ -392,17 +413,30 @@ namespace hex::plugin::builtin { while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { const auto &instruction = this->m_disassembly[i]; + ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::Selectable(("##DisassemblyLine"s + std::to_string(i)).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { + + // Draw a selectable label for the address + ImGui::PushID(i); + if (ImGui::Selectable("##DisassemblyLine", false, ImGuiSelectableFlags_SpanAllColumns)) { ImHexApi::HexEditor::setSelection(instruction.offset, instruction.size); } + ImGui::PopID(); + + // Draw instruction address ImGui::SameLine(); ImGui::TextFormatted("0x{0:X}", instruction.address); + + // Draw instruction offset ImGui::TableNextColumn(); ImGui::TextFormatted("0x{0:X}", instruction.offset); + + // Draw instruction bytes ImGui::TableNextColumn(); ImGui::TextUnformatted(instruction.bytes.c_str()); + + // Draw instruction mnemonic and operands ImGui::TableNextColumn(); ImGui::TextFormattedColored(ImColor(0xFFD69C56), "{}", instruction.mnemonic); ImGui::SameLine(); diff --git a/plugins/builtin/source/content/views/view_pattern_data.cpp b/plugins/builtin/source/content/views/view_pattern_data.cpp index d5455200c..551d26195 100644 --- a/plugins/builtin/source/content/views/view_pattern_data.cpp +++ b/plugins/builtin/source/content/views/view_pattern_data.cpp @@ -9,16 +9,18 @@ namespace hex::plugin::builtin { ViewPatternData::ViewPatternData() : View("hex.builtin.view.pattern_data.name") { - + // Handle tree style setting changes EventManager::subscribe(this, [this]() { auto patternStyle = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_tree_style", 0); this->m_patternDrawer.setTreeStyle(static_cast(patternStyle)); }); + // Reset the pattern drawer when the provider changes EventManager::subscribe(this, [this](auto, auto) { this->m_patternDrawer.reset(); }); + // Handle jumping to a pattern's location when it is clicked this->m_patternDrawer.setSelectionCallback([](Region region){ ImHexApi::HexEditor::setSelection(region); }); } @@ -29,13 +31,17 @@ namespace hex::plugin::builtin { void ViewPatternData::drawContent() { if (ImGui::Begin(View::toWindowName("hex.builtin.view.pattern_data.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { + // Draw the pattern tree if the provider is valid if (ImHexApi::Provider::isValid()) { + // Make sure the runtime has finished evaluating and produced valid patterns auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); if (!runtime.arePatternsValid()) { + // If the runtime is still evaluating, reset the pattern drawer this->m_shouldReset = true; this->m_patternDrawer.reset(); - this->m_patternDrawer.draw({}); + this->m_patternDrawer.draw({ }); } else { + // If the runtime has finished evaluating, draw the patterns if (TRY_LOCK(ContentRegistry::PatternLanguage::getRuntimeLock())) { auto runId = runtime.getRunId(); if (this->m_shouldReset || this->m_lastRunId != runId) { diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index cf272024b..192c95578 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -10,6 +10,7 @@ namespace hex::plugin::builtin { ViewSettings::ViewSettings() : View("hex.builtin.view.settings.name") { + // Handle window open requests EventManager::subscribe(this, [this](const std::string &name) { if (name == "Settings") { TaskManager::doLater([this] { @@ -19,8 +20,8 @@ namespace hex::plugin::builtin { } }); + // Add the settings menu item to the Extras menu ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.extras" }, 3000); - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.view.settings.name"_lang }, 4000, Shortcut::None, [&, this] { TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.settings.name").c_str()); }); this->getWindowOpenState() = true; @@ -37,32 +38,49 @@ namespace hex::plugin::builtin { if (ImGui::BeginTabBar("settings")) { auto &entries = ContentRegistry::Settings::impl::getEntries(); - std::vector::const_iterator> sortedCategories; + // Sort the categories by slot + auto sortedCategories = [&entries] { + std::vector::const_iterator> sortedCategories; - for (auto it = entries.cbegin(); it != entries.cend(); it++) { - sortedCategories.emplace_back(it); - } + for (auto it = entries.cbegin(); it != entries.cend(); it++) { + sortedCategories.emplace_back(it); + } - std::sort(sortedCategories.begin(), sortedCategories.end(), [](auto &item0, auto &item1) { - return item0->first.slot < item1->first.slot; - }); + std::sort(sortedCategories.begin(), sortedCategories.end(), [](auto &item0, auto &item1) { + return item0->first.slot < item1->first.slot; + }); + return sortedCategories; + }(); + + // Get the description of the current category const auto &descriptions = ContentRegistry::Settings::impl::getCategoryDescriptions(); - for (auto &it : sortedCategories) { - auto &[category, settings] = *it; + // Draw all categories + for (auto &iter : sortedCategories) { + auto &[category, settings] = *iter; + + // For each category, create a new tab if (ImGui::BeginTabItem(LangEntry(category.name))) { const std::string &categoryDesc = descriptions.contains(category.name) ? descriptions.at(category.name) : category.name; - LangEntry descriptionEntry{categoryDesc}; + // Draw the category description + LangEntry descriptionEntry(categoryDesc); ImGui::TextFormattedWrapped("{}", descriptionEntry); ImGui::InfoTooltip(descriptionEntry); ImGui::Separator(); + // Draw all settings of that category for (auto &[name, requiresRestart, callback] : settings) { + // Get the current value of the setting auto &setting = ContentRegistry::Settings::impl::getSettingsData()[category.name][name]; + + // Execute the settings drawing callback if (callback(LangEntry(name), setting)) { - log::debug("Setting [{}]: {} was changed to {}", category.name, name, [&] -> std::string{ + // Handle a setting being changed + + // Print a debug message + log::debug("Setting [{}]: {} was changed to {}", category.name, name, [&] -> std::string { if (setting.is_number()) return std::to_string(setting.get()); else if (setting.is_string()) @@ -70,8 +88,11 @@ namespace hex::plugin::builtin { else return ""; }()); + + // Post an event EventManager::post(); + // Request a restart if the setting requires it if (requiresRestart) this->m_restartRequested = true; } @@ -87,6 +108,7 @@ namespace hex::plugin::builtin { } else this->getWindowOpenState() = false; + // If a restart is required, ask the user if they want to restart if (!this->getWindowOpenState() && this->m_restartRequested) { PopupQuestion::open("hex.builtin.view.settings.restart_question"_lang, ImHexApi::System::restartImHex, []{}); } diff --git a/plugins/builtin/source/content/views/view_theme_manager.cpp b/plugins/builtin/source/content/views/view_theme_manager.cpp index 9416496eb..e0bd9d0a3 100644 --- a/plugins/builtin/source/content/views/view_theme_manager.cpp +++ b/plugins/builtin/source/content/views/view_theme_manager.cpp @@ -17,15 +17,22 @@ namespace hex::plugin::builtin { if (ImGui::Begin(View::toWindowName("hex.builtin.view.theme_manager.name").c_str(), &this->m_viewOpen, ImGuiWindowFlags_NoCollapse)) { ImGui::Header("hex.builtin.view.theme_manager.colors"_lang, true); + // Draw theme handlers ImGui::PushID(1); - const auto &themeHandlers = ThemeManager::getThemeHandlers(); - for (auto &[name, handler] : themeHandlers) { + + // Loop over each theme handler + for (auto &[name, handler] : ThemeManager::getThemeHandlers()) { + // Create a new collapsable header for each category if (ImGui::CollapsingHeader(name.c_str())) { + + // Loop over all the individual theme settings for (auto &[colorName, colorId] : handler.colorMap) { + // Get the current color value auto color = handler.getFunction(colorId); - if (ImGui::ColorEdit4(colorName.c_str(), (float*)&color.Value, - ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf)) - { + + // Draw a color picker for the color + if (ImGui::ColorEdit4(colorName.c_str(), (float*)&color.Value, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf)) { + // Update the color value handler.setFunction(colorId, color); EventManager::post(); } @@ -37,12 +44,21 @@ namespace hex::plugin::builtin { ImGui::Header("hex.builtin.view.theme_manager.styles"_lang); + // Draw style handlers ImGui::PushID(2); + + // Loop over each style handler for (auto &[name, handler] : ThemeManager::getStyleHandlers()) { + // Create a new collapsable header for each category if (ImGui::CollapsingHeader(name.c_str())) { + + // Loop over all the individual style settings for (auto &[styleName, style] : handler.styleMap) { + // Get the current style value auto &[value, min, max, needsScaling] = style; + // Styles can either be floats or ImVec2s + // Determine which one it is and draw the appropriate slider if (auto floatValue = std::get_if(&value); floatValue != nullptr) { if (ImGui::SliderFloat(styleName.c_str(), *floatValue, min, max, "%.1f")) { EventManager::post(); @@ -57,12 +73,17 @@ namespace hex::plugin::builtin { } ImGui::PopID(); + // Draw export settings ImGui::Header("hex.builtin.view.theme_manager.export"_lang); ImGui::InputTextIcon("hex.builtin.view.theme_manager.export.name"_lang, ICON_VS_SYMBOL_KEY, this->m_themeName); + + // Draw the export buttons if (ImGui::Button("hex.builtin.view.theme_manager.save_theme"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { fs::openFileBrowser(fs::DialogMode::Save, { { "ImHex Theme", "json" } }, [this](const std::fs::path &path){ + // Export the current theme as json auto json = ThemeManager::exportCurrentTheme(this->m_themeName); + // Write the json to the file wolv::io::File outputFile(path, wolv::io::File::Mode::Create); outputFile.writeString(json.dump(4)); }); diff --git a/plugins/builtin/source/content/views/view_tools.cpp b/plugins/builtin/source/content/views/view_tools.cpp index d5e459ae6..a189e5e7f 100644 --- a/plugins/builtin/source/content/views/view_tools.cpp +++ b/plugins/builtin/source/content/views/view_tools.cpp @@ -13,21 +13,30 @@ namespace hex::plugin::builtin { auto &tools = ContentRegistry::Tools::impl::getEntries(); if (ImGui::Begin(View::toWindowName("hex.builtin.view.tools.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) { + + // Draw all tools for (auto iter = tools.begin(); iter != tools.end(); iter++) { auto &[name, function, detached] = *iter; + // If the tool has been detached from the main window, don't draw it here anymore if (detached) continue; + // Draw the tool if (ImGui::CollapsingHeader(LangEntry(name))) { function(); ImGui::NewLine(); } else { + // Handle dragging the tool out of the main window + + // If the user clicks on the header, start dragging the tool remember the iterator if (ImGui::IsMouseClicked(0) && ImGui::IsItemActivated() && this->m_dragStartIterator == tools.end()) this->m_dragStartIterator = iter; + // If the user released the mouse button, stop dragging the tool if (!ImGui::IsMouseDown(0)) this->m_dragStartIterator = tools.end(); + // Detach the tool if the user dragged it out of the main window if (!ImGui::IsItemHovered() && this->m_dragStartIterator == iter) { detached = true; } @@ -44,12 +53,16 @@ namespace hex::plugin::builtin { for (auto iter = tools.begin(); iter != tools.end(); iter++) { auto &[name, function, detached] = *iter; + // If the tool is still attached to the main window, don't draw it here if (!detached) continue; + // Create a new window for the tool ImGui::SetNextWindowSize(scaled(ImVec2(600, 0))); if (ImGui::Begin(View::toWindowName(name).c_str(), &detached, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) { + // Draw the tool function(); + // Handle the first frame after the tool has been detached if (ImGui::IsWindowAppearing() && this->m_dragStartIterator == iter) { this->m_dragStartIterator = tools.end();