You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1242 lines
40 KiB
1242 lines
40 KiB
|
|
from PyQt5.QtCore import *
|
|
from PyQt5.QtGui import *
|
|
from PyQt5.QtWidgets import *
|
|
import sys, struct, socket, time, os
|
|
|
|
import disassemble
|
|
|
|
|
|
class Message:
|
|
DSI = 0
|
|
ISI = 1
|
|
Program = 2
|
|
GetStat = 3
|
|
OpenFile = 4
|
|
ReadFile = 5
|
|
CloseFile = 6
|
|
SetPosFile = 7
|
|
GetStatFile = 8
|
|
|
|
Continue = 0
|
|
Step = 1
|
|
StepOver = 2
|
|
|
|
def __init__(self, type, data, arg):
|
|
self.type = type
|
|
self.data = data
|
|
self.arg = arg
|
|
|
|
|
|
class EventHolder(QObject):
|
|
Exception = pyqtSignal()
|
|
Connected = pyqtSignal()
|
|
Closed = pyqtSignal()
|
|
BreakPointChanged = pyqtSignal()
|
|
Continue = pyqtSignal()
|
|
events = EventHolder()
|
|
|
|
|
|
#I don't want to deal with the whole threading trouble to complete big
|
|
#file transfers without the UI becoming unresponsive. There probably is
|
|
#a better way to code this, but this is what I came up with.
|
|
class TaskMgr:
|
|
def __init__(self):
|
|
self.taskQueue = []
|
|
|
|
def add(self, task):
|
|
if not self.taskQueue:
|
|
window.mainWidget.statusWidget.disconnectButton.setEnabled(True)
|
|
self.taskQueue.append(task)
|
|
|
|
def pop(self, task):
|
|
assert task == self.taskQueue.pop()
|
|
|
|
if not self.taskQueue:
|
|
window.mainWidget.tabWidget.setEnabled(True)
|
|
window.mainWidget.statusWidget.cancelButton.setEnabled(True)
|
|
window.mainWidget.statusWidget.disconnectButton.setEnabled(True)
|
|
window.mainWidget.statusWidget.progressBar.setValue(0)
|
|
window.mainWidget.statusWidget.progressInfo.setText("Connected")
|
|
else:
|
|
self.taskQueue[-1].resume()
|
|
|
|
def isBlocking(self):
|
|
if not self.taskQueue:
|
|
return False
|
|
return self.taskQueue[-1].blocking
|
|
|
|
def cancel(self):
|
|
self.taskQueue[-1].canceled = True
|
|
taskMgr = TaskMgr()
|
|
|
|
|
|
class Task:
|
|
def __init__(self, blocking, cancelable):
|
|
taskMgr.add(self)
|
|
|
|
self.canceled = False
|
|
self.blocking = blocking
|
|
self.cancelable = cancelable
|
|
window.mainWidget.tabWidget.setEnabled(not blocking)
|
|
window.mainWidget.statusWidget.cancelButton.setEnabled(cancelable)
|
|
|
|
def setInfo(self, info, maxValue):
|
|
self.info = info
|
|
self.maxValue = maxValue
|
|
window.mainWidget.statusWidget.progressInfo.setText(info)
|
|
window.mainWidget.statusWidget.progressBar.setRange(0, maxValue)
|
|
|
|
def update(self, progress):
|
|
self.progress = progress
|
|
window.mainWidget.statusWidget.progressBar.setValue(progress)
|
|
app.processEvents()
|
|
|
|
def resume(self):
|
|
window.mainWidget.tabWidget.setEnabled(not self.blocking)
|
|
window.mainWidget.statusWidget.cancelButton.setEnabled(self.cancelable)
|
|
window.mainWidget.statusWidget.progressInfo.setText(self.info)
|
|
window.mainWidget.statusWidget.progressBar.setRange(0, self.maxValue)
|
|
window.mainWidget.statusWidget.progressBar.setValue(self.progress)
|
|
|
|
def end(self):
|
|
taskMgr.pop(self)
|
|
|
|
|
|
class Thread:
|
|
|
|
cores = {
|
|
1: "Core 0",
|
|
2: "Core 1",
|
|
4: "Core 2"
|
|
}
|
|
|
|
def __init__(self, data, offs=0):
|
|
self.core = self.cores[struct.unpack_from(">I", data, offs)[0]]
|
|
self.priority = struct.unpack_from(">I", data, offs + 4)[0]
|
|
self.stackBase = struct.unpack_from(">I", data, offs + 8)[0]
|
|
self.stackEnd = struct.unpack_from(">I", data, offs + 12)[0]
|
|
self.entryPoint = struct.unpack_from(">I", data, offs + 16)[0]
|
|
|
|
namelen = struct.unpack_from(">I", data, offs + 20)[0]
|
|
self.name = data[offs + 24 : offs + 24 + namelen].decode("ascii")
|
|
|
|
|
|
class DirEntry:
|
|
def __init__(self, flags, size, name):
|
|
self.flags = flags
|
|
self.size = size
|
|
self.name = name
|
|
|
|
def isDir(self):
|
|
return self.flags & 0x80000000
|
|
|
|
|
|
class PyBugger:
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.connected = False
|
|
self.breakPoints = []
|
|
|
|
self.basePath = b""
|
|
self.currentHandle = 0x12345678
|
|
self.files = {}
|
|
|
|
self.messageHandlers = {
|
|
Message.DSI: self.handleException,
|
|
Message.ISI: self.handleException,
|
|
Message.Program: self.handleException,
|
|
Message.GetStat: self.handleGetStat,
|
|
Message.OpenFile: self.handleOpenFile,
|
|
Message.ReadFile: self.handleReadFile,
|
|
Message.CloseFile: self.handleCloseFile,
|
|
Message.SetPosFile: self.handleSetPosFile,
|
|
Message.GetStatFile: self.handleGetStatFile
|
|
}
|
|
|
|
def handleException(self, msg):
|
|
exceptionState.load(msg.data, msg.type)
|
|
events.Exception.emit()
|
|
|
|
def handleGetStat(self, msg):
|
|
gamePath = msg.data.decode("ascii")
|
|
path = os.path.join(self.basePath, gamePath.strip("/vol"))
|
|
print("GetStat: %s" %gamePath)
|
|
self.sendFileMessage(os.path.getsize(path))
|
|
|
|
def handleOpenFile(self, msg):
|
|
mode = struct.pack(">I", msg.arg).decode("ascii").strip("\x00") + "b"
|
|
path = msg.data.decode("ascii")
|
|
print("Open: %s" %path)
|
|
|
|
f = open(os.path.join(self.basePath, path.strip("/vol")), mode)
|
|
self.files[self.currentHandle] = f
|
|
self.sendFileMessage(self.currentHandle)
|
|
self.currentHandle += 1
|
|
|
|
def handleReadFile(self, msg):
|
|
print("Read")
|
|
task = Task(blocking=False, cancelable=False)
|
|
bufferAddr, size, count, handle = struct.unpack(">IIII", msg.data)
|
|
|
|
data = self.files[handle].read(size * count)
|
|
task.setInfo("Sending file", len(data))
|
|
|
|
bytesSent = 0
|
|
while bytesSent < len(data):
|
|
length = min(len(data) - bytesSent, 0x8000)
|
|
self.sendall(b"\x03")
|
|
self.sendall(struct.pack(">II", bufferAddr, length))
|
|
self.sendall(data[bytesSent : bytesSent + length])
|
|
bufferAddr += length
|
|
bytesSent += length
|
|
task.update(bytesSent)
|
|
self.sendFileMessage(bytesSent // size)
|
|
task.end()
|
|
|
|
def handleCloseFile(self, msg):
|
|
print("Close")
|
|
self.files.pop(msg.arg).close()
|
|
self.sendFileMessage()
|
|
|
|
def handleSetPosFile(self, msg):
|
|
print("SetPos")
|
|
handle, pos = struct.unpack(">II", msg.data)
|
|
self.files[handle].seek(pos)
|
|
self.sendFileMessage()
|
|
|
|
def handleGetStatFile(self, msg):
|
|
print("GetStatFile")
|
|
f = self.files[msg.arg]
|
|
pos = f.tell()
|
|
f.seek(0, 2)
|
|
size = f.tell()
|
|
f.seek(pos)
|
|
self.sendFileMessage(size)
|
|
|
|
def connect(self, host):
|
|
self.s = socket.socket()
|
|
self.s.connect((host, 1559))
|
|
self.connected = True
|
|
self.closeRequest = False
|
|
events.Connected.emit()
|
|
|
|
def close(self):
|
|
self.sendall(b"\x01")
|
|
self.s.close()
|
|
self.connected = False
|
|
self.breakPoints = []
|
|
events.Closed.emit()
|
|
|
|
def updateMessages(self):
|
|
self.sendall(b"\x07")
|
|
count = struct.unpack(">I", self.recvall(4))[0]
|
|
for i in range(count):
|
|
type, ptr, length, arg = struct.unpack(">IIII", self.recvall(16))
|
|
data = None
|
|
if length:
|
|
data = self.recvall(length)
|
|
self.messageHandlers[type](Message(type, data, arg))
|
|
|
|
def read(self, addr, num):
|
|
self.sendall(b"\x02")
|
|
self.sendall(struct.pack(">II", addr, num))
|
|
data = self.recvall(num)
|
|
return data
|
|
|
|
def write(self, addr, data):
|
|
self.sendall(b"\x03")
|
|
self.sendall(struct.pack(">II", addr, len(data)))
|
|
self.sendall(data)
|
|
|
|
def writeCode(self, addr, instr):
|
|
self.sendall(b"\x04")
|
|
self.sendall(struct.pack(">II", addr, instr))
|
|
|
|
def getThreadList(self):
|
|
self.sendall(b"\x05")
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
data = self.recvall(length)
|
|
|
|
offset = 0
|
|
threads = []
|
|
while offset < length:
|
|
thread = Thread(data, offset)
|
|
threads.append(thread)
|
|
offset += 24 + len(thread.name)
|
|
return threads
|
|
|
|
def toggleBreakPoint(self, addr):
|
|
if addr in self.breakPoints: self.breakPoints.remove(addr)
|
|
else:
|
|
if len(self.breakPoints) >= 10:
|
|
return
|
|
self.breakPoints.append(addr)
|
|
|
|
self.sendall(b"\x0A")
|
|
self.sendall(struct.pack(">I", addr))
|
|
events.BreakPointChanged.emit()
|
|
|
|
def continueBreak(self): self.sendCrashMessage(Message.Continue)
|
|
def stepBreak(self): self.sendCrashMessage(Message.Step)
|
|
def stepOver(self): self.sendCrashMessage(Message.StepOver)
|
|
|
|
def sendCrashMessage(self, message):
|
|
self.sendMessage(message)
|
|
events.Continue.emit()
|
|
|
|
def sendMessage(self, message, data0=0, data1=0, data2=0):
|
|
self.sendall(b"\x06")
|
|
self.sendall(struct.pack(">IIII", message, data0, data1, data2))
|
|
|
|
def sendFileMessage(self, data0=0, data1=0, data2=0):
|
|
self.sendall(b"\x0F")
|
|
self.sendall(struct.pack(">IIII", 0, data0, data1, data2))
|
|
|
|
def getStackTrace(self):
|
|
self.sendall(b"\x08")
|
|
count = struct.unpack(">I", self.recvall(4))[0]
|
|
trace = struct.unpack(">%iI" %count, self.recvall(4 * count))
|
|
return trace
|
|
|
|
def pokeExceptionRegisters(self):
|
|
self.sendall(b"\x09")
|
|
data = struct.pack(">32I32d", *exceptionState.gpr, *exceptionState.fpr)
|
|
self.sendall(data)
|
|
|
|
def readDirectory(self, path):
|
|
self.sendall(b"\x0B")
|
|
self.sendall(struct.pack(">I", len(path)))
|
|
self.sendall(path.encode("ascii"))
|
|
|
|
entries = []
|
|
namelen = struct.unpack(">I", self.recvall(4))[0]
|
|
while namelen != 0:
|
|
flags = struct.unpack(">I", self.recvall(4))[0]
|
|
|
|
size = -1
|
|
if not flags & 0x80000000:
|
|
size = struct.unpack(">I", self.recvall(4))[0]
|
|
|
|
name = self.recvall(namelen).decode("ascii")
|
|
entries.append(DirEntry(flags, size, name))
|
|
|
|
namelen = struct.unpack(">I", self.recvall(4))[0]
|
|
return entries
|
|
|
|
def dumpFile(self, gamePath, outPath, task):
|
|
if task.canceled:
|
|
return
|
|
|
|
self.sendall(b"\x0C")
|
|
self.sendall(struct.pack(">I", len(gamePath)))
|
|
self.sendall(gamePath.encode("ascii"))
|
|
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
task.setInfo("Dumping %s" %gamePath, length)
|
|
|
|
with open(outPath, "wb") as f:
|
|
bytesDumped = 0
|
|
while bytesDumped < length:
|
|
data = self.s.recv(length - bytesDumped)
|
|
f.write(data)
|
|
bytesDumped += len(data)
|
|
task.update(bytesDumped)
|
|
|
|
def getModuleName(self):
|
|
self.sendall(b"\x0D")
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
return self.recvall(length).decode("ascii") + ".rpx"
|
|
|
|
def setPatchFiles(self, fileList, basePath):
|
|
self.basePath = basePath
|
|
self.sendall(b"\x0E")
|
|
|
|
fileBuffer = struct.pack(">I", len(fileList))
|
|
for path in fileList:
|
|
fileBuffer += struct.pack(">H", len(path))
|
|
fileBuffer += path.encode("ascii")
|
|
|
|
self.sendall(struct.pack(">I", len(fileBuffer)))
|
|
self.sendall(fileBuffer)
|
|
|
|
def clearPatchFiles(self):
|
|
self.sendall(b"\x10")
|
|
|
|
def sendall(self, data):
|
|
try:
|
|
self.s.sendall(data)
|
|
except socket.error:
|
|
self.connected = False
|
|
events.Closed.emit()
|
|
|
|
def recvall(self, num):
|
|
try:
|
|
data = b""
|
|
while len(data) < num:
|
|
data += self.s.recv(num - len(data))
|
|
except socket.error:
|
|
self.connected = False
|
|
events.Closed.emit()
|
|
return b"\x00" * num
|
|
|
|
return data
|
|
|
|
|
|
class HexSpinBox(QAbstractSpinBox):
|
|
def __init__(self, parent, stepSize = 1):
|
|
super().__init__(parent)
|
|
self._value = 0
|
|
self.stepSize = stepSize
|
|
|
|
def validate(self, text, pos):
|
|
if all([char in "0123456789abcdefABCDEF" for char in text]):
|
|
if not text:
|
|
return QValidator.Intermediate, text.upper(), pos
|
|
|
|
value = int(text, 16)
|
|
if value <= 0xFFFFFFFF:
|
|
self._value = value
|
|
if value % self.stepSize:
|
|
self._value -= value % self.stepSize
|
|
return QValidator.Acceptable, text.upper(), pos
|
|
return QValidator.Acceptable, text.upper(), pos
|
|
|
|
return QValidator.Invalid, text.upper(), pos
|
|
|
|
def stepBy(self, steps):
|
|
self._value = min(max(self._value + steps * self.stepSize, 0), 0x100000000 - self.stepSize)
|
|
self.lineEdit().setText("%X" %self._value)
|
|
|
|
def stepEnabled(self):
|
|
return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled
|
|
|
|
def setValue(self, value):
|
|
self._value = value
|
|
self.lineEdit().setText("%X" %self._value)
|
|
|
|
def value(self):
|
|
return self._value
|
|
|
|
|
|
class ExceptionState:
|
|
|
|
exceptionNames = ["DSI", "ISI", "Program"]
|
|
|
|
def load(self, context, type):
|
|
#Convert tuple to list to make it mutable
|
|
self.gpr = list(struct.unpack_from(">32I", context, 8))
|
|
self.cr, self.lr, self.ctr, self.xer = struct.unpack_from(">4I", context, 0x88)
|
|
self.srr0, self.srr1, self.ex0, self.ex1 = struct.unpack_from(">4I", context, 0x98)
|
|
self.fpr = list(struct.unpack_from(">32d", context, 0xB8))
|
|
self.gqr = list(struct.unpack_from(">8I", context, 0x1BC))
|
|
self.psf = list(struct.unpack_from(">32d", context, 0x1E0))
|
|
|
|
self.exceptionName = self.exceptionNames[type]
|
|
|
|
def isBreakPoint(self):
|
|
return self.exceptionName == "Program" and self.srr1 & 0x20000
|
|
|
|
|
|
def format_hex(blob, offs):
|
|
return "%02X" %blob[offs]
|
|
|
|
def format_ascii(blob, offs):
|
|
if 0x30 <= blob[offs] <= 0x39 or 0x41 <= blob[offs] <= 0x5A or 0x61 <= blob[offs] <= 0x7A:
|
|
return chr(blob[offs])
|
|
return "?"
|
|
|
|
def format_float(blob, offs):
|
|
value = struct.unpack_from(">f", blob, offs)[0]
|
|
if abs(value) >= 1000000 or 0 < abs(value) < 0.000001:
|
|
return "%e" %value
|
|
return ("%.8f" %value).rstrip("0")
|
|
|
|
|
|
class MemoryViewer(QWidget):
|
|
|
|
class Format:
|
|
Hex = 0
|
|
Ascii = 1
|
|
Float = 2
|
|
|
|
Width = 1, 1, 4
|
|
Funcs = format_hex, format_ascii, format_float
|
|
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
|
|
self.layout = QGridLayout()
|
|
|
|
for i in range(16):
|
|
self.layout.addWidget(QLabel("%X" %i, self), 0, i + 1)
|
|
self.addrLabels = []
|
|
for i in range(16):
|
|
label = QLabel("%X" %(i * 0x10), self)
|
|
self.layout.addWidget(label, i + 1, 0)
|
|
self.addrLabels.append(label)
|
|
self.dataCells = []
|
|
|
|
self.base = 0
|
|
self.format = self.Format.Hex
|
|
self.updateData()
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
|
|
def connected(self):
|
|
self.setBase(0x10000000)
|
|
|
|
def setFormat(self, format):
|
|
self.format = format
|
|
self.updateData()
|
|
|
|
def setBase(self, base):
|
|
window.mainWidget.tabWidget.memoryTab.memoryInfo.baseBox.setValue(base)
|
|
self.base = base
|
|
for i in range(16):
|
|
self.addrLabels[i].setText("%X" %(self.base + i * 0x10))
|
|
self.updateData()
|
|
|
|
def updateData(self):
|
|
for cell in self.dataCells:
|
|
self.layout.removeWidget(cell)
|
|
cell.setParent(None)
|
|
|
|
if bugger.connected:
|
|
blob = bugger.read(self.base, 0x100)
|
|
else:
|
|
blob = b"\x00" * 0x100
|
|
|
|
width = self.Width[self.format]
|
|
func = self.Funcs[self.format]
|
|
for i in range(16 // width):
|
|
for j in range(16):
|
|
label = QLabel(func(blob, j * 0x10 + i * width), self)
|
|
self.layout.addWidget(label, j + 1, i * width + 1, 1, width)
|
|
self.dataCells.append(label)
|
|
|
|
|
|
class MemoryInfo(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.dataTypeLabel = QLabel("Data type:")
|
|
self.dataTypeBox = QComboBox(self)
|
|
self.dataTypeBox.addItems(["Hex", "Ascii", "Float"])
|
|
self.dataTypeBox.currentIndexChanged.connect(self.updateDataType)
|
|
|
|
self.baseLabel = QLabel("Address:")
|
|
self.baseBox = HexSpinBox(self, 0x10)
|
|
self.baseButton = QPushButton("Update", self)
|
|
self.baseButton.clicked.connect(self.updateMemoryBase)
|
|
|
|
self.pokeAddr = HexSpinBox(self, 4)
|
|
self.pokeValue = HexSpinBox(self)
|
|
self.pokeButton = QPushButton("Poke", self)
|
|
self.pokeButton.clicked.connect(self.pokeMemory)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.baseLabel, 0, 0)
|
|
self.layout.addWidget(self.baseBox, 0, 1)
|
|
self.layout.addWidget(self.baseButton, 0, 2)
|
|
self.layout.addWidget(self.pokeAddr, 1, 0)
|
|
self.layout.addWidget(self.pokeValue, 1, 1)
|
|
self.layout.addWidget(self.pokeButton, 1, 2)
|
|
self.layout.addWidget(self.dataTypeLabel, 2, 0)
|
|
self.layout.addWidget(self.dataTypeBox, 2, 1, 1, 2)
|
|
self.setLayout(self.layout)
|
|
|
|
def updateDataType(self, index):
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.setFormat(index)
|
|
|
|
def updateMemoryBase(self):
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.setBase(self.baseBox.value())
|
|
|
|
def pokeMemory(self):
|
|
bugger.write(self.pokeAddr.value(), struct.pack(">I", self.pokeValue.value()))
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.updateData()
|
|
|
|
|
|
class MemoryTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.memoryInfo = MemoryInfo(self)
|
|
self.memoryViewer = MemoryViewer(self)
|
|
self.layout = QHBoxLayout()
|
|
self.layout.addWidget(self.memoryInfo)
|
|
self.layout.addWidget(self.memoryViewer)
|
|
self.button = QPushButton("Dump", self)
|
|
self.button.clicked.connect(self.dump)
|
|
self.setLayout(self.layout)
|
|
|
|
def dump(self):
|
|
dumpStart = 0x1AB00000
|
|
dumpLength = 0x600000
|
|
dumpFile = "dump.bin"
|
|
with open(dumpFile, 'wb') as f:
|
|
f.write(bugger.read(dumpStart, dumpLength))
|
|
|
|
class DisassemblyWidget(QTextEdit):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.setTextInteractionFlags(Qt.NoTextInteraction)
|
|
|
|
self.currentInstruction = None
|
|
self.selectedAddress = 0
|
|
self.setBase(0)
|
|
|
|
events.BreakPointChanged.connect(self.updateHighlight)
|
|
events.Continue.connect(self.handleContinue)
|
|
|
|
def handleContinue(self):
|
|
self.currentInstruction = None
|
|
self.updateHighlight()
|
|
|
|
def setCurrentInstruction(self, instr):
|
|
self.currentInstruction = instr
|
|
self.setBase(instr - 0x20)
|
|
|
|
def setBase(self, base):
|
|
self.base = base
|
|
self.updateText()
|
|
self.updateHighlight()
|
|
|
|
def updateText(self):
|
|
if bugger.connected:
|
|
blob = bugger.read(self.base, 0x60)
|
|
else:
|
|
blob = b"\x00" * 0x60
|
|
|
|
text = ""
|
|
for i in range(24):
|
|
address = self.base + i * 4
|
|
value = struct.unpack_from(">I", blob, i * 4)[0]
|
|
instr = disassemble.disassemble(value, address)
|
|
text += "%08X: %08X %s\n" %(address, value, instr)
|
|
self.setPlainText(text)
|
|
|
|
def updateHighlight(self):
|
|
selections = []
|
|
for i in range(24):
|
|
address = self.base + i * 4
|
|
|
|
color = self.getColor(address)
|
|
if color:
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QTextCursor.Down, n=i)
|
|
cursor.select(QTextCursor.LineUnderCursor)
|
|
format = QTextCharFormat()
|
|
format.setBackground(QBrush(QColor(color)))
|
|
selection = QTextEdit.ExtraSelection()
|
|
selection.cursor = cursor
|
|
selection.format = format
|
|
selections.append(selection)
|
|
self.setExtraSelections(selections)
|
|
|
|
def getColor(self, addr):
|
|
colors = []
|
|
if addr in bugger.breakPoints:
|
|
colors.append((255, 0, 0))
|
|
if addr == self.currentInstruction:
|
|
colors.append((0, 255, 0))
|
|
if addr == self.selectedAddress:
|
|
colors.append((0, 0, 255))
|
|
|
|
if not colors:
|
|
return None
|
|
|
|
color = [sum(l)//len(colors) for l in zip(*colors)]
|
|
return "#%02X%02X%02X" %tuple(color)
|
|
|
|
def mousePressEvent(self, e):
|
|
super().mousePressEvent(e)
|
|
line = self.cursorForPosition(e.pos()).blockNumber()
|
|
self.selectedAddress = self.base + line * 4
|
|
if e.button() == Qt.MidButton:
|
|
bugger.toggleBreakPoint(self.selectedAddress)
|
|
self.updateHighlight()
|
|
|
|
|
|
class DisassemblyInfo(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.baseLabel = QLabel("Address:")
|
|
self.baseBox = HexSpinBox(self, 4)
|
|
self.baseButton = QPushButton("Update", self)
|
|
self.baseButton.clicked.connect(self.updateDisassemblyBase)
|
|
|
|
self.pokeBox = HexSpinBox(self)
|
|
self.pokeButton = QPushButton("Poke", self)
|
|
self.pokeButton.clicked.connect(self.poke)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.baseLabel, 0, 0)
|
|
self.layout.addWidget(self.baseBox, 0, 1)
|
|
self.layout.addWidget(self.baseButton, 0, 2)
|
|
self.layout.addWidget(self.pokeBox, 1, 0)
|
|
self.layout.addWidget(self.pokeButton, 1, 1, 1, 2)
|
|
self.setLayout(self.layout)
|
|
self.setMinimumWidth(300)
|
|
|
|
def updateDisassemblyBase(self):
|
|
window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(self.baseBox.value())
|
|
|
|
def poke(self):
|
|
disassembly = window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget
|
|
if disassembly.selectedAddress:
|
|
bugger.writeCode(disassembly.selectedAddress, self.pokeBox.value())
|
|
disassembly.updateText()
|
|
|
|
|
|
class DisassemblyTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.disassemblyInfo = DisassemblyInfo(self)
|
|
self.disassemblyWidget = DisassemblyWidget(self)
|
|
self.layout = QHBoxLayout()
|
|
self.layout.addWidget(self.disassemblyInfo)
|
|
self.layout.addWidget(self.disassemblyWidget)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
|
|
def connected(self):
|
|
self.disassemblyWidget.setBase(0x10000000)
|
|
|
|
|
|
class ThreadList(QTableWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(0, 5, parent)
|
|
self.setHorizontalHeaderLabels(["Name", "Priority", "Core", "Stack", "Entry Point"])
|
|
self.setEditTriggers(self.NoEditTriggers)
|
|
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
|
|
events.Connected.connect(self.updateThreads)
|
|
|
|
def updateThreads(self):
|
|
threads = bugger.getThreadList()
|
|
self.setRowCount(len(threads))
|
|
for i in range(len(threads)):
|
|
thread = threads[i]
|
|
self.setItem(i, 0, QTableWidgetItem(thread.name))
|
|
self.setItem(i, 1, QTableWidgetItem(str(thread.priority)))
|
|
self.setItem(i, 2, QTableWidgetItem(thread.core))
|
|
self.setItem(i, 3, QTableWidgetItem("0x%x - 0x%x" %(thread.stackEnd, thread.stackBase)))
|
|
self.setItem(i, 4, QTableWidgetItem(hex(thread.entryPoint)))
|
|
|
|
|
|
class ThreadingTab(QTableWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.threadList = ThreadList(self)
|
|
self.updateButton = QPushButton("Update", self)
|
|
self.updateButton.clicked.connect(self.threadList.updateThreads)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.threadList)
|
|
self.layout.addWidget(self.updateButton)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
class BreakPointList(QListWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.itemDoubleClicked.connect(self.goToDisassembly)
|
|
events.BreakPointChanged.connect(self.updateList)
|
|
|
|
def updateList(self):
|
|
self.clear()
|
|
for bp in bugger.breakPoints:
|
|
self.addItem("0x%08X" %bp)
|
|
|
|
def goToDisassembly(self, item):
|
|
address = bugger.breakPoints[self.row(item)]
|
|
window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(address)
|
|
window.mainWidget.tabWidget.setCurrentIndex(1)
|
|
|
|
|
|
class BreakPointTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.list = BreakPointList(self)
|
|
self.button = QPushButton("Remove", self)
|
|
self.button.clicked.connect(self.removeBreakPoint)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.list)
|
|
self.layout.addWidget(self.button)
|
|
self.setLayout(self.layout)
|
|
|
|
def removeBreakPoint(self):
|
|
if self.list.currentRow() != -1:
|
|
bugger.toggleBreakPoint(bugger.breakPoints[self.list.currentRow()])
|
|
|
|
|
|
class RegisterTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.gprLabels = []
|
|
self.gprBoxes = []
|
|
self.fprLabels = []
|
|
self.fprBoxes = []
|
|
for i in range(32):
|
|
self.gprLabels.append(QLabel("r%i" %i, self))
|
|
self.fprLabels.append(QLabel("f%i" % i, self))
|
|
gprBox = HexSpinBox(self)
|
|
fprBox = QDoubleSpinBox(self)
|
|
fprBox.setRange(float("-inf"), float("inf"))
|
|
self.gprBoxes.append(gprBox)
|
|
self.fprBoxes.append(fprBox)
|
|
|
|
self.layout = QGridLayout()
|
|
for i in range(32):
|
|
self.layout.addWidget(self.gprLabels[i], i % 16, i // 16 * 2)
|
|
self.layout.addWidget(self.gprBoxes[i], i % 16, i // 16 * 2 + 1)
|
|
self.layout.addWidget(self.fprLabels[i], i % 16, i // 16 * 2 + 4)
|
|
self.layout.addWidget(self.fprBoxes[i], i % 16, i // 16 * 2 + 5)
|
|
self.setLayout(self.layout)
|
|
|
|
self.pokeButton = QPushButton("Poke", self)
|
|
self.resetButton = QPushButton("Reset", self)
|
|
self.pokeButton.clicked.connect(self.pokeRegisters)
|
|
self.resetButton.clicked.connect(self.updateRegisters)
|
|
self.layout.addWidget(self.pokeButton, 16, 0, 1, 4)
|
|
self.layout.addWidget(self.resetButton, 16, 4, 1, 4)
|
|
|
|
self.setEditEnabled(False)
|
|
|
|
events.Exception.connect(self.exceptionOccurred)
|
|
events.Continue.connect(lambda: self.setEditEnabled(False))
|
|
|
|
def setEditEnabled(self, enabled):
|
|
for i in range(32):
|
|
self.gprBoxes[i].setEnabled(enabled)
|
|
self.fprBoxes[i].setEnabled(enabled)
|
|
self.pokeButton.setEnabled(enabled)
|
|
self.resetButton.setEnabled(enabled)
|
|
|
|
def exceptionOccurred(self):
|
|
self.updateRegisters()
|
|
self.setEditEnabled(exceptionState.isBreakPoint())
|
|
|
|
def updateRegisters(self):
|
|
for i in range(32):
|
|
self.gprBoxes[i].setValue(exceptionState.gpr[i])
|
|
self.fprBoxes[i].setValue(exceptionState.fpr[i])
|
|
|
|
def pokeRegisters(self):
|
|
for i in range(32):
|
|
exceptionState.gpr[i] = self.gprBoxes[i].value()
|
|
exceptionState.fpr[i] = self.fprBoxes[i].value()
|
|
bugger.pokeExceptionRegisters()
|
|
|
|
|
|
class ExceptionInfo(QGroupBox):
|
|
def __init__(self, parent):
|
|
super().__init__("Info", parent)
|
|
self.typeLabel = QLabel(self)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.typeLabel)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Exception.connect(self.updateInfo)
|
|
|
|
def updateInfo(self):
|
|
self.typeLabel.setText("Type: %s" %exceptionState.exceptionName)
|
|
|
|
|
|
class SpecialRegisters(QGroupBox):
|
|
def __init__(self, parent):
|
|
super().__init__("Special registers", parent)
|
|
self.cr = QLabel(self)
|
|
self.lr = QLabel(self)
|
|
self.ctr = QLabel(self)
|
|
self.xer = QLabel(self)
|
|
self.srr0 = QLabel(self)
|
|
self.srr1 = QLabel(self)
|
|
self.ex0 = QLabel(self)
|
|
self.ex1 = QLabel(self)
|
|
|
|
self.layout = QHBoxLayout()
|
|
self.userLayout = QFormLayout()
|
|
self.kernelLayout = QFormLayout()
|
|
|
|
self.userLayout.addRow("CR:", self.cr)
|
|
self.userLayout.addRow("LR:", self.lr)
|
|
self.userLayout.addRow("CTR:", self.ctr)
|
|
self.userLayout.addRow("XER:", self.xer)
|
|
|
|
self.kernelLayout = QFormLayout()
|
|
self.kernelLayout.addRow("SRR0:", self.srr0)
|
|
self.kernelLayout.addRow("SRR1:", self.srr1)
|
|
self.kernelLayout.addRow("EX0:", self.ex0)
|
|
self.kernelLayout.addRow("EX1:", self.ex1)
|
|
|
|
self.layout.addLayout(self.userLayout)
|
|
self.layout.addLayout(self.kernelLayout)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Exception.connect(self.updateRegisters)
|
|
|
|
def updateRegisters(self):
|
|
self.cr.setText("%X" %exceptionState.cr)
|
|
self.lr.setText("%X" %exceptionState.lr)
|
|
self.ctr.setText("%X" %exceptionState.ctr)
|
|
self.xer.setText("%X" %exceptionState.xer)
|
|
self.srr0.setText("%X" %exceptionState.srr0)
|
|
self.srr1.setText("%X" %exceptionState.srr1)
|
|
self.ex0.setText("%X" %exceptionState.ex0)
|
|
self.ex1.setText("%X" %exceptionState.ex1)
|
|
|
|
|
|
class ExceptionInfoTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.exceptionInfo = ExceptionInfo(self)
|
|
self.specialRegisters = SpecialRegisters(self)
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.exceptionInfo, 0, 0)
|
|
self.layout.addWidget(self.specialRegisters, 0, 1)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
class StackTrace(QListWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
events.Exception.connect(self.updateTrace)
|
|
|
|
def updateTrace(self):
|
|
self.clear()
|
|
stackTrace = bugger.getStackTrace()
|
|
for address in (exceptionState.srr0, exceptionState.lr) + stackTrace:
|
|
self.addItem("%X" %address)
|
|
|
|
|
|
class BreakPointActions(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.continueButton = QPushButton("Continue", self)
|
|
self.stepButton = QPushButton("Step", self)
|
|
self.stepOverButton = QPushButton("Step over", self)
|
|
self.continueButton.clicked.connect(bugger.continueBreak)
|
|
self.stepButton.clicked.connect(bugger.stepBreak)
|
|
self.stepOverButton.clicked.connect(bugger.stepOver)
|
|
|
|
self.layout = QHBoxLayout()
|
|
self.layout.addWidget(self.continueButton)
|
|
self.layout.addWidget(self.stepButton)
|
|
self.layout.addWidget(self.stepOverButton)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Exception.connect(self.updateButtons)
|
|
events.Continue.connect(self.disableButtons)
|
|
|
|
def disableButtons(self):
|
|
self.setButtonsEnabled(False)
|
|
|
|
def updateButtons(self):
|
|
self.setButtonsEnabled(exceptionState.isBreakPoint())
|
|
|
|
def setButtonsEnabled(self, enabled):
|
|
self.continueButton.setEnabled(enabled)
|
|
self.stepButton.setEnabled(enabled)
|
|
self.stepOverButton.setEnabled(enabled)
|
|
|
|
|
|
class StackTraceTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.stackTrace = StackTrace(self)
|
|
self.disassembly = DisassemblyWidget(self)
|
|
self.breakPointActions = BreakPointActions(self)
|
|
self.layout = QVBoxLayout()
|
|
hlayout = QHBoxLayout()
|
|
hlayout.addWidget(self.stackTrace)
|
|
hlayout.addWidget(self.disassembly)
|
|
self.layout.addLayout(hlayout)
|
|
self.layout.addWidget(self.breakPointActions)
|
|
self.setLayout(self.layout)
|
|
|
|
self.stackTrace.itemDoubleClicked.connect(self.jumpDisassembly)
|
|
events.Exception.connect(self.exceptionOccurred)
|
|
|
|
def exceptionOccurred(self):
|
|
self.disassembly.setCurrentInstruction(exceptionState.srr0)
|
|
|
|
def jumpDisassembly(self, item):
|
|
self.disassembly.setBase(int(item.text(), 16) - 0x20)
|
|
|
|
|
|
class ExceptionTab(QTabWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.infoTab = ExceptionInfoTab(self)
|
|
self.registerTab = RegisterTab(self)
|
|
self.stackTab = StackTraceTab(self)
|
|
self.addTab(self.infoTab, "General")
|
|
self.addTab(self.registerTab, "Registers")
|
|
self.addTab(self.stackTab, "Stack trace")
|
|
|
|
events.Exception.connect(self.exceptionOccurred)
|
|
|
|
def exceptionOccurred(self):
|
|
self.setCurrentIndex(2) #Stack trace
|
|
|
|
|
|
def formatFileSize(size):
|
|
if size >= 1024 ** 3:
|
|
return "%.1f GiB" %(size / (1024 ** 3))
|
|
if size >= 1024 ** 2:
|
|
return "%.1f MiB" %(size / (1024 ** 2))
|
|
if size >= 1024:
|
|
return "%.1f KiB" %(size / 1024)
|
|
return "%i B" %size
|
|
|
|
class FileTreeNode(QTreeWidgetItem):
|
|
def __init__(self, parent, name, size, path):
|
|
super().__init__(parent)
|
|
self.name = name
|
|
self.size = size
|
|
self.path = path
|
|
|
|
self.setText(0, name)
|
|
if size == -1: #It's a folder
|
|
self.loaded = False
|
|
else: #It's a file
|
|
self.setText(1, formatFileSize(size))
|
|
self.loaded = True
|
|
|
|
def loadChildren(self):
|
|
if not self.loaded:
|
|
for i in range(self.childCount()):
|
|
child = self.child(i)
|
|
if not child.loaded:
|
|
self.child(i).loadContent()
|
|
self.loaded = True
|
|
|
|
def loadContent(self):
|
|
entries = bugger.readDirectory(self.path)
|
|
for entry in entries:
|
|
FileTreeNode(self, entry.name, entry.size, self.path + "/" + entry.name)
|
|
|
|
def dump(self, outdir, task):
|
|
if task.canceled:
|
|
return
|
|
|
|
outpath = os.path.join(outdir, self.name)
|
|
if self.size == -1:
|
|
if os.path.isfile(outpath):
|
|
os.remove(outpath)
|
|
if not os.path.exists(outpath):
|
|
os.mkdir(outpath)
|
|
|
|
self.loadChildren()
|
|
for i in range(self.childCount()):
|
|
self.child(i).dump(outpath, task)
|
|
else:
|
|
bugger.dumpFile(self.path, outpath, task)
|
|
|
|
|
|
class FileTreeWidget(QTreeWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.setHeaderLabels(["Name", "Size"])
|
|
self.itemExpanded.connect(self.handleItemExpanded)
|
|
events.Connected.connect(self.initFileTree)
|
|
|
|
def initFileTree(self):
|
|
self.clear()
|
|
rootItem = FileTreeNode(self, "content", -1, "/vol/content")
|
|
rootItem.loadContent()
|
|
self.resizeColumnToContents(0)
|
|
|
|
def handleItemExpanded(self, item):
|
|
item.loadChildren()
|
|
self.resizeColumnToContents(0)
|
|
|
|
|
|
class FileSystemTab(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.fileTree = FileTreeWidget(self)
|
|
self.dumpButton = QPushButton("Dump", self)
|
|
self.dumpButton.clicked.connect(self.dump)
|
|
self.patchButton = QPushButton("Load patch", self)
|
|
self.patchButton.clicked.connect(self.loadPatch)
|
|
self.clearButton = QPushButton("Clear patch", self)
|
|
self.clearButton.clicked.connect(self.clearPatch)
|
|
self.clearButton.setEnabled(True)
|
|
|
|
self.layout = QVBoxLayout()
|
|
hlayout = QHBoxLayout()
|
|
hlayout.addWidget(self.dumpButton)
|
|
hlayout.addWidget(self.patchButton)
|
|
hlayout.addWidget(self.clearButton)
|
|
self.layout.addWidget(self.fileTree)
|
|
self.layout.addLayout(hlayout)
|
|
self.setLayout(self.layout)
|
|
|
|
def dump(self):
|
|
item = self.fileTree.currentItem()
|
|
if item:
|
|
outdir = QFileDialog.getExistingDirectory(self, "Dump")
|
|
if outdir:
|
|
task = Task(blocking=True, cancelable=True)
|
|
item.dump(outdir, task)
|
|
task.end()
|
|
|
|
def loadPatch(self):
|
|
patchDir = QFileDialog.getExistingDirectory(self, "Load patch")
|
|
if patchDir:
|
|
baseLength = len(patchDir)
|
|
fileList = []
|
|
for dirname, subdirs, files in os.walk(patchDir):
|
|
for filename in files:
|
|
gamePath = "/vol" + dirname[baseLength:].replace("\\", "/") + "/" + filename
|
|
fileList.append(gamePath)
|
|
|
|
bugger.setPatchFiles(fileList, patchDir)
|
|
self.clearButton.setEnabled(True)
|
|
|
|
def clearPatch(self):
|
|
bugger.clearPatchFiles()
|
|
self.clearButton.setEnabled(True)
|
|
|
|
|
|
class DebuggerTabs(QTabWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.memoryTab = MemoryTab(self)
|
|
self.disassemblyTab = DisassemblyTab(self)
|
|
self.threadingTab = ThreadingTab(self)
|
|
self.breakPointTab = BreakPointTab(self)
|
|
self.exceptionTab = ExceptionTab(self)
|
|
self.fileSystemTab = FileSystemTab(self)
|
|
self.addTab(self.memoryTab, "Memory")
|
|
self.addTab(self.disassemblyTab, "Disassembly")
|
|
self.addTab(self.threadingTab, "Threads")
|
|
self.addTab(self.breakPointTab, "Breakpoints")
|
|
self.addTab(self.exceptionTab, "Exceptions")
|
|
self.addTab(self.fileSystemTab, "File System")
|
|
self.setTabEnabled(4, True)
|
|
|
|
events.Exception.connect(self.exceptionOccurred)
|
|
events.Connected.connect(self.connected)
|
|
events.Closed.connect(self.disconnected)
|
|
|
|
def exceptionOccurred(self):
|
|
self.setTabEnabled(4, True)
|
|
self.setCurrentIndex(4) #Exceptions
|
|
|
|
def connected(self):
|
|
self.setEnabled(True)
|
|
|
|
def disconnected(self):
|
|
self.setEnabled(True)
|
|
self.setTabEnabled(4, True)
|
|
|
|
|
|
class StatusWidget(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.serverLabel = QLabel("Wii U IP:")
|
|
self.serverBox = QLineEdit(self)
|
|
self.serverBox.returnPressed.connect(self.connect)
|
|
self.connectButton = QPushButton("Connect", self)
|
|
self.connectButton.clicked.connect(self.connect)
|
|
self.disconnectButton = QPushButton("Disconnect", self)
|
|
self.disconnectButton.clicked.connect(bugger.close)
|
|
self.disconnectButton.setEnabled(True)
|
|
|
|
self.progressBar = QProgressBar(self)
|
|
self.progressInfo = QLabel("Disconnected", self)
|
|
self.cancelButton = QPushButton("Cancel", self)
|
|
self.cancelButton.clicked.connect(taskMgr.cancel)
|
|
self.cancelButton.setEnabled(True)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.serverLabel, 0, 0)
|
|
self.layout.addWidget(self.serverBox, 1, 0)
|
|
self.layout.addWidget(self.connectButton, 0, 1)
|
|
self.layout.addWidget(self.disconnectButton, 1, 1)
|
|
self.layout.addWidget(self.progressBar, 2, 0)
|
|
self.layout.addWidget(self.cancelButton, 2, 1)
|
|
self.layout.addWidget(self.progressInfo, 3, 0, 1, 2)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
events.Closed.connect(self.disconnected)
|
|
|
|
def connect(self):
|
|
try: bugger.connect(str(self.serverBox.text()))
|
|
except: pass
|
|
|
|
def connected(self):
|
|
self.progressInfo.setText("Connected")
|
|
self.connectButton.setEnabled(True)
|
|
self.serverBox.setEnabled(True)
|
|
self.disconnectButton.setEnabled(True)
|
|
|
|
def disconnected(self):
|
|
self.progressInfo.setText("Disconnected")
|
|
self.connectButton.setEnabled(True)
|
|
self.serverBox.setEnabled(True)
|
|
self.disconnectButton.setEnabled(True)
|
|
|
|
|
|
class MainWidget(QWidget):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self.tabWidget = DebuggerTabs(self)
|
|
self.statusWidget = StatusWidget(self)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.tabWidget)
|
|
self.layout.addWidget(self.statusWidget)
|
|
self.tabWidget.setEnabled(True)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def init(self):
|
|
self.mainWidget = MainWidget(self)
|
|
self.setCentralWidget(self.mainWidget)
|
|
|
|
self.setWindowTitle("DiiBugger")
|
|
self.resize(1080, 720)
|
|
|
|
self.timer = QTimer(self)
|
|
self.timer.setInterval(100)
|
|
self.timer.timeout.connect(self.updateBugger)
|
|
self.timer.start()
|
|
|
|
events.Connected.connect(self.updateTitle)
|
|
events.Closed.connect(self.updateTitle)
|
|
|
|
def updateTitle(self):
|
|
if bugger.connected:
|
|
name = bugger.getModuleName()
|
|
self.setWindowTitle("DiiBugger - %s" %name)
|
|
else:
|
|
self.setWindowTitle("DiiBugger")
|
|
|
|
def updateBugger(self):
|
|
if bugger.connected and not taskMgr.isBlocking():
|
|
bugger.updateMessages()
|
|
|
|
def closeEvent(self, e):
|
|
if taskMgr.taskQueue:
|
|
e.ignore()
|
|
else:
|
|
e.accept()
|
|
|
|
|
|
exceptionState = ExceptionState()
|
|
bugger = PyBugger()
|
|
app = QApplication(sys.argv)
|
|
app.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
|
|
window = MainWindow()
|
|
window.init()
|
|
window.show()
|
|
app.exec()
|
|
if bugger.connected:
|
|
bugger.close()
|