diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/binaryview.py | 32 | ||||
| -rw-r--r-- | python/filemetadata.py | 37 | ||||
| -rw-r--r-- | python/undo.py | 81 |
3 files changed, 148 insertions, 2 deletions
diff --git a/python/binaryview.py b/python/binaryview.py index 68eedc54..dee0375e 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -79,6 +79,7 @@ from . import deprecation from . import typecontainer from . import externallibrary from . import project +from . import undo PathType = Union[str, os.PathLike] @@ -667,6 +668,10 @@ class BinaryDataNotificationCallbacks: 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) + + self._cb.undoEntryAdded = self._cb.undoEntryAdded.__class__(self._undo_entry_added) + self._cb.undoEntryTaken = self._cb.undoEntryTaken.__class__(self._undo_entry_taken) + self._cb.redoEntryTaken = self._cb.redoEntryTaken.__class__(self._redo_entry_taken) else: if notify.notifications & NotificationType.NotificationBarrier: self._cb.notificationBarrier = self._cb.notificationBarrier.__class__(self._notification_barrier) @@ -756,6 +761,13 @@ class BinaryDataNotificationCallbacks: if notify.notifications & NotificationType.TypeArchiveDisconnected: self._cb.typeArchiveDisconnected = self._cb.typeArchiveDisconnected.__class__(self._type_archive_disconnected) + if notify.notifications & NotificationType.UndoEntryAdded: + self._cb.undoEntryAdded = self._cb.undoEntryAdded.__class__(self._undo_entry_added) + if notify.notifications & NotificationType.UndoEntryTaken: + self._cb.undoEntryTaken = self._cb.undoEntryTaken.__class__(self._undo_entry_taken) + if notify.notifications & NotificationType.RedoEntryTaken: + self._cb.redoEntryTaken = self._cb.redoEntryTaken.__class__(self._redo_entry_taken) + def _register(self) -> None: core.BNRegisterDataNotification(self._view.handle, self._cb) @@ -1152,6 +1164,26 @@ class BinaryDataNotificationCallbacks: except: log_error(traceback.format_exc()) + def _undo_entry_added(self, ctxt, view: core.BNBinaryView, archive: core.BNUndoEntry): + try: + py_entry = undo.UndoEntry(handle=core.BNNewUndoEntryReference(archive)) + self._notify.undo_entry_added(self._view, py_entry) + except: + log_error(traceback.format_exc()) + + def _undo_entry_taken(self, ctxt, view: core.BNBinaryView, archive: core.BNUndoEntry): + try: + py_entry = undo.UndoEntry(handle=core.BNNewUndoEntryReference(archive)) + self._notify.undo_entry_taken(self._view, py_entry) + except: + log_error(traceback.format_exc()) + + def _redo_entry_taken(self, ctxt, view: core.BNBinaryView, archive: core.BNUndoEntry): + try: + py_entry = undo.UndoEntry(handle=core.BNNewUndoEntryReference(archive)) + self._notify.redo_entry_taken(self._view, py_entry) + except: + log_error(traceback.format_exc()) @property def view(self) -> 'BinaryView': diff --git a/python/filemetadata.py b/python/filemetadata.py index e0222fd2..a78f548b 100644 --- a/python/filemetadata.py +++ b/python/filemetadata.py @@ -32,6 +32,7 @@ from . import binaryview from . import database from . import deprecation from . import project +from . import undo ProgressFuncType = Callable[[int, int], bool] ViewName = str @@ -139,7 +140,6 @@ class FileMetadata: self._nav: Optional[NavigationHandler] = None assert _handle is not None self.handle = _handle - self._previous_undos = [] def __repr__(self): return f"<FileMetadata: {self.filename}>" @@ -384,7 +384,6 @@ class FileMetadata: >>> """ id = core.BNBeginUndoActions(self.handle, anonymous_allowed) - self._previous_undos.append(id) return id def commit_undo_actions(self, id: Optional[str] = None) -> None: @@ -516,6 +515,40 @@ class FileMetadata: """ core.BNRedo(self.handle) + @property + def undo_entries(self) -> List['undo.UndoEntry']: + count = ctypes.c_ulonglong() + + entries = core.BNGetUndoEntries(self.handle, count) + assert entries is not None, "core.BNGetUndoEntries returned None" + + result = [] + try: + for i in range(0, count.value): + tag_handle = core.BNNewUndoEntryReference(entries[i]) + assert tag_handle is not None, "core.BNNewUndoEntryReference returned None" + result.append(undo.UndoEntry(tag_handle)) + return result + finally: + core.BNFreeUndoEntryList(entries, count.value) + + @property + def redo_entries(self) -> List['undo.UndoEntry']: + count = ctypes.c_ulonglong() + + entries = core.BNGetRedoEntries(self.handle, count) + assert entries is not None, "core.BNGetRedoEntries returned None" + + result = [] + try: + for i in range(0, count.value): + tag_handle = core.BNNewUndoEntryReference(entries[i]) + assert tag_handle is not None, "core.BNNewUndoEntryReference returned None" + result.append(undo.UndoEntry(tag_handle)) + return result + finally: + core.BNFreeUndoEntryList(entries, count.value) + def navigate(self, view: ViewName, offset: int) -> bool: """ ``navigate`` navigates the UI to the specified virtual address diff --git a/python/undo.py b/python/undo.py new file mode 100644 index 00000000..4a0a7124 --- /dev/null +++ b/python/undo.py @@ -0,0 +1,81 @@ +# Copyright (c) 2015-2024 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + + +import ctypes +from typing import List + +from . import _binaryninjacore as core + + +class UndoAction: + """ + Class representing an action in an UndoEntry + """ + def __init__(self, handle: core.BNUndoActionHandle): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeUndoAction(self._handle) + + def __repr__(self) -> str: + return f"<UndoAction: {self}>" + + def __str__(self): + return self.summary_text + + @property + def summary_text(self) -> str: + return core.BNUndoActionGetSummaryText(self._handle) # type: ignore + +class UndoEntry: + """ + Class representing an entry in undo/redo history + """ + def __init__(self, handle: core.BNUndoEntryHandle): + self._handle = handle + + def __del__(self): + if core is not None: + core.BNFreeUndoEntry(self._handle) + + @property + def actions(self) -> List[UndoAction]: + """ + Get the list of actions in this entry + + :return: List of UndoAction in this UndoEntry + """ + + count = ctypes.c_size_t() + value = core.BNUndoEntryGetActions(self._handle, count) + if value is None: + raise Exception("Failed to get list undo actions") + result = [] + try: + for i in range(count.value): + folder_handle = core.BNNewUndoActionReference(value[i]) + if folder_handle is None: + raise Exception("core.BNNewUndoActionReference returned None") + result.append(UndoAction(folder_handle)) + return result + finally: + core.BNFreeUndoActionList(value, count.value) |
