From 92638c53d5110b40c1e3c04659f4f4b5896f18c3 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Sat, 7 Feb 2026 12:04:49 -0800 Subject: [PATCH 1/2] Refactor where architecture selection is performed for universal binaries Responsibility for selecting an architecture is moved out of `UniversalTransform` and into a new `ContainerOpenRequest` class. `UniversalTransform` still handles architecture selection in headless operation (for now). --- ui/containerbrowser.h | 5 ++++ ui/containeropenrequest.h | 46 +++++++++++++++++++++++++++++++ view/macho/universaltransform.cpp | 9 +++--- view/macho/universalview.cpp | 2 +- 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 ui/containeropenrequest.h diff --git a/ui/containerbrowser.h b/ui/containerbrowser.h index 363e54468..b9ba278f0 100644 --- a/ui/containerbrowser.h +++ b/ui/containerbrowser.h @@ -15,6 +15,7 @@ #include +class ContainerOpenRequest; class ContainerTreeModel : public QAbstractItemModel { @@ -121,4 +122,8 @@ class BINARYNINJAUIAPI ContainerBrowser : public QDialog bool openWithOptionsRequested() const { return m_openWithOptionsRequested; } static std::vector openContainerFile(const QString& path, bool forceShowDialog = false, bool* outOpenWithOptions = nullptr); + + // Show the container browser dialog for the given open request. + // Returns the selected contexts, or empty if the user cancelled. + static std::vector showBrowser(ContainerOpenRequest& request, bool* outOpenWithOptions = nullptr); }; diff --git a/ui/containeropenrequest.h b/ui/containeropenrequest.h new file mode 100644 index 000000000..55d8923e4 --- /dev/null +++ b/ui/containeropenrequest.h @@ -0,0 +1,46 @@ +#pragma once + +#include "uitypes.h" + +#include +#include +#include + + +// Captures user settings and intent for opening a container file, and provides +// policy decisions (e.g. whether to show the container browser) after processing. +class BINARYNINJAUIAPI ContainerOpenRequest +{ +public: + enum Action { + Cancel, + AutoOpen, + BrowseContainer, + }; + + explicit ContainerOpenRequest(const std::string& path, bool forceContainerBrowser = false); + + TransformSessionRef session() const { return m_session; } + + // Create the session, process it, and determine what action the caller + // should take. Returns Cancel if the session could not be created. + Action resolve(); + + // Get the default selection from a processed session. If no selection has + // been made (e.g. because the session auto-opened), selects the current leaf. + std::vector selectedContexts(); + + // When the container is a universal binary, the available architectures and the + // index of the preferred one (if any). + const std::vector& architectureContexts() const { return m_archContexts; } + std::optional preferredArchitectureIndex() const { return m_preferredArch; } + +private: + Action resolveUniversal(TransformContextRef universalCtx); + + TransformSessionRef m_session; + std::vector m_archContexts; + std::optional m_preferredArch; + bool m_forceContainerBrowser = false; + bool m_autoOpen = false; +}; diff --git a/view/macho/universaltransform.cpp b/view/macho/universaltransform.cpp index 3309d8534..331642151 100644 --- a/view/macho/universaltransform.cpp +++ b/view/macho/universaltransform.cpp @@ -266,19 +266,20 @@ bool UniversalTransform::DecodeWithContext(Ref context, const architectures.push_back(archName); } - if (!context->IsInteractive()) + // TODO: It is surprising that this is UniversalTransform's responsibility. + if (!BinaryNinja::IsUIEnabled()) { + // When headless, filter to the preferred architecture if one is configured. vector archPref = context->GetSettings()->Get>("files.universal.architecturePreference"); if (auto result = find_first_of(archPref.begin(), archPref.end(), architectures.begin(), architectures.end()); result != archPref.end()) { - // Filter to preferred architecture to support container auto-open policy size_t archIndex = find(architectures.begin(), architectures.end(), *result) - architectures.begin(); context->SetAvailableFiles({architectures[archIndex]}); return false; } - // Preserve original headless load behavior when no architecturePreference is specified - if (!BinaryNinja::IsUIEnabled() && archPref.empty() && architectures.size()) + // Load the first architecture if no preference is found. + if (archPref.empty() && architectures.size()) { context->SetAvailableFiles({architectures[0]}); return false; diff --git a/view/macho/universalview.cpp b/view/macho/universalview.cpp index 16e973f56..bd8fc7ef2 100644 --- a/view/macho/universalview.cpp +++ b/view/macho/universalview.cpp @@ -30,7 +30,7 @@ void BinaryNinja::InitUniversalViewType() "type" : "array", "sorted" : false, "default" : [], - "description" : "Specify an architecture preference for automatic loading of a Mach-O file from a Universal archive. When unset, headless operation defaults to the first available architecture, while interactive operation presents all available architectures for selection.", + "description" : "Architectures to prefer when loading Universal (fat) Mach-O archives, in order of priority. Determines which architecture is pre-selected in the architecture picker, or loaded automatically when prompting is set to 'automatic' or 'never'.", "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] })"); From 4a383a6dad44fb36aba95f78f0bd2669f0691f7e Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 18 Feb 2026 14:08:38 -0800 Subject: [PATCH 2/2] Use a dedicated architecture picker when opening Universal Mach-O files --- ui/containeropenrequest.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/containeropenrequest.h b/ui/containeropenrequest.h index 55d8923e4..5067534dd 100644 --- a/ui/containeropenrequest.h +++ b/ui/containeropenrequest.h @@ -16,6 +16,7 @@ class BINARYNINJAUIAPI ContainerOpenRequest Cancel, AutoOpen, BrowseContainer, + SelectArchitecture, }; explicit ContainerOpenRequest(const std::string& path, bool forceContainerBrowser = false);