1use std::ffi::c_void;
4use std::fmt::Debug;
5use std::hash::Hash;
6use std::path::{Path, PathBuf};
7use std::ptr::{null_mut, NonNull};
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9
10use binaryninjacore_sys::*;
11
12use crate::metadata::Metadata;
13use crate::progress::{NoProgressCallback, ProgressCallback};
14use crate::project::file::ProjectFile;
15use crate::project::folder::ProjectFolder;
16use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
17use crate::string::{BnString, IntoCStr};
18
19pub mod file;
20pub mod folder;
21
22pub struct Project {
23 pub(crate) handle: NonNull<BNProject>,
24}
25
26impl Project {
27 pub unsafe fn from_raw(handle: NonNull<BNProject>) -> Self {
28 Project { handle }
29 }
30
31 pub unsafe fn ref_from_raw(handle: NonNull<BNProject>) -> Ref<Self> {
32 Ref::new(Self { handle })
33 }
34
35 pub fn all_open() -> Array<Project> {
37 let mut count = 0;
38 let result = unsafe { BNGetOpenProjects(&mut count) };
39 assert!(!result.is_null());
40 unsafe { Array::new(result, count, ()) }
41 }
42
43 pub fn create(path: impl AsRef<Path>, name: &str) -> Option<Ref<Self>> {
48 let path_raw = path.as_ref().to_cstr();
49 let name_raw = name.to_cstr();
50 let handle = unsafe { BNCreateProject(path_raw.as_ptr(), name_raw.as_ptr()) };
51 NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
52 }
53
54 pub fn open_project(path: impl AsRef<Path>) -> Option<Ref<Self>> {
58 let path_raw = path.as_ref().to_cstr();
59 let handle = unsafe { BNOpenProject(path_raw.as_ptr()) };
60 NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
61 }
62
63 pub fn is_open(&self) -> bool {
65 unsafe { BNProjectIsOpen(self.handle.as_ptr()) }
66 }
67
68 pub fn open(&self) -> Result<(), ()> {
70 if unsafe { BNProjectOpen(self.handle.as_ptr()) } {
71 Ok(())
72 } else {
73 Err(())
74 }
75 }
76
77 pub fn close(&self) -> Result<(), ()> {
79 if unsafe { BNProjectClose(self.handle.as_ptr()) } {
80 Ok(())
81 } else {
82 Err(())
83 }
84 }
85
86 pub fn id(&self) -> String {
88 unsafe { BnString::into_string(BNProjectGetId(self.handle.as_ptr())) }
89 }
90
91 pub fn path(&self) -> PathBuf {
93 let path_str = unsafe { BnString::into_string(BNProjectGetPath(self.handle.as_ptr())) };
94 PathBuf::from(path_str)
95 }
96
97 pub fn name(&self) -> String {
99 unsafe { BnString::into_string(BNProjectGetName(self.handle.as_ptr())) }
100 }
101
102 pub fn set_name(&self, value: &str) -> bool {
104 let value = value.to_cstr();
105 unsafe { BNProjectSetName(self.handle.as_ptr(), value.as_ptr()) }
106 }
107
108 pub fn description(&self) -> String {
110 unsafe { BnString::into_string(BNProjectGetDescription(self.handle.as_ptr())) }
111 }
112
113 pub fn set_description(&self, value: &str) -> bool {
115 let value = value.to_cstr();
116 unsafe { BNProjectSetDescription(self.handle.as_ptr(), value.as_ptr()) }
117 }
118
119 pub fn query_metadata(&self, key: &str) -> Ref<Metadata> {
121 let key = key.to_cstr();
122 let result = unsafe { BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ptr()) };
123 unsafe { Metadata::ref_from_raw(result) }
124 }
125
126 pub fn store_metadata(&self, key: &str, value: &Metadata) -> bool {
131 let key_raw = key.to_cstr();
132 unsafe { BNProjectStoreMetadata(self.handle.as_ptr(), key_raw.as_ptr(), value.handle) }
133 }
134
135 pub fn remove_metadata(&self, key: &str) -> bool {
137 let key_raw = key.to_cstr();
138 unsafe { BNProjectRemoveMetadata(self.handle.as_ptr(), key_raw.as_ptr()) }
139 }
140
141 pub fn push_folder(&self, file: &ProjectFolder) -> bool {
143 unsafe { BNProjectPushFolder(self.handle.as_ptr(), file.handle.as_ptr()) }
144 }
145
146 pub fn create_folder_from_path(
152 &self,
153 path: impl AsRef<Path>,
154 parent: Option<&ProjectFolder>,
155 description: &str,
156 ) -> Result<Ref<ProjectFolder>, ()> {
157 self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback)
158 }
159
160 pub fn create_folder_from_path_with_progress<PC>(
167 &self,
168 path: impl AsRef<Path>,
169 parent: Option<&ProjectFolder>,
170 description: &str,
171 mut progress: PC,
172 ) -> Result<Ref<ProjectFolder>, ()>
173 where
174 PC: ProgressCallback,
175 {
176 let path_raw = path.as_ref().to_cstr();
177 let description_raw = description.to_cstr();
178 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
179
180 unsafe {
181 let result = BNProjectCreateFolderFromPath(
182 self.handle.as_ptr(),
183 path_raw.as_ptr(),
184 parent_ptr,
185 description_raw.as_ptr(),
186 &mut progress as *mut PC as *mut c_void,
187 Some(PC::cb_progress_callback),
188 );
189 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
190 }
191 }
192
193 pub fn create_folder(
199 &self,
200 parent: Option<&ProjectFolder>,
201 name: &str,
202 description: &str,
203 ) -> Result<Ref<ProjectFolder>, ()> {
204 let name_raw = name.to_cstr();
205 let description_raw = description.to_cstr();
206 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
207 unsafe {
208 let result = BNProjectCreateFolder(
209 self.handle.as_ptr(),
210 parent_ptr,
211 name_raw.as_ptr(),
212 description_raw.as_ptr(),
213 );
214 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
215 }
216 }
217
218 pub unsafe fn create_folder_unsafe(
226 &self,
227 parent: Option<&ProjectFolder>,
228 name: &str,
229 description: &str,
230 id: &str,
231 ) -> Result<Ref<ProjectFolder>, ()> {
232 let name_raw = name.to_cstr();
233 let description_raw = description.to_cstr();
234 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
235 let id_raw = id.to_cstr();
236 unsafe {
237 let result = BNProjectCreateFolderUnsafe(
238 self.handle.as_ptr(),
239 parent_ptr,
240 name_raw.as_ptr(),
241 description_raw.as_ptr(),
242 id_raw.as_ptr(),
243 );
244 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
245 }
246 }
247
248 pub fn folders(&self) -> Array<ProjectFolder> {
250 let mut count = 0;
251 let result = unsafe { BNProjectGetFolders(self.handle.as_ptr(), &mut count) };
252 assert!(!result.is_null());
253 unsafe { Array::new(result, count, ()) }
254 }
255
256 pub fn folder_by_id(&self, id: &str) -> Option<Ref<ProjectFolder>> {
258 let raw_id = id.to_cstr();
259 let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), raw_id.as_ptr()) };
260 let handle = NonNull::new(result)?;
261 Some(unsafe { ProjectFolder::ref_from_raw(handle) })
262 }
263
264 pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> {
268 self.delete_folder_with_progress(folder, NoProgressCallback)
269 }
270
271 pub fn delete_folder_with_progress<PC: ProgressCallback>(
276 &self,
277 folder: &ProjectFolder,
278 mut progress: PC,
279 ) -> Result<(), ()> {
280 let result = unsafe {
281 BNProjectDeleteFolder(
282 self.handle.as_ptr(),
283 folder.handle.as_ptr(),
284 &mut progress as *mut PC as *mut c_void,
285 Some(PC::cb_progress_callback),
286 )
287 };
288
289 if result {
290 Ok(())
291 } else {
292 Err(())
293 }
294 }
295
296 pub fn push_file(&self, file: &ProjectFile) -> bool {
298 unsafe { BNProjectPushFile(self.handle.as_ptr(), file.handle.as_ptr()) }
299 }
300
301 pub fn create_file_from_path(
308 &self,
309 path: impl AsRef<Path>,
310 folder: Option<&ProjectFolder>,
311 name: &str,
312 description: &str,
313 ) -> Result<Ref<ProjectFile>, ()> {
314 self.create_file_from_path_with_progress(
315 path,
316 folder,
317 name,
318 description,
319 NoProgressCallback,
320 )
321 }
322
323 pub fn create_file_from_path_with_progress<PC>(
331 &self,
332 path: impl AsRef<Path>,
333 folder: Option<&ProjectFolder>,
334 name: &str,
335 description: &str,
336 mut progress: PC,
337 ) -> Result<Ref<ProjectFile>, ()>
338 where
339 PC: ProgressCallback,
340 {
341 let path_raw = path.as_ref().to_cstr();
342 let name_raw = name.to_cstr();
343 let description_raw = description.to_cstr();
344 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
345
346 unsafe {
347 let result = BNProjectCreateFileFromPath(
348 self.handle.as_ptr(),
349 path_raw.as_ptr(),
350 folder_ptr,
351 name_raw.as_ptr(),
352 description_raw.as_ptr(),
353 &mut progress as *mut PC as *mut c_void,
354 Some(PC::cb_progress_callback),
355 );
356 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
357 }
358 }
359
360 pub unsafe fn create_file_from_path_unsafe(
369 &self,
370 path: impl AsRef<Path>,
371 folder: Option<&ProjectFolder>,
372 name: &str,
373 description: &str,
374 id: &str,
375 creation_time: SystemTime,
376 ) -> Result<Ref<ProjectFile>, ()> {
377 self.create_file_from_path_unsafe_with_progress(
378 path,
379 folder,
380 name,
381 description,
382 id,
383 creation_time,
384 NoProgressCallback,
385 )
386 }
387
388 #[allow(clippy::too_many_arguments)]
398 pub unsafe fn create_file_from_path_unsafe_with_progress<PC>(
399 &self,
400 path: impl AsRef<Path>,
401 folder: Option<&ProjectFolder>,
402 name: &str,
403 description: &str,
404 id: &str,
405 creation_time: SystemTime,
406 mut progress: PC,
407 ) -> Result<Ref<ProjectFile>, ()>
408 where
409 PC: ProgressCallback,
410 {
411 let path_raw = path.as_ref().to_cstr();
412 let name_raw = name.to_cstr();
413 let description_raw = description.to_cstr();
414 let id_raw = id.to_cstr();
415 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
416
417 unsafe {
418 let result = BNProjectCreateFileFromPathUnsafe(
419 self.handle.as_ptr(),
420 path_raw.as_ptr(),
421 folder_ptr,
422 name_raw.as_ptr(),
423 description_raw.as_ptr(),
424 id_raw.as_ptr(),
425 systime_to_bntime(creation_time).unwrap(),
426 &mut progress as *mut PC as *mut c_void,
427 Some(PC::cb_progress_callback),
428 );
429 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
430 }
431 }
432
433 pub fn create_file(
440 &self,
441 contents: &[u8],
442 folder: Option<&ProjectFolder>,
443 name: &str,
444 description: &str,
445 ) -> Result<Ref<ProjectFile>, ()> {
446 self.create_file_with_progress(contents, folder, name, description, NoProgressCallback)
447 }
448
449 pub fn create_file_with_progress<PC>(
457 &self,
458 contents: &[u8],
459 folder: Option<&ProjectFolder>,
460 name: &str,
461 description: &str,
462 mut progress: PC,
463 ) -> Result<Ref<ProjectFile>, ()>
464 where
465 PC: ProgressCallback,
466 {
467 let name_raw = name.to_cstr();
468 let description_raw = description.to_cstr();
469 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
470
471 unsafe {
472 let result = BNProjectCreateFile(
473 self.handle.as_ptr(),
474 contents.as_ptr(),
475 contents.len(),
476 folder_ptr,
477 name_raw.as_ptr(),
478 description_raw.as_ptr(),
479 &mut progress as *mut PC as *mut c_void,
480 Some(PC::cb_progress_callback),
481 );
482 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
483 }
484 }
485
486 pub unsafe fn create_file_unsafe(
495 &self,
496 contents: &[u8],
497 folder: Option<&ProjectFolder>,
498 name: &str,
499 description: &str,
500 id: &str,
501 creation_time: SystemTime,
502 ) -> Result<Ref<ProjectFile>, ()> {
503 self.create_file_unsafe_with_progress(
504 contents,
505 folder,
506 name,
507 description,
508 id,
509 creation_time,
510 NoProgressCallback,
511 )
512 }
513
514 #[allow(clippy::too_many_arguments)]
524 pub unsafe fn create_file_unsafe_with_progress<PC>(
525 &self,
526 contents: &[u8],
527 folder: Option<&ProjectFolder>,
528 name: &str,
529 description: &str,
530 id: &str,
531 creation_time: SystemTime,
532 mut progress: PC,
533 ) -> Result<Ref<ProjectFile>, ()>
534 where
535 PC: ProgressCallback,
536 {
537 let name_raw = name.to_cstr();
538 let description_raw = description.to_cstr();
539 let id_raw = id.to_cstr();
540 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
541
542 unsafe {
543 let result = BNProjectCreateFileUnsafe(
544 self.handle.as_ptr(),
545 contents.as_ptr(),
546 contents.len(),
547 folder_ptr,
548 name_raw.as_ptr(),
549 description_raw.as_ptr(),
550 id_raw.as_ptr(),
551 systime_to_bntime(creation_time).unwrap(),
552 &mut progress as *mut PC as *mut c_void,
553 Some(PC::cb_progress_callback),
554 );
555 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
556 }
557 }
558
559 pub fn files(&self) -> Array<ProjectFile> {
561 let mut count = 0;
562 let result = unsafe { BNProjectGetFiles(self.handle.as_ptr(), &mut count) };
563 assert!(!result.is_null());
564 unsafe { Array::new(result, count, ()) }
565 }
566
567 pub fn file_by_id(&self, id: &str) -> Option<Ref<ProjectFile>> {
569 let raw_id = id.to_cstr();
570 let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), raw_id.as_ptr()) };
571 let handle = NonNull::new(result)?;
572 Some(unsafe { ProjectFile::ref_from_raw(handle) })
573 }
574
575 pub fn file_by_path(&self, path: &Path) -> Option<Ref<ProjectFile>> {
577 let path_raw = path.to_cstr();
578 let result =
579 unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_raw.as_ptr()) };
580 let handle = NonNull::new(result)?;
581 Some(unsafe { ProjectFile::ref_from_raw(handle) })
582 }
583
584 pub fn files_by_project_path(&self, path: &Path) -> Array<ProjectFile> {
588 let path_raw = path.to_cstr();
589 let mut count = 0;
590 let result = unsafe {
591 BNProjectGetFilesByPathInProject(self.handle.as_ptr(), path_raw.as_ptr(), &mut count)
592 };
593 unsafe { Array::new(result, count, ()) }
594 }
595
596 pub fn files_in_folder(&self, folder: Option<&ProjectFolder>) -> Array<ProjectFile> {
598 let folder_ptr = folder.map(|f| f.handle.as_ptr()).unwrap_or(null_mut());
599 let mut count = 0;
600 let result =
601 unsafe { BNProjectGetFilesInFolder(self.handle.as_ptr(), folder_ptr, &mut count) };
602 unsafe { Array::new(result, count, ()) }
603 }
604
605 pub fn delete_file(&self, file: &ProjectFile) -> bool {
607 unsafe { BNProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) }
608 }
609
610 pub fn bulk_operation(&mut self) -> Result<ProjectBulkOperationLock<'_>, ()> {
631 Ok(ProjectBulkOperationLock::lock(self))
632 }
633}
634
635impl Debug for Project {
636 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637 f.debug_struct("Project")
638 .field("id", &self.id())
639 .field("name", &self.name())
640 .field("description", &self.description())
641 .finish()
642 }
643}
644
645impl PartialEq for Project {
646 fn eq(&self, other: &Self) -> bool {
647 self.id() == other.id()
648 }
649}
650
651impl Eq for Project {}
652
653impl Hash for Project {
654 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
655 self.id().hash(state);
656 }
657}
658
659impl ToOwned for Project {
660 type Owned = Ref<Self>;
661
662 fn to_owned(&self) -> Self::Owned {
663 unsafe { RefCountable::inc_ref(self) }
664 }
665}
666
667unsafe impl RefCountable for Project {
668 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
669 Ref::new(Self {
670 handle: NonNull::new(BNNewProjectReference(handle.handle.as_ptr())).unwrap(),
671 })
672 }
673
674 unsafe fn dec_ref(handle: &Self) {
675 BNFreeProject(handle.handle.as_ptr());
676 }
677}
678
679unsafe impl Send for Project {}
680unsafe impl Sync for Project {}
681
682impl CoreArrayProvider for Project {
683 type Raw = *mut BNProject;
684 type Context = ();
685 type Wrapped<'a> = Guard<'a, Project>;
686}
687
688unsafe impl CoreArrayProviderInner for Project {
689 unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
690 BNFreeProjectList(raw, count)
691 }
692
693 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
694 let raw_ptr = NonNull::new(*raw).unwrap();
695 Guard::new(Self::from_raw(raw_ptr), context)
696 }
697}
698
699pub struct ProjectBulkOperationLock<'a> {
701 lock: &'a mut Project,
702}
703
704impl<'a> ProjectBulkOperationLock<'a> {
705 pub fn lock(project: &'a mut Project) -> Self {
706 unsafe { BNProjectBeginBulkOperation(project.handle.as_ptr()) };
707 Self { lock: project }
708 }
709
710 pub fn unlock(self) {
711 }
713}
714
715impl std::ops::Deref for ProjectBulkOperationLock<'_> {
716 type Target = Project;
717 fn deref(&self) -> &Self::Target {
718 self.lock
719 }
720}
721
722impl Drop for ProjectBulkOperationLock<'_> {
723 fn drop(&mut self) {
724 unsafe { BNProjectEndBulkOperation(self.lock.handle.as_ptr()) };
725 }
726}
727
728fn systime_from_bntime(time: i64) -> Option<SystemTime> {
729 let m = Duration::from_secs(time.try_into().ok()?);
730 Some(UNIX_EPOCH + m)
731}
732
733fn systime_to_bntime(time: SystemTime) -> Option<i64> {
734 time.duration_since(UNIX_EPOCH)
735 .ok()?
736 .as_secs()
737 .try_into()
738 .ok()
739}