diff options
| author | kat <kat@vector35.com> | 2024-10-23 21:56:29 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2024-10-23 21:58:47 -0400 |
| commit | 3006c68e108a18f9b8782bc937dc9ec7e2cb3b54 (patch) | |
| tree | f589bb38909349142c09b6215244205aef0a57ac /view/sharedcache/ui | |
| parent | 80fcccec8f686e0e5c89626b60af121a97baf856 (diff) | |
Initial commit of the alpha dyld_shared_cache view API Plugin.
This is an early release of our DSC processing plugin. We're still hard at work improving this feature. You should be able to just drop in a dyld_shared_cache and use the 'Shared Cache Triage' view to load and analyze images.
Diffstat (limited to 'view/sharedcache/ui')
| -rw-r--r-- | view/sharedcache/ui/CMakeLists.txt | 72 | ||||
| -rw-r--r-- | view/sharedcache/ui/Plugin.cpp | 25 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheBDNotifications.cpp | 69 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheBDNotifications.h | 20 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheUINotifications.cpp | 143 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheUINotifications.h | 23 | ||||
| -rw-r--r-- | view/sharedcache/ui/dscpicker.cpp | 42 | ||||
| -rw-r--r-- | view/sharedcache/ui/dscpicker.h | 12 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 1012 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.h | 297 | ||||
| -rw-r--r-- | view/sharedcache/ui/dscwidget.cpp | 437 | ||||
| -rw-r--r-- | view/sharedcache/ui/dscwidget.h | 190 |
12 files changed, 2342 insertions, 0 deletions
diff --git a/view/sharedcache/ui/CMakeLists.txt b/view/sharedcache/ui/CMakeLists.txt new file mode 100644 index 00000000..04e2f3cb --- /dev/null +++ b/view/sharedcache/ui/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(sharedcacheui) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) + +file(GLOB SOURCES *.cpp *.h) +list(FILTER SOURCES EXCLUDE REGEX moc_.*) +list(FILTER SOURCES EXCLUDE REGEX qrc_.*) + +add_library(sharedcacheui SHARED ${SOURCES}) + +if (VIEW_NAME) + target_compile_definitions(sharedcacheui PRIVATE VIEW_NAME="${VIEW_NAME}") +else() + error("VIEW_NAME must be defined") +endif() + +if(BN_INTERNAL_BUILD) + set_target_properties(sharedcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else() + set_target_properties(sharedcacheui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins + ) +endif() + +set_target_properties(sharedcacheui PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ) + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(sharedcacheapi INCLUDES) + +target_include_directories(sharedcacheui PRIVATE ${INCLUDES}) + +target_link_libraries(sharedcacheui sharedcacheapi sharedcache binaryninjaui Qt6::Core Qt6::Gui Qt6::Widgets) + diff --git a/view/sharedcache/ui/Plugin.cpp b/view/sharedcache/ui/Plugin.cpp new file mode 100644 index 00000000..1d52fd6a --- /dev/null +++ b/view/sharedcache/ui/Plugin.cpp @@ -0,0 +1,25 @@ +// +// Created by kat on 8/6/24. +// +#include <binaryninjaapi.h> +#include "SharedCacheUINotifications.h" +#include "dsctriage.h" + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + BN_DECLARE_UI_ABI_VERSION + + BINARYNINJAPLUGIN bool UIPluginInit() + { + UINotifications::init(); + UIAction::registerAction("Load Image by Name"); + UIAction::registerAction("Load Section by Address"); + UIAction::registerAction("Load ADDRHERE"); + UIAction::registerAction("Load IMGHERE"); + + DSCTriageViewType::Register(); + + return true; + } +}
\ No newline at end of file diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.cpp b/view/sharedcache/ui/SharedCacheBDNotifications.cpp new file mode 100644 index 00000000..f59f313c --- /dev/null +++ b/view/sharedcache/ui/SharedCacheBDNotifications.cpp @@ -0,0 +1,69 @@ +// +// Created by kat on 8/22/24. +// + +#include "SharedCacheBDNotifications.h" + + +SharedCacheBDNotifications::SharedCacheBDNotifications(Ref<BinaryView> view) + : BinaryDataNotification(FunctionUpdates | DataVariableUpdates) +{ +} + +void SharedCacheBDNotifications::OnAnalysisFunctionAdded(BinaryView* view, Function* func) +{ + // + // We just cannot do this until one of: + // "Component::AddAutoFunction" + // BinaryView::BeginIgnoredUndoActions + // some similar fix + + /* + if (view->GetTypeName() == VIEW_NAME) + { + auto sections = view->GetSectionsAt(func->GetStart()); + if (sections.size() > 0) + { + auto section = sections[0]; + auto imageName = section->GetName().substr(0, section->GetName().find("::")); + auto id = view->BeginUndoActions(); + auto comp = view->GetComponentByPath(imageName); + if (!comp) + { + comp = view->CreateComponentWithName(imageName); + } + comp.value()->AddFunction(func); + view->ForgetUndoActions(id); + } + } + */ +} + + +void SharedCacheBDNotifications::OnSectionAdded(BinaryView* data, Section* section) +{ + +} + + +void SharedCacheBDNotifications::OnDataVariableAdded(BinaryView* view, const DataVariable& var) +{ + /* + if (view->GetTypeName() == VIEW_NAME) + { + auto sections = view->GetSectionsAt(var.address); + if (sections.size() > 0) + { + auto section = sections[0]; + auto imageName = section->GetName().substr(0, section->GetName().find("::")); + auto comp = view->GetComponentByPath(imageName); + auto id = view->BeginUndoActions(); + if (!comp) + { + comp = view->CreateComponentWithName(imageName); + } + comp.value()->AddDataVariable(var); + view->ForgetUndoActions(id); + } + }*/ +} diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.h b/view/sharedcache/ui/SharedCacheBDNotifications.h new file mode 100644 index 00000000..632fca28 --- /dev/null +++ b/view/sharedcache/ui/SharedCacheBDNotifications.h @@ -0,0 +1,20 @@ +// +// Created by kat on 8/22/24. +// + +#pragma once + +#include <binaryninjaapi.h> +#include "ui/uicontext.h" +#include "SharedCacheUINotifications.h" + +using namespace BinaryNinja; + +class SharedCacheBDNotifications : public BinaryDataNotification +{ +public: + SharedCacheBDNotifications(Ref<BinaryView> view); + void OnAnalysisFunctionAdded(BinaryView* view, Function* func) override; + void OnDataVariableAdded(BinaryView* view, const DataVariable& var) override; + void OnSectionAdded(BinaryView* data, Section* section) override; +}; diff --git a/view/sharedcache/ui/SharedCacheUINotifications.cpp b/view/sharedcache/ui/SharedCacheUINotifications.cpp new file mode 100644 index 00000000..6c64c9ef --- /dev/null +++ b/view/sharedcache/ui/SharedCacheUINotifications.cpp @@ -0,0 +1,143 @@ +// +// Created by kat on 5/8/23. +// + +#include "SharedCacheUINotifications.h" +#include <QLayout> +#include <sharedcacheapi.h> +#include "ui/sidebar.h" +#include "ui/linearview.h" +#include "ui/viewframe.h" +#include "dscpicker.h" +#include "progresstask.h" +#include "SharedCacheBDNotifications.h" + +UINotifications* UINotifications::m_instance = nullptr; + +void UINotifications::init() +{ + m_instance = new UINotifications; + UIContext::registerNotification(m_instance); +} + + +void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type) +{ + if (!frame) + return; + + // FIXME there is a bv func for this + static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) { + if (view && view->GetTypeName() == VIEW_NAME) + { + for (const auto& seg : view->GetSegments()) + { + if (seg->GetStart() <= addr && seg->GetEnd() > addr) + return true; + } + } + return false; + }; + + auto view = frame->getCurrentBinaryView(); + if (view && view->GetTypeName() == VIEW_NAME) + { + if (auto viewInt = frame->getCurrentViewInterface()) + { + auto ah = viewInt->actionHandler(); + if (!ah->isBoundAction("Load Image by Name")) + { + ah->bindAction("Load Image by Name", UIAction([view = view](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(view); + DisplayDSCPicker(ctx.context, view); + })); + ah->bindAction("Load Section by Address", UIAction([view = view](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = 0; + bool gotAddr = GetAddressInput(addr, "Address", "Address"); + if (gotAddr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadSectionAtAddress(addr); + })->start(); + } + })); + ah->bindAction("Load ADDRHERE", + UIAction( + [](const UIActionContext& ctx) { + Ref<BinaryView> view = ctx.binaryView; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadSectionAtAddress(addr); + })->start(); + } + }, + [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (isAddrMapped(ctx.binaryView, addr)) + return false; + return addr && cache->GetNameForAddress(addr) != ""; // bool + })); + ah->bindAction("Load IMGHERE", + UIAction( + [](const UIActionContext& ctx) { + Ref<BinaryView> view = ctx.binaryView; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(view); + uint64_t addr = ctx.token.token.value; + if (addr) + { + BackgroundThread::create(ctx.context->mainWindow())->thenBackground( + [cache=cache, addr=addr]() { + cache->LoadImageContainingAddress(addr); + })->start(); + } + }, + [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (isAddrMapped(ctx.binaryView, addr)) + return false; + return addr && cache->GetImageNameForAddress(addr) != ""; // bool + })); + ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + return QString("Load ") + cache->GetNameForAddress(addr).c_str(); + return QString("Error"); + }); + ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); + uint64_t addr = ctx.token.token.value; + if (addr) + return QString("Load ") + cache->GetImageNameForAddress(addr).c_str(); + return QString("Error"); + }); + if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) + { + linearView->contextMenu().addAction("Load ADDRHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); + linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); + linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); + linearView->contextMenu().setGroupOrdering("DSCView2", 1); + } + } + } + } +} +void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) +{ + if (frame->getCurrentBinaryView()) + { + auto listener = new SharedCacheBDNotifications(frame->getCurrentBinaryView()); + frame->getCurrentBinaryView()->RegisterNotification(listener); + } + UIContextNotification::OnAfterOpenFile(context, file, frame); +} diff --git a/view/sharedcache/ui/SharedCacheUINotifications.h b/view/sharedcache/ui/SharedCacheUINotifications.h new file mode 100644 index 00000000..9ea4b50d --- /dev/null +++ b/view/sharedcache/ui/SharedCacheUINotifications.h @@ -0,0 +1,23 @@ +// +// Created by kat on 5/8/23. +// +#include "ui/uicontext.h" + +#ifndef SHAREDCACHE_NOTIFICATIONS_H +#define SHAREDCACHE_NOTIFICATIONS_H + +class UINotifications : public UIContextNotification { + static UINotifications* m_instance; + + std::vector<size_t> m_sessionsAlreadyDisplayedPickerFor; + +public: + virtual void OnViewChange(UIContext *context, ViewFrame *frame, const QString &type) override; + // bool OnAfterOpenDatabase(UIContext* context, FileMetadataRef metadata, BinaryViewRef data) override; + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + + static void init(); +}; + + +#endif //SHAREDCACHE_NOTIFICATIONS_H diff --git a/view/sharedcache/ui/dscpicker.cpp b/view/sharedcache/ui/dscpicker.cpp new file mode 100644 index 00000000..33877675 --- /dev/null +++ b/view/sharedcache/ui/dscpicker.cpp @@ -0,0 +1,42 @@ +// +// Created by kat on 5/22/23. +// + +#include "dscpicker.h" +#include <sharedcacheapi.h> +#include "progresstask.h" + +#include <utility> + +using namespace BinaryNinja; + +void DisplayDSCPicker(UIContext* ctx, Ref<BinaryView> dscView) +{ + BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr)->thenBackground( + [dscView=dscView](QVariant var) { + QStringList entries; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + + for (const auto& img : cache->GetAvailableImages()) + entries.push_back(QString::fromStdString(img)); + + return entries; + })->thenMainThread([ctx](QVariant var){ + QStringList entries = var.toStringList(); + + auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); + choiceDialog->AddWidthRequiredByItem(ctx, 300); + choiceDialog->AddHeightRequiredByItem(ctx, 150); + choiceDialog->exec(); + + if (choiceDialog->GetChosenEntry().has_value()) + return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); + else + return QVariant(""); + })->thenBackground([dscView=dscView](QVariant var){ + if (var.toString().isEmpty()) + return; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + cache->LoadImageWithInstallName(var.toString().toStdString()); + })->start(); +} diff --git a/view/sharedcache/ui/dscpicker.h b/view/sharedcache/ui/dscpicker.h new file mode 100644 index 00000000..6c4b15b5 --- /dev/null +++ b/view/sharedcache/ui/dscpicker.h @@ -0,0 +1,12 @@ +// +// Created by kat on 5/22/23. +// + +#ifndef SHAREDCACHE_DSCPICKER_H +#define SHAREDCACHE_DSCPICKER_H + +#include <binaryninjaapi.h> +#include <ui/metadatachoicedialog.h> +void DisplayDSCPicker(UIContext* ctx = nullptr, BinaryNinja::Ref<BinaryNinja::BinaryView> dscView = nullptr); + +#endif //SHAREDCACHE_DSCPICKER_H diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp new file mode 100644 index 00000000..0dca96bf --- /dev/null +++ b/view/sharedcache/ui/dsctriage.cpp @@ -0,0 +1,1012 @@ +// +// Created by kat on 8/15/24. +// + +#include "dsctriage.h" +#include "ui/fontsettings.h" +#include <QPainter> +#include <QTextBrowser> +#include "tabwidget.h" +#include "globalarea.h" +#include "progresstask.h" + +#include <cmath> +#include <QMessageBox> + + +#define QSETTINGS_KEY_SELECTED_TAB "DSCTriage-SelectedTab" +#define QSETTINGS_KEY_TAB_LAYOUT "DSCTriage-TabLayout" +#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "DSCTriage-ImageLoadTabLayout" +#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "DSCTriage-AlphaPopupSeen" + + +DSCCacheBlocksView::DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache) + : QWidget(parent), m_data(data), m_cache(cache) +{ + setMouseTracking(true); + m_backingCacheCount = SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data); + m_blockLuminance.resize(m_backingCacheCount, 128); + m_blockSizeRatios.resize(m_backingCacheCount, 1); + m_currentProgress = m_cache->GetLoadProgress(data); + m_targetBlockSizeForAnimation.resize(m_backingCacheCount, 0); + + m_blockWaveAnimation = Animation::create(this) + ->withDuration(1200) + ->withEasingCurve(QEasingCurve::Linear) + ->thenOnValueChanged([this](double v) + { + for (size_t i = 0; i < m_backingCacheCount; i++) + { + // Create a wave effect. + // We use sine to create the initial wave effect, and then cube it to make it more pronounced. + m_blockLuminance[i] = 128 + 95 * (pow((sin(v * 2 * M_PI + i * M_PI / m_backingCacheCount) + 1) / 2, 3)); + } + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + m_currentProgress = m_cache->GetLoadProgress(m_data); + if (m_currentProgress == BNDSCViewLoadProgress::LoadProgressFinished) + { + m_backingCaches = m_cache->GetBackingCaches(); + m_blockExpandAnimation->start(); + } + else + { + m_blockWaveAnimation->start(); + } + }); + m_blockExpandAnimation = Animation::create(this) + ->withDuration(600) + ->withEasingCurve(QEasingCurve::InOutCirc) + ->thenOnStart([this](QAbstractAnimation::Direction) + { + uint64_t totalSize = 0; + uint64_t sumCountForAvg = 0; + for (size_t i = 0; i < m_backingCacheCount; i++) + { + const auto& backingCache = m_backingCaches[i]; + double sizeSum = 0.0; + + for (const auto& mapping : backingCache.mappings) + { + sizeSum += mapping.size; + } + m_targetBlockSizeForAnimation[i] = sizeSum; + totalSize += sizeSum; + sumCountForAvg++; + } + + uint64_t avgSize = totalSize / sumCountForAvg; + + for (size_t i = 0; i < m_backingCacheCount; i++) + { + m_blockSizeRatios[i] = avgSize; + } + + m_averageBlockSizeForAnimationInterp = avgSize; + }) + ->thenOnValueChanged([this](double v) + { + for (size_t i = 0; i < m_backingCacheCount; i++) + { + m_blockSizeRatios[i] = m_averageBlockSizeForAnimationInterp + (v/2) * (m_targetBlockSizeForAnimation[i] - ((1.0 - (v/2)) * m_averageBlockSizeForAnimationInterp)); + + // Adjust luminance based on animation progress + m_blockLuminance[i] = 128 + (63 * v); + } + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + update(); + // wait 300, somehow + emit loadDone(); + m_selectedBlock = 0; + m_blockAutoselectAnimation->start(); + }); + + m_blockAutoselectAnimation = Animation::create(this) + ->withDuration(100) + ->withEasingCurve(QEasingCurve::InOutCirc) + ->thenOnValueChanged([this](double v){ + m_blockLuminance[0] = 191 + (64 * v); + update(); + }) + ->thenOnEnd([this](QAbstractAnimation::Direction) + { + emit selectionChanged(m_backingCaches[0], true); + }); + + m_blockWaveAnimation->setDirection(QAbstractAnimation::Backward); + m_blockWaveAnimation->start(); + +} + +DSCCacheBlocksView::~DSCCacheBlocksView() +{ + +} + +void DSCCacheBlocksView::mousePressEvent(QMouseEvent* event) +{ + if (m_currentProgress != BNDSCViewLoadProgress::LoadProgressFinished + || m_selectedBlock == -1) + { + return; + } + int blockIndex = getBlockIndexAtPosition(event->pos()); + blockSelected(blockIndex); + QWidget::mousePressEvent(event); +} + + +void DSCCacheBlocksView::mouseReleaseEvent(QMouseEvent* event) +{ + QWidget::mouseReleaseEvent(event); +} + + +void DSCCacheBlocksView::mouseDoubleClickEvent(QMouseEvent* event) +{ + QWidget::mouseDoubleClickEvent(event); +} + + +void DSCCacheBlocksView::mouseMoveEvent(QMouseEvent* event) +{ + if (m_selectedBlock == -1) + { + return; + } + uint64_t hoveredIndex = getBlockIndexAtPosition(event->pos()); + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + if (hoveredIndex != -1) + { + m_blockLuminance[hoveredIndex] = 255 - 32; + } + m_blockLuminance[m_selectedBlock] = 255; + update(); +} + + +void DSCCacheBlocksView::keyPressEvent(QKeyEvent* event) +{ + QWidget::keyPressEvent(event); +} + + +void DSCCacheBlocksView::keyReleaseEvent(QKeyEvent* event) +{ + QWidget::keyReleaseEvent(event); + if (m_selectedBlock == -1) + { + return; + } + + // left/right arrows, inc/dec m_selectedBlock + if (event->key() == Qt::Key_Left) + { + if (m_selectedBlock > 0) + { + blockSelected(m_selectedBlock - 1); + } + } + else if (event->key() == Qt::Key_Right) + { + if (m_selectedBlock < m_backingCacheCount - 1) + { + blockSelected(m_selectedBlock + 1); + } + } +} + + +void DSCCacheBlocksView::focusInEvent(QFocusEvent* event) +{ + QWidget::focusInEvent(event); +} + + +void DSCCacheBlocksView::focusOutEvent(QFocusEvent* event) +{ + QWidget::focusOutEvent(event); +} + + +void DSCCacheBlocksView::enterEvent(QEnterEvent* event) +{ + QWidget::enterEvent(event); +} + + +void DSCCacheBlocksView::leaveEvent(QEvent* event) +{ + QWidget::leaveEvent(event); +} + +void DSCCacheBlocksView::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Initial X position and total width of the widget + int totalWidth = this->width(); + int totalHeight = 30; // Height of the rectangles + int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; + int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding + + // Calculate the total ratio of block sizes + uint64_t totalRatio = 0; + for (const auto& ratio : m_blockSizeRatios) { + totalRatio += ratio; + } + + std::vector<int> originalWidths; + originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); + + + // Calculate center points for each block + std::vector<int> centers; + centers.reserve(m_blockSizeRatios.size()); + int currentX = 50; + for (size_t i = 0; i < originalWidths.size(); ++i) { + centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point + currentX += originalWidths[i] + 5; // Update currentX for the next block + } + + // Now draw the blocks, adjusting the position to keep the center point constant + currentX = 50; + uint64_t lastBlockEnd = currentX - 5; + for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) { + // Recalculate the width during animation + uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; + int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); + + // Calculate the new X position to maintain the center + int newX = centers[i] - (blockWidth / 2); + if (newX > lastBlockEnd + 5) + { + int diff = newX - (lastBlockEnd + 5); + newX -= diff; + blockWidth += diff; + } + if (newX < lastBlockEnd + 5) + { + int diff = (lastBlockEnd + 5) - newX; + newX += diff; + blockWidth -= diff; + } + lastBlockEnd = newX + blockWidth; + + QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); + QColor blockColor(m_blockLuminance[i], m_blockLuminance[i], m_blockLuminance[i]); + painter.setBrush(blockColor); + painter.setPen(blockColor); + painter.drawRect(blockRect); + + currentX += blockWidth + 5; // Move to the next block's position + } +} + + +int DSCCacheBlocksView::getBlockIndexAtPosition(const QPoint& clickPosition) +{ + // Initial X position and total width of the widget + int totalWidth = this->width(); + int totalHeight = 50; // Height of the rectangles + int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; + int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding + + // Calculate the total ratio of block sizes + uint64_t totalRatio = 0; + for (const auto& ratio : m_blockSizeRatios) + { + totalRatio += ratio; + } + + // Calculate center points for each block + std::vector<int> originalWidths; + originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); + + std::vector<int> centers; + centers.reserve(m_blockSizeRatios.size()); + int currentX = 50; + for (size_t i = 0; i < originalWidths.size(); ++i) + { + centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point + currentX += originalWidths[i] + 5; // Update currentX for the next block + } + + // Now find the block that contains the click + currentX = 50; + uint64_t lastBlockEnd = currentX - 5; + for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) + { + // Recalculate the width during animation + uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; + int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); + + // Calculate the new X position to maintain the center + int newX = centers[i] - (blockWidth / 2); + if (newX > lastBlockEnd + 5) + { + int diff = newX - (lastBlockEnd + 5); + newX -= diff; + blockWidth += diff; + } + if (newX < lastBlockEnd + 5) + { + int diff = (lastBlockEnd + 5) - newX; + newX += diff; + blockWidth -= diff; + } + lastBlockEnd = newX + blockWidth; + + // Check if the clickPosition is inside the current block's rectangle + QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); + if (blockRect.contains(clickPosition)) + { + return static_cast<int>(i); // Return the index of the clicked block + } + + currentX += blockWidth + 5; // Move to the next block's position + } + + return -1; // Return -1 if no block was clicked +} + + +void DSCCacheBlocksView::blockSelected(int index) +{ + std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); + m_selectedBlock = index; + if (index != -1) + m_blockLuminance[index] = 255; + update(); + if (index != -1) + emit selectionChanged(m_backingCaches[index], false); +} + + +void DSCCacheBlocksView::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); +} + + +QSize DSCCacheBlocksView::sizeHint() const +{ + return QWidget::sizeHint(); +} + + +QSize DSCCacheBlocksView::minimumSizeHint() const +{ + return QWidget::minimumSizeHint(); +} + + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(parent) { +} + +int SymbolTableModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + return static_cast<int>(m_symbols.size()); +} + +int SymbolTableModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + // We have 3 columns: Address, Name, and Image + return 3; +} + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const SharedCacheAPI::DSCSymbol& symbol = m_symbols.at(index.row()); + + switch (index.column()) { + case 0: // Address column + return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal + case 1: // Name column + return QString::fromStdString(symbol.name); + case 2: // Image column + return QString::fromStdString(symbol.image); + default: + return QVariant(); + } +} + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Name"); + case 2: + return QString("Image"); + default: + return QVariant(); + } +} + +void SymbolTableModel::updateSymbols() { + m_symbols = m_parent->m_symbols; + setFilter(m_filter); +} + +const SharedCacheAPI::DSCSymbol& SymbolTableModel::symbolAt(int row) const { + return m_symbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_symbols.clear(); + + if (m_filter.empty()) + { + m_symbols = m_parent->m_symbols; + } + else + { + m_symbols.reserve(m_parent->m_symbols.size()); + for (const auto& symbol : m_parent->m_symbols) + { + if (symbol.name.find(m_filter) != std::string::npos) + { + m_symbols.push_back(symbol); + } + } + m_symbols.shrink_to_fit(); + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache) + : m_model(new SymbolTableModel(this)){ + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + BackgroundThread::create(this)->thenBackground([this, cache=cache](){ + // LogInfo("Symbol Search: Loading symbols..."); + m_symbols = cache->LoadAllSymbolsAndWait(); + // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); + })->thenMainThread([this](){ + m_model->updateSymbols(); + })->start(); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} + + +DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new SharedCacheAPI::SharedCache(data)) +{ + setBinaryDataNavigable(false); + setupView(this); + + m_triageCollection = new DockableTabCollection(); + m_triageTabs = new SplitTabWidget(m_triageCollection); + + auto triageTabStyle = new GlobalAreaTabStyle(); + m_triageTabs->setTabStyle(triageTabStyle); + + auto cacheInfoWidget = new QWidget; + auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); + + QSplitter* containerWidget = new QSplitter; + containerWidget->setOrientation(Qt::Vertical); + + DSCCacheBlocksView* cacheBlocksView = new DSCCacheBlocksView(containerWidget, data, m_cache); + cacheBlocksView->setMinimumHeight(60); + + auto cacheInfo = new CollapsibleSection(this); + cacheInfo->setTitle(QString::fromStdString(data->GetFile()->GetOriginalFilename().substr(data->GetFile()->GetOriginalFilename().find_last_of('/') + 1))); + + auto cacheInfoSubwidget = new QWidget; + + auto mappingTable = new QTableView(cacheInfoSubwidget); + auto mappingModel = new QStandardItemModel(0, 3, mappingTable); + mappingModel->setHorizontalHeaderLabels({"VM Address", "File Address", "Size"}); + + mappingTable->setModel(mappingModel); + + mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + + auto sectionTable = new QTableView(cacheInfoSubwidget); + auto sectionModel = new QStandardItemModel(0, 3, sectionTable); + sectionModel->setHorizontalHeaderLabels({"Name", "VM Address", "Size"}); + + sectionTable->setModel(sectionModel); + + sectionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + sectionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + sectionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + + auto mappingLabel = new QLabel("Mappings"); + auto sectionLabel = new QLabel("Sections"); + + auto mappingLayout = new QVBoxLayout; + mappingLayout->addWidget(mappingLabel); + mappingLayout->addWidget(mappingTable); + + auto sectionLayout = new QVBoxLayout; + sectionLayout->addWidget(sectionLabel); + sectionLayout->addWidget(sectionTable); + + cacheInfoLayout->addLayout(mappingLayout); + cacheInfoLayout->addLayout(sectionLayout); + + cacheInfo->setContentWidget(cacheInfoSubwidget); + + cacheInfo->setMinimumHeight(170); + + connect(cacheBlocksView, &DSCCacheBlocksView::selectionChanged, [this, sectionModel, cacheInfo, cacheInfoWidget, mappingModel](const SharedCacheAPI::BackingCache& index, bool _auto) + { + if (!_auto) + m_triageTabs->selectWidget(cacheInfoWidget); + mappingModel->removeRows(0, mappingModel->rowCount()); + sectionModel->removeRows(0, sectionModel->rowCount()); + auto basename = index.path.substr(index.path.find_last_of('/') + 1); + cacheInfo->setTitle(QString::fromStdString(basename)); + size_t sizeInBits = 0; + for (const auto& mapping : index.mappings) + { + sizeInBits += mapping.size; + mappingModel->appendRow({ + new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16))}); + } + + for (const auto& header : m_headers) + { + uint64_t i = 0; + for (const auto& section : header.sections) + { + for (const auto& mapping : index.mappings) + { + if (section.addr >= mapping.vmAddress && section.addr < mapping.vmAddress + mapping.size) + { + sectionModel->appendRow({ + new QStandardItem(QString::fromStdString(header.sectionNames[i])), + new QStandardItem(QString("0x%1").arg(section.addr, 0, 16)), + new QStandardItem(QString("0x%1").arg(section.size, 0, 16))}); + break; + } + } + i++; + } + continue; + } + + std::string sizeStr; + if (sizeInBits < 1024) + { + sizeStr = std::to_string(sizeInBits) + " B"; + } + else if (sizeInBits < 1024 * 1024) + { + sizeStr = std::to_string(sizeInBits / 1024) + " KB"; + } + else if (sizeInBits < 1024 * 1024 * 1024) + { + sizeStr = std::to_string(sizeInBits / (1024 * 1024)) + " MB"; + } + else + { + sizeStr = std::to_string(sizeInBits / (1024 * 1024 * 1024)) + " GB"; + } + + cacheInfo->setSubtitleRight(QString::fromStdString(sizeStr)); + }); + + containerWidget->addWidget(cacheInfo); + + QWidget* defaultWidget; + + // check for alpha popup qsetting + QSettings settings; + if (!(settings.contains(QSETTINGS_KEY_ALPHA_POPUP_SEEN) && settings.value(QSETTINGS_KEY_ALPHA_POPUP_SEEN).toBool())) + { + + QTextBrowser *tb = new QTextBrowser(this); + { + tb->setOpenExternalLinks(true); + auto alphaHtml = + R"( +<br> +<h1>Shared Cache Alpha</h1> + +<p> This is the alpha release of the sharedcache viewer! We are hard at work improving this and adding features, but we wanted +to make it available for users to play with as soon as possible. </p> + +<h2> Supported Platforms </h2> +<ul> + <li> iOS 11-17 (full) </li> + <li> iOS 18 (partial, Objective-C optimization parsing is not implemented yet.) </li> + <li> macOS x86/arm64e (partial) </li> +</ul> + +<p> iOS parsing should work well for now. macOS parsing should be usable, but is still a work in progress. </p> + +<h2> Getting the latest version of the plugin </h2> + +<p> We frequently release "dev" builds which will contain the latest version of the SharedCache plugin (and many other things). + +You can find instructions on how to install these builds <a href="https://docs.binary.ninja/guide/index.html#development-branch">here</a>. </p> + +<h3> Reading / building the source </h3> +<p>You can read the source and find instructions for building it <a href="https://github.com/Vector35/binaryninja-api/tree/dev/view/sharedcache">here</a>. + +Contributions are always welcome! </p> +)"; + tb->setHtml(alphaHtml); + + m_triageTabs->addTab(tb, "Shared Cache Alpha"); + + } + settings.setValue(QSETTINGS_KEY_ALPHA_POPUP_SEEN, true); + defaultWidget = tb; + } + + m_bottomRegionCollection = new DockableTabCollection(); + m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); + m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); + + auto loadImageTable = new FilterableTableView; + { + auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); + { + connect( + cacheBlocksView, &DSCCacheBlocksView::loadDone, [this, loadImageModel, cacheInfo]() + { + for (const auto& img : m_cache->GetImages()) + { + if (auto header = m_cache->GetMachOHeaderForAddress(img.headerAddress); header) + { + m_headers.push_back(*header); + } + loadImageModel->appendRow({ + new QStandardItem(QString::fromStdString(img.name)), + new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16))}); + } + }); + loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); + } // loadImageModel + + auto loadImageButton = new CustomStyleFlatPushButton(); + { + connect(loadImageButton, &QPushButton::clicked, + [this, loadImageTable, cacheInfo, mappingModel, sectionModel](bool) { + auto selected = loadImageTable->selectionModel()->selectedRows(); + if (selected.size() == 0) + { + return; + } + + auto name = selected[0].data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + }); + loadImageButton->setText("Load"); + + loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + loadImageButton->setMinimumWidth(100); + loadImageButton->setMinimumHeight(30); + + } // loadImageButton + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + auto loadImageFilterEdit = new FilterEdit(loadImageTable); + { + connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { + loadImageTable->setFilter(filter.toStdString()); + }); + } // loadImageFilterEdit + + connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) + { + auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); + WorkerPriorityEnqueue([this, name]() + { + m_cache->LoadImageWithInstallName(name); + }); + }); + connect(loadImageTable, &FilterableTableView::doubleClicked, this, [=](const QModelIndex& index) + { + auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); + WorkerPriorityEnqueue([this, name]() + { + m_cache->LoadImageWithInstallName(name); + }); + }); + + auto loadImageLayout = new QVBoxLayout; + loadImageLayout->addWidget(loadImageFilterEdit); + loadImageLayout->addWidget(loadImageTable); + loadImageLayout->addWidget(loadImageButton); + + auto loadImageWidget = new QWidget; + loadImageWidget->setLayout(loadImageLayout); + + m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + + loadImageTable->setModel(loadImageModel); + + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + loadImageTable->setSelectionMode(QAbstractItemView::SingleSelection); + + m_triageTabs->addTab(loadImageWidget, "Images"); + if (!defaultWidget) + defaultWidget = loadImageWidget; + m_triageTabs->setCanCloseTab(loadImageWidget, false); + } // loadImageTable + + auto symbolSearch = new SymbolTableView(this, m_cache); + { + auto symbolFilterEdit = new FilterEdit(symbolSearch); + { + connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { + symbolSearch->setFilter(filter.toStdString()); + }); + } + + auto symbolLayout = new QVBoxLayout; + symbolLayout->addWidget(symbolFilterEdit); + symbolLayout->addWidget(symbolSearch); + + auto symbolWidget = new QWidget; + symbolWidget->setLayout(symbolLayout); + + symbolSearch->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address + symbolSearch->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name + symbolSearch->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image + + symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); + symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) + { + auto symbol = symbolSearch->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); + dialog->setText("Load " + QString::fromStdString(symbol.image) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) + { + if (button == dialog->button(QMessageBox::Yes)) + { + WorkerPriorityEnqueue([this, symbol]() + { + m_cache->LoadImageWithInstallName(symbol.image); + }); + } + }); + dialog->exec(); + }); + + m_triageTabs->addTab(symbolWidget, "Symbol Search"); + m_triageTabs->setCanCloseTab(symbolWidget, false); + } // symbolSearch + + auto loadedRegions = new QTreeView; + { + auto loadedRegionsModel = new QStandardItemModel(0, 3, loadedRegions); + loadedRegionsModel->setHorizontalHeaderLabels({"VM Address", "Size", "Pretty Name"}); + + auto loadedRegionsLayout = new QVBoxLayout; + loadedRegionsLayout->addWidget(loadedRegions); + + auto loadedRegionsWidget = new QWidget; + loadedRegionsWidget->setLayout(loadedRegionsLayout); + + loadedRegions->setModel(loadedRegionsModel); + + loadedRegions->header()->setSectionResizeMode(QHeaderView::Stretch); + + loadedRegions->setSelectionBehavior(QAbstractItemView::SelectRows); + loadedRegions->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(loadedRegions, &QTreeView::doubleClicked, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + connect(loadedRegions, &QTreeView::activated, this, [=](const QModelIndex& index) + { + auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + }); + + // m_triageTabs->addTab(loadedRegionsWidget, "Loaded Regions"); + } // loadedRegions + + containerWidget->addWidget(m_bottomRegionTabs); + + m_triageTabs->addTab(cacheInfoWidget, "Cache Info"); + m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + + m_layout = new QVBoxLayout(this); + m_layout->addWidget(cacheBlocksView); + m_layout->addWidget(m_triageTabs); + setLayout(m_layout); + + m_triageTabs->selectWidget(defaultWidget); +} + + +DSCTriageView::~DSCTriageView() {} + + +QFont DSCTriageView::getFont() +{ + return getMonospaceFont(this); +} + + +BinaryViewRef DSCTriageView::getData() +{ + return m_data; +} + + +bool DSCTriageView::navigate(uint64_t offset) +{ + return true; +} + + +uint64_t DSCTriageView::getCurrentOffset() +{ + return 0; +} + + +CollapsibleSection::CollapsibleSection(QWidget* parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + { + layout->setContentsMargins(0, 0, 0, 0); + + auto hLayout = new QHBoxLayout; + { + hLayout->setContentsMargins(0, 0, 0, 0); + + m_titleLabel = new QLabel; + m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); + hLayout->addWidget(m_titleLabel, 1); + + m_subtitleRightLabel = new QLabel; + m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); + hLayout->addWidget(m_subtitleRightLabel); + + m_collapseButton = new CustomStyleFlatPushButton; + m_collapseButton->setFlat(true); + m_collapseButton->setCheckable(true); + } + + layout->addLayout(hLayout); + } + + m_contentWidgetContainer = new QWidget; + { + layout->addWidget(m_contentWidgetContainer); + new QVBoxLayout(m_contentWidgetContainer); + } + +} + + +void CollapsibleSection::setTitle(const QString& title) +{ + m_titleLabel->setText(title); +} + + +void CollapsibleSection::setSubtitleRight(const QString& subtitle) +{ + m_subtitleRightLabel->setVisible(subtitle != ""); + m_subtitleRightLabel->setText(subtitle); +} + + +void CollapsibleSection::setContentWidget(QWidget* contentWidget) +{ + m_contentWidget = contentWidget; + m_contentWidgetContainer->layout()->addWidget(contentWidget); +} + + +QSize CollapsibleSection::sizeHint() const +{ + return QWidget::sizeHint(); +} + + +void CollapsibleSection::setCollapsed(bool collapsed, bool animated) +{ + if (collapsed == m_collapsed) + { + return; + } + + m_collapsed = collapsed; + + if (m_collapsed) + { + m_contentWidget->hide(); + } + else + { + m_contentWidget->show(); + } + + if (animated) + { + m_onContentAddedAnimation->start(); + } +} + + +DSCTriageViewType::DSCTriageViewType() + : ViewType("DSCTriage", "Shared Cache Triage") +{ + +} + + +int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +{ + if (data->GetTypeName() == VIEW_NAME) + { + return 100; + } + return 1; +} + + +QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) +{ + if (data->GetTypeName() != VIEW_NAME) + { + return nullptr; + } + return new DSCTriageView(viewFrame, data); +} + + +void DSCTriageViewType::Register() +{ + ViewType::registerViewType(new DSCTriageViewType()); +} diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h new file mode 100644 index 00000000..96099a53 --- /dev/null +++ b/view/sharedcache/ui/dsctriage.h @@ -0,0 +1,297 @@ +// +// Created by kat on 8/15/24. +// + +#include <sharedcacheapi.h> +#include <binaryninjaapi.h> +#include "uitypes.h" +#include "viewframe.h" +#include "animation.h" +#include "uicontext.h" + +#include <QTableView> +#include <QStandardItemModel> +#include <QSortFilterProxyModel> +#include <QHeaderView> +#include "filter.h" + +#ifndef BINARYNINJA_DSCTRIAGE_H +#define BINARYNINJA_DSCTRIAGE_H + + +class DSCCacheBlocksView : public QWidget +{ + Q_OBJECT + + BinaryViewRef m_data; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + + uint64_t m_backingCacheCount = 0; + std::vector<SharedCacheAPI::BackingCache> m_backingCaches; + + std::atomic<BNDSCViewLoadProgress> m_currentProgress; + std::vector<uint64_t> m_blockSizeRatios; + std::vector<uint64_t> m_targetBlockSizeForAnimation; + uint64_t m_averageBlockSizeForAnimationInterp = 0; + std::vector<uint64_t> m_blockLuminance; + Animation* m_blockWaveAnimation; + Animation* m_blockExpandAnimation; + Animation* m_blockAutoselectAnimation; + + int m_selectedBlock = -1; + + int getBlockIndexAtPosition(const QPoint& clickPosition); + + void blockSelected(int index); + +public: + DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache); + virtual ~DSCCacheBlocksView() override; + +protected: + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + void enterEvent(QEnterEvent* event) override; + void leaveEvent(QEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +public: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +signals: + void loadDone(); + void selectionChanged(const SharedCacheAPI::BackingCache& index, bool automatic); +}; + + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + + QLabel* m_titleLabel; + QLabel* m_subtitleRightLabel; + QPushButton* m_collapseButton; + + bool m_collapsed = true; + + Animation* m_onContentAddedAnimation; + + QWidget* m_contentWidgetContainer; + QWidget* m_contentWidget; + +protected: + QSize sizeHint() const override; + +public: + CollapsibleSection(QWidget* parent); + void setTitle(const QString& title); + void setSubtitleRight(const QString& subtitle); + + void setContentWidget(QWidget* contentWidget); + + void setCollapsed(bool collapsed, bool animated = true); + bool isCollapsed() const { return m_collapsed; } +}; + + +class FilterableTableView : public QTableView, public FilterTarget { + Q_OBJECT + + bool m_filterByHiding; + +public: + FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) + : QTableView(parent), m_filterByHiding(filterByHiding) { + viewport()->installEventFilter(this); + } + + ~FilterableTableView() override {} + + void setFilter(const std::string& filter) override { + if (!m_filterByHiding) + { + emit filterTextChanged(QString::fromStdString(filter)); + return; + } + QString qFilter = QString::fromStdString(filter); + for (int row = 0; row < model()->rowCount(); ++row) { + bool match = false; + for (int col = 0; col < model()->columnCount(); ++col) { + QModelIndex index = model()->index(row, col); + QString data = model()->data(index).toString(); + if (data.contains(qFilter, Qt::CaseInsensitive)) { + match = true; + break; + } + } + setRowHidden(row, !match); + } + } + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + bool eventFilter(QObject* obj, QEvent* event) override { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Escape) { + clearSelection(); + return true; + } + if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { + emit activated(currentIndex()); + return true; + } + } + return QTableView::eventFilter(obj, event); + } + +signals: + void filterTextChanged(const QString& text); +}; + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector<SharedCacheAPI::DSCSymbol> m_symbols; + +public: + explicit SymbolTableModel(SymbolTableView* parent); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void updateSymbols(); + + void setFilter(std::string text); + + const SharedCacheAPI::DSCSymbol& symbolAt(int row) const; +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ + Q_OBJECT + friend class SymbolTableModel; + + std::vector<SharedCacheAPI::DSCSymbol> m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent, SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> cache); + virtual ~SymbolTableView() override; + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + SharedCacheAPI::DSCSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +}; + + +class DSCTriageView : public QWidget, public View +{ + BinaryViewRef m_data; + QVBoxLayout* m_layout; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + + SplitTabWidget* m_triageTabs; + DockableTabCollection* m_triageCollection; + + SplitTabWidget* m_bottomRegionTabs; + QTimer* m_tabLayoutTimer; + DockableTabCollection* m_bottomRegionCollection; + + std::vector<SharedCacheAPI::SharedCacheMachOHeader> m_headers; + +public: + DSCTriageView(QWidget* parent, BinaryViewRef data); + virtual ~DSCTriageView() override; + BinaryViewRef getData() override; + void setSelectionOffsets(BNAddressRange range) override {}; + QFont getFont() override; + bool navigate(uint64_t offset) override; + uint64_t getCurrentOffset() override; +}; + + +class DSCTriageViewType : public ViewType +{ +public: + DSCTriageViewType(); + int getPriority(BinaryViewRef data, const QString& filename) override; + QWidget* create(BinaryViewRef data, ViewFrame* viewFrame) override; + static void Register(); +}; + + +#endif // BINARYNINJA_DSCTRIAGE_H diff --git a/view/sharedcache/ui/dscwidget.cpp b/view/sharedcache/ui/dscwidget.cpp new file mode 100644 index 00000000..2483a26e --- /dev/null +++ b/view/sharedcache/ui/dscwidget.cpp @@ -0,0 +1,437 @@ +// +// by kat // 9/15/22. +// + +// CURRENTLY UNUSED CODE + +#include "dscwidget.h" + +#include "ui/viewframe.h" +#include "ui/progresstask.h" + +#include <QtCore/QMimeData> +#include <QtWidgets/QHeaderView> +#include <QtWidgets/QVBoxLayout> +#include <filesystem> +#include <QtWidgets> + +namespace fs = std::filesystem; + + +/// Format an address as hexadecimal. Does not include leading '0x' prefix. +QString formatAddress(uint64_t address) +{ + return QString::number(address, 16).rightJustified(8, '0'); +}; + +//===-- DSCContentsModelItem ------------------------------------------------===// + +DSCContentsModelItem::DSCContentsModelItem(DSCContentsModelItem* parent) : DSCContentsModelItem(nullptr, {}, {}, parent) +{} + +DSCContentsModelItem::DSCContentsModelItem( + BinaryViewRef view, std::string name, std::string installName, DSCContentsModelItem* parent) : + m_bv(view), + m_name(name), m_installName(installName), m_parent(parent) +{ + if (!installName.empty()) + m_type = ImageModelItem; + else + m_type = FolderModelItem; +} + +QString DSCContentsModelItem::displayName() const +{ + return QString::fromStdString(m_name); +} + +size_t DSCContentsModelItem::childCount() const +{ + return m_children.size(); +} + +DSCContentsModelItem* DSCContentsModelItem::child(size_t index) +{ + if (index < 0 || index >= m_children.size()) + return nullptr; + + return m_children[index]; +} + +void DSCContentsModelItem::addChild(DSCContentsModelItem* item) +{ + item->m_parent = this; + m_children.push_back(item); +} + +DSCContentsModelItem* DSCContentsModelItem::parent() const +{ + return m_parent; +} + +size_t DSCContentsModelItem::row() const +{ + if (!m_parent) + return 0; + auto it = std::find(m_parent->m_children.begin(), m_parent->m_children.end(), this); + return it - m_parent->m_children.begin(); +} + +QVariant DSCContentsModelItem::data(int column) const +{ + switch (column) + { + case DSCContentsModel::NameColumn: + return displayName(); + + default: + return QVariant(); + } +} + +QImage DSCContentsModelItem::icon() const +{ + auto kind = data(DSCContentsModel::KindColumn).toString(); + auto icon = QImage(":/icons/images/ComponentTree_" + kind + ".png"); + + return icon.scaled(16, 16, Qt::KeepAspectRatio); +} + +//===-- DSCContentsModel ----------------------------------------------------===// + +DSCContentsModel::DSCContentsModel(BinaryViewRef bv, QObject* parent) : QAbstractItemModel(parent), m_bv(bv) +{ + m_cache = new SharedCacheAPI::SharedCache(bv); + refresh(); +} + +struct ItemNode +{ + ItemNode* parent = nullptr; + std::string fullPath; + std::string path; + DSCContentsModelItem* assignedModelItem = nullptr; + std::unordered_map<std::string, ItemNode*> edges {}; +}; + +std::vector<std::string> split(std::string str, std::string token) +{ + std::vector<std::string> result; + while (str.size()) + { + int index = str.find(token); + if (index != std::string::npos) + { + result.push_back(str.substr(0, index)); + str = str.substr(index + token.size()); + if (str.size() == 0) + result.push_back(str); + } + else + { + result.push_back(str); + str = ""; + } + } + return result; +} + +void DSCContentsModel::refresh() +{ + std::scoped_lock<std::mutex> lock(m_updateMutex); + + // Using `{begin,end}ResetModel` here is not ideal and is a temporary + // hack at best. Actual model indices should be updated. That requires + // more work and will be implemented after more important things have + // been taken care of. + beginResetModel(); + + auto inames = m_cache->GetAvailableImages(); + + m_root = new DSCContentsModelItem(); + + std::unordered_map<std::string, DSCContentsModelItem*> folders {}; + folders["/"] = m_root; + for (const auto& iname : inames) + { + auto pathItems = split(iname, "/"); + pathItems.pop_back(); // skip filenames + std::string fullPath = "/"; + + for (const auto& item : pathItems) + { + if (item.empty()) + continue; + auto parentPath = fullPath; + fullPath += item + "/"; + if (folders.count(fullPath) == 0) + { + auto pnode = folders.at(parentPath); + auto* nnode = new DSCContentsModelItem(m_bv, item, "", pnode); + pnode->addChild(nnode); + folders[fullPath] = nnode; + } + } + } + + // Ok, all our folders are in place. Put files in them. + + for (const auto& iname : inames) + { + auto file = fs::path(iname).filename().string(); + auto folderName = fs::path(iname).parent_path().string() + "/"; + if (auto folder = folders.find(folderName); folder != folders.end()) + { + auto* nnode = new DSCContentsModelItem(m_bv, file, iname, folder->second); + folder->second->addChild(nnode); + } + else + BNLogError("DSCView Sidebar Logic Error: Couldn't find folder for %s %s %s", iname.c_str(), file.c_str(), + folderName.c_str()); + } + + endResetModel(); +} + +QModelIndex DSCContentsModel::index(int row, int column, const QModelIndex& parentIndex) const +{ + if (!hasIndex(row, column, parentIndex)) + return QModelIndex(); + + // Use the parent index's item if it is valid, otherwise use the root. + DSCContentsModelItem* parent = nullptr; + if (parentIndex.isValid()) + parent = static_cast<DSCContentsModelItem*>(parentIndex.internalPointer()); + else + parent = m_root; + + // If the child is found, create an index for it; use an invalid index otherwise. + auto item = parent->child(row); + if (item) + return createIndex(row, column, item); + + return QModelIndex(); +} + +QModelIndex DSCContentsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + auto child = static_cast<DSCContentsModelItem*>(index.internalPointer()); + auto parent = child->parent(); + if (parent == m_root || parent == nullptr) + return QModelIndex(); + + return createIndex(parent->row(), 0, parent); +} + +QVariant DSCContentsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case DSCContentsModel::NameColumn: + return "Name"; + default: + return ""; + } + } + + return QAbstractItemModel::headerData(section, orientation, role); +} + +constexpr int ComponentGuidDataRole = 64; + +QVariant DSCContentsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + auto item = static_cast<DSCContentsModelItem*>(index.internalPointer()); + if (!item) + return {}; + + switch (role) + { + case Qt::DisplayRole: + return item->data(index.column()); + default: + return {}; + } +} + +bool DSCContentsModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + return false; +} + +Qt::ItemFlags DSCContentsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; // Root node + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + return flags; +} + + +int DSCContentsModel::rowCount(const QModelIndex& parent) const +{ + DSCContentsModelItem* item; + if (!parent.isValid()) + item = m_root; + else + item = static_cast<DSCContentsModelItem*>(parent.internalPointer()); + + return item->childCount(); +} + +int DSCContentsModel::columnCount(const QModelIndex& parent) const +{ + return 1; +} + +Qt::DropActions DSCContentsModel::supportedDropActions() const +{ + return Qt::IgnoreAction; +} + + +//===-- ComponentFilterModel ----------------------------------------------===// + +DSCFilterModel::DSCFilterModel(BinaryViewRef data, QObject* parent) : + QSortFilterProxyModel(parent), m_model(new DSCContentsModel(data)) +{ + setSourceModel(m_model); +} + +bool DSCFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + if (!index.isValid()) + return false; + + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} + +DSCSidebarView::DSCSidebarView(ViewFrame* frame, BinaryViewRef data, QWidget* parent) : + QTreeView(parent), m_data(data), m_frame(frame), m_parent(parent) +{ + connect(this, &DSCSidebarView::doubleClicked, this, &DSCSidebarView::navigateToIndex); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &DSCSidebarView::customContextMenuRequested, [this](const QPoint& p) { + auto menu = createContextMenu(); + menu->popup(viewport()->mapToGlobal(p)); + }); +} + + +void DSCSidebarView::navigateToIndex(const QModelIndex& index) +{ + auto filterParent = static_cast<DSCSidebarWidget*>(m_parent); + if (!filterParent) + return; + auto modelItem = static_cast<DSCContentsModelItem*>(filterParent->m_model->mapToSource(index).internalPointer()); + + if (modelItem->m_installName.empty()) + return; + + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, "Load Image", "Load " + QString::fromStdString(modelItem->m_name) + "?", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + SharedCacheAPI::SharedCache* cache = new SharedCacheAPI::SharedCache(m_data); + cache->LoadImageWithInstallName(modelItem->m_installName); + m_data->UpdateAnalysis(); + } +} + +QMenu* DSCSidebarView::createContextMenu() +{ + auto menu = new QMenu(); + + return menu; +} + +//===-- ComponentTree -----------------------------------------------------===// + +DSCSidebarWidget::DSCSidebarWidget(ViewFrame* frame, BinaryViewRef data) : + SidebarWidget("dyld_shared_cache"), m_data(data), m_frame(frame), m_header(new QWidget) +{ + auto view = data; + m_tree = new DSCSidebarView(frame, view, this); + m_model = new DSCFilterModel(view); + m_tree->setDragDropMode(QAbstractItemView::DragDrop); + m_tree->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_tree->setDragEnabled(true); + m_tree->setAcceptDrops(true); + m_tree->setDropIndicatorShown(true); + m_tree->header()->setSectionsMovable(false); + + m_tree->setModel(m_model); + m_model->setRecursiveFilteringEnabled(true); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_tree, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search Shared Cache Files"); + + auto headerLayout = new QHBoxLayout(m_header); + headerLayout->setContentsMargins(0, 0, 0, 0); + headerLayout->addWidget(m_filterEdit); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_filterView); +} + +//===-- ComponentTree - FilterTarget --------------------------------------===// + +void DSCSidebarWidget::setFilter(const std::string& filter) +{ + m_model->setFilterFixedString(QString::fromStdString(filter)); +} + +void DSCSidebarWidget::scrollToFirstItem() {} + +void DSCSidebarWidget::scrollToCurrentItem() {} + +void DSCSidebarWidget::selectFirstItem() {} + +void DSCSidebarWidget::activateFirstItem() {} + +//===-- DSCSidebarWidget - SidebarWidget -------------------------------------===// + +QWidget* DSCSidebarWidget::headerWidget() +{ + return m_header; +} + +void DSCSidebarWidget::focus() {} + +QImage temporaryIcon() +{ + QImage icon(56, 56, QImage::Format_RGB32); + icon.fill(0); + + QPainter p; + p.begin(&icon); + p.setFont({"Inter", 16}); + p.setPen({255, 255, 255, 255}); + p.drawText(QRectF {0, 0, 56, 56}, Qt::AlignCenter, "DSC"); + p.end(); + + return icon; +} + +DSCSidebarWidgetType::DSCSidebarWidgetType() : SidebarWidgetType(temporaryIcon(), "Shared Cache") {} + +SidebarWidget* DSCSidebarWidgetType::createWidget(ViewFrame* frame, BinaryViewRef data) +{ + return new DSCSidebarWidget(frame, data); +} diff --git a/view/sharedcache/ui/dscwidget.h b/view/sharedcache/ui/dscwidget.h new file mode 100644 index 00000000..0d5b9e0b --- /dev/null +++ b/view/sharedcache/ui/dscwidget.h @@ -0,0 +1,190 @@ +// +// by kat // 9/15/22. +// + +#ifndef SHAREDCACHE_DSCSIDEBARWIDGET_H +#define SHAREDCACHE_DSCSIDEBARWIDGET_H + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QSortFilterProxyModel> +#include <QtWidgets/QTreeView> + +#include "ui/filter.h" +#include "ui/sidebar.h" +#include "ui/uitypes.h" +#include <binaryninjaapi.h> +#include <sharedcacheapi.h> + +#include <mutex> + + +class DSCContentsModel; + +class DSCFilterModel; + +class DSCSidebarView; + +enum ModelItemType { + FolderModelItem, + ImageModelItem +}; + +class DSCContentsModelItem { + friend class ComponentModel; + + friend class ComponentFilterModel; + + friend class DSCSidebarView; + + ModelItemType m_type; + + DSCContentsModelItem *m_parent; + std::vector<DSCContentsModelItem *> m_children; + + BinaryViewRef m_bv; + + std::string m_name; + std::string m_installName; // only set on images, not dirs + + bool m_hasDataVar = false; + BinaryNinja::DataVariable m_dataVar; + +public: + explicit DSCContentsModelItem(DSCContentsModelItem *parent = nullptr); + + explicit DSCContentsModelItem(BinaryViewRef, std::string, std::string, DSCContentsModelItem *parent = nullptr); + + /// Get the "name" that should be displayed for an item. + QString displayName() const; + + size_t childCount() const; + + DSCContentsModelItem *child(size_t); + + void addChild(DSCContentsModelItem *); + + DSCContentsModelItem *parent() const; + + size_t row() const; + + QVariant data(int column) const; + + QImage icon() const; +}; + +class DSCContentsModel : public QAbstractItemModel { +Q_OBJECT + + BinaryViewRef m_bv; + SharedCacheAPI::SCRef<SharedCacheAPI::SharedCache> m_cache; + DSCContentsModelItem *m_root; + + std::unordered_map<std::string, DSCContentsModelItem *> m_dscItems; + + std::mutex m_updateMutex; + + void refresh(); + +public: + enum Column : int { + NameColumn = 0, + AddressColumn, + KindColumn, + }; + + DSCContentsModel(BinaryViewRef, QObject *parent = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &) const override; + + QVariant headerData(int, Qt::Orientation, int role = Qt::DisplayRole) const override; + + QVariant data(const QModelIndex &, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex &) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + Qt::DropActions supportedDropActions() const override; + +}; + +/// Filtering model to wrap a `ComponentModel`. +class DSCFilterModel : public QSortFilterProxyModel { +Q_OBJECT + + DSCContentsModel *m_model; + +public: + DSCFilterModel(BinaryViewRef, QObject *parent = nullptr); + + [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; +}; + +class DSCSidebarView : public QTreeView { + BinaryViewRef m_data; + ViewFrame *m_frame; + QWidget *m_parent; + + void navigateToIndex(const QModelIndex &); + + QMenu *createContextMenu(); + +public: + DSCSidebarView(ViewFrame *, BinaryViewRef, QWidget *parent = nullptr); +}; + +class DSCSidebarWidget : public SidebarWidget, public FilterTarget { +Q_OBJECT + + friend DSCSidebarView; + + BinaryViewRef m_data; + ViewFrame *m_frame; + QWidget *m_header; + + DSCSidebarView *m_tree; + + QSortFilterProxyModel *m_model; + + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + +public: + DSCSidebarWidget(ViewFrame *, BinaryViewRef); + + QWidget *headerWidget() override; + + void focus() override; + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override; + + void scrollToCurrentItem() override; + + void selectFirstItem() override; + + void activateFirstItem() override; +}; + +class DSCSidebarWidgetType : public SidebarWidgetType { +public: + DSCSidebarWidgetType(); + + bool ValidForView(BinaryNinja::BinaryView* view) + { + if (!view) + return false; + return (view->GetTypeName() == VIEW_NAME); + } + + SidebarWidget *createWidget(ViewFrame *, BinaryViewRef) override; +}; + +#endif //SHAREDCACHE_DSCSIDEBARWIDGET_H |
