summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorGlenn Smith <glenn@vector35.com>2023-11-01 17:03:17 -0400
committerGlenn Smith <glenn@vector35.com>2024-02-07 17:23:49 -0500
commit8037455723546000448135499957273623776581 (patch)
tree268322ef34d16e067210df592dd782d50f294e3c /python
parente1542ed05f3f8c273206bb9ead8d39fea87430bb (diff)
Type Archives
Diffstat (limited to 'python')
-rw-r--r--python/__init__.py1
-rw-r--r--python/binaryview.py463
-rw-r--r--python/typearchive.py758
3 files changed, 1222 insertions, 0 deletions
diff --git a/python/__init__.py b/python/__init__.py
index 2b12d827..144d022a 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -69,6 +69,7 @@ from .secretsprovider import *
from .typeparser import *
from .typeprinter import *
from .component import *
+from .typearchive import *
from .typecontainer import *
from .exceptions import *
from .project import *
diff --git a/python/binaryview.py b/python/binaryview.py
index 96c93876..74f6c2ff 100644
--- a/python/binaryview.py
+++ b/python/binaryview.py
@@ -68,6 +68,7 @@ from . import highlevelil
from . import debuginfo
from . import flowgraph
from . import project
+from . import typearchive
# 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
@@ -174,6 +175,10 @@ class NotificationType(IntFlag):
ComponentFunctionRemoved = 1 << 36
ComponentDataVariableAdded = 1 << 37
ComponentDataVariableRemoved = 1 << 38
+ TypeArchiveAttached = 1 << 39
+ TypeArchiveDetached = 1 << 40
+ TypeArchiveConnected = 1 << 41
+ TypeArchiveDisconnected = 1 << 42
BinaryDataUpdates = DataWritten | DataInserted | DataRemoved
FunctionLifetime = FunctionAdded | FunctionRemoved
FunctionUpdates = FunctionLifetime | FunctionUpdated
@@ -191,6 +196,7 @@ class NotificationType(IntFlag):
SectionLifetime = SectionAdded | SectionRemoved
SectionUpdates = SectionLifetime | SectionUpdated
ComponentUpdates = (ComponentAdded | ComponentRemoved | ComponentMoved | ComponentFunctionAdded | ComponentFunctionRemoved | ComponentDataVariableAdded | ComponentDataVariableRemoved)
+ TypeArchiveUpdates = (TypeArchiveAttached | TypeArchiveDetached | TypeArchiveConnected | TypeArchiveDisconnected)
class BinaryDataNotification:
@@ -386,7 +392,17 @@ class BinaryDataNotification:
def component_data_var_removed(self, view: 'BinaryView', _component: component.Component, var: 'DataVariable'):
pass
+ def type_archive_attached(self, view: 'BinaryView', id: str, path: str):
+ pass
+
+ def type_archive_detached(self, view: 'BinaryView', id: str, path: str):
+ pass
+ def type_archive_connected(self, view: 'BinaryView', archive: 'typearchive.TypeArchive'):
+ pass
+
+ def type_archive_disconnected(self, view: 'BinaryView', archive: 'typearchive.TypeArchive'):
+ pass
class StringReference:
@@ -635,6 +651,11 @@ class BinaryDataNotificationCallbacks:
self._cb.componentFunctionRemoved = self._cb.componentFunctionRemoved.__class__(self._component_function_removed)
self._cb.componentDataVariableAdded = self._cb.componentDataVariableAdded.__class__(self._component_data_variable_added)
self._cb.componentDataVariableRemoved = self._cb.componentDataVariableRemoved.__class__(self._component_data_variable_removed)
+
+ self._cb.typeArchiveAttached = self._cb.typeArchiveAttached.__class__(self._type_archive_attached)
+ self._cb.typeArchiveDetached = self._cb.typeArchiveDetached.__class__(self._type_archive_detached)
+ self._cb.typeArchiveConnected = self._cb.typeArchiveConnected.__class__(self._type_archive_connected)
+ self._cb.typeArchiveDisconnected = self._cb.typeArchiveDisconnected.__class__(self._type_archive_disconnected)
else:
if notify.notifications & NotificationType.NotificationBarrier:
self._cb.notificationBarrier = self._cb.notificationBarrier.__class__(self._notification_barrier)
@@ -715,6 +736,15 @@ class BinaryDataNotificationCallbacks:
if notify.notifications & NotificationType.ComponentDataVariableRemoved:
self._cb.componentDataVariableRemoved = self._cb.componentDataVariableRemoved.__class__(self._component_data_variable_removed)
+ if notify.notifications & NotificationType.TypeArchiveAttached:
+ self._cb.typeArchiveAttached = self._cb.typeArchiveAttached.__class__(self._type_archive_attached)
+ if notify.notifications & NotificationType.TypeArchiveDetached:
+ self._cb.typeArchiveDetached = self._cb.typeArchiveDetached.__class__(self._type_archive_detached)
+ if notify.notifications & NotificationType.TypeArchiveConnected:
+ self._cb.typeArchiveConnected = self._cb.typeArchiveConnected.__class__(self._type_archive_connected)
+ if notify.notifications & NotificationType.TypeArchiveDisconnected:
+ self._cb.typeArchiveDisconnected = self._cb.typeArchiveDisconnected.__class__(self._type_archive_disconnected)
+
def _register(self) -> None:
core.BNRegisterDataNotification(self._view.handle, self._cb)
@@ -1085,6 +1115,33 @@ class BinaryDataNotificationCallbacks:
except:
log_error(traceback.format_exc())
+ def _type_archive_attached(self, ctxt, view: core.BNBinaryView, id: ctypes.c_char_p, path: ctypes.c_char_p):
+ try:
+ self._notify.type_archive_attached(self._view, core.pyNativeStr(id), core.pyNativeStr(path))
+ except:
+ log_error(traceback.format_exc())
+
+ def _type_archive_detached(self, ctxt, view: core.BNBinaryView, id: ctypes.c_char_p, path: ctypes.c_char_p):
+ try:
+ self._notify.type_archive_detached(self._view, core.pyNativeStr(id), core.pyNativeStr(path))
+ except:
+ log_error(traceback.format_exc())
+
+ def _type_archive_connected(self, ctxt, view: core.BNBinaryView, archive: core.BNTypeArchive):
+ try:
+ py_archive = typearchive.TypeArchive(handle=core.BNNewTypeArchiveReference(archive))
+ self._notify.type_archive_connected(self._view, py_archive)
+ except:
+ log_error(traceback.format_exc())
+
+ def _type_archive_disconnected(self, ctxt, view: core.BNBinaryView, archive: core.BNTypeArchive):
+ try:
+ py_archive = typearchive.TypeArchive(handle=core.BNNewTypeArchiveReference(archive))
+ self._notify.type_archive_disconnected(self._view, py_archive)
+ except:
+ log_error(traceback.format_exc())
+
+
@property
def view(self) -> 'BinaryView':
return self._view
@@ -2842,6 +2899,31 @@ class BinaryView:
core.BNFreeTypeLibraryList(libraries, count.value)
@property
+ def attached_type_archives(self) -> Mapping['str', 'str']:
+ """All attached type archive ids and paths (read-only)"""
+ ids = ctypes.POINTER(ctypes.c_char_p)()
+ paths = ctypes.POINTER(ctypes.c_char_p)()
+ count = core.BNBinaryViewGetTypeArchives(self.handle, ids, paths)
+ result = {}
+ try:
+ for i in range(0, count):
+ result[core.pyNativeStr(ids[i])] = core.pyNativeStr(paths[i])
+ return result
+ finally:
+ core.BNFreeStringList(ids, count)
+ core.BNFreeStringList(paths, count)
+
+ @property
+ def connected_type_archives(self) -> List['typearchive.TypeArchive']:
+ """All connected type archive objects (read-only)"""
+ result = []
+ for (id, path) in self.attached_type_archives.items():
+ archive = self.get_type_archive(id)
+ if archive is not None:
+ result.append(archive)
+ return result
+
+ @property
def segments(self) -> List['Segment']:
"""List of segments (read-only)"""
count = ctypes.c_ulonglong(0)
@@ -7707,6 +7789,387 @@ class BinaryView:
core.BNFreeQualifiedName(result_name)
return lib, name
+ def attach_type_archive(self, archive: 'typearchive.TypeArchive'):
+ """
+ Attach a given type archive to the owned analysis and try to connect to it.
+ Names from that archive will be cached in the mapping but no types will actually be associated by calling this.
+ :param archive: New archive
+ """
+ attached = self.attach_type_archive_by_id(archive.id, archive.path)
+ assert attached == archive
+
+ def attach_type_archive_by_id(self, id: str, path: str) -> Optional['typearchive.TypeArchive']:
+ """
+ Attach a type archive to the owned analysis and try to connect to it.
+ If attaching was successful, names from that archive will be cached in the mapping,
+ but no types will actually be associated by calling this.
+
+ The behavior of this function is rather complicated, in an attempt to enable
+ control of both attaching and connecting Type Archives.
+
+ If there was a previously connected Type Archive whose id matches `id`, nothing
+ will happen and it will simply be returned.
+ If there was no previously connected Type Archive whose id matches `id`, and the
+ file at `path` contains a Type Archive whose id matches `id`, it will be
+ attached and connected.
+
+ If the file at `path` does not exist, nothing will happen and None will be returned.
+ If the file at `path` exists but does not contain a Type Archive whose id matches `id`,
+ nothing will happen and None will be returned.
+ If there was a previously attached but disconnected Type Archive whose id matches `id`,
+ and the file at `path` contains a Type Archive whose id matches `id`, the
+ previously attached Type Archive will have its saved path updated to point
+ to `path`. The Type Archive at `path` will be connected and returned.
+
+ :param id: Id of Type Archive to attach
+ :param path: Path to file of Type Archive to attach
+ :return: Attached archive object, if it could be connected.
+ """
+ archive = core.BNBinaryViewAttachTypeArchive(self.handle, id, path)
+ if not archive:
+ return None
+ return typearchive.TypeArchive(handle=archive)
+
+ def detach_type_archive(self, archive: 'typearchive.TypeArchive'):
+ """
+ Detach from a type archive, breaking all associations to types with the archive
+ :param archive: Type archive to detach
+ """
+ self.detach_type_archive_by_id(archive.id)
+
+ def detach_type_archive_by_id(self, id: str):
+ """
+ Detach from a type archive, breaking all associations to types with the archive
+ :param id: Id of archive to detach
+ """
+ if not core.BNBinaryViewDetachTypeArchive(self.handle, id):
+ raise RuntimeError("BNBinaryViewDetachTypeArchive")
+
+ def get_type_archive(self, id: str) -> Optional['typearchive.TypeArchive']:
+ """
+ Look up a connected archive by its id
+ :param id: Id of archive
+ :return: Archive, if one exists with that id. Otherwise None
+ """
+ result = core.BNBinaryViewGetTypeArchive(self.handle, id)
+ if result is None:
+ return None
+ return typearchive.TypeArchive(result)
+
+ def get_type_archive_path(self, id: str) -> Optional[str]:
+ """
+ Look up the path for an attached (but not necessarily connected) type archive by its id
+ :param id: Id of archive
+ :return: Archive path, if it is attached. Otherwise None.
+ """
+ result = core.BNBinaryViewGetTypeArchivePath(self.handle, id)
+ if result is None:
+ return None
+ return result
+
+ @property
+ def type_archive_type_names(self) -> Mapping['_types.QualifiedName', List[Tuple['typearchive.TypeArchive', str]]]:
+ """
+ Get a list of all available type names in all connected archives, and their archive/type id pair
+ :return: name <-> [(archive, archive type id)] for all type names
+ """
+ names = ctypes.POINTER(core.BNQualifiedName)()
+ name_count = core.BNBinaryViewGetTypeArchiveTypeNameList(self.handle, names)
+
+ result = {}
+ try:
+ for i in range(0, name_count):
+ name = _types.QualifiedName._from_core_struct(names[i])
+ result[name] = self.get_type_archives_for_type_name(name)
+ return result
+ finally:
+ core.BNFreeTypeNameList(names, name_count)
+
+ def get_type_archives_for_type_name(self, name: '_types.QualifiedNameType') -> List[Tuple['typearchive.TypeArchive', str]]:
+ """
+ Get a list of all connected type archives that have a given type name
+ :return: (archive, archive type id) for all archives
+ """
+ name = _types.QualifiedName(name)
+ archive_ids = ctypes.POINTER(ctypes.c_char_p)()
+ type_ids = ctypes.POINTER(ctypes.c_char_p)()
+ id_count = core.BNBinaryViewGetTypeArchiveTypeNames(self.handle, name._to_core_struct(), archive_ids, type_ids)
+ ids = []
+
+ type_archives = self.connected_type_archives
+ type_archives_by_id = {}
+ for archive in type_archives:
+ type_archives_by_id[archive.id] = archive
+ try:
+ for j in range(0, id_count):
+ ids.append((type_archives_by_id[core.pyNativeStr(archive_ids[j])], core.pyNativeStr(type_ids[j])))
+ return ids
+ finally:
+ core.BNFreeStringList(archive_ids, id_count)
+ core.BNFreeStringList(type_ids, id_count)
+
+ @property
+ def associated_type_archive_types(self) -> Mapping['_types.QualifiedName', Tuple[Optional['typearchive.TypeArchive'], str]]:
+ """
+ Get a list of all types in the analysis that are associated with attached type archives
+ :return: Map of all analysis types to their corresponding archive / id.
+ If a type is associated with a disconnected type archive, the archive will be None.
+ """
+ result = {}
+
+ type_archives = self.attached_type_archives
+ type_archives_by_id = {}
+ for (archive_id, _) in type_archives.items():
+ type_archives_by_id[archive_id] = self.get_type_archive(archive_id)
+
+ for type_id, (archive_id, archive_type_id) in self.associated_type_archive_type_ids.items():
+ name = self.get_type_name_by_id(type_id)
+ if name is None:
+ continue
+ result[name] = (type_archives_by_id.get(archive_id, None), archive_type_id)
+ return result
+
+ @property
+ def associated_type_archive_type_ids(self) -> Mapping[str, Tuple[str, str]]:
+ """
+ Get a list of all types in the analysis that are associated with type archives
+ :return: Map of all analysis types to their corresponding archive / id
+ """
+
+ type_ids = ctypes.POINTER(ctypes.c_char_p)()
+ archive_ids = ctypes.POINTER(ctypes.c_char_p)()
+ archive_type_ids = ctypes.POINTER(ctypes.c_char_p)()
+ count = core.BNBinaryViewGetAssociatedTypeArchiveTypes(self.handle, type_ids, archive_ids, archive_type_ids)
+
+ result = {}
+ try:
+ for i in range(0, count):
+ type_id = core.pyNativeStr(type_ids[i])
+ archive_id = core.pyNativeStr(archive_ids[i])
+ archive_type_id = core.pyNativeStr(archive_type_ids[i])
+ result[type_id] = (archive_id, archive_type_id)
+ return result
+ finally:
+ core.BNFreeStringList(type_ids, count)
+ core.BNFreeStringList(archive_ids, count)
+ core.BNFreeStringList(archive_type_ids, count)
+
+ def get_associated_types_from_archive(self, archive: 'typearchive.TypeArchive') -> Mapping['_types.QualifiedName', str]:
+ """
+ Get a list of all types in the analysis that are associated with a specific type archive
+ :return: Map of all analysis types to their corresponding archive id
+ """
+ result = {}
+
+ for type_id, archive_type_id in self.get_associated_types_from_archive_by_id(archive.id).items():
+ name = self.get_type_name_by_id(type_id)
+ if name is None:
+ continue
+ result[name] = archive_type_id
+ return result
+
+ def get_associated_types_from_archive_by_id(self, archive_id: str) -> Mapping[str, str]:
+ """
+ Get a list of all types in the analysis that are associated with a specific type archive
+ :return: Map of all analysis types to their corresponding archive id
+ """
+
+ type_ids = ctypes.POINTER(ctypes.c_char_p)()
+ archive_type_ids = ctypes.POINTER(ctypes.c_char_p)()
+ count = core.BNBinaryViewGetAssociatedTypesFromArchive(self.handle, archive_id, type_ids, archive_type_ids)
+
+ result = {}
+ try:
+ for i in range(0, count):
+ type_id = core.pyNativeStr(type_ids[i])
+ archive_type_id = core.pyNativeStr(archive_type_ids[i])
+ result[type_id] = archive_type_id
+ return result
+ finally:
+ core.BNFreeStringList(type_ids, count)
+ core.BNFreeStringList(archive_type_ids, count)
+
+ def get_associated_type_archive_type_target(self, name: '_types.QualifiedNameType') -> Optional[Tuple[Optional['typearchive.TypeArchive'], str]]:
+ """
+ Determine the target archive / type id of a given analysis type
+ :param name: Analysis type
+ :return: (archive, archive type id) if the type is associated. None otherwise.
+ """
+ type_id = self.get_type_id(name)
+ if type_id == '':
+ return None
+ result = self.get_associated_type_archive_type_target_by_id(type_id)
+ if result is None:
+ return None
+ archive_id, type_id = result
+ archive = self.get_type_archive(archive_id)
+ return archive, type_id
+
+ def get_associated_type_archive_type_target_by_id(self, type_id: str) -> Optional[Tuple[str, str]]:
+ """
+ Determine the target archive / type id of a given analysis type
+ :param type_id: Analysis type id
+ :return: (archive id, archive type id) if the type is associated. None otherwise.
+ """
+ archive_id = ctypes.c_char_p()
+ archive_type_id = ctypes.c_char_p()
+ if not core.BNBinaryViewGetAssociatedTypeArchiveTypeTarget(self.handle, type_id, archive_id, archive_type_id):
+ return None
+ result = (core.pyNativeStr(archive_id.value), core.pyNativeStr(archive_type_id.value))
+ core.free_string(archive_id)
+ core.free_string(archive_type_id)
+ return result
+
+ def get_associated_type_archive_type_source(self, archive: 'typearchive.TypeArchive', archive_type: '_types.QualifiedNameType') -> Optional['_types.QualifiedName']:
+ """
+ Determine the local source type name for a given archive type
+ :param archive: Target type archive
+ :param archive_type: Name of target archive type
+ :return: Name of source analysis type, if this type is associated. None otherwise.
+ """
+ archive_type_id = archive.get_type_id(archive_type)
+ if archive_type_id is None:
+ return None
+ result = self.get_associated_type_archive_type_source_by_id(archive.id, archive_type_id)
+ if result is None:
+ return None
+ return self.get_type_name_by_id(result)
+
+ def get_associated_type_archive_type_source_by_id(self, archive_id: str, archive_type_id: str) -> Optional[str]:
+ """
+ Determine the local source type id for a given archive type
+ :param archive_id: Id of target type archive
+ :param archive_type_id: Id of target archive type
+ :return: Id of source analysis type, if this type is associated. None otherwise.
+ """
+ type_id = ctypes.c_char_p()
+ if not core.BNBinaryViewGetAssociatedTypeArchiveTypeSource(self.handle, archive_id, archive_type_id, type_id):
+ return None
+ result = core.pyNativeStr(type_id.value)
+ core.free_string(type_id)
+ return result
+
+ def disassociate_type_archive_type(self, type: '_types.QualifiedNameType') -> bool:
+ """
+ Disassociate an associated type, so that it will no longer receive updates from its connected type archive
+ :param type: Name of type in analysis
+ :return: True if successful
+ """
+ type_id = self.get_type_id(type)
+ if type_id == '':
+ return False
+ return self.disassociate_type_archive_type_by_id(type_id)
+
+ def disassociate_type_archive_type_by_id(self, type_id: str) -> bool:
+ """
+ Disassociate an associated type id, so that it will no longer receive updates from its connected type archive
+ :param type_id: Id of type in analysis
+ :return: True if successful
+ """
+ return core.BNBinaryViewDisassociateTypeArchiveType(self.handle, type_id)
+
+ def pull_types_from_archive(self, archive: 'typearchive.TypeArchive', names: List['_types.QualifiedNameType']) \
+ -> Optional[Mapping['_types.QualifiedName', Tuple['_types.QualifiedName', '_types.Type']]]:
+ """
+ Pull types from a type archive, updating them and any dependencies
+ :param archive: Target type archive
+ :param names: Names of desired types in type archive
+ :return: { name: (name, type) } Mapping from archive name to (analysis name, definition), None on error
+ """
+ archive_type_ids = []
+ for name in names:
+ archive_type_id = archive.get_type_id(name)
+ if archive_type_id is None:
+ return None
+ archive_type_ids.append(archive_type_id)
+ result = self.pull_types_from_archive_by_id(archive.id, archive_type_ids)
+ if result is None:
+ return None
+
+ results = {}
+ for (archive_type_id, analysis_type_id) in result.items():
+ results[archive.get_type_name_by_id(archive_type_id)] = (self.get_type_name_by_id(analysis_type_id), self.get_type_by_id(analysis_type_id))
+
+ return results
+
+ def pull_types_from_archive_by_id(self, archive_id: str, archive_type_ids: List[str]) \
+ -> Optional[Mapping[str, str]]:
+ """
+ Pull types from a type archive by id, updating them and any dependencies
+ :param archive_id: Target type archive id
+ :param archive_type_ids: Ids of desired types in type archive
+ :return: { id: id } Mapping from archive type id to analysis type id, None on error
+ """
+ api_ids = (ctypes.c_char_p * len(archive_type_ids))()
+ for i, id in enumerate(archive_type_ids):
+ api_ids[i] = core.cstr(id)
+
+ updated_archive_type_strs = ctypes.POINTER(ctypes.c_char_p)()
+ updated_analysis_type_strs = ctypes.POINTER(ctypes.c_char_p)()
+ updated_type_count = ctypes.c_size_t(0)
+ try:
+ if not core.BNBinaryViewPullTypeArchiveTypes(self.handle, archive_id, api_ids, len(archive_type_ids), updated_archive_type_strs, updated_analysis_type_strs, updated_type_count):
+ return None
+
+ results = {}
+ for i in range(0, updated_type_count.value):
+ results[core.pyNativeStr(updated_archive_type_strs[i])] = core.pyNativeStr(updated_analysis_type_strs[i])
+ return results
+ finally:
+ core.BNFreeStringList(updated_archive_type_strs, updated_type_count.value)
+ core.BNFreeStringList(updated_analysis_type_strs, updated_type_count.value)
+
+ def push_types_to_archive(self, archive: 'typearchive.TypeArchive', names: List['_types.QualifiedNameType']) \
+ -> Optional[Mapping['_types.QualifiedName', Tuple['_types.QualifiedName', '_types.Type']]]:
+ """
+ Push a collection of types, and all their dependencies, into a type archive
+ :param archive: Target type archive
+ :param names: Names of types in analysis
+ :return: { name: (name, type) } Mapping from analysis name to (archive name, definition), None on error
+ """
+ analysis_type_ids = []
+ for name in names:
+ analysis_type_id = self.get_type_id(name)
+ if analysis_type_id is None:
+ return None
+ analysis_type_ids.append(analysis_type_id)
+ result = self.push_types_to_archive_by_id(archive.id, analysis_type_ids)
+ if result is None:
+ return None
+
+ results = {}
+ for (analysis_type_id, archive_type_id) in result.items():
+ results[self.get_type_name_by_id(analysis_type_id)] = (archive.get_type_name_by_id(archive_type_id), archive.get_type_by_id(archive_type_id))
+
+ return results
+
+ def push_types_to_archive_by_id(self, archive_id: str, type_ids: List[str]) \
+ -> Optional[Mapping[str, str]]:
+ """
+ Push a collection of types, and all their dependencies, into a type archive
+ :param archive_id: Id of target type archive
+ :param type_ids: Ids of types in analysis
+ :return: True if successful
+ """
+ api_ids = (ctypes.c_char_p * len(type_ids))()
+ for i, id in enumerate(type_ids):
+ api_ids[i] = core.cstr(id)
+
+ updated_analysis_type_strs = ctypes.POINTER(ctypes.c_char_p)()
+ updated_archive_type_strs = ctypes.POINTER(ctypes.c_char_p)()
+ updated_type_count = ctypes.c_size_t(0)
+ try:
+ if not core.BNBinaryViewPushTypeArchiveTypes(self.handle, archive_id, api_ids, len(type_ids), updated_analysis_type_strs, updated_archive_type_strs, updated_type_count):
+ return None
+
+ results = {}
+ for i in range(0, updated_type_count.value):
+ results[core.pyNativeStr(updated_analysis_type_strs[i])] = core.pyNativeStr(updated_archive_type_strs[i])
+ return results
+ finally:
+ core.BNFreeStringList(updated_analysis_type_strs, updated_type_count.value)
+ core.BNFreeStringList(updated_archive_type_strs, updated_type_count.value)
+
def register_platform_types(self, platform: '_platform.Platform') -> None:
"""
``register_platform_types`` ensures that the platform-specific types for a :py:class:`Platform` are available
diff --git a/python/typearchive.py b/python/typearchive.py
new file mode 100644
index 00000000..c4e94a5e
--- /dev/null
+++ b/python/typearchive.py
@@ -0,0 +1,758 @@
+# 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
+import traceback
+from typing import Optional, List, Dict, Union, Tuple
+
+# Binary Ninja components
+import binaryninja
+from . import _binaryninjacore as core
+from . import types as ty_
+from . import log
+from . import metadata
+from . import databuffer
+from . import platform
+from . import architecture
+from . import binaryview
+
+
+class TypeArchive:
+ def __init__(self, handle: core.BNTypeArchiveHandle):
+ binaryninja._init_plugins()
+ self.handle: core.BNTypeArchiveHandle = core.handle_of_type(handle, core.BNTypeArchive)
+ self._notifications = {}
+
+ def __hash__(self):
+ return hash(ctypes.addressof(self.handle.contents))
+
+ def __del__(self):
+ if core is not None:
+ core.BNFreeTypeArchiveReference(self.handle)
+
+ def __repr__(self):
+ return f"<type archive '{self.path}'>"
+
+ def __eq__(self, other):
+ if not isinstance(other, TypeArchive):
+ return False
+ return self.id == other.id
+
+ @staticmethod
+ def open(path: str) -> Optional['TypeArchive']:
+ """
+ Open the Type Archive at the given path, if it exists.
+ :param path: Path to Type Archive file
+ :return: Type Archive, or None if it could not be loaded.
+ """
+ handle = core.BNOpenTypeArchive(path)
+ if handle is None:
+ return None
+ return TypeArchive(handle=handle)
+
+ @staticmethod
+ def create(path: str, platform: 'platform.Platform') -> Optional['TypeArchive']:
+ """
+ Create a Type Archive at the given path.
+ :param path: Path to Type Archive file
+ :param platform: Relevant platform for types in the archive
+ :return: Type Archive, or None if it could not be created.
+ """
+ handle = core.BNCreateTypeArchive(path, platform.handle)
+ if handle is None:
+ return None
+ return TypeArchive(handle=handle)
+
+ @staticmethod
+ def lookup_by_id(id: str) -> Optional['TypeArchive']:
+ """
+ Get a reference to the Type Archive with the known id, if one exists.
+ :param id: Type Archive id
+ :return: Type archive, or None if it could not be found.
+ """
+ handle = core.BNLookupTypeArchiveById(id)
+ if handle is None:
+ return None
+ return TypeArchive(handle=handle)
+
+ @property
+ def path(self) -> Optional[str]:
+ """
+ Get the path to the Type Archive's file
+ :return: File path
+ """
+ return core.BNGetTypeArchivePath(self.handle)
+
+ @property
+ def id(self) -> Optional[str]:
+ """
+ Get the guid for a Type Archive
+ :return: Guid string
+ """
+ return core.BNGetTypeArchiveId(self.handle)
+
+ @property
+ def platform(self) -> 'platform.Platform':
+ """
+ Get the associated Platform for a Type Archive
+ :return: Platform object
+ """
+ handle = core.BNGetTypeArchivePlatform(self.handle)
+ assert handle is not None
+ return platform.Platform(handle=handle)
+
+ @property
+ def current_snapshot_id(self) -> str:
+ """
+ Get the id of the current snapshot in the type archive
+ :return: Snapshot id
+ """
+ result = core.BNGetTypeArchiveCurrentSnapshotId(self.handle)
+ if result is None:
+ raise RuntimeError("BNGetTypeArchiveCurrentSnapshotId")
+ return result
+
+ @current_snapshot_id.setter
+ def current_snapshot_id(self, value: str):
+ """
+ Revert the type archive's current snapshot to the given snapshot
+ :param value: Snapshot id
+ """
+ core.BNSetTypeArchiveCurrentSnapshot(self.handle, value)
+
+ @property
+ def all_snapshot_ids(self) -> List[str]:
+ """
+ Get a list of every snapshot's id
+ :return: All ids (including the empty first snapshot)
+ """
+ count = ctypes.c_ulonglong(0)
+ ids = core.BNGetTypeArchiveAllSnapshotIds(self.handle, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveAllSnapshotIds")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def get_snapshot_parent_ids(self, snapshot: str) -> Optional[List[str]]:
+ """
+ Get the ids of the parents to the given snapshot
+ :param snapshot: Child snapshot id
+ :return: Parent snapshot ids, or empty list if the snapshot is a root
+ """
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveSnapshotParentIds(self.handle, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveSnapshotParentIds")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def get_snapshot_child_ids(self, snapshot: str) -> Optional[List[str]]:
+ """
+ Get the ids of the children to the given snapshot
+ :param snapshot: Parent snapshot id
+ :return: Child snapshot ids, or empty list if the snapshot is a root
+ """
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveSnapshotChildIds(self.handle, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveSnapshotChildIds")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def add_type(self, name: 'ty_.QualifiedNameType', type: 'ty_.Type') -> None:
+ """
+ Add named types to the type archive. Type must have all dependant named types added
+ prior to being added, or this function will fail.
+ If the type already exists, it will be overwritten.
+ :param name: Name of new type
+ :param type: Definition of new type
+ """
+ self.add_types([(name, type)])
+
+ def add_types(self, new_types: List[Tuple['ty_.QualifiedNameType', 'ty_.Type']]) -> None:
+ """
+ Add named types to the type archive. Types must have all dependant named
+ types prior to being added, or this function will fail.
+ Types already existing with any added names will be overwritten.
+ :param new_types: Names and definitions of new types
+ """
+ api_types = (core.BNQualifiedNameAndType * len(new_types))()
+ i = 0
+ for (name, type) in new_types:
+ if not isinstance(name, ty_.QualifiedName):
+ name = ty_.QualifiedName(name)
+ type = type.immutable_copy()
+ if not isinstance(type, ty_.Type):
+ raise ValueError("parameter type must be a Type")
+ api_types[i].name = name._to_core_struct()
+ api_types[i].type = type.handle
+ i += 1
+
+ if not core.BNAddTypeArchiveTypes(self.handle, api_types, len(new_types)):
+ raise RuntimeError("BNAddTypeArchiveTypes")
+
+ def rename_type(self, old_name: 'ty_.QualifiedNameType', new_name: 'ty_.QualifiedNameType') -> None:
+ """
+ Change the name of an existing type in the type archive.
+ :param old_name: Old type name in archive
+ :param new_name: New type name
+ """
+ id = self.get_type_id(old_name)
+ return self.rename_type_by_id(id, new_name)
+
+ def rename_type_by_id(self, id: str, new_name: 'ty_.QualifiedNameType') -> None:
+ """
+ Change the name of an existing type in the type archive.
+ :param id: Old id of type in archive
+ :param new_name: New type name
+ """
+ if not isinstance(new_name, ty_.QualifiedName):
+ new_name = ty_.QualifiedName(new_name)
+ if not core.BNRenameTypeArchiveType(self.handle, id, new_name._to_core_struct()):
+ raise RuntimeError("BNRenameTypeArchiveType")
+
+ def delete_type(self, name: 'ty_.QualifiedNameType') -> None:
+ """
+ Delete an existing type in the type archive.
+ :param name: Type name
+ """
+ id = self.get_type_id(name)
+ if id is None:
+ raise RuntimeError(f"Unknown type {name}")
+ self.delete_type_by_id(id)
+
+ def delete_type_by_id(self, id: str) -> None:
+ """
+ Delete an existing type in the type archive.
+ :param id: Type id
+ """
+ if not core.BNDeleteTypeArchiveType(self.handle, id):
+ raise RuntimeError("BNDeleteTypeArchiveType")
+
+ def get_type_by_name(self, name: 'ty_.QualifiedNameType', snapshot: Optional[str] = None) -> Optional[ty_.Type]:
+ """
+ Retrieve a stored type in the archive
+ :param name: Type name
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Type, if it exists. Otherwise None
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ if not isinstance(name, ty_.QualifiedName):
+ name = ty_.QualifiedName(name)
+ t = core.BNGetTypeArchiveTypeByName(self.handle, name._to_core_struct(), snapshot)
+ if t is None:
+ return None
+ return ty_.Type.create(t)
+
+ def get_type_by_id(self, id: str, snapshot: Optional[str] = None) -> Optional[ty_.Type]:
+ """
+ Retrieve a stored type in the archive by id
+ :param id: Type id
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Type, if it exists. Otherwise None
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ t = core.BNGetTypeArchiveTypeById(self.handle, id, snapshot)
+ if t is None:
+ return None
+ return ty_.Type.create(t)
+
+ def get_type_name_by_id(self, id: str, snapshot: Optional[str] = None) -> Optional['ty_.QualifiedName']:
+ """
+ Retrieve a type's name by its id
+ :param id: Type id
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Type name, if it exists. Otherwise None
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ name = core.BNGetTypeArchiveTypeName(self.handle, id, snapshot)
+ if name is None:
+ return None
+ qname = ty_.QualifiedName._from_core_struct(name)
+ if len(qname.name) == 0:
+ return None
+ return qname
+
+ def get_type_id(self, name: 'ty_.QualifiedNameType', snapshot: Optional[str] = None) -> Optional[str]:
+ """
+ Retrieve a type's id by its name
+ :param name: Type name
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Type id, if it exists. Otherwise None
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ if not isinstance(name, ty_.QualifiedName):
+ name = ty_.QualifiedName(name)
+ t = core.BNGetTypeArchiveTypeId(self.handle, name._to_core_struct(), snapshot)
+ if t is None:
+ return None
+ if t == "":
+ return None
+ return t
+
+ @property
+ def types(self) -> Dict[ty_.QualifiedName, ty_.Type]:
+ """
+ Retrieve all stored types in the archive at the current snapshot
+ :return: Map of all types, by name
+ """
+ return self.get_types()
+
+ @property
+ def types_and_ids(self) -> Dict[str, Tuple[ty_.QualifiedName, ty_.Type]]:
+ """
+ Retrieve all stored types in the archive at the current snapshot
+ :return: Map of type id to type name and definition
+ """
+ return self.get_types_and_ids()
+
+ def get_types(self, snapshot: Optional[str] = None) -> Dict[ty_.QualifiedName, ty_.Type]:
+ """
+ Retrieve all stored types in the archive at a snapshot
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Map of all types, by name
+ """
+ result = {}
+ for id, (name, type) in self.get_types_and_ids(snapshot).items():
+ result[name] = type
+ return result
+
+ def get_types_and_ids(self, snapshot: Optional[str] = None) -> Dict[str, Tuple[ty_.QualifiedName, ty_.Type]]:
+ """
+ Retrieve all stored types in the archive at a snapshot
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Map of type id to type name and definition
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ count = ctypes.c_ulonglong(0)
+ result = {}
+ named_types = core.BNGetTypeArchiveTypes(self.handle, snapshot, count)
+ assert named_types is not None, "core.BNGetTypeArchiveTypes returned None"
+ try:
+ for i in range(0, count.value):
+ name = ty_.QualifiedName._from_core_struct(named_types[i].name)
+ id = core.pyNativeStr(named_types[i].id)
+ result[id] = (name, ty_.Type.create(core.BNNewTypeReference(named_types[i].type)))
+ return result
+ finally:
+ core.BNFreeTypeIdList(named_types, count.value)
+
+ @property
+ def type_ids(self) -> List[str]:
+ """
+ Get a list of all types' ids in the archive at the current snapshot
+ :return: All type ids
+ """
+ return self.get_type_ids()
+
+ def get_type_ids(self, snapshot: Optional[str] = None) -> List[str]:
+ """
+ Get a list of all types' ids in the archive at a snapshot
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: All type ids
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ count = ctypes.c_ulonglong(0)
+ result = []
+ ids = core.BNGetTypeArchiveTypeIds(self.handle, snapshot, count)
+ assert ids is not None, "core.BNGetTypeArchiveTypeIds returned None"
+ try:
+ for i in range(count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ @property
+ def type_names(self) -> List['ty_.QualifiedName']:
+ """
+ Get a list of all types' names in the archive at the current snapshot
+ :return: All type names
+ """
+ return self.get_type_names()
+
+ def get_type_names(self, snapshot: Optional[str] = None) -> List['ty_.QualifiedName']:
+ """
+ Get a list of all types' names in the archive at a snapshot
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: All type names
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ count = ctypes.c_ulonglong(0)
+ result = []
+ names = core.BNGetTypeArchiveTypeNames(self.handle, snapshot, count)
+ assert names is not None, "core.BNGetTypeArchiveTypeNames returned None"
+ try:
+ for i in range(count.value):
+ result.append(ty_.QualifiedName._from_core_struct(names[i]))
+ return result
+ finally:
+ core.BNFreeQualifiedNameArray(names, count.value)
+
+ @property
+ def type_names_and_ids(self) -> Dict[str, 'ty_.QualifiedName']:
+ """
+ Get a list of all types' names and ids in the archive at the current snapshot
+ :return: Mapping of all type ids to names
+ """
+ return self.get_type_names_and_ids()
+
+ def get_type_names_and_ids(self, snapshot: Optional[str] = None) -> Dict[str, 'ty_.QualifiedName']:
+ """
+ Get a list of all types' names and ids in the archive at a current snapshot
+ :param snapshot: Snapshot id to search for types, or None to search the latest snapshot
+ :return: Mapping of all type ids to names
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ names = ctypes.POINTER(core.BNQualifiedName)()
+ ids = ctypes.POINTER(ctypes.c_char_p)()
+ count = ctypes.c_ulonglong(0)
+ result = {}
+ if not core.BNGetTypeArchiveTypeNamesAndIds(self.handle, snapshot, names, ids, count):
+ raise RuntimeError("core.BNGetTypeArchiveTypeNamesAndIds returned False")
+ try:
+ for i in range(count.value):
+ id = core.pyNativeStr(ids[i])
+ name = ty_.QualifiedName._from_core_struct(names[i])
+ result[id] = name
+ return result
+ finally:
+ core.BNFreeQualifiedNameArray(names, count.value)
+
+ def get_outgoing_direct_references(self, id: str, snapshot: Optional[str] = None) -> List[str]:
+ """
+ Get all types a given type references directly
+ :param id: Source type id
+ :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot
+ :return: Target type ids
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveOutgoingDirectTypeReferences(self.handle, id, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveOutgoingDirectTypeReferences")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def get_outgoing_recursive_references(self, id: str, snapshot: Optional[str] = None) -> List[str]:
+ """
+ Get all types a given type references, and any types that the referenced types reference
+ :param id: Source type id
+ :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot
+ :return: Target type ids
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveOutgoingRecursiveTypeReferences(self.handle, id, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveOutgoingRecursiveTypeReferences")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def get_incoming_direct_references(self, id: str, snapshot: Optional[str] = None) -> List[str]:
+ """
+ Get all types that reference a given type
+ :param id: Target type id
+ :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot
+ :return: Source type ids
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveIncomingDirectTypeReferences(self.handle, id, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveIncomingDirectTypeReferences")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def get_incoming_recursive_references(self, id: str, snapshot: Optional[str] = None) -> List[str]:
+ """
+ Get all types that reference a given type, and all types that reference them, recursively
+ :param id: Target type id
+ :param snapshot: Snapshot id to search for types, or empty string to search the latest snapshot
+ :return: Source type ids
+ """
+ if snapshot is None:
+ snapshot = self.current_snapshot_id
+ assert id is not None
+ count = ctypes.c_size_t(0)
+ ids = core.BNGetTypeArchiveIncomingRecursiveTypeReferences(self.handle, id, snapshot, count)
+ if ids is None:
+ raise RuntimeError("BNGetTypeArchiveIncomingRecursiveTypeReferences")
+ result = []
+ try:
+ for i in range(0, count.value):
+ result.append(core.pyNativeStr(ids[i]))
+ return result
+ finally:
+ core.BNFreeStringList(ids, count.value)
+
+ def query_metadata(self, key: str) -> Optional['metadata.MetadataValueType']:
+ """
+ Look up a metadata entry in the archive
+ :param string key: key to query
+ :rtype: Metadata associated with the key, if it exists. Otherwise, None
+ :Example:
+
+ >>> ta: TypeArchive
+ >>> ta.store_metadata("ordinals", {"9": "htons"})
+ >>> ta.query_metadata("ordinals")["9"]
+ "htons"
+ """
+ md_handle = core.BNTypeArchiveQueryMetadata(self.handle, key)
+ if md_handle is None:
+ return None
+ return metadata.Metadata(handle=md_handle).value
+
+ def store_metadata(self, key: str, md: 'metadata.MetadataValueType') -> None:
+ """
+ Store a key/value pair in the archive's metadata storage
+ :param string key: key value to associate the Metadata object with
+ :param Varies md: object to store.
+ :Example:
+
+ >>> ta: TypeArchive
+ >>> ta.store_metadata("ordinals", {"9": "htons"})
+ >>> ta.query_metadata("ordinals")["9"]
+ "htons"
+ """
+ if not isinstance(md, metadata.Metadata):
+ md = metadata.Metadata(md)
+ if not core.BNTypeArchiveStoreMetadata(self.handle, key, md.handle):
+ raise RuntimeError("BNTypeArchiveStoreMetadata")
+
+ def remove_metadata(self, key: str) -> None:
+ """
+ Delete a given metadata entry in the archive
+ :param string key: key associated with metadata
+ :Example:
+
+ >>> ta: TypeArchive
+ >>> ta.store_metadata("integer", 1337)
+ >>> ta.remove_metadata("integer")
+ """
+ core.BNTypeArchiveRemoveMetadata(self.handle, key)
+
+ def serialize_snapshot(self, snapshot: str) -> 'databuffer.DataBuffer':
+ """
+ Turn a given snapshot into a data stream
+ :param snapshot: Snapshot id
+ :return: Buffer containing serialized snapshot data
+ """
+ result = core.BNTypeArchiveSerializeSnapshot(self.handle, snapshot)
+ if not result:
+ raise RuntimeError("BNTypeArchiveDeserializeSnapshot")
+ return databuffer.DataBuffer(handle=result)
+
+ def deserialize_snapshot(self, data: 'databuffer.DataBufferInputType') -> str:
+ """
+ Take a serialized snapshot data stream and create a new snapshot from it
+ :param data: Snapshot data
+ :return: String of created snapshot id
+ """
+ buffer = databuffer.DataBuffer(data)
+ result = core.BNTypeArchiveDeserializeSnapshot(self.handle, buffer.handle)
+ if not result:
+ raise RuntimeError("BNTypeArchiveDeserializeSnapshot")
+ return result
+
+ def register_notification(self, notify: 'TypeArchiveNotification') -> None:
+ """
+ Register a notification listener
+ :param notify: Object to receive notifications
+ """
+ cb = TypeArchiveNotificationCallbacks(self, notify)
+ cb._register()
+ self._notifications[notify] = cb
+
+ def unregister_notification(self, notify: 'TypeArchiveNotification') -> None:
+ """
+ Unregister a notification listener
+ :param notify: Object to no longer receive notifications
+ """
+ if notify in self._notifications:
+ self._notifications[notify]._unregister()
+ del self._notifications[notify]
+
+
+class TypeArchiveNotification:
+ def __init__(self):
+ pass
+
+ def view_attached(self, archive: 'TypeArchive', view: 'binaryview.BinaryView') -> None:
+ """
+ Called when a new view attaches to the type archive.
+ :param archive: Source Type archive
+ :param view: View attaching the archive
+ """
+ pass
+
+ def view_detached(self, archive: 'TypeArchive', view: 'binaryview.BinaryView') -> None:
+ """
+ Called when a view that has previously attached the archive detaches it
+ :param archive: Source Type archive
+ :param view: View detaching the archive
+ """
+ pass
+
+ def type_added(self, archive: 'TypeArchive', id: str, definition: 'ty_.Type') -> None:
+ """
+ Called when a type is added to the archive
+ :param archive: Source Type archive
+ :param id: Id of type added
+ :param definition: Definition of type
+ """
+ pass
+
+ def type_updated(self, archive: 'TypeArchive', id: str, old_definition: 'ty_.Type', new_definition: 'ty_.Type') -> None:
+ """
+ Called when a type in the archive is updated to a new definition
+ :param archive: Source Type archive
+ :param id: Id of type
+ :param old_definition: Previous definition
+ :param new_definition: Current definition
+ """
+ pass
+
+ def type_renamed(self, archive: 'TypeArchive', id: str, old_name: 'ty_.QualifiedName', new_name: 'ty_.QualifiedName') -> None:
+ """
+ Called when a type in the archive is renamed
+ :param archive: Source Type archive
+ :param id: Type id
+ :param old_name: Previous name
+ :param new_name: Current name
+ """
+ pass
+
+ def type_deleted(self, archive: 'TypeArchive', id: str, definition: 'ty_.Type') -> None:
+ """
+ Called when a type in the archive is deleted from the archive
+ :param archive: Source Type archive
+ :param id: Id of type deleted
+ :param definition: Definition of type deleted
+ """
+ pass
+
+
+class TypeArchiveNotificationCallbacks:
+ def __init__(self, archive: 'TypeArchive', notify: 'TypeArchiveNotification'):
+ self._archive = archive
+ self._notify = notify
+ self._cb = core.BNTypeArchiveNotification()
+ self._cb.context = 0
+ self._cb.typeAdded = self._cb.typeAdded.__class__(self._type_added)
+ self._cb.typeUpdated = self._cb.typeUpdated.__class__(self._type_updated)
+ self._cb.typeRenamed = self._cb.typeRenamed.__class__(self._type_renamed)
+ self._cb.typeDeleted = self._cb.typeDeleted.__class__(self._type_deleted)
+
+ def _register(self) -> None:
+ core.BNRegisterTypeArchiveNotification(self._archive.handle, self._cb)
+
+ def _unregister(self) -> None:
+ core.BNUnregisterTypeArchiveNotification(self._archive.handle, self._cb)
+
+ def _view_attached(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), view: ctypes.POINTER(core.BNBinaryView)) -> None:
+ try:
+ self._notify.view_attached(self._archive, binaryview.BinaryView(handle=core.BNNewViewReference(view)))
+ except:
+ log.log_error(traceback.format_exc())
+
+ def _view_detached(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), view: ctypes.POINTER(core.BNBinaryView)) -> None:
+ try:
+ self._notify.view_detached(self._archive, binaryview.BinaryView(handle=core.BNNewViewReference(view)))
+ except:
+ log.log_error(traceback.format_exc())
+
+ def _type_added(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), id: ctypes.c_char_p, definition: ctypes.POINTER(core.BNType)) -> None:
+ try:
+ self._notify.type_added(self._archive, core.pyNativeStr(id), ty_.Type.create(handle=core.BNNewTypeReference(definition)))
+ except:
+ log.log_error(traceback.format_exc())
+
+ def _type_updated(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), id: ctypes.c_char_p, old_definition: ctypes.POINTER(core.BNType), new_definition: ctypes.POINTER(core.BNType)) -> None:
+ try:
+ self._notify.type_updated(self._archive, core.pyNativeStr(id), ty_.Type.create(handle=core.BNNewTypeReference(old_definition)), ty_.Type.create(handle=core.BNNewTypeReference(new_definition)))
+ except:
+ log.log_error(traceback.format_exc())
+
+ def _type_renamed(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), id: ctypes.c_char_p, old_name: ctypes.POINTER(core.BNQualifiedName), new_name: ctypes.POINTER(core.BNQualifiedName)) -> None:
+ try:
+ self._notify.type_renamed(self._archive, core.pyNativeStr(id), ty_.QualifiedName._from_core_struct(old_name.contents), ty_.QualifiedName._from_core_struct(new_name.contents))
+ except:
+ log.log_error(traceback.format_exc())
+
+ def _type_deleted(self, ctxt, archive: ctypes.POINTER(core.BNTypeArchive), id: ctypes.c_char_p, definition: ctypes.POINTER(core.BNType)) -> None:
+ try:
+ self._notify.type_deleted(self._archive, core.pyNativeStr(id), ty_.Type.create(handle=core.BNNewTypeReference(definition)))
+ except:
+ log.log_error(traceback.format_exc())
+
+ @property
+ def archive(self) -> 'TypeArchive':
+ return self._archive
+
+ @property
+ def notify(self) -> 'TypeArchiveNotification':
+ return self._notify \ No newline at end of file