diff options
| author | Glenn Smith <glenn@vector35.com> | 2023-11-01 17:03:17 -0400 |
|---|---|---|
| committer | Glenn Smith <glenn@vector35.com> | 2024-02-07 17:23:49 -0500 |
| commit | 8037455723546000448135499957273623776581 (patch) | |
| tree | 268322ef34d16e067210df592dd782d50f294e3c /python | |
| parent | e1542ed05f3f8c273206bb9ead8d39fea87430bb (diff) | |
Type Archives
Diffstat (limited to 'python')
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/binaryview.py | 463 | ||||
| -rw-r--r-- | python/typearchive.py | 758 |
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 |
