impr: Added lots of comments and cleaned up many views
This commit is contained in:
parent
10ad239fb9
commit
4288f876e2
@ -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;
|
||||
|
@ -39,6 +39,10 @@ namespace hex::plugin::builtin {
|
||||
DifferenceType type;
|
||||
};
|
||||
|
||||
private:
|
||||
std::function<std::optional<color_t>(u64, const u8*, size_t)> createCompareFunction(size_t otherIndex);
|
||||
void analyze(prv::Provider *providerA, prv::Provider *providerB);
|
||||
|
||||
private:
|
||||
std::array<Column, 2> m_columns;
|
||||
|
||||
|
@ -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<EventAchievementUnlocked>(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<AchievementManager::AchievementNode*> &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<std::string> 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();
|
||||
|
@ -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<RequestAddBookmark>(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<EventBookmarkCreated>(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<color_t> {
|
||||
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<ImColor, ColorCount> 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);
|
||||
}
|
||||
|
@ -3,11 +3,10 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
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<RequestOpenPopup>("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::CommandResult> ViewCommandPalette::getCommandResults(const std::string &input) {
|
||||
constexpr static auto MatchCommand = [](const std::string &currCommand, const std::string &commandToMatch) -> std::pair<MatchType, std::string_view> {
|
||||
// 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<CommandResult> 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;
|
||||
|
||||
|
@ -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<ViewConstants *>(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]];
|
||||
|
@ -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<EventRegionSelected>(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<EventProviderClosed>(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<u8> 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<std::string, pl::core::Token::Literal> 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<ContentRegistry::DataInspector::impl::EditingFunction> editingFunction;
|
||||
if (!formatWriteFunction.empty()) {
|
||||
editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector<u8> {
|
||||
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<u8> 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<std::string, pl::core::Token::Literal> 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<ContentRegistry::DataInspector::impl::EditingFunction> editingFunction;
|
||||
if (!formatWriteFunction.empty()) {
|
||||
editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector<u8> {
|
||||
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();
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
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<EventProviderClosed>(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<color_t> {
|
||||
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<std::optional<color_t>(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<color_t> {
|
||||
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<Diff> 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<Diff> 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:
|
||||
|
@ -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<u8> 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<int *>(&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<int *>(&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<const char *, cs_mode> 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<const char *, cs_mode> 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<const char *, cs_mode> 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<const char*, cs_mode> 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<const char*, cs_mode> 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<const char *, cs_mode> 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<const char *, cs_mode> 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<const char *, cs_mode> 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<const char*, cs_mode> 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<const char*, cs_mode> 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();
|
||||
|
@ -9,16 +9,18 @@
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
ViewPatternData::ViewPatternData() : View("hex.builtin.view.pattern_data.name") {
|
||||
|
||||
// Handle tree style setting changes
|
||||
EventManager::subscribe<EventSettingsChanged>(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<ui::PatternDrawer::TreeStyle>(patternStyle));
|
||||
});
|
||||
|
||||
// Reset the pattern drawer when the provider changes
|
||||
EventManager::subscribe<EventProviderChanged>(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) {
|
||||
|
@ -10,6 +10,7 @@
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
ViewSettings::ViewSettings() : View("hex.builtin.view.settings.name") {
|
||||
// Handle window open requests
|
||||
EventManager::subscribe<RequestOpenWindow>(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<std::decay_t<decltype(entries)>::const_iterator> sortedCategories;
|
||||
// Sort the categories by slot
|
||||
auto sortedCategories = [&entries] {
|
||||
std::vector<std::decay_t<decltype(entries)>::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<int>());
|
||||
else if (setting.is_string())
|
||||
@ -70,8 +88,11 @@ namespace hex::plugin::builtin {
|
||||
else
|
||||
return "";
|
||||
}());
|
||||
|
||||
// Post an event
|
||||
EventManager::post<EventSettingsChanged>();
|
||||
|
||||
// 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, []{});
|
||||
}
|
||||
|
@ -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<EventThemeChanged>();
|
||||
}
|
||||
@ -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<float*>(&value); floatValue != nullptr) {
|
||||
if (ImGui::SliderFloat(styleName.c_str(), *floatValue, min, max, "%.1f")) {
|
||||
EventManager::post<EventThemeChanged>();
|
||||
@ -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));
|
||||
});
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user