binaryninja/
file_metadata.rs

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