summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Potchik <brian@vector35.com>2026-05-14 07:51:48 -0400
committerBrian Potchik <brian@vector35.com>2026-05-14 07:51:48 -0400
commit759f0eb73239dd747cd0b8eab55af01fa21837a4 (patch)
tree1cef76f74e7415bc43c5e50208f0ca884e288849
parent92a7b85b7a0868f62db224f8b213027d3658f912 (diff)
Refine FileMetadata display name APIs and update UI consumers.
-rw-r--r--binaryninjaapi.h42
-rw-r--r--filemetadata.cpp2
-rw-r--r--python/filemetadata.py37
-rw-r--r--rust/src/file_metadata.rs38
-rw-r--r--ui/filecontext.h8
5 files changed, 99 insertions, 28 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index d9e1efc0..c48a5a6c 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -3809,9 +3809,21 @@ namespace BinaryNinja {
*/
void SetFilename(const std::string& name);
- /*! Get the path to the container file if the current file is inside a container (e.g. ZIP, TAR, etc.)
+ /*! Get the transform-chain identity for this file in the current session There are three meaningful states:
- \return The path to the container file if the current file is inside a container, otherwise an empty string
+ * Empty - not yet processed by the transform system.
+ * Equal to GetFilename() - processed, no transform chain applied (plain file,
+ database, or container system disabled via ``files.container.mode``).
+ * Non-empty and different from GetFilename() - derived container entry.
+
+ Session-scoped: save-as does not persist the chain. Reopening the saved artifact
+ yields whatever chain that session's access path produces.
+
+ Use this for cache keys, identity-sensitive operations, or testing whether a file
+ has been processed. Use GetFilename() for the physical path, GetDisplayName() for
+ UI display.
+
+ \return The transform chain, or empty string if not yet processed.
*/
std::string GetVirtualPath() const;
@@ -3821,10 +3833,30 @@ namespace BinaryNinja {
*/
void SetVirtualPath(const std::string& path);
- /*! Get the display name for the file. For container entries, this returns the synthesized name
- representing the extracted artifact. For normal files, this returns the filename.
+ /*! True if this file was produced by the container transform system (e.g. an entry
+ extracted from a Zip). False for plain files, databases, and FileMetadata that
+ has not yet been processed by the transform system (virtual_path empty).
+
+ \return Whether this FileMetadata represents a derived container entry.
+ */
+ bool IsContainerEntry() const
+ {
+ std::string virtualPath = GetVirtualPath();
+ return !virtualPath.empty() && GetFilename() != virtualPath;
+ }
+
+ /*! A leaf-shaped human-readable name for UI presentation. Never contains a directory
+ path. Resolution order:
+
+ * An explicitly set display name (project-assigned, transform-synthesized for
+ container entries, or set by a plugin or user).
+ * Otherwise the leaf of GetFilename().
+
+ Use this for tab titles, save-dialog default leaf names, logs, and any UI surface
+ where you'd refer to the file by name. Use GetFilename() for the physical path that
+ can be reopened.
- \return The display name for UI purposes (tab titles, save dialogs, etc.)
+ \return The display name for UI purposes.
*/
std::string GetDisplayName() const;
diff --git a/filemetadata.cpp b/filemetadata.cpp
index 18c30591..28384250 100644
--- a/filemetadata.cpp
+++ b/filemetadata.cpp
@@ -73,8 +73,8 @@ FileMetadata::FileMetadata(const string& filename)
FileMetadata::FileMetadata(Ref<ProjectFile> projectFile)
{
m_object = BNCreateFileMetadata();
- BNSetProjectFile(m_object, projectFile->m_object);
BNSetFilename(m_object, projectFile->GetPathOnDisk().c_str());
+ BNSetProjectFile(m_object, projectFile->m_object);
}
diff --git a/python/filemetadata.py b/python/filemetadata.py
index bfce4f98..de3472e3 100644
--- a/python/filemetadata.py
+++ b/python/filemetadata.py
@@ -211,11 +211,18 @@ class FileMetadata:
@property
def virtual_path(self) -> str:
"""
- ``virtual_path`` is a logical (non-filesystem) path that describes how this file was derived from container transform system.
+ ``virtual_path`` is a logical (non-filesystem) path describing how this file was derived from the
+ container transform system in the current session. There are three meaningful states:
- This path records provenance for files extracted from the transform system. It may include a sequence of transform steps and selection names.
+ * Empty - not yet processed by the transform system.
+ * Equal to ``filename`` - processed, no transform chain applied (plain file, database, or container
+ system disabled via ``files.container.mode``).
+ * Non-empty and different from ``filename`` - derived container entry.
- .. note:: An empty `virtual_path` indicates the file has not yet been processed by the transform system. If `virtual_path` matches `filename`, the file is not the result of an extraction or transform.
+ Session-scoped: save-as does not persist the chain. Reopening the saved artifact yields whatever chain
+ that session's access path produces.
+
+ Use this for cache keys or identity-sensitive operations. Use ``filename`` for the physical path and ``display_name`` for UI display.
"""
return core.BNGetVirtualPath(self.handle)
@@ -224,17 +231,27 @@ class FileMetadata:
core.BNSetVirtualPath(self.handle, str(value))
@property
- def display_name(self) -> str:
+ def is_container_entry(self) -> bool:
+ """
+ ``True`` if this file was produced by the container transform system (e.g. an entry extracted from a Zip
+ archive). ``False`` for plain files, databases, and FileMetadata that has not yet been processed by the
+ transform system.
"""
- ``display_name`` is the synthesized name for UI display purposes.
+ virtual = self.virtual_path
+ return bool(virtual) and virtual != self.filename
- For container entries, this contains a virtual filename representing the extracted artifact (e.g., "/path/to/entry").
- For normal files, this equals ``filename``.
+ @property
+ def display_name(self) -> str:
+ """
+ ``display_name`` is a leaf-shaped human-readable name for UI presentation. It never contains a directory
+ path. Resolution order:
- Use this property for tab titles, save dialog defaults, and other UI display purposes.
- Use ``filename`` for the actual physical file path that can be reopened.
+ * An explicitly set display name (project-assigned, transform-synthesized for container entries, or set
+ by a plugin or user).
+ * Otherwise the leaf of ``filename``.
- .. note:: For normal files, ``filename`` == ``virtual_path`` == ``display_name``. For container files, ``filename`` is the container path, ``virtual_path`` is the transform chain, and ``display_name`` is the extracted entry name.
+ Use this for tab titles, save-dialog default leaf names, logs, and any UI surface where you'd refer to
+ the file by name. Use ``filename`` for the physical path that can be reopened.
"""
return core.BNGetDisplayName(self.handle)
diff --git a/rust/src/file_metadata.rs b/rust/src/file_metadata.rs
index 7b8a60fd..064d1fe9 100644
--- a/rust/src/file_metadata.rs
+++ b/rust/src/file_metadata.rs
@@ -176,8 +176,15 @@ impl FileMetadata {
}
}
- /// The display name of the file. Useful for presenting to the user. Can differ from the original
- /// name of the file and can be overridden with [`FileMetadata::set_display_name`].
+ /// A leaf-shaped human-readable name for UI presentation. Never contains a directory path.
+ /// Resolution order:
+ /// * An explicitly set display name (project-assigned, transform-synthesized for container
+ /// entries, or set via [`FileMetadata::set_display_name`]).
+ /// * Otherwise the leaf of [`FileMetadata::file_path`].
+ ///
+ /// Use this for tab titles, save-dialog default leaf names, logs, and any UI surface where
+ /// you'd refer to the file by name. Use [`FileMetadata::file_path`] for the physical path
+ /// that can be reopened.
pub fn display_name(&self) -> String {
let raw_name = unsafe {
let raw = BNGetDisplayName(self.handle);
@@ -243,20 +250,23 @@ impl FileMetadata {
}
}
- /// The non-filesystem path that describes how this file was derived from the container
- /// transform system, detailing the sequence of transform steps and selection names.
+ /// The non-filesystem path describing how this file was derived from the container transform
+ /// system in the current session. There are three meaningful states:
+ /// * `None` - not yet processed by the transform system.
+ /// * `Some(p)` where `p == file_path()` - processed, no transform chain applied (plain file,
+ /// database, or the container system was disabled via `files.container.mode`).
+ /// * `Some(p)` where `p != file_path()` - derived container entry.
///
- /// NOTE: Returns `None` if this [`FileMetadata`] was not processed by the transform system and
- /// does not differ from that of the "physical" file path reported by [`FileMetadata::file_path`].
+ /// Session-scoped: save-as does not persist the chain. Reopening the saved artifact yields
+ /// whatever chain that session's access path produces.
pub fn virtual_path(&self) -> Option<String> {
unsafe {
let raw = BNGetVirtualPath(self.handle);
let path = BnString::into_string(raw);
- // For whatever reason the core may report there being a virtual path as the file path.
- // In the case where that occurs, we wish not to report there being one to the user.
- match path.is_empty() || path == self.file_path() {
- true => None,
- false => Some(path),
+ if path.is_empty() {
+ None
+ } else {
+ Some(path)
}
}
}
@@ -270,6 +280,12 @@ impl FileMetadata {
}
}
+ /// `true` if this file was produced by the container transform system, `false` for plain files,
+ /// databases, and FileMetadata that has not yet been processed by the transform system.
+ pub fn is_container_entry(&self) -> bool {
+ matches!(self.virtual_path(), Some(p) if p != self.file_path())
+ }
+
/// Whether the file is currently flagged as modified.
///
/// When this returns `true`, the UI will prompt to save the database on close, as well as display
diff --git a/ui/filecontext.h b/ui/filecontext.h
index 6a8c1d51..47581b53 100644
--- a/ui/filecontext.h
+++ b/ui/filecontext.h
@@ -65,7 +65,13 @@ class BINARYNINJAUIAPI FileContext : public FileContextBase, public BinaryNinja:
FileMetadataRef getMetadata() const { return m_file; }
QString getFilename() const { return m_filename; }
void setFilename(QString newName) { m_filename = newName; }
- QString getDisplayName() const;
+
+ // Returns the virtual path if the FileMetadata has one (transform session set it), otherwise
+ // the physical filename. Use this anywhere you need a stable per-file identity.
+ // Empty virtual_path carries real meaning ("this FileMetadata has not been processed by the
+ // transform system"), so callers must not collapse that state at the core layer; this helper
+ // is the intended fallback at the use site.
+ static QString getVirtualPathOrFilename(FileMetadataRef file);
ViewFrame* getCurrentViewFrame() const { return m_currentViewFrame; }
QString getTabName(QWidget* widget);
QString getShortFileName(QWidget* widget);