from PySide6.QtWidgets import QTreeView, QVBoxLayout, QWidget from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize from binaryninja.enums import SymbolType, SymbolBinding import binaryninjaui from binaryninjaui import ViewFrame, FilterTarget, FilteredView, UIContext, UIActionHandler class GenericExportsModel(QAbstractItemModel): def __init__(self, data): super(GenericExportsModel, self).__init__() self.allEntries = [] self.addr_col = 0 self.name_col = 1 self.ordinal_col = None self.total_cols = 2 self.sortCol = 0 self.sortOrder = Qt.AscendingOrder for sym in data.get_symbols_of_type(SymbolType.FunctionSymbol): if sym.binding == SymbolBinding.GlobalBinding: self.allEntries.append(sym) for sym in data.get_symbols_of_type(SymbolType.DataSymbol): if sym.binding == SymbolBinding.GlobalBinding: self.allEntries.append(sym) if data.view_type == "PE": self.ordinal_col = 0 self.addr_col = 1 self.name_col = 2 self.total_cols = 3 self.entries = list(self.allEntries) def columnCount(self, parent): return self.total_cols def rowCount(self, parent): if parent.isValid(): return 0 return len(self.entries) def data(self, index, role): if role != Qt.DisplayRole: return None if index.row() >= len(self.entries): return None if index.column() == self.addr_col: return "0x%x" % self.entries[index.row()].address if index.column() == self.name_col: return self.entries[index.row()].full_name if index.column() == self.ordinal_col: return str(self.entries[index.row()].ordinal) return None def headerData(self, section, orientation, role): if orientation == Qt.Vertical: return None if role != Qt.DisplayRole: return None if section == self.addr_col: return "Address" if section == self.name_col: return "Name" if section == self.ordinal_col: return "Ordinal" return None def index(self, row, col, parent): if parent.isValid(): return QModelIndex() if row >= len(self.entries): return QModelIndex() if col >= self.total_cols: return QModelIndex() return self.createIndex(row, col) def parent(self, index): return QModelIndex() def getSymbol(self, index): if index.row() >= len(self.entries): return None return self.entries[index.row()] def performSort(self, col, order): if col == self.addr_col: self.entries.sort(key = lambda sym: sym.address, reverse = order != Qt.AscendingOrder) elif col == self.name_col: self.entries.sort(key = lambda sym: sym.full_name, reverse = order != Qt.AscendingOrder) elif col == self.ordinal_col: self.entries.sort(key = lambda sym: sym.ordinal, reverse = order != Qt.AscendingOrder) def sort(self, col, order): self.beginResetModel() self.sortCol = col self.sortOrder = order self.performSort(col, order) self.endResetModel() def setFilter(self, filterText): self.beginResetModel() self.entries = [] for entry in self.allEntries: if FilteredView.match(entry.full_name, filterText): self.entries.append(entry) self.performSort(self.sortCol, self.sortOrder) self.endResetModel() class ExportsTreeView(QTreeView, FilterTarget): def __init__(self, parent, view, data): QTreeView.__init__(self, parent) FilterTarget.__init__(self) self.data = data self.parent = parent self.view = view # Allow view-specific shortcuts when exports are focused self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.actionHandler.setActionContext(lambda: self.view.actionContext()) self.model = GenericExportsModel(self.data) self.setModel(self.model) self.setRootIsDecorated(False) self.setUniformRowHeights(True) self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) if self.model.ordinal_col is not None: self.setColumnWidth(self.model.ordinal_col, 55) self.setFont(binaryninjaui.getMonospaceFont(self)) self.selectionModel().currentChanged.connect(self.exportSelected) self.doubleClicked.connect(self.exportDoubleClicked) def exportSelected(self, cur, prev): sym = self.model.getSymbol(cur) if sym is not None: self.view.setCurrentOffset(sym.address) def exportDoubleClicked(self, cur): sym = self.model.getSymbol(cur) if sym is not None: viewFrame = ViewFrame.viewFrameForWidget(self) if len(self.data.get_functions_at(sym.address)) > 0: viewFrame.navigate("Graph:" + viewFrame.getCurrentDataType(), sym.address) else: viewFrame.navigate("Linear:" + viewFrame.getCurrentDataType(), sym.address) def setFilter(self, filterText): self.model.setFilter(filterText) def scrollToFirstItem(self): self.scrollToTop() def scrollToCurrentItem(self): self.scrollTo(self.currentIndex()) def selectFirstItem(self): self.setCurrentIndex(self.model.index(0, 0, QModelIndex())) def activateFirstItem(self): self.exportDoubleClicked(self.model.index(0, 0, QModelIndex())) def closeFilter(self): self.setFocus(Qt.OtherFocusReason) def keyPressEvent(self, event): if (len(event.text()) == 1) and (ord(event.text()[0:1]) > 32) and (ord(event.text()[0:1]) < 127): self.parent.filter.showFilter(event.text()) event.accept() elif (event.key() == Qt.Key_Return) or (event.key() == Qt.Key_Enter): sel = self.selectedIndexes() if len(sel) != 0: self.exportDoubleClicked(sel[0]) super(ExportsTreeView, self).keyPressEvent(event) class ExportsWidget(QWidget): def __init__(self, parent, view, data): super(ExportsWidget, self).__init__(parent) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.imports = ExportsTreeView(self, view, data) self.filter = FilteredView(self, self.imports, self.imports) layout.addWidget(self.filter, 1) self.setLayout(layout) self.setMinimumSize(UIContext.getScaledWindowSize(100, 196))