class MainWidget(QWidget): def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) logger.add(self.log) settings = QSettings() self.mainlayout = QVBoxLayout() self.mainlayout.setContentsMargins(5, 5, 5, 5) self.setLayout(self.mainlayout) # summary summarylayout = FlowLayout() summarylayout.setContentsMargins(0, 0, 0, 0) self.summary = QWidget() self.summary.setLayout(summarylayout) self.mainlayout.addWidget(self.summary) self.summary.setVisible( settings.value('showSummary', 'True') == 'True') detailslayout = QHBoxLayout() detailslayout.setContentsMargins(1, 0, 0, 0) detailslayout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) detailslayout.setSpacing(15) details = QWidget() details.setLayout(detailslayout) summarylayout.addWidget(details) self.modstotal = QLabel() detailslayout.addWidget(self.modstotal) self.modsenabled = QLabel() detailslayout.addWidget(self.modsenabled) self.overridden = QLabel() detailslayout.addWidget(self.overridden) self.conflicts = QLabel() detailslayout.addWidget(self.conflicts) buttonslayout = QHBoxLayout() buttonslayout.setContentsMargins(0, 0, 0, 0) buttonslayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter) buttons = QWidget() buttons.setLayout(buttonslayout) summarylayout.addWidget(buttons) self.startscriptmerger = QPushButton('Start Script Merger') self.startscriptmerger.setContentsMargins(0, 0, 0, 0) self.startscriptmerger.setMinimumWidth(140) self.startscriptmerger.setIcon( QIcon(str(getRuntimePath('resources/icons/script.ico')))) self.startscriptmerger.clicked.connect(lambda: [ openExecutable(Path(str(settings.value('scriptMergerPath'))), True) ]) self.startscriptmerger.setEnabled( verifyScriptMergerPath( Path(str(settings.value('scriptMergerPath')))) is not None) buttonslayout.addWidget(self.startscriptmerger) self.startgame = QPushButton('Start Game') self.startgame.setContentsMargins(0, 0, 0, 0) self.startgame.setMinimumWidth(100) self.startgame.setIcon( QIcon(str(getRuntimePath('resources/icons/w3b.ico')))) buttonslayout.addWidget(self.startgame) # splitter self.splitter = QSplitter(Qt.Vertical) self.stack = QStackedWidget() self.splitter.addWidget(self.stack) # mod list widget self.modlistwidget = QWidget() self.modlistlayout = QVBoxLayout() self.modlistlayout.setContentsMargins(0, 0, 0, 0) self.modlistwidget.setLayout(self.modlistlayout) self.stack.addWidget(self.modlistwidget) # search bar self.searchbar = QLineEdit() self.searchbar.setPlaceholderText('Search...') self.modlistlayout.addWidget(self.searchbar) # mod list self.modlist = ModList(self, model) self.modlistlayout.addWidget(self.modlist) self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e)) # welcome message welcomelayout = QVBoxLayout() welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomewidget = QWidget() welcomewidget.setLayout(welcomelayout) welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent # type: ignore welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent # type: ignore welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent # type: ignore welcomewidget.dropEvent = self.modlist.dropEvent # type: ignore welcomewidget.setAcceptDrops(True) icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico'))) iconpixmap = icon.pixmap(32, 32) icon = QLabel() icon.setPixmap(iconpixmap) icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) icon.setContentsMargins(4, 4, 4, 4) welcomelayout.addWidget(icon) welcome = QLabel('''<p><font> No mod installed yet. Drag a mod into this area to get started! </font></p>''') welcome.setAttribute(Qt.WA_TransparentForMouseEvents) welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomelayout.addWidget(welcome) self.stack.addWidget(welcomewidget) # output log self.output = QTextEdit(self) self.output.setTextInteractionFlags(Qt.NoTextInteraction) self.output.setReadOnly(True) self.output.setContextMenuPolicy(Qt.NoContextMenu) self.output.setPlaceholderText('Program output...') self.splitter.addWidget(self.output) # TODO: enhancement: show indicator if scripts have to be merged self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 0) self.mainlayout.addWidget(self.splitter) # TODO: incomplete: make start game button functional if len(model): self.stack.setCurrentIndex(0) self.splitter.setSizes([self.splitter.size().height(), 50]) else: self.stack.setCurrentIndex(1) self.splitter.setSizes([self.splitter.size().height(), 0]) model.updateCallbacks.append(self.modelUpdateEvent) asyncio.create_task(model.loadInstalled()) def keyPressEvent(self, event: QKeyEvent) -> None: if event.key() == Qt.Key_Escape: self.modlist.setFocus() self.searchbar.setText('') elif event.matches(QKeySequence.Find): self.searchbar.setFocus() elif event.matches(QKeySequence.Paste): self.pasteEvent() # TODO: enhancement: add start game / start script merger shortcuts else: super().keyPressEvent(event) def pasteEvent(self) -> None: clipboard = QApplication.clipboard().text().splitlines() if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]): self.parentWidget().showDownloadModDialog() else: urls = [ url for url in QApplication.clipboard().text().splitlines() if len(str(url.strip())) ] if all( isValidModDownloadUrl(url) or isValidFileUrl(url) for url in urls): asyncio.create_task(self.modlist.checkInstallFromURLs(urls)) def modelUpdateEvent(self, model: Model) -> None: total = len(model) enabled = len([mod for mod in model if model[mod].enabled]) overridden = sum( len(file) for file in model.conflicts.bundled.values()) conflicts = sum(len(file) for file in model.conflicts.scripts.values()) self.modstotal.setText( f'<font color="#73b500" size="4">{total}</font> \ <font color="#888" text-align="center">Installed Mod{"" if total == 1 else "s"}</font>' ) self.modsenabled.setText( f'<font color="#73b500" size="4">{enabled}</font> \ <font color="#888">Enabled Mod{"" if enabled == 1 else "s"}</font>' ) self.overridden.setText( f'<font color="{"#b08968" if overridden > 0 else "#84C318"}" size="4">{overridden}</font> \ <font color="#888">Overridden File{"" if overridden == 1 else "s"}</font> ' ) self.conflicts.setText( f'<font color="{"#E55934" if conflicts > 0 else "#aad576"}" size="4">{conflicts}</font> \ <font color="#888">Unresolved Conflict{"" if conflicts == 1 else "s"}</font> ' ) if len(model) > 0: if self.stack.currentIndex() != 0: self.stack.setCurrentIndex(0) self.repaint() else: if self.stack.currentIndex() != 1: self.stack.setCurrentIndex(1) self.repaint() def unhideOutput(self) -> None: if self.splitter.sizes()[1] < 10: self.splitter.setSizes([self.splitter.size().height(), 50]) def unhideModList(self) -> None: if self.splitter.sizes()[0] < 10: self.splitter.setSizes([50, self.splitter.size().height()]) def log(self, message: Any) -> None: # format log messages to user readable output settings = QSettings() record = message.record message = record['message'] extra = record['extra'] level = record['level'].name.lower() name = str(extra['name'] ) if 'name' in extra and extra['name'] is not None else '' path = str(extra['path'] ) if 'path' in extra and extra['path'] is not None else '' dots = bool( extra['dots'] ) if 'dots' in extra and extra['dots'] is not None else False newline = bool( extra['newline'] ) if 'newline' in extra and extra['newline'] is not None else False output = bool( extra['output'] ) if 'output' in extra and extra['output'] is not None else bool( message) modlist = bool( extra['modlist'] ) if 'modlist' in extra and extra['modlist'] is not None else False if level in ['debug' ] and settings.value('debugOutput', 'False') != 'True': if newline: self.output.append(f'') return n = '<br>' if newline else '' d = '...' if dots else '' if len(name) and len(path): path = f' ({path})' if output: message = html.escape(message, quote=True) if level in ['success', 'error', 'warning']: message = f'<strong>{message}</strong>' if level in ['success']: message = f'<font color="#04c45e">{message}</font>' if level in ['error', 'critical']: message = f'<font color="#ee3b3b">{message}</font>' if level in ['warning']: message = f'<font color="#ff6500">{message}</font>' if level in ['debug', 'trace']: message = f'<font color="#aaa">{message}</font>' path = f'<font color="#aaa">{path}</font>' if path else '' d = f'<font color="#aaa">{d}</font>' if d else '' time = record['time'].astimezone( tz=None).strftime('%Y-%m-%d %H:%M:%S') message = f'<font color="#aaa">{time}</font> {message}' self.output.append( f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}' ) else: self.output.append(f'') self.output.verticalScrollBar().setValue( self.output.verticalScrollBar().maximum()) self.output.repaint() if modlist: self.unhideModList() if settings.value('unhideOutput', 'True') == 'True' and output: self.unhideOutput()
def __init__(self, model): """Initialize MNELAB main window. Parameters ---------- model : mnelab.model.Model instance The main window needs to connect to a model containing all data sets. This decouples the GUI from the data (model/view). """ super().__init__() self.model = model # data model self.setWindowTitle("MNELAB") # restore settings settings = read_settings() self.recent = settings["recent"] # list of recent files self.resize(settings["size"]) self.move(settings["pos"]) # remove None entries from self.recent self.recent = [recent for recent in self.recent if recent is not None] # trigger theme setting QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")]) self.event(QEvent(QEvent.PaletteChange)) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon.fromTheme("open-file") self.actions["open_file"] = file_menu.addAction( icon, "&Open...", self.open_data, QKeySequence.Open) self.recent_menu = file_menu.addMenu("Open recent") self.recent_menu.aboutToShow.connect(self._update_recent_menu) self.recent_menu.triggered.connect(self._load_recent) if not self.recent: self.recent_menu.setEnabled(False) self.actions["close_file"] = file_menu.addAction( "&Close", self.model.remove_data, QKeySequence.Close) self.actions["close_all"] = file_menu.addAction( "Close all", self.close_all) file_menu.addSeparator() icon = QIcon.fromTheme("meta-info") self.actions["meta_info"] = file_menu.addAction( icon, "Show information...", self.meta_info) file_menu.addSeparator() self.actions["import_bads"] = file_menu.addAction( "Import bad channels...", lambda: self.import_file( model.import_bads, "Import bad channels", "*.csv")) self.actions["import_events"] = file_menu.addAction( "Import events...", lambda: self.import_file( model.import_events, "Import events", "*.csv")) self.actions["import_annotations"] = file_menu.addAction( "Import annotations...", lambda: self.import_file( model.import_annotations, "Import annotations", "*.csv")) self.actions["import_ica"] = file_menu.addAction( "Import &ICA...", lambda: self.open_file( model.import_ica, "Import ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.export_menu = file_menu.addMenu("Export data") for ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") self.actions[action] = self.export_menu.addAction( f"{ext[1:].upper()} ({description[1]})...", partial(self.export_file, model.export_data, "Export data", "*" + ext)) self.actions["export_bads"] = file_menu.addAction( "Export &bad channels...", lambda: self.export_file( model.export_bads, "Export bad channels", "*.csv")) self.actions["export_events"] = file_menu.addAction( "Export &events...", lambda: self.export_file( model.export_events, "Export events", "*.csv")) self.actions["export_annotations"] = file_menu.addAction( "Export &annotations...", lambda: self.export_file( model.export_annotations, "Export annotations", "*.csv")) self.actions["export_ica"] = file_menu.addAction( "Export ICA...", lambda: self.export_file( model.export_ica, "Export ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.actions["xdf_chunks"] = file_menu.addAction( "Show XDF chunks...", self.xdf_chunks) file_menu.addSeparator() self.actions["quit"] = file_menu.addAction("&Quit", self.close, QKeySequence.Quit) edit_menu = self.menuBar().addMenu("&Edit") self.actions["pick_chans"] = edit_menu.addAction( "P&ick channels...", self.pick_channels) icon = QIcon.fromTheme("chan-props") self.actions["chan_props"] = edit_menu.addAction( icon, "Channel &properties...", self.channel_properties) self.actions["set_montage"] = edit_menu.addAction( "Set &montage...", self.set_montage) edit_menu.addSeparator() self.actions["set_ref"] = edit_menu.addAction("Set &reference...", self.set_reference) edit_menu.addSeparator() self.actions["annotations"] = edit_menu.addAction( "&Annotations...", self.edit_annotations) self.actions["events"] = edit_menu.addAction("&Events...", self.edit_events) edit_menu.addSeparator() self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop) self.actions["append_data"] = edit_menu.addAction( "Appen&d data...", self.append_data) plot_menu = self.menuBar().addMenu("&Plot") icon = QIcon.fromTheme("plot-data") self.actions["plot_data"] = plot_menu.addAction( icon, "&Data", self.plot_data) icon = QIcon.fromTheme("plot-psd") self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density", self.plot_psd) icon = QIcon.fromTheme("plot-locations") self.actions["plot_locations"] = plot_menu.addAction( icon, "&Channel locations", self.plot_locations) self.actions["plot_erds"] = plot_menu.addAction( "&ERDS maps...", self.plot_erds) plot_menu.addSeparator() self.actions["plot_ica_components"] = plot_menu.addAction( "ICA &components...", self.plot_ica_components) self.actions["plot_ica_sources"] = plot_menu.addAction( "ICA &sources...", self.plot_ica_sources) tools_menu = self.menuBar().addMenu("&Tools") icon = QIcon.fromTheme("filter-data") self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon.fromTheme("find-events") self.actions["find_events"] = tools_menu.addAction( icon, "Find &events...", self.find_events) self.actions["events_from_annotations"] = tools_menu.addAction( "Create events from annotations", self.events_from_annotations) self.actions["annotations_from_events"] = tools_menu.addAction( "Create annotations from events", self.annotations_from_events) tools_menu.addSeparator() nirs_menu = tools_menu.addMenu("NIRS") self.actions["convert_od"] = nirs_menu.addAction( "Convert to &optical density", self.convert_od) self.actions["convert_bl"] = nirs_menu.addAction( "Convert to &haemoglobin", self.convert_bl) tools_menu.addSeparator() icon = QIcon.fromTheme("run-ica") self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...", self.run_ica) self.actions["apply_ica"] = tools_menu.addAction( "Apply &ICA", self.apply_ica) tools_menu.addSeparator() self.actions["interpolate_bads"] = tools_menu.addAction( "Interpolate bad channels...", self.interpolate_bads) tools_menu.addSeparator() icon = QIcon.fromTheme("epoch-data") self.actions["epoch_data"] = tools_menu.addAction( icon, "Create epochs...", self.epoch_data) view_menu = self.menuBar().addMenu("&View") self.actions["history"] = view_menu.addAction("&History...", self.show_history) self.actions["toolbar"] = view_menu.addAction("&Toolbar", self._toggle_toolbar) self.actions["toolbar"].setCheckable(True) self.actions["statusbar"] = view_menu.addAction( "&Statusbar", self._toggle_statusbar) self.actions["statusbar"].setCheckable(True) help_menu = self.menuBar().addMenu("&Help") self.actions["about"] = help_menu.addAction("&About", self.show_about) self.actions["about_qt"] = help_menu.addAction("About &Qt", self.show_about_qt) # actions that are always enabled self.always_enabled = [ "open_file", "about", "about_qt", "quit", "xdf_chunks", "toolbar", "statusbar" ] # set up toolbar self.toolbar = self.addToolBar("toolbar") self.toolbar.setObjectName("toolbar") self.toolbar.addAction(self.actions["open_file"]) self.toolbar.addAction(self.actions["meta_info"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["chan_props"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["plot_data"]) self.toolbar.addAction(self.actions["plot_psd"]) self.toolbar.addAction(self.actions["plot_locations"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["filter"]) self.toolbar.addAction(self.actions["find_events"]) self.toolbar.addAction(self.actions["epoch_data"]) self.toolbar.addAction(self.actions["run_ica"]) self.toolbar.setMovable(False) self.setUnifiedTitleAndToolBarOnMac(True) if settings["toolbar"]: self.toolbar.show() self.actions["toolbar"].setChecked(True) else: self.toolbar.hide() self.actions["toolbar"].setChecked(False) # set up data model for sidebar (list of open files) self.names = QStringListModel() self.names.dataChanged.connect(self._update_names) splitter = QSplitter() self.sidebar = QListView() self.sidebar.setFrameStyle(QFrame.NoFrame) self.sidebar.setFocusPolicy(Qt.NoFocus) self.sidebar.setModel(self.names) self.sidebar.clicked.connect(self._update_data) splitter.addWidget(self.sidebar) self.infowidget = InfoWidget() splitter.addWidget(self.infowidget) width = splitter.size().width() splitter.setSizes((int(width * 0.3), int(width * 0.7))) self.setCentralWidget(splitter) self.status_label = QLabel() self.statusBar().addPermanentWidget(self.status_label) if settings["statusbar"]: self.statusBar().show() self.actions["statusbar"].setChecked(True) else: self.statusBar().hide() self.actions["statusbar"].setChecked(False) self.setAcceptDrops(True) self.data_changed()
class DebugView(QWidget, View): class DebugViewHistoryEntry(HistoryEntry): def __init__(self, memory_addr, address, is_raw): HistoryEntry.__init__(self) self.memory_addr = memory_addr self.address = address self.is_raw = is_raw def __repr__(self): if self.is_raw: return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr) return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr) def __init__(self, parent, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = binjaplug.get_state(data) memory_view = self.debug_state.memory_view self.debug_state.ui.debug_view = self QWidget.__init__(self, parent) self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state) View.__init__(self) self.setupView(self) self.current_offset = 0 self.splitter = QSplitter(Qt.Orientation.Horizontal, self) frame = ViewFrame.viewFrameForWidget(self) self.memory_editor = LinearView(memory_view, frame) self.binary_editor = DisassemblyContainer(frame, data, frame) self.binary_text = TokenizedTextView(self, memory_view) self.is_raw_disassembly = False self.raw_address = 0 self.is_navigating_history = False self.memory_history_addr = 0 # TODO: Handle these and change views accordingly # Currently they are just disabled as the DisassemblyContainer gets confused # about where to go and just shows a bad view self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction()) self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction()) self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction()) self.memory_editor.actionHandler().bindAction("View in Types View", UIAction()) small_font = QApplication.font() small_font.setPointSize(11) bv_layout = QVBoxLayout() bv_layout.setSpacing(0) bv_layout.setContentsMargins(0, 0, 0, 0) bv_label = QLabel("Loaded File") bv_label.setFont(small_font) bv_layout.addWidget(bv_label) bv_layout.addWidget(self.binary_editor) self.bv_widget = QWidget() self.bv_widget.setLayout(bv_layout) disasm_layout = QVBoxLayout() disasm_layout.setSpacing(0) disasm_layout.setContentsMargins(0, 0, 0, 0) disasm_label = QLabel("Raw Disassembly at PC") disasm_label.setFont(small_font) disasm_layout.addWidget(disasm_label) disasm_layout.addWidget(self.binary_text) self.disasm_widget = QWidget() self.disasm_widget.setLayout(disasm_layout) memory_layout = QVBoxLayout() memory_layout.setSpacing(0) memory_layout.setContentsMargins(0, 0, 0, 0) memory_label = QLabel("Debugged Process") memory_label.setFont(small_font) memory_layout.addWidget(memory_label) memory_layout.addWidget(self.memory_editor) self.memory_widget = QWidget() self.memory_widget.setLayout(memory_layout) self.splitter.addWidget(self.bv_widget) self.splitter.addWidget(self.memory_widget) # Equally sized self.splitter.setSizes([0x7fffffff, 0x7fffffff]) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.controls) layout.addWidget(self.splitter, 100) self.setLayout(layout) self.needs_update = True self.update_timer = QTimer(self) self.update_timer.setInterval(200) self.update_timer.setSingleShot(False) self.update_timer.timeout.connect(lambda: self.updateTimerEvent()) self.add_scripting_ref() # set initial breakpoint when view is switched if self.debug_state.bv and self.debug_state.bv.entry_point: local_entry_offset = self.debug_state.bv.entry_point - self.debug_state.bv.start if not self.debug_state.breakpoints.contains_offset(self.debug_state.bv.file.original_filename, local_entry_offset): self.debug_state.breakpoints.add_offset(self.debug_state.bv.file.original_filename, local_entry_offset) if self.debug_state.ui is not None: self.debug_state.ui.breakpoint_tag_add(self.debug_state.bv.entry_point) self.debug_state.ui.update_highlights() self.debug_state.ui.update_breakpoints() def add_scripting_ref(self): # Hack: The interpreter is just a thread, so look through all threads # and assign our state to the interpreter's locals for thread in threading.enumerate(): if type(thread) == PythonScriptingInstance.InterpreterThread: thread.locals["dbg"] = self.debug_state def getData(self): return self.bv def getFont(self): return binaryninjaui.getMonospaceFont(self) def getCurrentOffset(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentOffset() return self.raw_address def getSelectionOffsets(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getSelectionOffsets() return (self.raw_address, self.raw_address) def getCurrentFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentFunction() return None def getCurrentBasicBlock(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentBasicBlock() return None def getCurrentArchitecture(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentArchitecture() return None def getCurrentLowLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction() return None def getCurrentMediumLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction() return None def getHistoryEntry(self): if self.is_navigating_history: return None memory_addr = self.memory_editor.getCurrentOffset() if memory_addr != self.memory_history_addr: self.memory_history_addr = memory_addr if self.is_raw_disassembly and self.debug_state.connected: rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address) return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True) else: address = self.binary_editor.getDisassembly().getCurrentOffset() return DebugView.DebugViewHistoryEntry(memory_addr, address, False) def navigateToFunction(self, func, offset): return self.navigate(offset) def navigateToHistoryEntry(self, entry): self.is_navigating_history = True if hasattr(entry, 'is_raw'): self.memory_editor.navigate(entry.memory_addr) if entry.is_raw: if self.debug_state.connected: address = self.debug_state.modules.relative_addr_to_absolute(entry.address) self.navigate_raw(address) else: self.navigate_live(entry.address) View.navigateToHistoryEntry(self, entry) self.is_navigating_history = False def navigate(self, addr): # If we're not connected we cannot even check if the address is remote if not self.debug_state.connected: return self.navigate_live(addr) if self.debug_state.memory_view.is_local_addr(addr): local_addr = self.debug_state.memory_view.remote_addr_to_local(addr) if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0: return self.navigate_live(local_addr) # This runs into conflicts if some other address space is mapped over # where the local BV is currently loaded, but this is was less likely # than the user navigating to a function from the UI if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0: return self.navigate_live(addr) return self.navigate_raw(addr) def navigate_live(self, addr): self.show_raw_disassembly(False) return self.binary_editor.getDisassembly().navigate(addr) def navigate_raw(self, addr): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return False self.raw_address = addr self.show_raw_disassembly(True) self.load_raw_disassembly(addr) return True def notifyMemoryChanged(self): self.needs_update = True def updateTimerEvent(self): if self.needs_update: self.needs_update = False # Refresh the editor if not self.debug_state.connected: self.memory_editor.navigate(0) return # self.memory_editor.navigate(self.debug_state.stack_pointer) def showEvent(self, event): if not event.spontaneous(): self.update_timer.start() self.add_scripting_ref() def hideEvent(self, event): if not event.spontaneous(): self.update_timer.stop() def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def load_raw_disassembly(self, start_ip): # Read a few instructions from rip and disassemble them inst_count = 50 arch_dis = self.debug_state.remote_arch rip = self.debug_state.ip # Assume the worst, just in case read_length = arch_dis.max_instr_length * inst_count data = self.debug_state.memory_view.read(start_ip, read_length) lines = [] # Append header line tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")] contents = DisassemblyTextLine(tokens, start_ip) line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents) lines.append(line) total_read = 0 for i in range(inst_count): line_addr = start_ip + total_read (insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr) if insn_tokens is None: insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")] length = arch_dis.instr_alignment if length == 0: length = 1 # terrible libshiboken workaround, see #101 for tok in insn_tokens: if tok.value.bit_length() == 64: tok.value ^= 0x8000000000000000 tokens = [] color = HighlightStandardColor.NoHighlightColor if line_addr == rip: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint & pc tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5)) color = HighlightStandardColor.RedHighlightColor else: # PC tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> ")) color = HighlightStandardColor.BlueHighlightColor else: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5)) color = HighlightStandardColor.RedHighlightColor else: # Regular line tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) # Address tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr)) tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) tokens.extend(insn_tokens) # Convert to linear disassembly line contents = DisassemblyTextLine(tokens, line_addr, color=color) line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents) lines.append(line) total_read += length self.binary_text.setLines(lines) def show_raw_disassembly(self, raw): if raw != self.is_raw_disassembly: self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget) self.is_raw_disassembly = raw def refresh_raw_disassembly(self): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return if self.is_raw_disassembly: self.load_raw_disassembly(self.getCurrentOffset())