summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2026-03-20 07:51:04 -0400
committerBrian Potchik <brian@vector35.com>2026-03-20 07:51:04 -0400
commit7228ba0b889765bc3474fbd5475870e24efc79ca (patch)
treed5a2f7d6f6f58f633e21a638ba9dbe10495f32df
parent9ca4a08fbeabc22bed721ab522cf0a7dc4484a9f (diff)
Add option to disable dedicated Universal architecture picker and use Container Browser
Add the ui.files.universal.dedicatedPicker setting, allowing users to choose between the dedicated architecture picker dialog and the standard container browser for opening Universal (fat) Mach-O binaries. Previously the dedicated picker was always used with no way to opt out. Implementing this required restructuring how Universal binaries are handled during file open. The previous approach intercepted Universal binaries during container processing, bypassing the container browser's normal pipeline with dedicated routing logic. This tightly coupled Universal-specific behavior into the container system, prevented the container browser from handling Universal binaries natively, and introduced several bugs around exclusion settings, nested containers, and multi-child container hierarchies. Bug fixes: - The previous implementation always intercepted Universal binaries during container processing with its own routing logic, bypassing the container browser entirely. There was no way for the container browser to handle Universal binaries natively, even when that was the desired behavior. - Universal binaries inside multi-child containers were not detected. The previous implementation only traversed single-child paths in the container hierarchy, silently ignoring Universal binaries that appeared as siblings among multiple children. - Nested containers (e.g., archives) inside Universal slices were not handled. The previous implementation intercepted Universal binaries at the transform routing level before the container browser could perform recursive delayering. When the dedicated picker is disabled, the container browser now processes the full hierarchy including Universal slices and any containers within them. - Reverted incorrect IsInteractive to IsUIEnabled change in UniversalTransform. The previous change conflated headless mode with non-interactive processing. The UI can be enabled while still performing a non-interactive auto-open (e.g., container browser auto-resolving with a preferred architecture). The IsInteractive() flag captures the actual intent and enables the transform to produce only the preferred architecture child for non-interactive sessions, avoiding unnecessary BinaryView construction for all slices.
-rw-r--r--ui/containerbrowser.h5
-rw-r--r--ui/containeropenrequest.h47
-rw-r--r--view/macho/universaltransform.cpp9
3 files changed, 4 insertions, 57 deletions
diff --git a/ui/containerbrowser.h b/ui/containerbrowser.h
index b9ba278f..363e5446 100644
--- a/ui/containerbrowser.h
+++ b/ui/containerbrowser.h
@@ -15,7 +15,6 @@
#include <vector>
-class ContainerOpenRequest;
class ContainerTreeModel : public QAbstractItemModel
{
@@ -122,8 +121,4 @@ 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
deleted file mode 100644
index 5067534d..00000000
--- a/ui/containeropenrequest.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#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,
- SelectArchitecture,
- };
-
- 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 33164215..3309d853 100644
--- a/view/macho/universaltransform.cpp
+++ b/view/macho/universaltransform.cpp
@@ -266,20 +266,19 @@ bool UniversalTransform::DecodeWithContext(Ref<TransformContext> context, const
architectures.push_back(archName);
}
- // TODO: It is surprising that this is UniversalTransform's responsibility.
- if (!BinaryNinja::IsUIEnabled())
+ if (!context->IsInteractive())
{
- // 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;
}
- // Load the first architecture if no preference is found.
- if (archPref.empty() && architectures.size())
+ // Preserve original headless load behavior when no architecturePreference is specified
+ if (!BinaryNinja::IsUIEnabled() && archPref.empty() && architectures.size())
{
context->SetAvailableFiles({architectures[0]});
return false;