Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0350b0c538 | |||
| 88051b0652 | |||
| 1d7e4b321c | |||
| 602bb611a4 | |||
| c5cc03d7dc | |||
| 720b036a9d | |||
| b8ff268c86 | |||
| 88ceac7b19 | |||
| 9a56463103 |
@@ -20,5 +20,5 @@
|
||||
"type": [
|
||||
"helper"
|
||||
],
|
||||
"version": "0.9.3"
|
||||
}
|
||||
"version": "0.9.4"
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ class CoverageDirector(object):
|
||||
"""
|
||||
Subscribe a callback for director refresh events.
|
||||
"""
|
||||
register_callback(self._refreshed_callbacks, callback)
|
||||
return register_callback(self._refreshed_callbacks, callback)
|
||||
|
||||
def _notify_refreshed(self):
|
||||
"""
|
||||
@@ -345,6 +345,13 @@ class CoverageDirector(object):
|
||||
"""
|
||||
notify_callback(self._refreshed_callbacks)
|
||||
|
||||
def unregister_refreshed(self, callback_ref):
|
||||
"""
|
||||
Unsubscribe a callback reference from director refresh events.
|
||||
"""
|
||||
|
||||
unregister_callback(self._refreshed_callbacks, callback_ref)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Batch Loading
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
@@ -25,7 +25,7 @@ class LighthouseCore(object):
|
||||
# Plugin Metadata
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
PLUGIN_VERSION = "0.9.3"
|
||||
PLUGIN_VERSION = "0.9.4-DEV"
|
||||
AUTHORS = "Markus Gaasedelen"
|
||||
DATE = "2024"
|
||||
|
||||
@@ -190,7 +190,8 @@ class LighthouseCore(object):
|
||||
"""
|
||||
for lctx in self.lighthouse_contexts.values():
|
||||
lctx.director.refresh_theme()
|
||||
lctx.coverage_overview.refresh_theme()
|
||||
if lctx.coverage_overview:
|
||||
lctx.coverage_overview.refresh_theme()
|
||||
lctx.painter.force_repaint()
|
||||
|
||||
def open_coverage_overview(self, dctx=None):
|
||||
|
||||
@@ -29,6 +29,7 @@ class CoverageOverview(object):
|
||||
|
||||
self.lctx.coverage_overview = self
|
||||
self.initialized = False
|
||||
self._refreshed_callback = None
|
||||
|
||||
# see the EventProxy class below for more details
|
||||
self._events = EventProxy(self)
|
||||
@@ -42,7 +43,7 @@ class CoverageOverview(object):
|
||||
self.refresh()
|
||||
|
||||
# register for cues from the director
|
||||
self.director.refreshed(self.refresh)
|
||||
self._refreshed_callback = self.director.refreshed(self.refresh)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Pseudo Widget Functions
|
||||
@@ -64,11 +65,27 @@ class CoverageOverview(object):
|
||||
"""
|
||||
The CoverageOverview is being hidden / deleted.
|
||||
"""
|
||||
if self.widget is None:
|
||||
return
|
||||
|
||||
if self._refreshed_callback:
|
||||
self.director.unregister_refreshed(self._refreshed_callback)
|
||||
self._refreshed_callback = None
|
||||
|
||||
if self.lctx.coverage_overview is self:
|
||||
self.lctx.coverage_overview = None
|
||||
|
||||
self.initialized = False
|
||||
self._combobox = None
|
||||
self._shell = None
|
||||
self._toolbar = None
|
||||
self._table_view = None
|
||||
self._table_controller = None
|
||||
self._table_model = None
|
||||
self._settings_button = None
|
||||
self._settings_menu = None
|
||||
self._shell_elements = None
|
||||
self._events = None
|
||||
self.widget = None
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
@@ -229,6 +246,9 @@ class CoverageOverview(object):
|
||||
"""
|
||||
Refresh the Coverage Overview.
|
||||
"""
|
||||
if not (self._table_model and self._shell and self._combobox):
|
||||
return
|
||||
|
||||
self._table_model.refresh()
|
||||
self._shell.refresh()
|
||||
self._combobox.refresh()
|
||||
@@ -238,6 +258,9 @@ class CoverageOverview(object):
|
||||
"""
|
||||
Update visual elements based on theme change.
|
||||
"""
|
||||
if not (self._table_view and self._table_model and self._shell and self._combobox):
|
||||
return
|
||||
|
||||
self._table_view.refresh_theme()
|
||||
self._table_model.refresh_theme()
|
||||
self._shell.refresh_theme()
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import os
|
||||
import json
|
||||
import struct
|
||||
import glob
|
||||
import shutil
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
# NOTE: Py2/Py3 compat
|
||||
try:
|
||||
@@ -260,8 +259,13 @@ class LighthousePalette(object):
|
||||
user_theme_dir = self.get_user_theme_dir()
|
||||
makedirs(user_theme_dir)
|
||||
|
||||
# enumerate all in-box / default themes
|
||||
plugin_theme_dir = self.get_plugin_theme_dir()
|
||||
json_files = glob.glob(os.path.join(plugin_theme_dir, "*.json"))
|
||||
|
||||
# copy the default themes into the user directory if they don't exist
|
||||
for theme_name in self._default_themes.values():
|
||||
for default_theme_file in json_files:
|
||||
theme_name = os.path.basename(default_theme_file)
|
||||
|
||||
#
|
||||
# check if lighthouse has copied the default themes into the user
|
||||
@@ -274,8 +278,7 @@ class LighthousePalette(object):
|
||||
continue
|
||||
|
||||
# copy the in-box themes to the user theme directory
|
||||
plugin_theme_file = os.path.join(self.get_plugin_theme_dir(), theme_name)
|
||||
shutil.copy(plugin_theme_file, user_theme_file)
|
||||
shutil.copy(default_theme_file, user_theme_file)
|
||||
|
||||
#
|
||||
# if the user tries to switch themes, ensure the file dialog will start
|
||||
@@ -546,7 +549,11 @@ class LighthousePalette(object):
|
||||
# lmao, don't ask me why they forgot about this attribute from 5.0 - 5.6
|
||||
#
|
||||
|
||||
if disassembler.NAME == "BINJA":
|
||||
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
|
||||
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
|
||||
if disassembler.NAME == "BINJA" or (disassembler.NAME == "IDA"
|
||||
and disassembler._version_major == 9
|
||||
and disassembler._version_minor >= 2):
|
||||
test_widget.setAttribute(QtCore.Qt.WA_DontShowOnScreen)
|
||||
else:
|
||||
test_widget.setAttribute(103) # taken from http://doc.qt.io/qt-5/qt.html
|
||||
|
||||
@@ -150,14 +150,21 @@ class IDACoreAPI(DisassemblerCoreAPI):
|
||||
self._dockable_factory[dockable_name] = create_widget_callback
|
||||
|
||||
def create_dockable_widget(self, parent, dockable_name):
|
||||
import sip
|
||||
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
|
||||
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
|
||||
if USING_PYSIDE6:
|
||||
from shiboken6 import wrapInstance
|
||||
else:
|
||||
import sip
|
||||
def wrapInstance(ptr, base=None):
|
||||
return sip.wrapinstance(int(ptr), base)
|
||||
|
||||
# create a dockable widget, and save a reference to it for later use
|
||||
twidget = idaapi.create_empty_widget(dockable_name)
|
||||
self._dockable_widgets[dockable_name] = twidget
|
||||
|
||||
# cast the IDA 'twidget' as a Qt widget for use
|
||||
widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget)
|
||||
widget = wrapInstance(int(twidget), QtWidgets.QWidget)
|
||||
widget.name = dockable_name
|
||||
widget.visible = False
|
||||
|
||||
@@ -226,7 +233,12 @@ class IDACoreAPI(DisassemblerCoreAPI):
|
||||
# attempt to generate an 'html' dump of the first 0x20 bytes (instructions)
|
||||
ida_fd = idaapi.fopenWT(path)
|
||||
idaapi.gen_file(idaapi.OFILE_LST, ida_fd, imagebase, imagebase+0x20, idaapi.GENFLG_GENHTML)
|
||||
idaapi.eclose(ida_fd)
|
||||
# IDA 9.x SDK fix: removed `idaapi.eclose`, added `ida_fpro.qfclose`
|
||||
if int(idaapi.get_kernel_version()[0]) >= 9:
|
||||
import ida_fpro
|
||||
ida_fpro.qfclose(ida_fd)
|
||||
else:
|
||||
idaapi.eclose(ida_fd)
|
||||
|
||||
# read the dumped text
|
||||
with open(path, "r") as fd:
|
||||
@@ -290,9 +302,16 @@ class IDACoreAPI(DisassemblerCoreAPI):
|
||||
# touch the target form so we know it is populated
|
||||
self._touch_ida_window(twidget)
|
||||
|
||||
# locate the Qt Widget for a form and take 1px image slice of it
|
||||
import sip
|
||||
widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget)
|
||||
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
|
||||
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
|
||||
if USING_PYSIDE6:
|
||||
from shiboken6 import wrapInstance
|
||||
else:
|
||||
import sip
|
||||
def wrapInstance(ptr, base=None):
|
||||
return sip.wrapinstance(int(ptr), base)
|
||||
|
||||
widget = wrapInstance(int(twidget), QtWidgets.QWidget)
|
||||
pixmap = widget.grab(QtCore.QRect(0, 10, widget.width(), 1))
|
||||
|
||||
# convert the raw pixmap into an image (easier to interface with)
|
||||
@@ -592,4 +611,3 @@ def lex_citem_indexes(line):
|
||||
|
||||
# return all the citem indexes extracted from this line of text
|
||||
return indexes
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ def register_callback(callback_list, callback):
|
||||
|
||||
# 'register' the callback
|
||||
callback_list.append(callback_ref)
|
||||
return callback_ref
|
||||
|
||||
def notify_callback(callback_list, *args):
|
||||
"""
|
||||
@@ -207,4 +208,13 @@ def notify_callback(callback_list, *args):
|
||||
# remove the deleted callbacks
|
||||
for callback_ref in cleanup:
|
||||
callback_list.remove(callback_ref)
|
||||
|
||||
|
||||
def unregister_callback(callback_list, callback_ref):
|
||||
"""
|
||||
Remove a previously-registered callback reference.
|
||||
"""
|
||||
|
||||
try:
|
||||
callback_list.remove(callback_ref)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
@@ -29,11 +29,16 @@ USING_PYSIDE6 = False
|
||||
# specific dependencies in here...
|
||||
#
|
||||
|
||||
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
|
||||
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
|
||||
try:
|
||||
import ida_idaapi
|
||||
USING_IDA = True
|
||||
import idaapi
|
||||
ver_major, ver_minor = map(int, idaapi.get_kernel_version().split("."))
|
||||
USING_NEW_IDA = ver_major == 9 and ver_minor >= 2
|
||||
USING_OLD_IDA = not(USING_NEW_IDA)
|
||||
except ImportError:
|
||||
USING_IDA = False
|
||||
USING_NEW_IDA = False
|
||||
USING_OLD_IDA = False
|
||||
|
||||
try:
|
||||
import binaryninjaui
|
||||
@@ -47,8 +52,8 @@ except ImportError:
|
||||
# PyQt5 Compatibility
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# attempt to load PyQt5 (IDA 7.0+)
|
||||
if USING_IDA:
|
||||
# attempt to load PyQt5 (IDA from 7.0 to 9.1)
|
||||
if USING_OLD_IDA:
|
||||
|
||||
try:
|
||||
import PyQt5.QtGui as QtGui
|
||||
@@ -91,8 +96,8 @@ if not QT_AVAILABLE and USING_OLD_BINJA:
|
||||
# PySide6 Compatibility
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# If all else fails, try to load PySide6 (New Binary Ninja)
|
||||
if not QT_AVAILABLE and USING_NEW_BINJA:
|
||||
# If all else fails, try to load PySide6 (New Binary Ninja and IDA)
|
||||
if not QT_AVAILABLE and (USING_NEW_BINJA or USING_NEW_IDA):
|
||||
|
||||
try:
|
||||
import PySide6.QtGui as QtGui
|
||||
|
||||
@@ -32,6 +32,7 @@ def async_update_check(current_version, callback):
|
||||
An async worker thread to check for an plugin update.
|
||||
"""
|
||||
logger.debug("Checking for update...")
|
||||
current_version = "v" + current_version
|
||||
|
||||
try:
|
||||
response = urlopen(UPDATE_URL, timeout=5.0)
|
||||
@@ -54,8 +55,8 @@ def async_update_check(current_version, callback):
|
||||
|
||||
# notify the user if an update is available
|
||||
update_message = "An update is available for Lighthouse!\n\n" \
|
||||
" - Latest Version: %s\n" % (remote_version) + \
|
||||
" - Current Version: %s\n\n" % (current_version) + \
|
||||
" - Latest Version: %s\n" % (remote_version) + \
|
||||
" - Current Version: %s\n\n" % (current_version) + \
|
||||
"Please go download the update from GitHub."
|
||||
|
||||
callback(update_message)
|
||||
|
||||
Reference in New Issue
Block a user