1pub mod file;
2pub mod folder;
3
4use std::ffi::c_void;
5use std::fmt::Debug;
6use std::path::Path;
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 struct Project {
20 pub(crate) handle: NonNull<BNProject>,
21}
22
23impl Project {
24 pub unsafe fn from_raw(handle: NonNull<BNProject>) -> Self {
25 Project { handle }
26 }
27
28 pub unsafe fn ref_from_raw(handle: NonNull<BNProject>) -> Ref<Self> {
29 Ref::new(Self { handle })
30 }
31
32 pub fn all_open() -> Array<Project> {
33 let mut count = 0;
34 let result = unsafe { BNGetOpenProjects(&mut count) };
35 assert!(!result.is_null());
36 unsafe { Array::new(result, count, ()) }
37 }
38
39 pub fn create(path: impl AsRef<Path>, name: &str) -> Option<Ref<Self>> {
45 let path_raw = path.as_ref().to_cstr();
46 let name_raw = name.to_cstr();
47 let handle = unsafe { BNCreateProject(path_raw.as_ptr(), name_raw.as_ptr()) };
48 NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
49 }
50
51 pub fn open_project(path: impl AsRef<Path>) -> Option<Ref<Self>> {
56 let path_raw = path.as_ref().to_cstr();
57 let handle = unsafe { BNOpenProject(path_raw.as_ptr()) };
58 NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
59 }
60
61 pub fn is_open(&self) -> bool {
63 unsafe { BNProjectIsOpen(self.handle.as_ptr()) }
64 }
65
66 pub fn open(&self) -> Result<(), ()> {
68 if unsafe { BNProjectOpen(self.handle.as_ptr()) } {
69 Ok(())
70 } else {
71 Err(())
72 }
73 }
74
75 pub fn close(&self) -> Result<(), ()> {
77 if unsafe { BNProjectClose(self.handle.as_ptr()) } {
78 Ok(())
79 } else {
80 Err(())
81 }
82 }
83
84 pub fn id(&self) -> String {
86 unsafe { BnString::into_string(BNProjectGetId(self.handle.as_ptr())) }
87 }
88
89 pub fn path(&self) -> String {
91 unsafe { BnString::into_string(BNProjectGetPath(self.handle.as_ptr())) }
92 }
93
94 pub fn name(&self) -> String {
96 unsafe { BnString::into_string(BNProjectGetName(self.handle.as_ptr())) }
97 }
98
99 pub fn set_name(&self, value: &str) -> bool {
101 let value = value.to_cstr();
102 unsafe { BNProjectSetName(self.handle.as_ptr(), value.as_ptr()) }
103 }
104
105 pub fn description(&self) -> String {
107 unsafe { BnString::into_string(BNProjectGetDescription(self.handle.as_ptr())) }
108 }
109
110 pub fn set_description(&self, value: &str) -> bool {
112 let value = value.to_cstr();
113 unsafe { BNProjectSetDescription(self.handle.as_ptr(), value.as_ptr()) }
114 }
115
116 pub fn query_metadata(&self, key: &str) -> Ref<Metadata> {
118 let key = key.to_cstr();
119 let result = unsafe { BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ptr()) };
120 unsafe { Metadata::ref_from_raw(result) }
121 }
122
123 pub fn store_metadata(&self, key: &str, value: &Metadata) -> bool {
128 let key_raw = key.to_cstr();
129 unsafe { BNProjectStoreMetadata(self.handle.as_ptr(), key_raw.as_ptr(), value.handle) }
130 }
131
132 pub fn remove_metadata(&self, key: &str) -> bool {
134 let key_raw = key.to_cstr();
135 unsafe { BNProjectRemoveMetadata(self.handle.as_ptr(), key_raw.as_ptr()) }
136 }
137
138 pub fn push_folder(&self, file: &ProjectFolder) -> bool {
139 unsafe { BNProjectPushFolder(self.handle.as_ptr(), file.handle.as_ptr()) }
140 }
141
142 pub fn create_folder_from_path(
148 &self,
149 path: impl AsRef<Path>,
150 parent: Option<&ProjectFolder>,
151 description: &str,
152 ) -> Result<Ref<ProjectFolder>, ()> {
153 self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback)
154 }
155
156 pub fn create_folder_from_path_with_progress<PC>(
163 &self,
164 path: impl AsRef<Path>,
165 parent: Option<&ProjectFolder>,
166 description: &str,
167 mut progress: PC,
168 ) -> Result<Ref<ProjectFolder>, ()>
169 where
170 PC: ProgressCallback,
171 {
172 let path_raw = path.as_ref().to_cstr();
173 let description_raw = description.to_cstr();
174 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
175
176 unsafe {
177 let result = BNProjectCreateFolderFromPath(
178 self.handle.as_ptr(),
179 path_raw.as_ptr(),
180 parent_ptr,
181 description_raw.as_ptr(),
182 &mut progress as *mut PC as *mut c_void,
183 Some(PC::cb_progress_callback),
184 );
185 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
186 }
187 }
188
189 pub fn create_folder(
195 &self,
196 parent: Option<&ProjectFolder>,
197 name: &str,
198 description: &str,
199 ) -> Result<Ref<ProjectFolder>, ()> {
200 let name_raw = name.to_cstr();
201 let description_raw = description.to_cstr();
202 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
203 unsafe {
204 let result = BNProjectCreateFolder(
205 self.handle.as_ptr(),
206 parent_ptr,
207 name_raw.as_ptr(),
208 description_raw.as_ptr(),
209 );
210 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
211 }
212 }
213
214 pub unsafe fn create_folder_unsafe(
221 &self,
222 parent: Option<&ProjectFolder>,
223 name: &str,
224 description: &str,
225 id: &str,
226 ) -> Result<Ref<ProjectFolder>, ()> {
227 let name_raw = name.to_cstr();
228 let description_raw = description.to_cstr();
229 let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
230 let id_raw = id.to_cstr();
231 unsafe {
232 let result = BNProjectCreateFolderUnsafe(
233 self.handle.as_ptr(),
234 parent_ptr,
235 name_raw.as_ptr(),
236 description_raw.as_ptr(),
237 id_raw.as_ptr(),
238 );
239 Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
240 }
241 }
242
243 pub fn folders(&self) -> Array<ProjectFolder> {
245 let mut count = 0;
246 let result = unsafe { BNProjectGetFolders(self.handle.as_ptr(), &mut count) };
247 assert!(!result.is_null());
248 unsafe { Array::new(result, count, ()) }
249 }
250
251 pub fn folder_by_id(&self, id: &str) -> Option<Ref<ProjectFolder>> {
253 let raw_id = id.to_cstr();
254 let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), raw_id.as_ptr()) };
255 let handle = NonNull::new(result)?;
256 Some(unsafe { ProjectFolder::ref_from_raw(handle) })
257 }
258
259 pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> {
263 self.delete_folder_with_progress(folder, NoProgressCallback)
264 }
265
266 pub fn delete_folder_with_progress<PC: ProgressCallback>(
271 &self,
272 folder: &ProjectFolder,
273 mut progress: PC,
274 ) -> Result<(), ()> {
275 let result = unsafe {
276 BNProjectDeleteFolder(
277 self.handle.as_ptr(),
278 folder.handle.as_ptr(),
279 &mut progress as *mut PC as *mut c_void,
280 Some(PC::cb_progress_callback),
281 )
282 };
283
284 if result {
285 Ok(())
286 } else {
287 Err(())
288 }
289 }
290
291 pub fn push_file(&self, file: &ProjectFile) -> bool {
292 unsafe { BNProjectPushFile(self.handle.as_ptr(), file.handle.as_ptr()) }
293 }
294
295 pub fn create_file_from_path(
302 &self,
303 path: impl AsRef<Path>,
304 folder: Option<&ProjectFolder>,
305 name: &str,
306 description: &str,
307 ) -> Result<Ref<ProjectFile>, ()> {
308 self.create_file_from_path_with_progress(
309 path,
310 folder,
311 name,
312 description,
313 NoProgressCallback,
314 )
315 }
316
317 pub fn create_file_from_path_with_progress<PC>(
325 &self,
326 path: impl AsRef<Path>,
327 folder: Option<&ProjectFolder>,
328 name: &str,
329 description: &str,
330 mut progress: PC,
331 ) -> Result<Ref<ProjectFile>, ()>
332 where
333 PC: ProgressCallback,
334 {
335 let path_raw = path.as_ref().to_cstr();
336 let name_raw = name.to_cstr();
337 let description_raw = description.to_cstr();
338 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
339
340 unsafe {
341 let result = BNProjectCreateFileFromPath(
342 self.handle.as_ptr(),
343 path_raw.as_ptr(),
344 folder_ptr,
345 name_raw.as_ptr(),
346 description_raw.as_ptr(),
347 &mut progress as *mut PC as *mut c_void,
348 Some(PC::cb_progress_callback),
349 );
350 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
351 }
352 }
353
354 pub unsafe fn create_file_from_path_unsafe(
363 &self,
364 path: impl AsRef<Path>,
365 folder: Option<&ProjectFolder>,
366 name: &str,
367 description: &str,
368 id: &str,
369 creation_time: SystemTime,
370 ) -> Result<Ref<ProjectFile>, ()> {
371 self.create_file_from_path_unsafe_with_progress(
372 path,
373 folder,
374 name,
375 description,
376 id,
377 creation_time,
378 NoProgressCallback,
379 )
380 }
381
382 #[allow(clippy::too_many_arguments)]
392 pub unsafe fn create_file_from_path_unsafe_with_progress<PC>(
393 &self,
394 path: impl AsRef<Path>,
395 folder: Option<&ProjectFolder>,
396 name: &str,
397 description: &str,
398 id: &str,
399 creation_time: SystemTime,
400 mut progress: PC,
401 ) -> Result<Ref<ProjectFile>, ()>
402 where
403 PC: ProgressCallback,
404 {
405 let path_raw = path.as_ref().to_cstr();
406 let name_raw = name.to_cstr();
407 let description_raw = description.to_cstr();
408 let id_raw = id.to_cstr();
409 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
410
411 unsafe {
412 let result = BNProjectCreateFileFromPathUnsafe(
413 self.handle.as_ptr(),
414 path_raw.as_ptr(),
415 folder_ptr,
416 name_raw.as_ptr(),
417 description_raw.as_ptr(),
418 id_raw.as_ptr(),
419 systime_to_bntime(creation_time).unwrap(),
420 &mut progress as *mut PC as *mut c_void,
421 Some(PC::cb_progress_callback),
422 );
423 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
424 }
425 }
426
427 pub fn create_file(
434 &self,
435 contents: &[u8],
436 folder: Option<&ProjectFolder>,
437 name: &str,
438 description: &str,
439 ) -> Result<Ref<ProjectFile>, ()> {
440 self.create_file_with_progress(contents, folder, name, description, NoProgressCallback)
441 }
442
443 pub fn create_file_with_progress<PC>(
451 &self,
452 contents: &[u8],
453 folder: Option<&ProjectFolder>,
454 name: &str,
455 description: &str,
456 mut progress: PC,
457 ) -> Result<Ref<ProjectFile>, ()>
458 where
459 PC: ProgressCallback,
460 {
461 let name_raw = name.to_cstr();
462 let description_raw = description.to_cstr();
463 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
464
465 unsafe {
466 let result = BNProjectCreateFile(
467 self.handle.as_ptr(),
468 contents.as_ptr(),
469 contents.len(),
470 folder_ptr,
471 name_raw.as_ptr(),
472 description_raw.as_ptr(),
473 &mut progress as *mut PC as *mut c_void,
474 Some(PC::cb_progress_callback),
475 );
476 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
477 }
478 }
479
480 pub unsafe fn create_file_unsafe(
489 &self,
490 contents: &[u8],
491 folder: Option<&ProjectFolder>,
492 name: &str,
493 description: &str,
494 id: &str,
495 creation_time: SystemTime,
496 ) -> Result<Ref<ProjectFile>, ()> {
497 self.create_file_unsafe_with_progress(
498 contents,
499 folder,
500 name,
501 description,
502 id,
503 creation_time,
504 NoProgressCallback,
505 )
506 }
507
508 #[allow(clippy::too_many_arguments)]
518 pub unsafe fn create_file_unsafe_with_progress<PC>(
519 &self,
520 contents: &[u8],
521 folder: Option<&ProjectFolder>,
522 name: &str,
523 description: &str,
524 id: &str,
525 creation_time: SystemTime,
526 mut progress: PC,
527 ) -> Result<Ref<ProjectFile>, ()>
528 where
529 PC: ProgressCallback,
530 {
531 let name_raw = name.to_cstr();
532 let description_raw = description.to_cstr();
533 let id_raw = id.to_cstr();
534 let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
535
536 unsafe {
537 let result = BNProjectCreateFileUnsafe(
538 self.handle.as_ptr(),
539 contents.as_ptr(),
540 contents.len(),
541 folder_ptr,
542 name_raw.as_ptr(),
543 description_raw.as_ptr(),
544 id_raw.as_ptr(),
545 systime_to_bntime(creation_time).unwrap(),
546 &mut progress as *mut PC as *mut c_void,
547 Some(PC::cb_progress_callback),
548 );
549 Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
550 }
551 }
552
553 pub fn files(&self) -> Array<ProjectFile> {
555 let mut count = 0;
556 let result = unsafe { BNProjectGetFiles(self.handle.as_ptr(), &mut count) };
557 assert!(!result.is_null());
558 unsafe { Array::new(result, count, ()) }
559 }
560
561 pub fn file_by_id(&self, id: &str) -> Option<Ref<ProjectFile>> {
563 let raw_id = id.to_cstr();
564 let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), raw_id.as_ptr()) };
565 let handle = NonNull::new(result)?;
566 Some(unsafe { ProjectFile::ref_from_raw(handle) })
567 }
568
569 pub fn file_by_path(&self, path: &Path) -> Option<Ref<ProjectFile>> {
571 let path_raw = path.to_cstr();
572 let result =
573 unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_raw.as_ptr()) };
574 let handle = NonNull::new(result)?;
575 Some(unsafe { ProjectFile::ref_from_raw(handle) })
576 }
577
578 pub fn files_by_project_path(&self, path: &Path) -> Array<ProjectFile> {
582 let path_raw = path.to_cstr();
583 let mut count = 0;
584 let result = unsafe {
585 BNProjectGetFilesByPathInProject(self.handle.as_ptr(), path_raw.as_ptr(), &mut count)
586 };
587 unsafe { Array::new(result, count, ()) }
588 }
589
590 pub fn files_in_folder(&self, folder: Option<&ProjectFolder>) -> Array<ProjectFile> {
592 let folder_ptr = folder.map(|f| f.handle.as_ptr()).unwrap_or(null_mut());
593 let mut count = 0;
594 let result =
595 unsafe { BNProjectGetFilesInFolder(self.handle.as_ptr(), folder_ptr, &mut count) };
596 unsafe { Array::new(result, count, ()) }
597 }
598
599 pub fn delete_file(&self, file: &ProjectFile) -> bool {
601 unsafe { BNProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) }
602 }
603
604 pub fn bulk_operation(&mut self) -> Result<ProjectBulkOperationLock<'_>, ()> {
625 Ok(ProjectBulkOperationLock::lock(self))
626 }
627}
628
629impl Debug for Project {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 f.debug_struct("Project")
632 .field("id", &self.id())
633 .field("name", &self.name())
634 .field("description", &self.description())
635 .finish()
636 }
637}
638
639impl ToOwned for Project {
640 type Owned = Ref<Self>;
641
642 fn to_owned(&self) -> Self::Owned {
643 unsafe { RefCountable::inc_ref(self) }
644 }
645}
646
647unsafe impl RefCountable for Project {
648 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
649 Ref::new(Self {
650 handle: NonNull::new(BNNewProjectReference(handle.handle.as_ptr())).unwrap(),
651 })
652 }
653
654 unsafe fn dec_ref(handle: &Self) {
655 BNFreeProject(handle.handle.as_ptr());
656 }
657}
658
659unsafe impl Send for Project {}
660unsafe impl Sync for Project {}
661
662impl CoreArrayProvider for Project {
663 type Raw = *mut BNProject;
664 type Context = ();
665 type Wrapped<'a> = Guard<'a, Project>;
666}
667
668unsafe impl CoreArrayProviderInner for Project {
669 unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
670 BNFreeProjectList(raw, count)
671 }
672
673 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
674 let raw_ptr = NonNull::new(*raw).unwrap();
675 Guard::new(Self::from_raw(raw_ptr), context)
676 }
677}
678
679pub struct ProjectBulkOperationLock<'a> {
681 lock: &'a mut Project,
682}
683
684impl<'a> ProjectBulkOperationLock<'a> {
685 pub fn lock(project: &'a mut Project) -> Self {
686 unsafe { BNProjectBeginBulkOperation(project.handle.as_ptr()) };
687 Self { lock: project }
688 }
689
690 pub fn unlock(self) {
691 }
693}
694
695impl std::ops::Deref for ProjectBulkOperationLock<'_> {
696 type Target = Project;
697 fn deref(&self) -> &Self::Target {
698 self.lock
699 }
700}
701
702impl Drop for ProjectBulkOperationLock<'_> {
703 fn drop(&mut self) {
704 unsafe { BNProjectEndBulkOperation(self.lock.handle.as_ptr()) };
705 }
706}
707
708fn systime_from_bntime(time: i64) -> Option<SystemTime> {
709 let m = Duration::from_secs(time.try_into().ok()?);
710 Some(UNIX_EPOCH + m)
711}
712
713fn systime_to_bntime(time: SystemTime) -> Option<i64> {
714 time.duration_since(UNIX_EPOCH)
715 .ok()?
716 .as_secs()
717 .try_into()
718 .ok()
719}