diff options
| author | Josh Ferrell <josh@vector35.com> | 2024-01-22 16:11:19 -0500 |
|---|---|---|
| committer | Josh Ferrell <josh@vector35.com> | 2024-01-22 16:11:19 -0500 |
| commit | 3dd22f40996fc128ffce6026e8e747ca66bcc21d (patch) | |
| tree | a5e7fc57dc620fc4d4a408ffdbf114bb66dcf91d /python | |
| parent | 96053ffc711aa27fcaeeb6cbfa89df0c253361f8 (diff) | |
Project support
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/binaryview.py | 79 | ||||
| -rw-r--r-- | python/exceptions.py | 4 | ||||
| -rw-r--r-- | python/externallibrary.py | 125 | ||||
| -rw-r--r-- | python/filemetadata.py | 23 | ||||
| -rw-r--r-- | python/project.py | 411 | ||||
| -rw-r--r-- | python/scriptingprovider.py | 16 | ||||
| -rw-r--r-- | python/settings.py | 2 |
8 files changed, 649 insertions, 12 deletions
diff --git a/python/__init__.py b/python/__init__.py index f06b0681..2b12d827 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -71,6 +71,7 @@ from .typeprinter import * from .component import * from .typecontainer import * from .exceptions import * +from .project import * # We import each of these by name to prevent conflicts between # log.py and the function 'log' which we don't import below from .log import ( diff --git a/python/binaryview.py b/python/binaryview.py index 0c475445..5b78fca9 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -68,6 +68,7 @@ from . import mediumlevelil from . import highlevelil from . import debuginfo from . import flowgraph +from . import project # The following are imported as such to allow the type checker disambiguate the module name # from properties and methods of the same name from . import workflow as _workflow @@ -76,6 +77,9 @@ from . import types as _types from . import platform as _platform from . import deprecation from . import typecontainer +from . import externallibrary +from . import project + PathType = Union[str, os.PathLike] InstructionsType = Generator[Tuple[List['_function.InstructionTextToken'], int], None, None] @@ -2322,7 +2326,7 @@ class BinaryView: return BinaryView(file_metadata=file_metadata, handle=view) @staticmethod - def load(source: Union[str, bytes, bytearray, 'databuffer.DataBuffer', 'os.PathLike', 'BinaryView'], update_analysis: Optional[bool] = True, + def load(source: Union[str, bytes, bytearray, 'databuffer.DataBuffer', 'os.PathLike', 'BinaryView', 'project.ProjectFile'], update_analysis: Optional[bool] = True, progress_func: Optional[ProgressFuncType] = None, options: Mapping[str, Any] = {}) -> Optional['BinaryView']: """ ``load`` opens, generates default load options (which are overridable), and returns the first available \ @@ -2371,6 +2375,8 @@ class BinaryView: source = str(source) if isinstance(source, BinaryView): handle = core.BNLoadBinaryView(source.handle, update_analysis, progress_cfunc, metadata.Metadata(options).handle, source.file.has_database) + elif isinstance(source, project.ProjectFile): + handle = core.BNLoadProjectFile(source._handle, update_analysis, progress_cfunc, metadata.Metadata(options).handle) elif isinstance(source, str): handle = core.BNLoadFilename(source, update_analysis, progress_cfunc, metadata.Metadata(options).handle) elif isinstance(source, bytes) or isinstance(source, bytearray) or isinstance(source, databuffer.DataBuffer): @@ -2951,6 +2957,14 @@ class BinaryView: def new_auto_function_analysis_suppressed(self, suppress: bool) -> None: core.BNSetNewAutoFunctionAnalysisSuppressed(self.handle, suppress) + @property + def project(self) -> Optional['project.Project']: + return self.file.project + + @property + def project_file(self) -> Optional['project.ProjectFile']: + return self.file.project_file + def _init(self, ctxt): try: return self.init() @@ -8647,6 +8661,69 @@ class BinaryView: def create_logger(self, logger_name:str) -> Logger: return Logger(self.file.session_id, logger_name) + def add_external_library(self, name: str, backing_file: Optional['project.ProjectFile'] = None, auto: bool = False) -> externallibrary.ExternalLibrary: + file_handle = None + if backing_file is not None: + file_handle = backing_file._handle + handle = core.BNBinaryViewAddExternalLibrary(self.handle, name, file_handle, auto) + assert handle is not None, "core.BNBinaryViewAddExternalLibrary returned None" + return externallibrary.ExternalLibrary(handle) + + def remove_external_library(self, name: str): + core.BNBinaryViewRemoveExternalLibrary(self.handle, name) + + def get_external_library(self, name: str) -> Optional[externallibrary.ExternalLibrary]: + handle = core.BNBinaryViewGetExternalLibrary(self.handle, name) + if handle is None: + return None + return externallibrary.ExternalLibrary(handle) + + def get_external_libraries(self) -> List[externallibrary.ExternalLibrary]: + count = ctypes.c_ulonglong(0) + handles = core.BNBinaryViewGetExternalLibraries(self.handle, count) + assert handles is not None, "core.BNBinaryViewGetExternalLibraries returned None" + result = [] + try: + for i in range(count.value): + new_handle = core.BNNewExternalLibraryReference(handles[i]) + assert new_handle is not None, "core.BNNewExternalLibraryReference returned None" + result.append(externallibrary.ExternalLibrary(new_handle)) + return result + finally: + core.BNFreeExternalLibraryList(handles, count.value) + + def add_external_location(self, symbol: '_types.CoreSymbol', library: Optional[externallibrary.ExternalLibrary], external_symbol: Optional[str], external_address: Optional[int], auto: bool = False) -> externallibrary.ExternalLocation: + c_addr = None + if external_address is not None: + c_addr = ctypes.c_ulonglong(external_address) + + handle = core.BNBinaryViewAddExternalLocation(self.handle, symbol.handle, library._handle if library else None, external_symbol, c_addr, auto) + assert handle is not None, "core.BNBinaryViewAddExternalLocation returned None" + return externallibrary.ExternalLocation(handle) + + def remove_external_location(self, symbol: '_types.CoreSymbol'): + core.BNBinaryViewRemoveExternalLocation(self.handle, symbol._handle) + + def get_external_location(self, symbol: '_types.CoreSymbol') -> Optional[externallibrary.ExternalLocation]: + handle = core.BNBinaryViewGetExternalLocation(self.handle, symbol.handle) + if handle is None: + return None + return externallibrary.ExternalLocation(handle) + + def get_external_locations(self) -> List[externallibrary.ExternalLocation]: + count = ctypes.c_ulonglong(0) + handles = core.BNBinaryViewGetExternalLocations(self.handle, count) + assert handles is not None, "core.BNBinaryViewGetExternalLocations returned None" + result = [] + try: + for i in range(count.value): + new_handle = core.BNNewExternalLocationReference(handles[i]) + assert new_handle is not None, "core.BNNewExternalLocationReference returned None" + result.append(externallibrary.ExternalLocation(handles[i])) + return result + finally: + core.BNFreeExternalLocationList(handles, count.value) + class BinaryReader: """ diff --git a/python/exceptions.py b/python/exceptions.py index cb285253..a2ba197a 100644 --- a/python/exceptions.py +++ b/python/exceptions.py @@ -5,3 +5,7 @@ class RelocationWriteException(Exception): class ILException(Exception): """ Exception raised when IL operations fail """ pass + +class ProjectException(Exception): + """ Exception raised when project operations fail """ + pass diff --git a/python/externallibrary.py b/python/externallibrary.py new file mode 100644 index 00000000..6ae7b806 --- /dev/null +++ b/python/externallibrary.py @@ -0,0 +1,125 @@ +# coding=utf-8 +# Copyright (c) 2015-2023 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. + +import ctypes + +from typing import Optional + +from . import _binaryninjacore as core +from . import project +from . import types + + +class ExternalLibrary: + def __init__(self, handle: core.BNExternalLibrary): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeExternalLibrary(self._handle) + + def __repr__(self) -> str: + return f'<ExternalLibrary: {self.name}>' + + def __str__(self) -> str: + return f'<ExternalLibrary: {self.name}>' + + @property + def name(self) -> str: + return core.BNExternalLibraryGetName(self._handle) # type: ignore + + @property + def backing_file(self) -> Optional[project.ProjectFile]: + handle = core.BNExternalLibraryGetBackingFile(self._handle) + if handle is None: + return None + return project.ProjectFile(handle) + + @backing_file.setter + def backing_file(self, new_file: Optional[project.ProjectFile]): + new_file_handle = None + if new_file is not None: + new_file_handle = new_file._handle + core.BNExternalLibrarySetBackingFile(self._handle, new_file_handle) + + +class ExternalLocation: + def __init__(self, handle: core.BNExternalLocation): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeExternalLocation(self._handle) + + def __repr__(self) -> str: + return f'<ExternalLocation: {self.internal_symbol}>' + + def __str__(self) -> str: + return f'<ExternalLocation: {self.internal_symbol}>' + + @property + def internal_symbol(self) -> 'types.CoreSymbol': + sym = core.BNExternalLocationGetInternalSymbol(self._handle) + assert sym is not None, "core.BNExternalLocationGetInternalSymbol returned None" + return types.CoreSymbol(sym) + + @property + def has_address(self) -> bool: + return core.BNExternalLocationHasAddress(self._handle) + + @property + def has_symbol(self) -> bool: + return core.BNExternalLocationHasSymbol(self._handle) + + @property + def address(self) -> Optional[int]: + if not self.has_address: + return None + return core.BNExternalLocationGetAddress(self._handle) + + @address.setter + def address(self, new_address: Optional[int]): + c_addr = None + if new_address is not None: + c_addr = ctypes.c_ulonglong(new_address) + return core.BNExternalLocationSetAddress(self._handle, c_addr) + + @property + def symbol(self) -> Optional[str]: + if not self.has_symbol: + return None + return core.BNExternalLocationGetSymbol(self._handle) + + @symbol.setter + def symbol(self, new_symbol: Optional[str]): + return core.BNExternalLocationSetSymbol(self._handle, new_symbol) + + @property + def library(self) -> Optional[ExternalLibrary]: + handle = core.BNExternalLocationGetExternalLibrary(self._handle) + if handle is None: + return None + return ExternalLibrary(handle) + + @library.setter + def library(self, new_library: Optional[ExternalLibrary]): + lib_handle = new_library._handle if new_library is not None else None + return core.BNExternalLocationSetExternalLibrary(self._handle, lib_handle) diff --git a/python/filemetadata.py b/python/filemetadata.py index ec0e16bb..e51436b1 100644 --- a/python/filemetadata.py +++ b/python/filemetadata.py @@ -31,6 +31,7 @@ from .log import log_error from . import binaryview from . import database from . import deprecation +from . import project ProgressFuncType = Callable[[int, int], bool] ViewName = str @@ -298,6 +299,20 @@ class FileMetadata: def snapshot_data_applied_without_error(self) -> bool: return core.BNIsSnapshotDataAppliedWithoutError(self.handle) + @property + def project(self) -> Optional['project.Project']: + project_file = self.project_file + if project_file is None: + return None + return project_file.project + + @property + def project_file(self) -> Optional['project.ProjectFile']: + handle = core.BNGetProjectFile(self.handle) + if handle is None: + return None + return project.ProjectFile(handle) + def close(self) -> None: """ Closes the underlying file handle. It is recommended that this is done in a @@ -606,14 +621,6 @@ class FileMetadata: return None return binaryview.BinaryView(file_metadata=self, handle=view) - def open_project(self) -> bool: - return core.BNOpenProject(self.handle) - - def close_project(self) -> None: - core.BNCloseProject(self.handle) - - def is_project_open(self) -> bool: - return core.BNIsProjectOpen(self.handle) @property def existing_views(self) -> List[ViewName]: diff --git a/python/project.py b/python/project.py new file mode 100644 index 00000000..e1f7171d --- /dev/null +++ b/python/project.py @@ -0,0 +1,411 @@ +# Copyright (c) 2015-2023 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. + +import ctypes + +from contextlib import contextmanager +from os import PathLike +from typing import Callable, List, Optional, Union + +from . import _binaryninjacore as core +from .exceptions import ProjectException +from .metadata import Metadata, MetadataValueType + + +ProgressFuncType = Callable[[int, int], bool] +AsPath = Union[PathLike, str] + +#TODO: notifications + +def nop(*args, **kwargs): + """ + Function that just returns True, used as default for callbacks + + :return: True + """ + return True + + +def 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 + """ + return ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_ulonglong)( + lambda ctxt, cur, total: progress_func(cur, total)) + + +class ProjectFile: + def __init__(self, handle: core.BNProjectFileHandle): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeProjectFile(self._handle) + + def __repr__(self) -> str: + path = self.name + parent = self.folder + while parent is not None: + path = parent.name + '/' + path + parent = parent.parent + return f'<ProjectFile: {self.project.name}/{path}>' + + def __str__(self) -> str: + path = self.name + parent = self.folder + while parent is not None: + path = parent.name + '/' + path + parent = parent.parent + return f'<ProjectFile: {self.project.name}/{path}>' + + @property + def project(self): + proj_handle = core.BNProjectFileGetProject(self._handle) + + if proj_handle is None: + raise ProjectException("Failed to get project for file") + + return Project(handle=proj_handle) + + @property + def path_on_disk(self) -> str: + return core.BNProjectFileGetPathOnDisk(self._handle) # type: ignore + + @property + def exists_on_disk(self) -> bool: + return core.BNProjectFileExistsOnDisk(self._handle) + + @property + def id(self) -> str: + return core.BNProjectFileGetId(self._handle) # type: ignore + + @property + def name(self) -> str: + return core.BNProjectFileGetName(self._handle) # type: ignore + + @name.setter + def name(self, new_name: str): + return core.BNProjectFileSetName(self._handle, new_name) + + @property + def description(self) -> str: + return core.BNProjectFileGetDescription(self._handle) # type: ignore + + @description.setter + def description(self, new_description: str): + return core.BNProjectFileSetDescription(self._handle, new_description) + + @property + def folder(self) -> Optional['ProjectFolder']: + folder_handle = core.BNProjectFileGetFolder(self._handle) + if folder_handle is None: + return None + return ProjectFolder(handle=folder_handle) + + @folder.setter + def folder(self, new_folder: Optional['ProjectFolder']): + folder_handle = None if new_folder is None else new_folder._handle + core.BNProjectFileSetFolder(self._handle, folder_handle) + + def export(self, dest: AsPath) -> bool: + return core.BNProjectFileExport(self._handle, str(dest)) + + +class ProjectFolder: + def __init__(self, handle: core.BNProjectFolderHandle): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeProjectFolder(self._handle) + + def __repr__(self) -> str: + path = self.name + parent = self.parent + while parent is not None: + path = parent.name + '/' + path + parent = parent.parent + return f'<ProjectFolder: {self.project.name}/{path}>' + + def __str__(self) -> str: + path = self.name + parent = self.parent + while parent is not None: + path = parent.name + '/' + path + parent = parent.parent + return f'<ProjectFolder: {self.project.name}/{path}>' + + @property + def project(self): + proj_handle = core.BNProjectFolderGetProject(self._handle) + + if proj_handle is None: + raise ProjectException("Failed to get project for folder") + + return Project(handle=proj_handle) + + @property + def id(self) -> str: + return core.BNProjectFolderGetId(self._handle) # type: ignore + + @property + def name(self) -> str: + return core.BNProjectFolderGetName(self._handle) # type: ignore + + @name.setter + def name(self, new_name: str): + return core.BNProjectFolderSetName(self._handle, new_name) + + @property + def description(self) -> str: + return core.BNProjectFolderGetDescription(self._handle) # type: ignore + + @description.setter + def description(self, new_description: str): + return core.BNProjectFolderSetDescription(self._handle, new_description) + + @property + def parent(self) -> Optional['ProjectFolder']: + folder_handle = core.BNProjectFolderGetParent(self._handle) + if folder_handle is None: + return None + return ProjectFolder(handle=folder_handle) + + @parent.setter + def parent(self, new_parent: Optional['ProjectFolder']): + parent_handle = None if new_parent is None else new_parent._handle + core.BNProjectFolderSetParent(self._handle, parent_handle) + + def export(self, dest: AsPath, progress_func: ProgressFuncType = nop) -> bool: + return core.BNProjectFolderExport(self._handle, str(dest), None, wrap_progress(progress_func)) + + +class Project: + def __init__(self, handle: core.BNProjectHandle): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeProject(self._handle) + + def __repr__(self) -> str: + return f'<Project: {self.name}>' + + def __str__(self) -> str: + return f'<Project: {self.name}>' + + @staticmethod + def open_project(path: AsPath) -> 'Project': + project_handle = core.BNOpenProject(str(path)) + if project_handle is None: + raise ProjectException("Failed to open project") + return Project(handle=project_handle) + + @staticmethod + def create_project(path: AsPath, name: str) -> 'Project': + project_handle = core.BNCreateProject(str(path), name) + if project_handle is None: + raise ProjectException("Failed to create project") + return Project(handle=project_handle) + + def open(self) -> bool: + return core.BNProjectOpen(self._handle) + + def close(self) -> bool: + return core.BNProjectClose(self._handle) + + @property + def id(self) -> str: + return core.BNProjectGetId(self._handle) # type: ignore + + @property + def is_open(self) -> bool: + return core.BNProjectIsOpen(self._handle) + + @property + def path(self) -> str: + return core.BNProjectGetPath(self._handle) # type: ignore + + @property + def name(self) -> str: + return core.BNProjectGetName(self._handle) # type: ignore + + @name.setter + def name(self, new_name: str): + core.BNProjectSetName(self._handle, new_name) + + @property + def description(self) -> str: + return core.BNProjectGetDescription(self._handle) # type: ignore + + @description.setter + def description(self, new_description: str): + core.BNProjectSetDescription(self._handle, new_description) + + def query_metadata(self, key: str) -> MetadataValueType: + md_handle = core.BNProjectQueryMetadata(self._handle, key) + if md_handle is None: + raise KeyError(key) + return Metadata(handle=md_handle).value + + def store_metadata(self, key: str, value: MetadataValueType): + _val = value + if not isinstance(_val, Metadata): + _val = Metadata(_val) + core.BNProjectStoreMetadata(self._handle, key, _val.handle) + + def remove_metadata(self, key: str): + core.BNProjectRemoveMetadata(self._handle, key) + + def create_folder_from_path(self, path: Union[PathLike, str], parent: Optional[ProjectFolder] = None, description: str = "", progress_func: ProgressFuncType = nop) -> ProjectFolder: + parent_handle = parent._handle if parent is not None else None + folder_handle = core.BNProjectCreateFolderFromPath( + project=self._handle, + path=str(path), + parent=parent_handle, + description=description, + ctxt=None, + progress=wrap_progress(progress_func) + ) + + if folder_handle is None: + raise ProjectException("Failed to create folder") + + return ProjectFolder(handle=folder_handle) + + def create_folder(self, parent: Optional[ProjectFolder], name: str, description: str = "") -> ProjectFolder: + parent_handle = parent._handle if parent is not None else None + folder_handle = core.BNProjectCreateFolder( + project=self._handle, + parent=parent_handle, + name=name, + description=description, + ) + + if folder_handle is None: + raise ProjectException("Failed to create folder") + + return ProjectFolder(handle=folder_handle) + + @property + def folders(self) -> List[ProjectFolder]: + count = ctypes.c_size_t() + value = core.BNProjectGetFolders(self._handle, count) + if value is None: + raise ProjectException("Failed to get list of project folders") + result = [] + try: + for i in range(count.value): + folder_handle = core.BNNewProjectFolderReference(value[i]) + if folder_handle is None: + raise ProjectException("core.BNNewProjectFolderReference returned None") + result.append(ProjectFolder(folder_handle)) + return result + finally: + core.BNFreeProjectFolderList(value, count.value) + + def get_folder_by_id(self, id: str) -> Optional[ProjectFolder]: + handle = core.BNProjectGetFolderById(self._handle, id) + if handle is None: + return None + folder = ProjectFolder(handle) + return folder + + def push_folder(self, folder: ProjectFolder): + core.BNProjectPushFolder(self._handle, folder._handle) + + def delete_folder(self, folder: ProjectFolder, progress_func: ProgressFuncType = nop): + core.BNProjectDeleteFolder(self._handle, folder._handle, None, wrap_progress(progress_func)) + + def create_file_from_path(self, path: AsPath, folder: Optional[ProjectFile], name: str, description: str = "", progress_func: ProgressFuncType = nop) -> ProjectFile: + folder_handle = folder._handle if folder is not None else None + file_handle = core.BNProjectCreateFileFromPath( + project=self._handle, + path=str(path), + folder=folder_handle, + name=name, + description=description, + ctxt=None, + progress=wrap_progress(progress_func) + ) + + if file_handle is None: + raise ProjectException("Failed to create file") + + return ProjectFile(handle=file_handle) + + def create_file(self, contents: bytes, folder: Optional[ProjectFile], name: str, description: str = "", progress_func: ProgressFuncType = nop) -> ProjectFile: + folder_handle = folder._handle if folder is not None else None + buf = (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) + ) + + if file_handle is None: + raise ProjectException("Failed to create file") + + return ProjectFile(handle=file_handle) + + @property + def files(self) -> List[ProjectFile]: + count = ctypes.c_size_t() + value = core.BNProjectGetFiles(self._handle, count) + if value is None: + raise ProjectException("Failed to get list of project files") + result = [] + try: + for i in range(count.value): + file_handle = core.BNNewProjectFileReference(value[i]) + if file_handle is None: + raise ProjectException("core.BNNewProjectFileReference returned None") + result.append(ProjectFile(file_handle)) + return result + finally: + core.BNFreeProjectFileList(value, count.value) + + def get_file_by_id(self, id: str) -> Optional[ProjectFile]: + handle = core.BNProjectGetFileById(self._handle, id) + if handle is None: + return None + file = ProjectFile(handle) + return file + + def push_file(self, file: ProjectFile): + core.BNProjectPushFile(self._handle, file._handle) + + def delete_file(self, file: ProjectFile): + core.BNProjectDeleteFile(self._handle, file._handle) + + @contextmanager + def bulk_operation(self): + core.BNProjectBeginBulkOperation(self._handle) + yield + core.BNProjectEndBulkOperation(self._handle) diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py index 7042b988..d7d5b5dc 100644 --- a/python/scriptingprovider.py +++ b/python/scriptingprovider.py @@ -660,6 +660,7 @@ class PythonScriptingInstance(ScriptingInstance): blacklisted_vars = { "current_thread", "current_view", + "current_project", "bv", "current_function", "current_basic_block", @@ -705,6 +706,7 @@ class PythonScriptingInstance(ScriptingInstance): self.current_selection_begin = 0 self.current_selection_end = 0 self.current_dbg = None + self.current_project = None # Selections that were current as of last issued command self.active_view = None @@ -821,6 +823,7 @@ from binaryninja import * self.active_selection_begin = self.current_selection_begin self.active_selection_end = self.current_selection_end self.active_dbg = self.current_dbg + self.active_project = self.current_project if self.active_view is not None: self.active_file_offset = self.active_view.get_data_offset_for_address(self.active_addr) else: @@ -829,6 +832,7 @@ from binaryninja import * self.locals.blacklist_enabled = False self.locals["current_thread"] = self.interpreter self.locals["current_view"] = self.active_view + self.locals["current_project"] = self.active_project self.locals["bv"] = self.active_view self.locals["current_function"] = self.active_func self.locals["current_basic_block"] = self.active_block @@ -895,10 +899,12 @@ from binaryninja import * action_handler = None view_frame = None view = None + project = None if context is not None: action_handler = context.getCurrentActionHandler() view_frame = context.getCurrentViewFrame() view = context.getCurrentView() + project = context.getProject() view_location = view_frame.getViewLocation() if view_frame is not None else None action_context = None @@ -970,6 +976,7 @@ from binaryninja import * self.active_il_function = None self.locals["current_ui_context"] = context + self.locals["current_project"] = project self.locals["current_ui_view_frame"] = view_frame self.locals["current_ui_view"] = view self.locals["current_ui_action_handler"] = action_handler @@ -983,6 +990,7 @@ from binaryninja import * if not ui_locals_valid: self.locals["current_ui_context"] = None + self.locals["current_project"] = None self.locals["current_ui_view_frame"] = None self.locals["current_ui_view"] = None self.locals["current_ui_action_handler"] = None @@ -1193,10 +1201,14 @@ from binaryninja import * @abc.abstractmethod def perform_set_current_binary_view(self, view): self.interpreter.current_view = view - if view is not None and self.debugger_imported: - self.interpreter.current_dbg = self.DebuggerController(view) + if view is not None: + if self.debugger_imported: + self.interpreter.current_dbg = self.DebuggerController(view) + self.interpreter.current_project = view.project + else: self.interpreter.current_dbg = None + self.interpreter.current_project = None # This is a workaround that allows BN to properly free up resources when the last tab of a binary view is closed. # Without this update, the interpreter local variables will NOT be updated until the user interacts with the diff --git a/python/settings.py b/python/settings.py index 8ddfc945..486d3d2f 100644 --- a/python/settings.py +++ b/python/settings.py @@ -50,7 +50,7 @@ class Settings: ================= ========================== ============== ============================================== Default SettingsDefaultScope Lowest Settings Schema User SettingsUserScope - <User Directory>/settings.json - Project SettingsProjectScope - <Project Directory>/.binaryninja/settings.json + Project SettingsProjectScope - <Project Directory>/settings.json Resource SettingsResourceScope Highest Raw BinaryView (Storage in BNDB) ================= ========================== ============== ============================================== |
