Source code for binaryninja.project

# Copyright (c) 2015-2025 Vector 35 Inc## Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to# deal in the Software without restriction, including without limitation the# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or# sell copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:## The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS# IN THE SOFTWARE.importctypesfromcontextlibimportcontextmanagerfromosimportPathLikefromtypingimportCallable,List,Optional,Unionimportbinaryninjafrom.import_binaryninjacoreascorefrom.exceptionsimportProjectExceptionfrom.metadataimportMetadata,MetadataValueTypeProgressFuncType=Callable[[int,int],bool]AsPath=Union[PathLike,str]#TODO: notificationsdef_nop(*args,**kwargs):"""Function that just returns True, used as default for callbacks:return: True"""returnTruedef_wrap_progress(progress_func:ProgressFuncType):"""Wraps a progress function in a ctypes function for passing to the FFI:param progress_func: Python progress function:return: Wrapped ctypes function"""returnctypes.CFUNCTYPE(ctypes.c_bool,ctypes.c_void_p,ctypes.c_ulonglong,ctypes.c_ulonglong)(lambdactxt,cur,total:progress_func(cur,total))
[docs]classProjectFile:"""Class representing a file in a project"""
[docs]def__init__(self,handle:core.BNProjectFileHandle):self._handle=handle
def__del__(self):ifcoreisnotNone:core.BNFreeProjectFile(self._handle)def__repr__(self)->str:path=self.nameparent=self.folderwhileparentisnotNone:path=parent.name+'/'+pathparent=parent.parentreturnf'<ProjectFile:{self.project.name}/{path}>'def__str__(self)->str:path=self.nameparent=self.folderwhileparentisnotNone:path=parent.name+'/'+pathparent=parent.parentreturnf'<ProjectFile:{self.project.name}/{path}>'@propertydefproject(self)->'Project':"""Get the project that owns this file:return: Project that owns this file"""proj_handle=core.BNProjectFileGetProject(self._handle)ifproj_handleisNone:raiseProjectException("Failed to get project for file")returnProject(handle=proj_handle)@propertydefpath_on_disk(self)->str:"""Get the path on disk to this file's contents:return: Path on disk as a string"""returncore.BNProjectFileGetPathOnDisk(self._handle)# type: ignore@propertydefexists_on_disk(self)->bool:"""Check if this file's contents exist on disk:return: True if this file's contents exist on disk, False otherwise"""returncore.BNProjectFileExistsOnDisk(self._handle)@propertydefid(self)->str:"""Get the unique id of this file:return: Unique identifier of this file"""returncore.BNProjectFileGetId(self._handle)# type: ignore@propertydefname(self)->str:"""Get the name of this file:return: Name of this file"""returncore.BNProjectFileGetName(self._handle)# type: ignore@name.setterdefname(self,new_name:str)->bool:"""Set the name of this file:param new_name: Desired name"""returncore.BNProjectFileSetName(self._handle,new_name)@propertydefdescription(self)->str:"""Get the description of this file:return: Description of this file"""returncore.BNProjectFileGetDescription(self._handle)# type: ignore@description.setterdefdescription(self,new_description:str)->bool:"""Set the description of this file:param new_description: Desired description"""returncore.BNProjectFileSetDescription(self._handle,new_description)@propertydeffolder(self)->Optional['ProjectFolder']:"""Get the folder that contains this file:return: Folder that contains this file, or None"""folder_handle=core.BNProjectFileGetFolder(self._handle)iffolder_handleisNone:returnNonereturnProjectFolder(handle=folder_handle)@folder.setterdeffolder(self,new_folder:Optional['ProjectFolder'])->bool:"""Set the folder that contains this file:param new_parent: The folder that will contain this file, or None"""folder_handle=Noneifnew_folderisNoneelsenew_folder._handlereturncore.BNProjectFileSetFolder(self._handle,folder_handle)
[docs]defexport(self,dest:AsPath)->bool:"""Export this file to disk:param dest: Destination path for the exported contents:return: True if the export succeeded, False otherwise"""returncore.BNProjectFileExport(self._handle,str(dest))
[docs]defget_path_on_disk(self)->Optional[str]:"""Get this file's path on disk:return: The path on disk of the file or None"""returncore.BNProjectFileGetPathOnDisk(self._handle)
[docs]defget_path_in_project(self)->Optional[str]:"""Get this file's path in its parent project:return: The path in the project or None"""returncore.BNProjectFileGetPathInProject(self._handle)
[docs]defadd_dependency(self,file:'ProjectFile')->bool:"""Add a ProjectFile as a dependency of this file:return: True on success, False otherwise"""returncore.BNProjectFileAddDependency(self._handle,file._handle)
[docs]defremove_dependency(self,file:'ProjectFile')->bool:"""Remove a ProjectFile as a dependency of this file:return: True on success, False otherwise"""returncore.BNProjectFileRemoveDependency(self._handle,file._handle)
[docs]defget_dependencies(self)->List['ProjectFile']:"""Get the list of files that this file depends on:return: List of ProjectFiles that this file depends on"""count=ctypes.c_size_t()value=core.BNProjectFileGetDependencies(self._handle,count)ifvalueisNone:raiseProjectException("Failed to get list of project file dependencies")result=[]try:foriinrange(count.value):file_handle=core.BNNewProjectFileReference(value[i])iffile_handleisNone:raiseProjectException("core.BNNewProjectFileReference returned None")result.append(ProjectFile(file_handle))returnresultfinally:core.BNFreeProjectFileList(value,count.value)
[docs]defget_required_by(self)->List['ProjectFile']:"""Get the list of files that depend on this file:return: List of ProjectFiles that depend on this file"""count=ctypes.c_size_t()value=core.BNProjectFileGetRequiredBy(self._handle,count)ifvalueisNone:raiseProjectException("Failed to get list of project files that depend on file")result=[]try:foriinrange(count.value):file_handle=core.BNNewProjectFileReference(value[i])iffile_handleisNone:raiseProjectException("core.BNNewProjectFileReference returned None")result.append(ProjectFile(file_handle))returnresultfinally:core.BNFreeProjectFileList(value,count.value)
[docs]classProjectFolder:"""Class representing a folder in a project"""
[docs]def__init__(self,handle:core.BNProjectFolderHandle):self._handle=handle
def__del__(self):ifcoreisnotNone:core.BNFreeProjectFolder(self._handle)def__repr__(self)->str:path=self.nameparent=self.parentwhileparentisnotNone:path=parent.name+'/'+pathparent=parent.parentreturnf'<ProjectFolder:{self.project.name}/{path}>'def__str__(self)->str:path=self.nameparent=self.parentwhileparentisnotNone:path=parent.name+'/'+pathparent=parent.parentreturnf'<ProjectFolder:{self.project.name}/{path}>'@propertydefproject(self)->'Project':"""Get the project that owns this folder:return: Project that owns this folder"""proj_handle=core.BNProjectFolderGetProject(self._handle)ifproj_handleisNone:raiseProjectException("Failed to get project for folder")returnProject(handle=proj_handle)@propertydefid(self)->str:"""Get the unique id of this folder:return: Unique identifier of this folder"""returncore.BNProjectFolderGetId(self._handle)# type: ignore@propertydefname(self)->str:"""Get the name of this folder:return: Name of this folder"""returncore.BNProjectFolderGetName(self._handle)# type: ignore@name.setterdefname(self,new_name:str)->bool:"""Set the name of this folder:param new_name: Desired name"""returncore.BNProjectFolderSetName(self._handle,new_name)@propertydefdescription(self)->str:"""Get the description of this folder:return: Description of this folder"""returncore.BNProjectFolderGetDescription(self._handle)# type: ignore@description.setterdefdescription(self,new_description:str)->bool:"""Set the description of this folder:param new_description: Desired description"""returncore.BNProjectFolderSetDescription(self._handle,new_description)@propertydefparent(self)->Optional['ProjectFolder']:"""Get the parent folder of this folder:return: Folder that contains this folder, or None if it is a root folder"""folder_handle=core.BNProjectFolderGetParent(self._handle)iffolder_handleisNone:returnNonereturnProjectFolder(handle=folder_handle)@parent.setterdefparent(self,new_parent:Optional['ProjectFolder'])->bool:"""Set the parent folder of this folder:param new_parent: The folder that will contain this folder, or None"""parent_handle=Noneifnew_parentisNoneelsenew_parent._handlereturncore.BNProjectFolderSetParent(self._handle,parent_handle)
[docs]defexport(self,dest:AsPath,progress_func:ProgressFuncType=_nop)->bool:"""Recursively export this folder to disk:param dest: Destination path for the exported contents:param progress_func: Progress function that will be called as contents are exporting:return: True if the export succeeded, False otherwise"""returncore.BNProjectFolderExport(self._handle,str(dest),None,_wrap_progress(progress_func))
[docs]classProject:"""Class representing a project"""
[docs]def__init__(self,handle:core.BNProjectHandle):self._handle=handle
def__del__(self):ifcoreisnotNone:core.BNFreeProject(self._handle)def__repr__(self)->str:returnf'<Project:{self.name}>'def__str__(self)->str:returnf'<Project:{self.name}>'
[docs]@staticmethoddefopen_project(path:AsPath)->'Project':"""Open an existing project:param path: Path to the project directory (.bnpr) or project metadata file (.bnpm):return: Opened project:raises ProjectException: If there was an error opening the project"""binaryninja._init_plugins()project_handle=core.BNOpenProject(str(path))ifproject_handleisNone:raiseProjectException("Failed to open project")returnProject(handle=project_handle)
[docs]@staticmethoddefcreate_project(path:AsPath,name:str)->'Project':"""Create a new project:param path: Path to the project directory (.bnpr):param name: Name of the new project:return: Opened project:raises ProjectException: If there was an error creating the project"""binaryninja._init_plugins()project_handle=core.BNCreateProject(str(path),name)ifproject_handleisNone:raiseProjectException("Failed to create project")returnProject(handle=project_handle)
[docs]defopen(self)->bool:"""Open a closed project:return: True if the project is now open, False otherwise"""returncore.BNProjectOpen(self._handle)
[docs]defclose(self)->bool:"""Close an opened project:return: True if the project is now closed, False otherwise"""returncore.BNProjectClose(self._handle)
@propertydefid(self)->str:"""Get the unique id of this project:return: Unique identifier of project"""returncore.BNProjectGetId(self._handle)# type: ignore@propertydefis_open(self)->bool:"""Check if the project is currently open:return: True if the project is currently open, False otherwise"""returncore.BNProjectIsOpen(self._handle)@propertydefpath(self)->str:"""Get the path of the project:return: Path of the project's .bnpr directory"""returncore.BNProjectGetPath(self._handle)# type: ignore@propertydefname(self)->str:"""Get the name of the project:return: Name of the project"""returncore.BNProjectGetName(self._handle)# type: ignore@name.setterdefname(self,new_name:str)->bool:"""Set the name of the project:param new_name: Desired name"""returncore.BNProjectSetName(self._handle,new_name)@propertydefdescription(self)->str:"""Get the description of the project:return: Description of the project"""returncore.BNProjectGetDescription(self._handle)# type: ignore@description.setterdefdescription(self,new_description:str)->bool:"""Set the description of the project:param new_description: Desired description"""returncore.BNProjectSetDescription(self._handle,new_description)
[docs]defquery_metadata(self,key:str)->MetadataValueType:"""Retrieves metadata stored under a key from the project:param str key: Key to query"""md_handle=core.BNProjectQueryMetadata(self._handle,key)ifmd_handleisNone:raiseKeyError(key)returnMetadata(handle=md_handle).value
[docs]defstore_metadata(self,key:str,value:MetadataValueType)->bool:"""Stores metadata within the project:param str key: Key under which to store the Metadata object:param Varies value: Object to store"""_val=valueifnotisinstance(_val,Metadata):_val=Metadata(_val)returncore.BNProjectStoreMetadata(self._handle,key,_val.handle)
[docs]defremove_metadata(self,key:str)->bool:"""Removes the metadata associated with this key from the project:param str key: Key associated with the metadata object to remove"""returncore.BNProjectRemoveMetadata(self._handle,key)
[docs]defcreate_folder_from_path(self,path:Union[PathLike,str],parent:Optional[ProjectFolder]=None,description:str="",progress_func:ProgressFuncType=_nop)->ProjectFolder:"""Recursively create files and folders in the project from a path on disk:param path: Path to folder on disk:param parent: Parent folder in the project that will contain the new contents:param description: Description for created root folder:param progress_func: Progress function that will be called:return: Created root folder"""parent_handle=parent._handleifparentisnotNoneelseNonefolder_handle=core.BNProjectCreateFolderFromPath(project=self._handle,path=str(path),parent=parent_handle,description=description,ctxt=None,progress=_wrap_progress(progress_func))iffolder_handleisNone:raiseProjectException("Failed to create folder")returnProjectFolder(handle=folder_handle)
[docs]defcreate_folder(self,parent:Optional[ProjectFolder],name:str,description:str="")->ProjectFolder:"""Recursively create files and folders in the project from a path on disk:param parent: Parent folder in the project that will contain the new folder:param name: Name for the created folder:param description: Description for created folder:return: Created folder"""parent_handle=parent._handleifparentisnotNoneelseNonefolder_handle=core.BNProjectCreateFolder(project=self._handle,parent=parent_handle,name=name,description=description,)iffolder_handleisNone:raiseProjectException("Failed to create folder")returnProjectFolder(handle=folder_handle)
@propertydeffolders(self)->List[ProjectFolder]:"""Get a list of folders in the project:return: List of folders in the project"""count=ctypes.c_size_t()value=core.BNProjectGetFolders(self._handle,count)ifvalueisNone:raiseProjectException("Failed to get list of project folders")result=[]try:foriinrange(count.value):folder_handle=core.BNNewProjectFolderReference(value[i])iffolder_handleisNone:raiseProjectException("core.BNNewProjectFolderReference returned None")result.append(ProjectFolder(folder_handle))returnresultfinally:core.BNFreeProjectFolderList(value,count.value)
[docs]defget_folder_by_id(self,id:str)->Optional[ProjectFolder]:"""Retrieve a folder in the project by unique id:param id: Unique identifier for a folder:return: Folder with the requested id or None"""handle=core.BNProjectGetFolderById(self._handle,id)ifhandleisNone:returnNonefolder=ProjectFolder(handle)returnfolder
[docs]defdelete_folder(self,folder:ProjectFolder,progress_func:ProgressFuncType=_nop)->bool:"""Recursively delete a folder from the project:param folder: Folder to delete recursively:param progress_func: Progress function that will be called as objects get deleted:return: True if the folder was deleted, False otherwise"""returncore.BNProjectDeleteFolder(self._handle,folder._handle,None,_wrap_progress(progress_func))
[docs]defcreate_file_from_path(self,path:AsPath,folder:Optional[ProjectFolder],name:str,description:str="",progress_func:ProgressFuncType=_nop)->ProjectFile:"""Create a file in the project from a path on disk:param path: Path on disk:param folder: Folder to place the created file in:param name: Name to assign to the created file:param description: Description to assign to the created file:param progress_func: Progress function that will be called as the file is being added"""folder_handle=folder._handleiffolderisnotNoneelseNonefile_handle=core.BNProjectCreateFileFromPath(project=self._handle,path=str(path),folder=folder_handle,name=name,description=description,ctxt=None,progress=_wrap_progress(progress_func))iffile_handleisNone:raiseProjectException("Failed to create file")returnProjectFile(handle=file_handle)
[docs]defcreate_file(self,contents:bytes,folder:Optional[ProjectFolder],name:str,description:str="",progress_func:ProgressFuncType=_nop)->ProjectFile:"""Create a file in the project:param contents: Bytes of the file that will be created:param folder: Folder to place the created file in:param name: Name to assign to the created file:param description: Description to assign to the created file:param progress_func: Progress function that will be called as the file is being added"""folder_handle=folder._handleiffolderisnotNoneelseNonebuf=(ctypes.c_ubyte*len(contents))()ctypes.memmove(buf,contents,len(contents))file_handle=core.BNProjectCreateFile(project=self._handle,contents=buf,contentsSize=len(contents),folder=folder_handle,name=name,description=description,ctxt=None,progress=_wrap_progress(progress_func))iffile_handleisNone:raiseProjectException("Failed to create file")returnProjectFile(handle=file_handle)
@propertydeffiles(self)->List[ProjectFile]:"""Get a list of files in the project:return: List of files in the project"""count=ctypes.c_size_t()value=core.BNProjectGetFiles(self._handle,count)ifvalueisNone:raiseProjectException("Failed to get list of project files")result=[]try:foriinrange(count.value):file_handle=core.BNNewProjectFileReference(value[i])iffile_handleisNone:raiseProjectException("core.BNNewProjectFileReference returned None")result.append(ProjectFile(file_handle))returnresultfinally:core.BNFreeProjectFileList(value,count.value)
[docs]defget_file_by_id(self,id:str)->Optional[ProjectFile]:"""Retrieve a file in the project by unique id:param id: Unique identifier for a file:return: File with the requested id or None"""handle=core.BNProjectGetFileById(self._handle,id)ifhandleisNone:returnNonefile=ProjectFile(handle)returnfile
[docs]defget_file_by_path_on_disk(self,path:str)->Optional[ProjectFile]:"""Retrieve a file in the project by its path on disk:param path: Path of the file on the disk:return: File with the requested path or None"""handle=core.BNProjectGetFileByPathOnDisk(self._handle,path)ifhandleisNone:returnNonefile=ProjectFile(handle)returnfile
[docs]defget_files_by_path_in_project(self,path:str)->List[ProjectFile]:"""Retrieve a file(s) by path in the projectNote that files in a project can share names and paths within the projectbut are uniquely identified by a disk path or id.:param path: Path of the file(s) in the project, separate from their path on disk.:return: List of files with the requested path"""count=ctypes.c_size_t()value=core.BNProjectGetFilesByPathInProject(self._handle,path,count)ifvalueisNone:raiseProjectException("Failed to get list of project files by path in project")result=[]try:foriinrange(count.value):file_handle=core.BNNewProjectFileReference(value[i])iffile_handleisNone:raiseProjectException("core.BNNewProjectFileReference returned None")result.append(ProjectFile(file_handle))returnresultfinally:core.BNFreeProjectFileList(value,count.value)
[docs]defdelete_file(self,file:ProjectFile)->bool:"""Delete a file from the project:param file: File to delete:return: True if the file was deleted, False otherwise"""returncore.BNProjectDeleteFile(self._handle,file._handle)
[docs]@contextmanagerdefbulk_operation(self):"""A context manager to speed up bulk project operations.Project modifications are synced to disk in chunks,and the project on disk vs in memory may not agree on stateif an exception occurs while a bulk operation is happening.:Example:>>> from pathlib import Path>>> with project.bulk_operation():... for i in Path('/bin/').iterdir():... if i.is_file() and not i.is_symlink():... project.create_file_from_path(i, None, i.name)"""core.BNProjectBeginBulkOperation(self._handle)yieldcore.BNProjectEndBulkOperation(self._handle)