pub mod file;
pub mod folder;
use std::ffi::c_void;
use std::fmt::Debug;
use std::path::Path;
use std::ptr::{null_mut, NonNull};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use binaryninjacore_sys::*;
use crate::metadata::Metadata;
use crate::progress::{NoProgressCallback, ProgressCallback};
use crate::project::file::ProjectFile;
use crate::project::folder::ProjectFolder;
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
use crate::string::{BnString, IntoCStr};
pub struct Project {
pub(crate) handle: NonNull<BNProject>,
}
impl Project {
pub unsafe fn from_raw(handle: NonNull<BNProject>) -> Self {
Project { handle }
}
pub unsafe fn ref_from_raw(handle: NonNull<BNProject>) -> Ref<Self> {
Ref::new(Self { handle })
}
pub fn all_open() -> Array<Project> {
let mut count = 0;
let result = unsafe { BNGetOpenProjects(&mut count) };
assert!(!result.is_null());
unsafe { Array::new(result, count, ()) }
}
pub fn create(path: impl AsRef<Path>, name: &str) -> Option<Ref<Self>> {
let path_raw = path.as_ref().to_cstr();
let name_raw = name.to_cstr();
let handle = unsafe { BNCreateProject(path_raw.as_ptr(), name_raw.as_ptr()) };
NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
}
pub fn open_project(path: impl AsRef<Path>) -> Option<Ref<Self>> {
let path_raw = path.as_ref().to_cstr();
let handle = unsafe { BNOpenProject(path_raw.as_ptr()) };
NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
}
pub fn is_open(&self) -> bool {
unsafe { BNProjectIsOpen(self.handle.as_ptr()) }
}
pub fn open(&self) -> Result<(), ()> {
if unsafe { BNProjectOpen(self.handle.as_ptr()) } {
Ok(())
} else {
Err(())
}
}
pub fn close(&self) -> Result<(), ()> {
if unsafe { BNProjectClose(self.handle.as_ptr()) } {
Ok(())
} else {
Err(())
}
}
pub fn id(&self) -> String {
unsafe { BnString::into_string(BNProjectGetId(self.handle.as_ptr())) }
}
pub fn path(&self) -> String {
unsafe { BnString::into_string(BNProjectGetPath(self.handle.as_ptr())) }
}
pub fn name(&self) -> String {
unsafe { BnString::into_string(BNProjectGetName(self.handle.as_ptr())) }
}
pub fn set_name(&self, value: &str) -> bool {
let value = value.to_cstr();
unsafe { BNProjectSetName(self.handle.as_ptr(), value.as_ptr()) }
}
pub fn description(&self) -> String {
unsafe { BnString::into_string(BNProjectGetDescription(self.handle.as_ptr())) }
}
pub fn set_description(&self, value: &str) -> bool {
let value = value.to_cstr();
unsafe { BNProjectSetDescription(self.handle.as_ptr(), value.as_ptr()) }
}
pub fn query_metadata(&self, key: &str) -> Ref<Metadata> {
let key = key.to_cstr();
let result = unsafe { BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ptr()) };
unsafe { Metadata::ref_from_raw(result) }
}
pub fn store_metadata(&self, key: &str, value: &Metadata) -> bool {
let key_raw = key.to_cstr();
unsafe { BNProjectStoreMetadata(self.handle.as_ptr(), key_raw.as_ptr(), value.handle) }
}
pub fn remove_metadata(&self, key: &str) -> bool {
let key_raw = key.to_cstr();
unsafe { BNProjectRemoveMetadata(self.handle.as_ptr(), key_raw.as_ptr()) }
}
pub fn push_folder(&self, file: &ProjectFolder) -> bool {
unsafe { BNProjectPushFolder(self.handle.as_ptr(), file.handle.as_ptr()) }
}
pub fn create_folder_from_path(
&self,
path: impl AsRef<Path>,
parent: Option<&ProjectFolder>,
description: &str,
) -> Result<Ref<ProjectFolder>, ()> {
self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback)
}
pub fn create_folder_from_path_with_progress<PC>(
&self,
path: impl AsRef<Path>,
parent: Option<&ProjectFolder>,
description: &str,
mut progress: PC,
) -> Result<Ref<ProjectFolder>, ()>
where
PC: ProgressCallback,
{
let path_raw = path.as_ref().to_cstr();
let description_raw = description.to_cstr();
let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFolderFromPath(
self.handle.as_ptr(),
path_raw.as_ptr(),
parent_ptr,
description_raw.as_ptr(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
);
Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub fn create_folder(
&self,
parent: Option<&ProjectFolder>,
name: &str,
description: &str,
) -> Result<Ref<ProjectFolder>, ()> {
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFolder(
self.handle.as_ptr(),
parent_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
);
Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub unsafe fn create_folder_unsafe(
&self,
parent: Option<&ProjectFolder>,
name: &str,
description: &str,
id: &str,
) -> Result<Ref<ProjectFolder>, ()> {
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
let id_raw = id.to_cstr();
unsafe {
let result = BNProjectCreateFolderUnsafe(
self.handle.as_ptr(),
parent_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
id_raw.as_ptr(),
);
Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub fn folders(&self) -> Array<ProjectFolder> {
let mut count = 0;
let result = unsafe { BNProjectGetFolders(self.handle.as_ptr(), &mut count) };
assert!(!result.is_null());
unsafe { Array::new(result, count, ()) }
}
pub fn folder_by_id(&self, id: &str) -> Option<Ref<ProjectFolder>> {
let raw_id = id.to_cstr();
let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), raw_id.as_ptr()) };
let handle = NonNull::new(result)?;
Some(unsafe { ProjectFolder::ref_from_raw(handle) })
}
pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> {
self.delete_folder_with_progress(folder, NoProgressCallback)
}
pub fn delete_folder_with_progress<PC: ProgressCallback>(
&self,
folder: &ProjectFolder,
mut progress: PC,
) -> Result<(), ()> {
let result = unsafe {
BNProjectDeleteFolder(
self.handle.as_ptr(),
folder.handle.as_ptr(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
)
};
if result {
Ok(())
} else {
Err(())
}
}
pub fn push_file(&self, file: &ProjectFile) -> bool {
unsafe { BNProjectPushFile(self.handle.as_ptr(), file.handle.as_ptr()) }
}
pub fn create_file_from_path(
&self,
path: impl AsRef<Path>,
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
) -> Result<Ref<ProjectFile>, ()> {
self.create_file_from_path_with_progress(
path,
folder,
name,
description,
NoProgressCallback,
)
}
pub fn create_file_from_path_with_progress<PC>(
&self,
path: impl AsRef<Path>,
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
mut progress: PC,
) -> Result<Ref<ProjectFile>, ()>
where
PC: ProgressCallback,
{
let path_raw = path.as_ref().to_cstr();
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFileFromPath(
self.handle.as_ptr(),
path_raw.as_ptr(),
folder_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
);
Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub unsafe fn create_file_from_path_unsafe(
&self,
path: impl AsRef<Path>,
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
id: &str,
creation_time: SystemTime,
) -> Result<Ref<ProjectFile>, ()> {
self.create_file_from_path_unsafe_with_progress(
path,
folder,
name,
description,
id,
creation_time,
NoProgressCallback,
)
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn create_file_from_path_unsafe_with_progress<PC>(
&self,
path: impl AsRef<Path>,
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
id: &str,
creation_time: SystemTime,
mut progress: PC,
) -> Result<Ref<ProjectFile>, ()>
where
PC: ProgressCallback,
{
let path_raw = path.as_ref().to_cstr();
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let id_raw = id.to_cstr();
let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFileFromPathUnsafe(
self.handle.as_ptr(),
path_raw.as_ptr(),
folder_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
id_raw.as_ptr(),
systime_to_bntime(creation_time).unwrap(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
);
Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub fn create_file(
&self,
contents: &[u8],
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
) -> Result<Ref<ProjectFile>, ()> {
self.create_file_with_progress(contents, folder, name, description, NoProgressCallback)
}
pub fn create_file_with_progress<PC>(
&self,
contents: &[u8],
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
mut progress: PC,
) -> Result<Ref<ProjectFile>, ()>
where
PC: ProgressCallback,
{
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFile(
self.handle.as_ptr(),
contents.as_ptr(),
contents.len(),
folder_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
);
Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub unsafe fn create_file_unsafe(
&self,
contents: &[u8],
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
id: &str,
creation_time: SystemTime,
) -> Result<Ref<ProjectFile>, ()> {
self.create_file_unsafe_with_progress(
contents,
folder,
name,
description,
id,
creation_time,
NoProgressCallback,
)
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn create_file_unsafe_with_progress<PC>(
&self,
contents: &[u8],
folder: Option<&ProjectFolder>,
name: &str,
description: &str,
id: &str,
creation_time: SystemTime,
mut progress: PC,
) -> Result<Ref<ProjectFile>, ()>
where
PC: ProgressCallback,
{
let name_raw = name.to_cstr();
let description_raw = description.to_cstr();
let id_raw = id.to_cstr();
let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
unsafe {
let result = BNProjectCreateFileUnsafe(
self.handle.as_ptr(),
contents.as_ptr(),
contents.len(),
folder_ptr,
name_raw.as_ptr(),
description_raw.as_ptr(),
id_raw.as_ptr(),
systime_to_bntime(creation_time).unwrap(),
&mut progress as *mut PC as *mut c_void,
Some(PC::cb_progress_callback),
);
Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
}
}
pub fn files(&self) -> Array<ProjectFile> {
let mut count = 0;
let result = unsafe { BNProjectGetFiles(self.handle.as_ptr(), &mut count) };
assert!(!result.is_null());
unsafe { Array::new(result, count, ()) }
}
pub fn file_by_id(&self, id: &str) -> Option<Ref<ProjectFile>> {
let raw_id = id.to_cstr();
let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), raw_id.as_ptr()) };
let handle = NonNull::new(result)?;
Some(unsafe { ProjectFile::ref_from_raw(handle) })
}
pub fn file_by_path(&self, path: &Path) -> Option<Ref<ProjectFile>> {
let path_raw = path.to_cstr();
let result =
unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_raw.as_ptr()) };
let handle = NonNull::new(result)?;
Some(unsafe { ProjectFile::ref_from_raw(handle) })
}
pub fn files_by_project_path(&self, path: &Path) -> Array<ProjectFile> {
let path_raw = path.to_cstr();
let mut count = 0;
let result = unsafe {
BNProjectGetFilesByPathInProject(self.handle.as_ptr(), path_raw.as_ptr(), &mut count)
};
unsafe { Array::new(result, count, ()) }
}
pub fn files_in_folder(&self, folder: Option<&ProjectFolder>) -> Array<ProjectFile> {
let folder_ptr = folder.map(|f| f.handle.as_ptr()).unwrap_or(null_mut());
let mut count = 0;
let result =
unsafe { BNProjectGetFilesInFolder(self.handle.as_ptr(), folder_ptr, &mut count) };
unsafe { Array::new(result, count, ()) }
}
pub fn delete_file(&self, file: &ProjectFile) -> bool {
unsafe { BNProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) }
}
pub fn bulk_operation(&mut self) -> Result<ProjectBulkOperationLock<'_>, ()> {
Ok(ProjectBulkOperationLock::lock(self))
}
}
impl Debug for Project {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Project")
.field("id", &self.id())
.field("name", &self.name())
.field("description", &self.description())
.finish()
}
}
impl ToOwned for Project {
type Owned = Ref<Self>;
fn to_owned(&self) -> Self::Owned {
unsafe { RefCountable::inc_ref(self) }
}
}
unsafe impl RefCountable for Project {
unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
Ref::new(Self {
handle: NonNull::new(BNNewProjectReference(handle.handle.as_ptr())).unwrap(),
})
}
unsafe fn dec_ref(handle: &Self) {
BNFreeProject(handle.handle.as_ptr());
}
}
unsafe impl Send for Project {}
unsafe impl Sync for Project {}
impl CoreArrayProvider for Project {
type Raw = *mut BNProject;
type Context = ();
type Wrapped<'a> = Guard<'a, Project>;
}
unsafe impl CoreArrayProviderInner for Project {
unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
BNFreeProjectList(raw, count)
}
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
let raw_ptr = NonNull::new(*raw).unwrap();
Guard::new(Self::from_raw(raw_ptr), context)
}
}
pub struct ProjectBulkOperationLock<'a> {
lock: &'a mut Project,
}
impl<'a> ProjectBulkOperationLock<'a> {
pub fn lock(project: &'a mut Project) -> Self {
unsafe { BNProjectBeginBulkOperation(project.handle.as_ptr()) };
Self { lock: project }
}
pub fn unlock(self) {
}
}
impl std::ops::Deref for ProjectBulkOperationLock<'_> {
type Target = Project;
fn deref(&self) -> &Self::Target {
self.lock
}
}
impl Drop for ProjectBulkOperationLock<'_> {
fn drop(&mut self) {
unsafe { BNProjectEndBulkOperation(self.lock.handle.as_ptr()) };
}
}
fn systime_from_bntime(time: i64) -> Option<SystemTime> {
let m = Duration::from_secs(time.try_into().ok()?);
Some(UNIX_EPOCH + m)
}
fn systime_to_bntime(time: SystemTime) -> Option<i64> {
time.duration_since(UNIX_EPOCH)
.ok()?
.as_secs()
.try_into()
.ok()
}