binaryninja/
file_metadata.rs

1// Copyright 2021-2026 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::binary_view::BinaryView;
16use crate::database::Database;
17use crate::rc::*;
18use crate::string::*;
19use binaryninjacore_sys::*;
20use binaryninjacore_sys::{BNCreateDatabaseWithProgress, BNOpenExistingDatabaseWithProgress};
21use std::ffi::c_void;
22use std::fmt::{Debug, Display, Formatter};
23use std::path::Path;
24
25use crate::progress::ProgressCallback;
26use crate::project::file::ProjectFile;
27use std::ptr::{self, NonNull};
28
29new_id_type!(SessionId, usize);
30
31#[derive(PartialEq, Eq, Hash)]
32pub struct FileMetadata {
33    pub(crate) handle: *mut BNFileMetadata,
34}
35
36impl FileMetadata {
37    pub(crate) fn from_raw(handle: *mut BNFileMetadata) -> Self {
38        Self { handle }
39    }
40
41    pub(crate) fn ref_from_raw(handle: *mut BNFileMetadata) -> Ref<Self> {
42        unsafe { Ref::new(Self { handle }) }
43    }
44
45    pub fn new() -> Ref<Self> {
46        Self::ref_from_raw(unsafe { BNCreateFileMetadata() })
47    }
48
49    pub fn with_filename(name: &str) -> Ref<Self> {
50        let ret = FileMetadata::new();
51        ret.set_filename(name);
52        ret
53    }
54
55    pub fn close(&self) {
56        unsafe {
57            BNCloseFile(self.handle);
58        }
59    }
60
61    pub fn session_id(&self) -> SessionId {
62        let raw = unsafe { BNFileMetadataGetSessionId(self.handle) };
63        SessionId(raw)
64    }
65
66    pub fn filename(&self) -> String {
67        unsafe {
68            let raw = BNGetFilename(self.handle);
69            BnString::into_string(raw)
70        }
71    }
72
73    pub fn set_filename(&self, name: &str) {
74        let name = name.to_cstr();
75
76        unsafe {
77            BNSetFilename(self.handle, name.as_ptr());
78        }
79    }
80
81    pub fn modified(&self) -> bool {
82        unsafe { BNIsFileModified(self.handle) }
83    }
84
85    pub fn mark_modified(&self) {
86        unsafe {
87            BNMarkFileModified(self.handle);
88        }
89    }
90
91    pub fn mark_saved(&self) {
92        unsafe {
93            BNMarkFileSaved(self.handle);
94        }
95    }
96
97    pub fn is_analysis_changed(&self) -> bool {
98        unsafe { BNIsAnalysisChanged(self.handle) }
99    }
100
101    pub fn is_database_backed(&self) -> bool {
102        self.is_database_backed_for_view_type("")
103    }
104
105    pub fn is_database_backed_for_view_type(&self, view_type: &str) -> bool {
106        let view_type = view_type.to_cstr();
107
108        unsafe { BNIsBackedByDatabase(self.handle, view_type.as_ref().as_ptr() as *const _) }
109    }
110
111    /// Runs a failable function where the failure state will revert any undo actions that occurred
112    /// during the time of the function's execution.
113    ///
114    /// NOTE: This will commit or undo any actions that occurred on **any** thread as this state is not thread local.
115    ///
116    /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
117    /// and the thread executing this function, you can deadlock. You should also never call this function
118    /// on multiple threads at a time. See the following issues:
119    ///  - <https://github.com/Vector35/binaryninja-api/issues/6289>
120    ///  - <https://github.com/Vector35/binaryninja-api/issues/6325>
121    pub fn run_undoable_transaction<F: FnOnce() -> Result<T, E>, T, E>(
122        &self,
123        func: F,
124    ) -> Result<T, E> {
125        let undo = self.begin_undo_actions(false);
126        let result = func();
127        match result {
128            Ok(t) => {
129                self.commit_undo_actions(&undo);
130                Ok(t)
131            }
132            Err(e) => {
133                self.revert_undo_actions(&undo);
134                Err(e)
135            }
136        }
137    }
138
139    /// Creates a new undo entry, any undo actions after this will be added to this entry.
140    ///
141    /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
142    /// and the thread executing this function, you can deadlock. You should also never call this function
143    /// on multiple threads at a time. See the following issues:
144    ///  - <https://github.com/Vector35/binaryninja-api/issues/6289>
145    ///  - <https://github.com/Vector35/binaryninja-api/issues/6325>
146    pub fn begin_undo_actions(&self, anonymous_allowed: bool) -> String {
147        unsafe { BnString::into_string(BNBeginUndoActions(self.handle, anonymous_allowed)) }
148    }
149
150    /// Commits the undo entry with the id to the undo buffer.
151    ///
152    /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
153    /// and the thread executing this function, you can deadlock. You should also never call this function
154    /// on multiple threads at a time. See the following issues:
155    ///  - <https://github.com/Vector35/binaryninja-api/issues/6289>
156    ///  - <https://github.com/Vector35/binaryninja-api/issues/6325>
157    pub fn commit_undo_actions(&self, id: &str) {
158        let id = id.to_cstr();
159        unsafe {
160            BNCommitUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
161        }
162    }
163
164    /// Reverts the undo actions committed in the undo entry.
165    ///
166    /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
167    /// and the thread executing this function, you can deadlock. You should also never call this function
168    /// on multiple threads at a time. See the following issues:
169    ///  - <https://github.com/Vector35/binaryninja-api/issues/6289>
170    ///  - <https://github.com/Vector35/binaryninja-api/issues/6325>
171    pub fn revert_undo_actions(&self, id: &str) {
172        let id = id.to_cstr();
173        unsafe {
174            BNRevertUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
175        }
176    }
177
178    /// Forgets the undo actions committed in the undo entry.
179    ///
180    /// NOTE: This is **NOT** thread safe, if you are holding any locks that might be held by both the main thread
181    /// and the thread executing this function, you can deadlock. You should also never call this function
182    /// on multiple threads at a time. See the following issues:
183    ///  - <https://github.com/Vector35/binaryninja-api/issues/6289>
184    ///  - <https://github.com/Vector35/binaryninja-api/issues/6325>
185    pub fn forget_undo_actions(&self, id: &str) {
186        let id = id.to_cstr();
187        unsafe {
188            BNForgetUndoActions(self.handle, id.as_ref().as_ptr() as *const _);
189        }
190    }
191
192    pub fn undo(&self) {
193        unsafe {
194            BNUndo(self.handle);
195        }
196    }
197
198    pub fn redo(&self) {
199        unsafe {
200            BNRedo(self.handle);
201        }
202    }
203
204    pub fn current_view(&self) -> String {
205        unsafe { BnString::into_string(BNGetCurrentView(self.handle)) }
206    }
207
208    pub fn current_offset(&self) -> u64 {
209        unsafe { BNGetCurrentOffset(self.handle) }
210    }
211
212    /// Navigate to an offset for a specific view.
213    ///
214    /// # Example
215    ///
216    /// ```no_run
217    /// use binaryninja::file_metadata::FileMetadata;
218    /// # let file: FileMetadata = unimplemented!();
219    /// file.navigate_to("Linear:Raw", 0x0).expect("Linear:Raw should always be present");
220    /// ```
221    pub fn navigate_to(&self, view: &str, offset: u64) -> Result<(), ()> {
222        let view = view.to_cstr();
223
224        unsafe {
225            if BNNavigate(self.handle, view.as_ref().as_ptr() as *const _, offset) {
226                Ok(())
227            } else {
228                Err(())
229            }
230        }
231    }
232
233    /// Get the [`BinaryView`] for the view type.
234    ///
235    /// # Example
236    ///
237    /// ```no_run
238    /// use binaryninja::file_metadata::FileMetadata;
239    /// # let file: FileMetadata = unimplemented!();
240    /// file.view_of_type("Raw").expect("Raw type should always be present");
241    /// ```
242    pub fn view_of_type(&self, view: &str) -> Option<Ref<BinaryView>> {
243        let view = view.to_cstr();
244
245        unsafe {
246            let raw_view_ptr = BNGetFileViewOfType(self.handle, view.as_ref().as_ptr() as *const _);
247            match raw_view_ptr.is_null() {
248                false => Some(BinaryView::ref_from_raw(raw_view_ptr)),
249                true => None,
250            }
251        }
252    }
253
254    pub fn view_types(&self) -> Array<BnString> {
255        let mut count = 0;
256        unsafe {
257            let types = BNGetExistingViews(self.handle, &mut count);
258            Array::new(types, count, ())
259        }
260    }
261
262    /// Get the [`ProjectFile`] for the [`FileMetadata`].
263    pub fn project_file(&self) -> Option<Ref<ProjectFile>> {
264        unsafe {
265            let res = NonNull::new(BNGetProjectFile(self.handle))?;
266            Some(ProjectFile::ref_from_raw(res))
267        }
268    }
269
270    pub fn create_database(&self, file_path: impl AsRef<Path>) -> bool {
271        // Databases are created with the root view (Raw).
272        let Some(raw_view) = self.view_of_type("Raw") else {
273            return false;
274        };
275
276        let file_path = file_path.as_ref().to_cstr();
277        unsafe {
278            BNCreateDatabase(
279                raw_view.handle,
280                file_path.as_ptr() as *mut _,
281                ptr::null_mut(),
282            )
283        }
284    }
285
286    // TODO: Pass settings?
287    pub fn create_database_with_progress<P: ProgressCallback>(
288        &self,
289        file_path: impl AsRef<Path>,
290        mut progress: P,
291    ) -> bool {
292        // Databases are created with the root view (Raw).
293        let Some(raw_view) = self.view_of_type("Raw") else {
294            return false;
295        };
296        let file_path = file_path.as_ref().to_cstr();
297        unsafe {
298            BNCreateDatabaseWithProgress(
299                raw_view.handle,
300                file_path.as_ptr() as *mut _,
301                &mut progress as *mut P as *mut c_void,
302                Some(P::cb_progress_callback),
303                ptr::null_mut(),
304            )
305        }
306    }
307
308    pub fn save_auto_snapshot(&self) -> bool {
309        // Snapshots are saved with the root view (Raw).
310        let Some(raw_view) = self.view_of_type("Raw") else {
311            return false;
312        };
313
314        unsafe { BNSaveAutoSnapshot(raw_view.handle, ptr::null_mut() as *mut _) }
315    }
316
317    pub fn open_database_for_configuration(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
318        let file = file.to_cstr();
319        unsafe {
320            let bv =
321                BNOpenDatabaseForConfiguration(self.handle, file.as_ref().as_ptr() as *const _);
322
323            if bv.is_null() {
324                Err(())
325            } else {
326                Ok(BinaryView::ref_from_raw(bv))
327            }
328        }
329    }
330
331    pub fn open_database(&self, file: &Path) -> Result<Ref<BinaryView>, ()> {
332        let file = file.to_cstr();
333        let view = unsafe { BNOpenExistingDatabase(self.handle, file.as_ptr()) };
334
335        if view.is_null() {
336            Err(())
337        } else {
338            Ok(unsafe { BinaryView::ref_from_raw(view) })
339        }
340    }
341
342    pub fn open_database_with_progress<P: ProgressCallback>(
343        &self,
344        file: &Path,
345        mut progress: P,
346    ) -> Result<Ref<BinaryView>, ()> {
347        let file = file.to_cstr();
348
349        let view = unsafe {
350            BNOpenExistingDatabaseWithProgress(
351                self.handle,
352                file.as_ptr(),
353                &mut progress as *mut P as *mut c_void,
354                Some(P::cb_progress_callback),
355            )
356        };
357
358        if view.is_null() {
359            Err(())
360        } else {
361            Ok(unsafe { BinaryView::ref_from_raw(view) })
362        }
363    }
364
365    /// Get the current database
366    pub fn database(&self) -> Option<Ref<Database>> {
367        let result = unsafe { BNGetFileMetadataDatabase(self.handle) };
368        NonNull::new(result).map(|handle| unsafe { Database::ref_from_raw(handle) })
369    }
370}
371
372impl Debug for FileMetadata {
373    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
374        f.debug_struct("FileMetadata")
375            .field("filename", &self.filename())
376            .field("session_id", &self.session_id())
377            .field("modified", &self.modified())
378            .field("is_analysis_changed", &self.is_analysis_changed())
379            .field("current_view_type", &self.current_view())
380            .field("current_offset", &self.current_offset())
381            .field("view_types", &self.view_types().to_vec())
382            .finish()
383    }
384}
385
386impl Display for FileMetadata {
387    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
388        f.write_str(&self.filename())
389    }
390}
391
392unsafe impl Send for FileMetadata {}
393unsafe impl Sync for FileMetadata {}
394
395impl ToOwned for FileMetadata {
396    type Owned = Ref<Self>;
397
398    fn to_owned(&self) -> Self::Owned {
399        unsafe { RefCountable::inc_ref(self) }
400    }
401}
402
403unsafe impl RefCountable for FileMetadata {
404    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
405        Ref::new(Self {
406            handle: BNNewFileReference(handle.handle),
407        })
408    }
409
410    unsafe fn dec_ref(handle: &Self) {
411        BNFreeFileMetadata(handle.handle);
412    }
413}