summaryrefslogtreecommitdiff
path: root/rust/src/database.rs
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-06-05 13:34:10 -0300
committerKyle Martin <krm504@nyu.edu>2024-07-09 18:13:10 -0400
commitae7ab2c432641e52b6c45b447c9974c1dc04f787 (patch)
tree793b371e56037319d79ccd16a424b6f13ac97e53 /rust/src/database.rs
parent5110ddda5e2f4f5c924f5d16699e0dc861084cb6 (diff)
impl rust Database
Diffstat (limited to 'rust/src/database.rs')
-rw-r--r--rust/src/database.rs657
1 files changed, 657 insertions, 0 deletions
diff --git a/rust/src/database.rs b/rust/src/database.rs
new file mode 100644
index 00000000..1e43b16a
--- /dev/null
+++ b/rust/src/database.rs
@@ -0,0 +1,657 @@
+use std::collections::HashMap;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use std::{ffi, mem, ptr};
+
+use binaryninjacore_sys::*;
+
+use crate::binaryview::BinaryView;
+use crate::databuffer::DataBuffer;
+use crate::disassembly::InstructionTextToken;
+use crate::filemetadata::FileMetadata;
+use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
+use crate::string::{BnStrCompatible, BnString};
+
+#[repr(transparent)]
+pub struct Database {
+ handle: ptr::NonNull<BNDatabase>,
+}
+
+impl Database {
+ pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNDatabase>) -> Self {
+ Self { handle }
+ }
+
+ #[allow(clippy::mut_from_ref)]
+ pub(crate) unsafe fn as_raw(&self) -> &mut BNDatabase {
+ &mut *self.handle.as_ptr()
+ }
+
+ /// Get a snapshot by its id, or None if no snapshot with that id exists
+ pub fn snapshot(&self, id: i64) -> Option<Snapshot> {
+ let result = unsafe { BNGetDatabaseSnapshot(self.as_raw(), id) };
+ ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) })
+ }
+
+ /// Get a list of all snapshots in the database
+ pub fn snapshots(&self) -> Array<Snapshot> {
+ let mut count = 0;
+ let result = unsafe { BNGetDatabaseSnapshots(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get the current snapshot
+ pub fn current_snapshot(&self) -> Option<Snapshot> {
+ let result = unsafe { BNGetDatabaseCurrentSnapshot(self.as_raw()) };
+ ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) })
+ }
+
+ pub fn set_current_snapshot(&self, value: &Snapshot) {
+ unsafe { BNSetDatabaseCurrentSnapshot(self.as_raw(), value.id()) }
+ }
+
+ pub fn write_snapshot_data<N: BnStrCompatible>(
+ &self,
+ parents: &[i64],
+ file: &BinaryView,
+ name: N,
+ data: &KeyValueStore,
+ auto_save: bool,
+ ) -> i64 {
+ let name_raw = name.into_bytes_with_nul();
+ let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe {
+ BNWriteDatabaseSnapshotData(
+ self.as_raw(),
+ parents.as_ptr() as *mut _,
+ parents.len(),
+ file.handle,
+ name_ptr,
+ data.as_raw(),
+ auto_save,
+ ptr::null_mut(),
+ Some(cb_progress_nop),
+ )
+ }
+ }
+
+ pub fn write_snapshot_data_with_progress<N, F>(
+ &self,
+ parents: &[i64],
+ file: &BinaryView,
+ name: N,
+ data: &KeyValueStore,
+ auto_save: bool,
+ mut progress: F,
+ ) -> i64
+ where
+ N: BnStrCompatible,
+ F: FnMut(usize, usize) -> bool,
+ {
+ let name_raw = name.into_bytes_with_nul();
+ let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char;
+ let ctxt = &mut progress as *mut _ as *mut ffi::c_void;
+ unsafe {
+ BNWriteDatabaseSnapshotData(
+ self.as_raw(),
+ parents.as_ptr() as *mut _,
+ parents.len(),
+ file.handle,
+ name_ptr,
+ data.as_raw(),
+ auto_save,
+ ctxt,
+ Some(cb_progress::<F>),
+ )
+ }
+ }
+
+ /// Trim a snapshot's contents in the database by id, but leave the parent/child
+ /// hierarchy intact. Future references to this snapshot will return False for has_contents
+ pub fn trim_snapshot(&self, id: i64) -> Result<(), ()> {
+ if unsafe { BNTrimDatabaseSnapshot(self.as_raw(), id) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ /// Remove a snapshot in the database by id, deleting its contents and references.
+ /// Attempting to remove a snapshot with children will raise an exception.
+ pub fn remove_snapshot(&self, id: i64) -> Result<(), ()> {
+ if unsafe { BNRemoveDatabaseSnapshot(self.as_raw(), id) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+ pub fn has_global<S: BnStrCompatible>(&self, key: S) -> bool {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNDatabaseHasGlobal(self.as_raw(), key_ptr) != 0 }
+ }
+
+ /// Get a list of keys for all globals in the database
+ pub fn global_keys(&self) -> Array<BnString> {
+ let mut count = 0;
+ let result = unsafe { BNGetDatabaseGlobalKeys(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get a dictionary of all globals
+ pub fn globals(&self) -> HashMap<String, String> {
+ self.global_keys()
+ .iter()
+ .map(|key| (key.to_string(), self.read_global(key).to_string()))
+ .collect()
+ }
+
+ /// Get a specific global by key
+ pub fn read_global<S: BnStrCompatible>(&self, key: S) -> BnString {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ let result = unsafe { BNReadDatabaseGlobal(self.as_raw(), key_ptr) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ /// Write a global into the database
+ pub fn write_global<K: BnStrCompatible, V: BnStrCompatible>(&self, key: K, value: V) -> bool {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ let value_raw = value.into_bytes_with_nul();
+ let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNWriteDatabaseGlobal(self.as_raw(), key_ptr, value_ptr) }
+ }
+
+ /// Get a specific global by key, as a binary buffer
+ pub fn read_global_data<S: BnStrCompatible>(&self, key: S) -> DataBuffer {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ let result = unsafe { BNReadDatabaseGlobalData(self.as_raw(), key_ptr) };
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ }
+
+ /// Write a binary buffer into a global in the database
+ pub fn write_global_data<K: BnStrCompatible>(&self, key: K, value: &DataBuffer) -> bool {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNWriteDatabaseGlobalData(self.as_raw(), key_ptr, value.as_raw()) }
+ }
+
+ /// Get the owning FileMetadata
+ pub fn file(&self) -> FileMetadata {
+ let result = unsafe { BNGetDatabaseFile(self.as_raw()) };
+ assert!(!result.is_null());
+ FileMetadata::from_raw(result)
+ }
+
+ /// Get the backing analysis cache kvs
+ pub fn analysis_cache(&self) -> KeyValueStore {
+ let result = unsafe { BNReadDatabaseAnalysisCache(self.as_raw()) };
+ unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) }
+ }
+
+ pub fn reload_connection(&self) {
+ unsafe { BNDatabaseReloadConnection(self.as_raw()) }
+ }
+
+ pub fn write_analysis_cache(&self, val: &KeyValueStore) -> Result<(), ()> {
+ if unsafe { BNWriteDatabaseAnalysisCache(self.as_raw(), val.as_raw()) } {
+ Ok(())
+ } else {
+ Err(())
+ }
+ }
+
+ pub fn snapshot_has_data(&self, id: i64) -> bool {
+ unsafe { BNSnapshotHasData(self.as_raw(), id) }
+ }
+}
+
+impl Clone for Database {
+ fn clone(&self) -> Self {
+ unsafe { Self::from_raw(ptr::NonNull::new(BNNewDatabaseReference(self.as_raw())).unwrap()) }
+ }
+}
+
+impl Drop for Database {
+ fn drop(&mut self) {
+ unsafe { BNFreeDatabase(self.as_raw()) }
+ }
+}
+
+#[repr(transparent)]
+pub struct Snapshot {
+ handle: ptr::NonNull<BNSnapshot>,
+}
+
+impl Snapshot {
+ pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNSnapshot>) -> Self {
+ Self { handle }
+ }
+
+ pub(crate) unsafe fn ref_from_raw(handle: &*mut BNSnapshot) -> &Self {
+ mem::transmute(handle)
+ }
+
+ #[allow(clippy::mut_from_ref)]
+ pub(crate) unsafe fn as_raw(&self) -> &mut BNSnapshot {
+ &mut *self.handle.as_ptr()
+ }
+
+ /// Get the owning database
+ pub fn database(&self) -> Database {
+ unsafe {
+ Database::from_raw(ptr::NonNull::new(BNGetSnapshotDatabase(self.as_raw())).unwrap())
+ }
+ }
+
+ /// Get the numerical id (read-only)
+ pub fn id(&self) -> i64 {
+ unsafe { BNGetSnapshotId(self.as_raw()) }
+ }
+
+ /// Get the displayed snapshot name
+ pub fn name(&self) -> BnString {
+ unsafe { BnString::from_raw(BNGetSnapshotName(self.as_raw())) }
+ }
+
+ /// Set the displayed snapshot name
+ pub fn set_name<S: BnStrCompatible>(&self, value: S) {
+ let value_raw = value.into_bytes_with_nul();
+ let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNSetSnapshotName(self.as_raw(), value_ptr) }
+ }
+
+ /// If the snapshot was the result of an auto-save
+ pub fn is_auto_save(&self) -> bool {
+ unsafe { BNIsSnapshotAutoSave(self.as_raw()) }
+ }
+
+ /// If the snapshot has contents, and has not been trimmed
+ pub fn has_contents(&self) -> bool {
+ unsafe { BNSnapshotHasContents(self.as_raw()) }
+ }
+
+ /// If the snapshot has undo data
+ pub fn has_undo(&self) -> bool {
+ unsafe { BNSnapshotHasUndo(self.as_raw()) }
+ }
+
+ /// Get the first parent of the snapshot, or None if it has no parents
+ pub fn first_parent(&self) -> Option<Snapshot> {
+ let result = unsafe { BNGetSnapshotFirstParent(self.as_raw()) };
+ ptr::NonNull::new(result).map(|s| unsafe { Snapshot::from_raw(s) })
+ }
+
+ /// Get a list of all parent snapshots of the snapshot
+ pub fn parents(&self) -> Array<Snapshot> {
+ let mut count = 0;
+ let result = unsafe { BNGetSnapshotParents(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get a list of all child snapshots of the snapshot
+ pub fn children(&self) -> Array<Snapshot> {
+ let mut count = 0;
+ let result = unsafe { BNGetSnapshotChildren(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get a buffer of the raw data at the time of the snapshot
+ pub fn file_contents(&self) -> Option<DataBuffer> {
+ self.has_contents().then(|| unsafe {
+ let result = BNGetSnapshotFileContents(self.as_raw());
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ })
+ }
+
+ /// Get a hash of the data at the time of the snapshot
+ pub fn file_contents_hash(&self) -> Option<DataBuffer> {
+ self.has_contents().then(|| unsafe {
+ let result = BNGetSnapshotFileContentsHash(self.as_raw());
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ })
+ }
+
+ /// Get a list of undo entries at the time of the snapshot
+ pub fn undo_entries(&self) -> Array<UndoEntry> {
+ assert!(self.has_undo());
+ let mut count = 0;
+ let result = unsafe { BNGetSnapshotUndoEntries(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ pub fn undo_entries_with_progress<F: FnMut(usize, usize) -> bool>(
+ &self,
+ mut progress: F,
+ ) -> Array<UndoEntry> {
+ assert!(self.has_undo());
+ let ctxt = &mut progress as *mut _ as *mut ffi::c_void;
+ let mut count = 0;
+ let result = unsafe {
+ BNGetSnapshotUndoEntriesWithProgress(
+ self.as_raw(),
+ ctxt,
+ Some(cb_progress::<F>),
+ &mut count,
+ )
+ };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get the backing kvs data with snapshot fields
+ pub fn read_data(&self) -> KeyValueStore {
+ let result = unsafe { BNReadSnapshotData(self.as_raw()) };
+ unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) }
+ }
+
+ pub fn read_data_with_progress<F: FnMut(usize, usize) -> bool>(
+ &self,
+ mut progress: F,
+ ) -> KeyValueStore {
+ let ctxt = &mut progress as *mut _ as *mut ffi::c_void;
+ let result =
+ unsafe { BNReadSnapshotDataWithProgress(self.as_raw(), ctxt, Some(cb_progress::<F>)) };
+ unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) }
+ }
+
+ pub fn undo_data(&self) -> DataBuffer {
+ let result = unsafe { BNGetSnapshotUndoData(self.as_raw()) };
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ }
+
+ pub fn store_data<F: FnMut(usize, usize) -> bool>(
+ &self,
+ data: KeyValueStore,
+ mut progress: F,
+ ) -> bool {
+ let ctxt = &mut progress as *mut _ as *mut ffi::c_void;
+ unsafe { BNSnapshotStoreData(self.as_raw(), data.as_raw(), ctxt, Some(cb_progress::<F>)) }
+ }
+
+ /// Determine if this snapshot has another as an ancestor
+ pub fn has_ancestor(self, other: &Snapshot) -> bool {
+ unsafe { BNSnapshotHasAncestor(self.as_raw(), other.as_raw()) }
+ }
+}
+
+impl Clone for Snapshot {
+ fn clone(&self) -> Self {
+ unsafe { Self::from_raw(ptr::NonNull::new(BNNewSnapshotReference(self.as_raw())).unwrap()) }
+ }
+}
+
+impl Drop for Snapshot {
+ fn drop(&mut self) {
+ unsafe { BNFreeSnapshot(self.as_raw()) }
+ }
+}
+
+impl CoreArrayProvider for Snapshot {
+ type Raw = *mut BNSnapshot;
+ type Context = ();
+ type Wrapped<'a> = &'a Self;
+}
+
+unsafe impl CoreArrayProviderInner for Snapshot {
+ unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
+ BNFreeSnapshotList(raw, count);
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Self::ref_from_raw(raw)
+ }
+}
+
+#[repr(transparent)]
+pub struct KeyValueStore {
+ handle: ptr::NonNull<BNKeyValueStore>,
+}
+
+impl KeyValueStore {
+ pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNKeyValueStore>) -> Self {
+ Self { handle }
+ }
+
+ #[allow(clippy::mut_from_ref)]
+ pub(crate) unsafe fn as_raw(&self) -> &mut BNKeyValueStore {
+ &mut *self.handle.as_ptr()
+ }
+
+ /// Get a list of all keys stored in the kvs
+ pub fn keys(&self) -> Array<BnString> {
+ let mut count = 0;
+ let result = unsafe { BNGetKeyValueStoreKeys(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ /// Get the value for a single key
+ pub fn value<S: BnStrCompatible>(&self, key: S) -> DataBuffer {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ let result = unsafe { BNGetKeyValueStoreBuffer(self.as_raw(), key_ptr) };
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ }
+
+ /// Set the value for a single key
+ pub fn set_value<S: BnStrCompatible>(&self, key: S, value: &DataBuffer) -> bool {
+ let key_raw = key.into_bytes_with_nul();
+ let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNSetKeyValueStoreBuffer(self.as_raw(), key_ptr, value.as_raw()) }
+ }
+
+ /// Get the stored representation of the kvs
+ pub fn serialized_data(&self) -> DataBuffer {
+ let result = unsafe { BNGetKeyValueStoreSerializedData(self.as_raw()) };
+ assert!(!result.is_null());
+ DataBuffer::from_raw(result)
+ }
+
+ /// Begin storing new keys into a namespace
+ pub fn begin_namespace<S: BnStrCompatible>(&self, name: S) {
+ let name_raw = name.into_bytes_with_nul();
+ let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char;
+ unsafe { BNBeginKeyValueStoreNamespace(self.as_raw(), name_ptr) }
+ }
+
+ /// End storing new keys into a namespace
+ pub fn end_namespace(&self) {
+ unsafe { BNEndKeyValueStoreNamespace(self.as_raw()) }
+ }
+
+ /// If the kvs is empty
+ pub fn empty(&self) -> bool {
+ unsafe { BNIsKeyValueStoreEmpty(self.as_raw()) }
+ }
+
+ /// Number of values in the kvs
+ pub fn value_size(&self) -> usize {
+ unsafe { BNGetKeyValueStoreValueSize(self.as_raw()) }
+ }
+
+ /// Length of serialized data
+ pub fn data_size(&self) -> usize {
+ unsafe { BNGetKeyValueStoreDataSize(self.as_raw()) }
+ }
+
+ /// Size of all data in storage
+ pub fn value_storage_size(self) -> usize {
+ unsafe { BNGetKeyValueStoreValueStorageSize(self.as_raw()) }
+ }
+
+ /// Number of namespaces pushed with begin_namespace
+ pub fn namespace_size(self) -> usize {
+ unsafe { BNGetKeyValueStoreNamespaceSize(self.as_raw()) }
+ }
+}
+
+impl Clone for KeyValueStore {
+ fn clone(&self) -> Self {
+ unsafe {
+ Self::from_raw(ptr::NonNull::new(BNNewKeyValueStoreReference(self.as_raw())).unwrap())
+ }
+ }
+}
+
+impl Drop for KeyValueStore {
+ fn drop(&mut self) {
+ unsafe { BNFreeKeyValueStore(self.as_raw()) }
+ }
+}
+
+#[repr(transparent)]
+pub struct UndoEntry {
+ handle: ptr::NonNull<BNUndoEntry>,
+}
+
+impl UndoEntry {
+ pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNUndoEntry>) -> Self {
+ Self { handle }
+ }
+
+ pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoEntry) -> &Self {
+ mem::transmute(handle)
+ }
+
+ #[allow(clippy::mut_from_ref)]
+ pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoEntry {
+ &mut *self.handle.as_ptr()
+ }
+
+ pub fn id(&self) -> BnString {
+ let result = unsafe { BNUndoEntryGetId(self.as_raw()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ pub fn actions(&self) -> Array<UndoAction> {
+ let mut count = 0;
+ let result = unsafe { BNUndoEntryGetActions(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ pub fn time(&self) -> SystemTime {
+ let m = Duration::from_secs(unsafe { BNUndoEntryGetTimestamp(self.as_raw()) });
+ UNIX_EPOCH + m
+ }
+}
+
+impl Clone for UndoEntry {
+ fn clone(&self) -> Self {
+ unsafe {
+ Self::from_raw(ptr::NonNull::new(BNNewUndoEntryReference(self.as_raw())).unwrap())
+ }
+ }
+}
+
+impl Drop for UndoEntry {
+ fn drop(&mut self) {
+ unsafe { BNFreeUndoEntry(self.as_raw()) }
+ }
+}
+
+impl CoreArrayProvider for UndoEntry {
+ type Raw = *mut BNUndoEntry;
+ type Context = ();
+ type Wrapped<'a> = &'a Self;
+}
+
+unsafe impl CoreArrayProviderInner for UndoEntry {
+ unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
+ BNFreeUndoEntryList(raw, count);
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Self::ref_from_raw(raw)
+ }
+}
+
+#[repr(transparent)]
+pub struct UndoAction {
+ handle: ptr::NonNull<BNUndoAction>,
+}
+
+impl UndoAction {
+ pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNUndoAction>) -> Self {
+ Self { handle }
+ }
+
+ pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoAction) -> &Self {
+ mem::transmute(handle)
+ }
+
+ #[allow(clippy::mut_from_ref)]
+ pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoAction {
+ &mut *self.handle.as_ptr()
+ }
+
+ pub fn summary_text(&self) -> BnString {
+ let result = unsafe { BNUndoActionGetSummaryText(self.as_raw()) };
+ assert!(!result.is_null());
+ unsafe { BnString::from_raw(result) }
+ }
+
+ pub fn summary(&self) -> Array<InstructionTextToken> {
+ let mut count = 0;
+ let result = unsafe { BNUndoActionGetSummary(self.as_raw(), &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
+ }
+}
+
+impl Clone for UndoAction {
+ fn clone(&self) -> Self {
+ unsafe {
+ Self::from_raw(ptr::NonNull::new(BNNewUndoActionReference(self.as_raw())).unwrap())
+ }
+ }
+}
+
+impl Drop for UndoAction {
+ fn drop(&mut self) {
+ unsafe { BNFreeUndoAction(self.as_raw()) }
+ }
+}
+
+impl CoreArrayProvider for UndoAction {
+ type Raw = *mut BNUndoAction;
+ type Context = ();
+ type Wrapped<'a> = &'a Self;
+}
+
+unsafe impl CoreArrayProviderInner for UndoAction {
+ unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
+ BNFreeUndoActionList(raw, count);
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Self::ref_from_raw(raw)
+ }
+}
+
+unsafe extern "C" fn cb_progress<F: FnMut(usize, usize) -> bool>(
+ ctxt: *mut ffi::c_void,
+ arg1: usize,
+ arg2: usize,
+) -> bool {
+ let ctxt: &mut F = &mut *(ctxt as *mut F);
+ ctxt(arg1, arg2)
+}
+
+unsafe extern "C" fn cb_progress_nop(_ctxt: *mut ffi::c_void, _arg1: usize, _arg2: usize) -> bool {
+ true
+}