summaryrefslogtreecommitdiff
path: root/view/sharedcache/ui
diff options
context:
space:
mode:
authorkat <kat@vector35.com>2024-10-23 21:56:29 -0400
committerkat <kat@vector35.com>2024-10-23 21:58:47 -0400
commit3006c68e108a18f9b8782bc937dc9ec7e2cb3b54 (patch)
treef589bb38909349142c09b6215244205aef0a57ac /view/sharedcache/ui
parent80fcccec8f686e0e5c89626b60af121a97baf856 (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.txt72
-rw-r--r--view/sharedcache/ui/Plugin.cpp25
-rw-r--r--view/sharedcache/ui/SharedCacheBDNotifications.cpp69
-rw-r--r--view/sharedcache/ui/SharedCacheBDNotifications.h20
-rw-r--r--view/sharedcache/ui/SharedCacheUINotifications.cpp143
-rw-r--r--view/sharedcache/ui/SharedCacheUINotifications.h23
-rw-r--r--view/sharedcache/ui/dscpicker.cpp42
-rw-r--r--view/sharedcache/ui/dscpicker.h12
-rw-r--r--view/sharedcache/ui/dsctriage.cpp1012
-rw-r--r--view/sharedcache/ui/dsctriage.h297
-rw-r--r--view/sharedcache/ui/dscwidget.cpp437
-rw-r--r--view/sharedcache/ui/dscwidget.h190
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