summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Rowe <mark@vector35.com>2026-02-07 12:04:49 -0800
committerMark Rowe <mark@vector35.com>2026-03-16 17:45:00 -0700
commit9b008755f81cf3019c81af9f59d82b05ade0d9b5 (patch)
tree53ee79494e4cec81cb5e983b4d5d88900bb79b4c
parent25213a836b1423cbc1aeef1f23aebc2167154e56 (diff)
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).
-rw-r--r--ui/containerbrowser.h5
-rw-r--r--ui/containeropenrequest.h46
-rw-r--r--view/macho/universaltransform.cpp9
-rw-r--r--view/macho/universalview.cpp2
4 files changed, 57 insertions, 5 deletions
diff --git a/ui/containerbrowser.h b/ui/containerbrowser.h
index 363e5446..b9ba278f 100644
--- a/ui/containerbrowser.h
+++ b/ui/containerbrowser.h
@@ -15,6 +15,7 @@
#include <vector>
+class ContainerOpenRequest;
class ContainerTreeModel : public QAbstractItemModel
{
@@ -121,4 +122,8 @@ public:
bool openWithOptionsRequested() const { return m_openWithOptionsRequested; }
static std::vector<TransformContextRef> 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<TransformContextRef> showBrowser(ContainerOpenRequest& request, bool* outOpenWithOptions = nullptr);
};
diff --git a/ui/containeropenrequest.h b/ui/containeropenrequest.h
new file mode 100644
index 00000000..55d8923e
--- /dev/null
+++ b/ui/containeropenrequest.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "uitypes.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+
+// 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<TransformContextRef> selectedContexts();
+
+ // When the container is a universal binary, the available architectures and the
+ // index of the preferred one (if any).
+ const std::vector<TransformContextRef>& architectureContexts() const { return m_archContexts; }
+ std::optional<size_t> preferredArchitectureIndex() const { return m_preferredArch; }
+
+private:
+ Action resolveUniversal(TransformContextRef universalCtx);
+
+ TransformSessionRef m_session;
+ std::vector<TransformContextRef> m_archContexts;
+ std::optional<size_t> m_preferredArch;
+ bool m_forceContainerBrowser = false;
+ bool m_autoOpen = false;
+};
diff --git a/view/macho/universaltransform.cpp b/view/macho/universaltransform.cpp
index 3309d853..33164215 100644
--- a/view/macho/universaltransform.cpp
+++ b/view/macho/universaltransform.cpp
@@ -266,19 +266,20 @@ bool UniversalTransform::DecodeWithContext(Ref<TransformContext> 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<string> archPref = context->GetSettings()->Get<vector<string>>("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 16e973f5..bd8fc7ef 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"]
})");