stUpiidhax, the Wii U 5.5.2 exploit (based on JSTypeHax) http://stupiid.ovh/

main.py 40KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. from PyQt5.QtCore import *
  2. from PyQt5.QtGui import *
  3. from PyQt5.QtWidgets import *
  4. import sys, struct, socket, time, os
  5. import disassemble
  6. class Message:
  7. DSI = 0
  8. ISI = 1
  9. Program = 2
  10. GetStat = 3
  11. OpenFile = 4
  12. ReadFile = 5
  13. CloseFile = 6
  14. SetPosFile = 7
  15. GetStatFile = 8
  16. Continue = 0
  17. Step = 1
  18. StepOver = 2
  19. def __init__(self, type, data, arg):
  20. self.type = type
  21. self.data = data
  22. self.arg = arg
  23. class EventHolder(QObject):
  24. Exception = pyqtSignal()
  25. Connected = pyqtSignal()
  26. Closed = pyqtSignal()
  27. BreakPointChanged = pyqtSignal()
  28. Continue = pyqtSignal()
  29. events = EventHolder()
  30. #I don't want to deal with the whole threading trouble to complete big
  31. #file transfers without the UI becoming unresponsive. There probably is
  32. #a better way to code this, but this is what I came up with.
  33. class TaskMgr:
  34. def __init__(self):
  35. self.taskQueue = []
  36. def add(self, task):
  37. if not self.taskQueue:
  38. window.mainWidget.statusWidget.disconnectButton.setEnabled(True)
  39. self.taskQueue.append(task)
  40. def pop(self, task):
  41. assert task == self.taskQueue.pop()
  42. if not self.taskQueue:
  43. window.mainWidget.tabWidget.setEnabled(True)
  44. window.mainWidget.statusWidget.cancelButton.setEnabled(True)
  45. window.mainWidget.statusWidget.disconnectButton.setEnabled(True)
  46. window.mainWidget.statusWidget.progressBar.setValue(0)
  47. window.mainWidget.statusWidget.progressInfo.setText("Connected")
  48. else:
  49. self.taskQueue[-1].resume()
  50. def isBlocking(self):
  51. if not self.taskQueue:
  52. return False
  53. return self.taskQueue[-1].blocking
  54. def cancel(self):
  55. self.taskQueue[-1].canceled = True
  56. taskMgr = TaskMgr()
  57. class Task:
  58. def __init__(self, blocking, cancelable):
  59. taskMgr.add(self)
  60. self.canceled = False
  61. self.blocking = blocking
  62. self.cancelable = cancelable
  63. window.mainWidget.tabWidget.setEnabled(not blocking)
  64. window.mainWidget.statusWidget.cancelButton.setEnabled(cancelable)
  65. def setInfo(self, info, maxValue):
  66. self.info = info
  67. self.maxValue = maxValue
  68. window.mainWidget.statusWidget.progressInfo.setText(info)
  69. window.mainWidget.statusWidget.progressBar.setRange(0, maxValue)
  70. def update(self, progress):
  71. self.progress = progress
  72. window.mainWidget.statusWidget.progressBar.setValue(progress)
  73. app.processEvents()
  74. def resume(self):
  75. window.mainWidget.tabWidget.setEnabled(not self.blocking)
  76. window.mainWidget.statusWidget.cancelButton.setEnabled(self.cancelable)
  77. window.mainWidget.statusWidget.progressInfo.setText(self.info)
  78. window.mainWidget.statusWidget.progressBar.setRange(0, self.maxValue)
  79. window.mainWidget.statusWidget.progressBar.setValue(self.progress)
  80. def end(self):
  81. taskMgr.pop(self)
  82. class Thread:
  83. cores = {
  84. 1: "Core 0",
  85. 2: "Core 1",
  86. 4: "Core 2"
  87. }
  88. def __init__(self, data, offs=0):
  89. self.core = self.cores[struct.unpack_from(">I", data, offs)[0]]
  90. self.priority = struct.unpack_from(">I", data, offs + 4)[0]
  91. self.stackBase = struct.unpack_from(">I", data, offs + 8)[0]
  92. self.stackEnd = struct.unpack_from(">I", data, offs + 12)[0]
  93. self.entryPoint = struct.unpack_from(">I", data, offs + 16)[0]
  94. namelen = struct.unpack_from(">I", data, offs + 20)[0]
  95. self.name = data[offs + 24 : offs + 24 + namelen].decode("ascii")
  96. class DirEntry:
  97. def __init__(self, flags, size, name):
  98. self.flags = flags
  99. self.size = size
  100. self.name = name
  101. def isDir(self):
  102. return self.flags & 0x80000000
  103. class PyBugger:
  104. def __init__(self):
  105. super().__init__()
  106. self.connected = False
  107. self.breakPoints = []
  108. self.basePath = b""
  109. self.currentHandle = 0x12345678
  110. self.files = {}
  111. self.messageHandlers = {
  112. Message.DSI: self.handleException,
  113. Message.ISI: self.handleException,
  114. Message.Program: self.handleException,
  115. Message.GetStat: self.handleGetStat,
  116. Message.OpenFile: self.handleOpenFile,
  117. Message.ReadFile: self.handleReadFile,
  118. Message.CloseFile: self.handleCloseFile,
  119. Message.SetPosFile: self.handleSetPosFile,
  120. Message.GetStatFile: self.handleGetStatFile
  121. }
  122. def handleException(self, msg):
  123. exceptionState.load(msg.data, msg.type)
  124. events.Exception.emit()
  125. def handleGetStat(self, msg):
  126. gamePath = msg.data.decode("ascii")
  127. path = os.path.join(self.basePath, gamePath.strip("/vol"))
  128. print("GetStat: %s" %gamePath)
  129. self.sendFileMessage(os.path.getsize(path))
  130. def handleOpenFile(self, msg):
  131. mode = struct.pack(">I", msg.arg).decode("ascii").strip("\x00") + "b"
  132. path = msg.data.decode("ascii")
  133. print("Open: %s" %path)
  134. f = open(os.path.join(self.basePath, path.strip("/vol")), mode)
  135. self.files[self.currentHandle] = f
  136. self.sendFileMessage(self.currentHandle)
  137. self.currentHandle += 1
  138. def handleReadFile(self, msg):
  139. print("Read")
  140. task = Task(blocking=False, cancelable=False)
  141. bufferAddr, size, count, handle = struct.unpack(">IIII", msg.data)
  142. data = self.files[handle].read(size * count)
  143. task.setInfo("Sending file", len(data))
  144. bytesSent = 0
  145. while bytesSent < len(data):
  146. length = min(len(data) - bytesSent, 0x8000)
  147. self.sendall(b"\x03")
  148. self.sendall(struct.pack(">II", bufferAddr, length))
  149. self.sendall(data[bytesSent : bytesSent + length])
  150. bufferAddr += length
  151. bytesSent += length
  152. task.update(bytesSent)
  153. self.sendFileMessage(bytesSent // size)
  154. task.end()
  155. def handleCloseFile(self, msg):
  156. print("Close")
  157. self.files.pop(msg.arg).close()
  158. self.sendFileMessage()
  159. def handleSetPosFile(self, msg):
  160. print("SetPos")
  161. handle, pos = struct.unpack(">II", msg.data)
  162. self.files[handle].seek(pos)
  163. self.sendFileMessage()
  164. def handleGetStatFile(self, msg):
  165. print("GetStatFile")
  166. f = self.files[msg.arg]
  167. pos = f.tell()
  168. f.seek(0, 2)
  169. size = f.tell()
  170. f.seek(pos)
  171. self.sendFileMessage(size)
  172. def connect(self, host):
  173. self.s = socket.socket()
  174. self.s.connect((host, 1559))
  175. self.connected = True
  176. self.closeRequest = False
  177. events.Connected.emit()
  178. def close(self):
  179. self.sendall(b"\x01")
  180. self.s.close()
  181. self.connected = False
  182. self.breakPoints = []
  183. events.Closed.emit()
  184. def updateMessages(self):
  185. self.sendall(b"\x07")
  186. count = struct.unpack(">I", self.recvall(4))[0]
  187. for i in range(count):
  188. type, ptr, length, arg = struct.unpack(">IIII", self.recvall(16))
  189. data = None
  190. if length:
  191. data = self.recvall(length)
  192. self.messageHandlers[type](Message(type, data, arg))
  193. def read(self, addr, num):
  194. self.sendall(b"\x02")
  195. self.sendall(struct.pack(">II", addr, num))
  196. data = self.recvall(num)
  197. return data
  198. def write(self, addr, data):
  199. self.sendall(b"\x03")
  200. self.sendall(struct.pack(">II", addr, len(data)))
  201. self.sendall(data)
  202. def writeCode(self, addr, instr):
  203. self.sendall(b"\x04")
  204. self.sendall(struct.pack(">II", addr, instr))
  205. def getThreadList(self):
  206. self.sendall(b"\x05")
  207. length = struct.unpack(">I", self.recvall(4))[0]
  208. data = self.recvall(length)
  209. offset = 0
  210. threads = []
  211. while offset < length:
  212. thread = Thread(data, offset)
  213. threads.append(thread)
  214. offset += 24 + len(thread.name)
  215. return threads
  216. def toggleBreakPoint(self, addr):
  217. if addr in self.breakPoints: self.breakPoints.remove(addr)
  218. else:
  219. if len(self.breakPoints) >= 10:
  220. return
  221. self.breakPoints.append(addr)
  222. self.sendall(b"\x0A")
  223. self.sendall(struct.pack(">I", addr))
  224. events.BreakPointChanged.emit()
  225. def continueBreak(self): self.sendCrashMessage(Message.Continue)
  226. def stepBreak(self): self.sendCrashMessage(Message.Step)
  227. def stepOver(self): self.sendCrashMessage(Message.StepOver)
  228. def sendCrashMessage(self, message):
  229. self.sendMessage(message)
  230. events.Continue.emit()
  231. def sendMessage(self, message, data0=0, data1=0, data2=0):
  232. self.sendall(b"\x06")
  233. self.sendall(struct.pack(">IIII", message, data0, data1, data2))
  234. def sendFileMessage(self, data0=0, data1=0, data2=0):
  235. self.sendall(b"\x0F")
  236. self.sendall(struct.pack(">IIII", 0, data0, data1, data2))
  237. def getStackTrace(self):
  238. self.sendall(b"\x08")
  239. count = struct.unpack(">I", self.recvall(4))[0]
  240. trace = struct.unpack(">%iI" %count, self.recvall(4 * count))
  241. return trace
  242. def pokeExceptionRegisters(self):
  243. self.sendall(b"\x09")
  244. data = struct.pack(">32I32d", *exceptionState.gpr, *exceptionState.fpr)
  245. self.sendall(data)
  246. def readDirectory(self, path):
  247. self.sendall(b"\x0B")
  248. self.sendall(struct.pack(">I", len(path)))
  249. self.sendall(path.encode("ascii"))
  250. entries = []
  251. namelen = struct.unpack(">I", self.recvall(4))[0]
  252. while namelen != 0:
  253. flags = struct.unpack(">I", self.recvall(4))[0]
  254. size = -1
  255. if not flags & 0x80000000:
  256. size = struct.unpack(">I", self.recvall(4))[0]
  257. name = self.recvall(namelen).decode("ascii")
  258. entries.append(DirEntry(flags, size, name))
  259. namelen = struct.unpack(">I", self.recvall(4))[0]
  260. return entries
  261. def dumpFile(self, gamePath, outPath, task):
  262. if task.canceled:
  263. return
  264. self.sendall(b"\x0C")
  265. self.sendall(struct.pack(">I", len(gamePath)))
  266. self.sendall(gamePath.encode("ascii"))
  267. length = struct.unpack(">I", self.recvall(4))[0]
  268. task.setInfo("Dumping %s" %gamePath, length)
  269. with open(outPath, "wb") as f:
  270. bytesDumped = 0
  271. while bytesDumped < length:
  272. data = self.s.recv(length - bytesDumped)
  273. f.write(data)
  274. bytesDumped += len(data)
  275. task.update(bytesDumped)
  276. def getModuleName(self):
  277. self.sendall(b"\x0D")
  278. length = struct.unpack(">I", self.recvall(4))[0]
  279. return self.recvall(length).decode("ascii") + ".rpx"
  280. def setPatchFiles(self, fileList, basePath):
  281. self.basePath = basePath
  282. self.sendall(b"\x0E")
  283. fileBuffer = struct.pack(">I", len(fileList))
  284. for path in fileList:
  285. fileBuffer += struct.pack(">H", len(path))
  286. fileBuffer += path.encode("ascii")
  287. self.sendall(struct.pack(">I", len(fileBuffer)))
  288. self.sendall(fileBuffer)
  289. def clearPatchFiles(self):
  290. self.sendall(b"\x10")
  291. def sendall(self, data):
  292. try:
  293. self.s.sendall(data)
  294. except socket.error:
  295. self.connected = False
  296. events.Closed.emit()
  297. def recvall(self, num):
  298. try:
  299. data = b""
  300. while len(data) < num:
  301. data += self.s.recv(num - len(data))
  302. except socket.error:
  303. self.connected = False
  304. events.Closed.emit()
  305. return b"\x00" * num
  306. return data
  307. class HexSpinBox(QAbstractSpinBox):
  308. def __init__(self, parent, stepSize = 1):
  309. super().__init__(parent)
  310. self._value = 0
  311. self.stepSize = stepSize
  312. def validate(self, text, pos):
  313. if all([char in "0123456789abcdefABCDEF" for char in text]):
  314. if not text:
  315. return QValidator.Intermediate, text.upper(), pos
  316. value = int(text, 16)
  317. if value <= 0xFFFFFFFF:
  318. self._value = value
  319. if value % self.stepSize:
  320. self._value -= value % self.stepSize
  321. return QValidator.Acceptable, text.upper(), pos
  322. return QValidator.Acceptable, text.upper(), pos
  323. return QValidator.Invalid, text.upper(), pos
  324. def stepBy(self, steps):
  325. self._value = min(max(self._value + steps * self.stepSize, 0), 0x100000000 - self.stepSize)
  326. self.lineEdit().setText("%X" %self._value)
  327. def stepEnabled(self):
  328. return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled
  329. def setValue(self, value):
  330. self._value = value
  331. self.lineEdit().setText("%X" %self._value)
  332. def value(self):
  333. return self._value
  334. class ExceptionState:
  335. exceptionNames = ["DSI", "ISI", "Program"]
  336. def load(self, context, type):
  337. #Convert tuple to list to make it mutable
  338. self.gpr = list(struct.unpack_from(">32I", context, 8))
  339. self.cr, self.lr, self.ctr, self.xer = struct.unpack_from(">4I", context, 0x88)
  340. self.srr0, self.srr1, self.ex0, self.ex1 = struct.unpack_from(">4I", context, 0x98)
  341. self.fpr = list(struct.unpack_from(">32d", context, 0xB8))
  342. self.gqr = list(struct.unpack_from(">8I", context, 0x1BC))
  343. self.psf = list(struct.unpack_from(">32d", context, 0x1E0))
  344. self.exceptionName = self.exceptionNames[type]
  345. def isBreakPoint(self):
  346. return self.exceptionName == "Program" and self.srr1 & 0x20000
  347. def format_hex(blob, offs):
  348. return "%02X" %blob[offs]
  349. def format_ascii(blob, offs):
  350. if 0x30 <= blob[offs] <= 0x39 or 0x41 <= blob[offs] <= 0x5A or 0x61 <= blob[offs] <= 0x7A:
  351. return chr(blob[offs])
  352. return "?"
  353. def format_float(blob, offs):
  354. value = struct.unpack_from(">f", blob, offs)[0]
  355. if abs(value) >= 1000000 or 0 < abs(value) < 0.000001:
  356. return "%e" %value
  357. return ("%.8f" %value).rstrip("0")
  358. class MemoryViewer(QWidget):
  359. class Format:
  360. Hex = 0
  361. Ascii = 1
  362. Float = 2
  363. Width = 1, 1, 4
  364. Funcs = format_hex, format_ascii, format_float
  365. def __init__(self, parent):
  366. super().__init__(parent)
  367. self.layout = QGridLayout()
  368. for i in range(16):
  369. self.layout.addWidget(QLabel("%X" %i, self), 0, i + 1)
  370. self.addrLabels = []
  371. for i in range(16):
  372. label = QLabel("%X" %(i * 0x10), self)
  373. self.layout.addWidget(label, i + 1, 0)
  374. self.addrLabels.append(label)
  375. self.dataCells = []
  376. self.base = 0
  377. self.format = self.Format.Hex
  378. self.updateData()
  379. self.setLayout(self.layout)
  380. events.Connected.connect(self.connected)
  381. def connected(self):
  382. self.setBase(0x10000000)
  383. def setFormat(self, format):
  384. self.format = format
  385. self.updateData()
  386. def setBase(self, base):
  387. window.mainWidget.tabWidget.memoryTab.memoryInfo.baseBox.setValue(base)
  388. self.base = base
  389. for i in range(16):
  390. self.addrLabels[i].setText("%X" %(self.base + i * 0x10))
  391. self.updateData()
  392. def updateData(self):
  393. for cell in self.dataCells:
  394. self.layout.removeWidget(cell)
  395. cell.setParent(None)
  396. if bugger.connected:
  397. blob = bugger.read(self.base, 0x100)
  398. else:
  399. blob = b"\x00" * 0x100
  400. width = self.Width[self.format]
  401. func = self.Funcs[self.format]
  402. for i in range(16 // width):
  403. for j in range(16):
  404. label = QLabel(func(blob, j * 0x10 + i * width), self)
  405. self.layout.addWidget(label, j + 1, i * width + 1, 1, width)
  406. self.dataCells.append(label)
  407. class MemoryInfo(QWidget):
  408. def __init__(self, parent):
  409. super().__init__(parent)
  410. self.dataTypeLabel = QLabel("Data type:")
  411. self.dataTypeBox = QComboBox(self)
  412. self.dataTypeBox.addItems(["Hex", "Ascii", "Float"])
  413. self.dataTypeBox.currentIndexChanged.connect(self.updateDataType)
  414. self.baseLabel = QLabel("Address:")
  415. self.baseBox = HexSpinBox(self, 0x10)
  416. self.baseButton = QPushButton("Update", self)
  417. self.baseButton.clicked.connect(self.updateMemoryBase)
  418. self.pokeAddr = HexSpinBox(self, 4)
  419. self.pokeValue = HexSpinBox(self)
  420. self.pokeButton = QPushButton("Poke", self)
  421. self.pokeButton.clicked.connect(self.pokeMemory)
  422. self.layout = QGridLayout()
  423. self.layout.addWidget(self.baseLabel, 0, 0)
  424. self.layout.addWidget(self.baseBox, 0, 1)
  425. self.layout.addWidget(self.baseButton, 0, 2)
  426. self.layout.addWidget(self.pokeAddr, 1, 0)
  427. self.layout.addWidget(self.pokeValue, 1, 1)
  428. self.layout.addWidget(self.pokeButton, 1, 2)
  429. self.layout.addWidget(self.dataTypeLabel, 2, 0)
  430. self.layout.addWidget(self.dataTypeBox, 2, 1, 1, 2)
  431. self.setLayout(self.layout)
  432. def updateDataType(self, index):
  433. window.mainWidget.tabWidget.memoryTab.memoryViewer.setFormat(index)
  434. def updateMemoryBase(self):
  435. window.mainWidget.tabWidget.memoryTab.memoryViewer.setBase(self.baseBox.value())
  436. def pokeMemory(self):
  437. bugger.write(self.pokeAddr.value(), struct.pack(">I", self.pokeValue.value()))
  438. window.mainWidget.tabWidget.memoryTab.memoryViewer.updateData()
  439. class MemoryTab(QWidget):
  440. def __init__(self, parent):
  441. super().__init__(parent)
  442. self.memoryInfo = MemoryInfo(self)
  443. self.memoryViewer = MemoryViewer(self)
  444. self.layout = QHBoxLayout()
  445. self.layout.addWidget(self.memoryInfo)
  446. self.layout.addWidget(self.memoryViewer)
  447. self.button = QPushButton("Dump", self)
  448. self.button.clicked.connect(self.dump)
  449. self.setLayout(self.layout)
  450. def dump(self):
  451. dumpStart = 0x1AB00000
  452. dumpLength = 0x600000
  453. dumpFile = "dump.bin"
  454. with open(dumpFile, 'wb') as f:
  455. f.write(bugger.read(dumpStart, dumpLength))
  456. class DisassemblyWidget(QTextEdit):
  457. def __init__(self, parent):
  458. super().__init__(parent)
  459. self.setTextInteractionFlags(Qt.NoTextInteraction)
  460. self.currentInstruction = None
  461. self.selectedAddress = 0
  462. self.setBase(0)
  463. events.BreakPointChanged.connect(self.updateHighlight)
  464. events.Continue.connect(self.handleContinue)
  465. def handleContinue(self):
  466. self.currentInstruction = None
  467. self.updateHighlight()
  468. def setCurrentInstruction(self, instr):
  469. self.currentInstruction = instr
  470. self.setBase(instr - 0x20)
  471. def setBase(self, base):
  472. self.base = base
  473. self.updateText()
  474. self.updateHighlight()
  475. def updateText(self):
  476. if bugger.connected:
  477. blob = bugger.read(self.base, 0x60)
  478. else:
  479. blob = b"\x00" * 0x60
  480. text = ""
  481. for i in range(24):
  482. address = self.base + i * 4
  483. value = struct.unpack_from(">I", blob, i * 4)[0]
  484. instr = disassemble.disassemble(value, address)
  485. text += "%08X: %08X %s\n" %(address, value, instr)
  486. self.setPlainText(text)
  487. def updateHighlight(self):
  488. selections = []
  489. for i in range(24):
  490. address = self.base + i * 4
  491. color = self.getColor(address)
  492. if color:
  493. cursor = self.textCursor()
  494. cursor.movePosition(QTextCursor.Down, n=i)
  495. cursor.select(QTextCursor.LineUnderCursor)
  496. format = QTextCharFormat()
  497. format.setBackground(QBrush(QColor(color)))
  498. selection = QTextEdit.ExtraSelection()
  499. selection.cursor = cursor
  500. selection.format = format
  501. selections.append(selection)
  502. self.setExtraSelections(selections)
  503. def getColor(self, addr):
  504. colors = []
  505. if addr in bugger.breakPoints:
  506. colors.append((255, 0, 0))
  507. if addr == self.currentInstruction:
  508. colors.append((0, 255, 0))
  509. if addr == self.selectedAddress:
  510. colors.append((0, 0, 255))
  511. if not colors:
  512. return None
  513. color = [sum(l)//len(colors) for l in zip(*colors)]
  514. return "#%02X%02X%02X" %tuple(color)
  515. def mousePressEvent(self, e):
  516. super().mousePressEvent(e)
  517. line = self.cursorForPosition(e.pos()).blockNumber()
  518. self.selectedAddress = self.base + line * 4
  519. if e.button() == Qt.MidButton:
  520. bugger.toggleBreakPoint(self.selectedAddress)
  521. self.updateHighlight()
  522. class DisassemblyInfo(QWidget):
  523. def __init__(self, parent):
  524. super().__init__(parent)
  525. self.baseLabel = QLabel("Address:")
  526. self.baseBox = HexSpinBox(self, 4)
  527. self.baseButton = QPushButton("Update", self)
  528. self.baseButton.clicked.connect(self.updateDisassemblyBase)
  529. self.pokeBox = HexSpinBox(self)
  530. self.pokeButton = QPushButton("Poke", self)
  531. self.pokeButton.clicked.connect(self.poke)
  532. self.layout = QGridLayout()
  533. self.layout.addWidget(self.baseLabel, 0, 0)
  534. self.layout.addWidget(self.baseBox, 0, 1)
  535. self.layout.addWidget(self.baseButton, 0, 2)
  536. self.layout.addWidget(self.pokeBox, 1, 0)
  537. self.layout.addWidget(self.pokeButton, 1, 1, 1, 2)
  538. self.setLayout(self.layout)
  539. self.setMinimumWidth(300)
  540. def updateDisassemblyBase(self):
  541. window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(self.baseBox.value())
  542. def poke(self):
  543. disassembly = window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget
  544. if disassembly.selectedAddress:
  545. bugger.writeCode(disassembly.selectedAddress, self.pokeBox.value())
  546. disassembly.updateText()
  547. class DisassemblyTab(QWidget):
  548. def __init__(self, parent):
  549. super().__init__(parent)
  550. self.disassemblyInfo = DisassemblyInfo(self)
  551. self.disassemblyWidget = DisassemblyWidget(self)
  552. self.layout = QHBoxLayout()
  553. self.layout.addWidget(self.disassemblyInfo)
  554. self.layout.addWidget(self.disassemblyWidget)
  555. self.setLayout(self.layout)
  556. events.Connected.connect(self.connected)
  557. def connected(self):
  558. self.disassemblyWidget.setBase(0x10000000)
  559. class ThreadList(QTableWidget):
  560. def __init__(self, parent):
  561. super().__init__(0, 5, parent)
  562. self.setHorizontalHeaderLabels(["Name", "Priority", "Core", "Stack", "Entry Point"])
  563. self.setEditTriggers(self.NoEditTriggers)
  564. self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
  565. events.Connected.connect(self.updateThreads)
  566. def updateThreads(self):
  567. threads = bugger.getThreadList()
  568. self.setRowCount(len(threads))
  569. for i in range(len(threads)):
  570. thread = threads[i]
  571. self.setItem(i, 0, QTableWidgetItem(thread.name))
  572. self.setItem(i, 1, QTableWidgetItem(str(thread.priority)))
  573. self.setItem(i, 2, QTableWidgetItem(thread.core))
  574. self.setItem(i, 3, QTableWidgetItem("0x%x - 0x%x" %(thread.stackEnd, thread.stackBase)))
  575. self.setItem(i, 4, QTableWidgetItem(hex(thread.entryPoint)))
  576. class ThreadingTab(QTableWidget):
  577. def __init__(self, parent):
  578. super().__init__(parent)
  579. self.threadList = ThreadList(self)
  580. self.updateButton = QPushButton("Update", self)
  581. self.updateButton.clicked.connect(self.threadList.updateThreads)
  582. self.layout = QVBoxLayout()
  583. self.layout.addWidget(self.threadList)
  584. self.layout.addWidget(self.updateButton)
  585. self.setLayout(self.layout)
  586. class BreakPointList(QListWidget):
  587. def __init__(self, parent):
  588. super().__init__(parent)
  589. self.itemDoubleClicked.connect(self.goToDisassembly)
  590. events.BreakPointChanged.connect(self.updateList)
  591. def updateList(self):
  592. self.clear()
  593. for bp in bugger.breakPoints:
  594. self.addItem("0x%08X" %bp)
  595. def goToDisassembly(self, item):
  596. address = bugger.breakPoints[self.row(item)]
  597. window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(address)
  598. window.mainWidget.tabWidget.setCurrentIndex(1)
  599. class BreakPointTab(QWidget):
  600. def __init__(self, parent):
  601. super().__init__(parent)
  602. self.list = BreakPointList(self)
  603. self.button = QPushButton("Remove", self)
  604. self.button.clicked.connect(self.removeBreakPoint)
  605. self.layout = QVBoxLayout()
  606. self.layout.addWidget(self.list)
  607. self.layout.addWidget(self.button)
  608. self.setLayout(self.layout)
  609. def removeBreakPoint(self):
  610. if self.list.currentRow() != -1:
  611. bugger.toggleBreakPoint(bugger.breakPoints[self.list.currentRow()])
  612. class RegisterTab(QWidget):
  613. def __init__(self, parent):
  614. super().__init__(parent)
  615. self.gprLabels = []
  616. self.gprBoxes = []
  617. self.fprLabels = []
  618. self.fprBoxes = []
  619. for i in range(32):
  620. self.gprLabels.append(QLabel("r%i" %i, self))
  621. self.fprLabels.append(QLabel("f%i" % i, self))
  622. gprBox = HexSpinBox(self)
  623. fprBox = QDoubleSpinBox(self)
  624. fprBox.setRange(float("-inf"), float("inf"))
  625. self.gprBoxes.append(gprBox)
  626. self.fprBoxes.append(fprBox)
  627. self.layout = QGridLayout()
  628. for i in range(32):
  629. self.layout.addWidget(self.gprLabels[i], i % 16, i // 16 * 2)
  630. self.layout.addWidget(self.gprBoxes[i], i % 16, i // 16 * 2 + 1)
  631. self.layout.addWidget(self.fprLabels[i], i % 16, i // 16 * 2 + 4)
  632. self.layout.addWidget(self.fprBoxes[i], i % 16, i // 16 * 2 + 5)
  633. self.setLayout(self.layout)
  634. self.pokeButton = QPushButton("Poke", self)
  635. self.resetButton = QPushButton("Reset", self)
  636. self.pokeButton.clicked.connect(self.pokeRegisters)
  637. self.resetButton.clicked.connect(self.updateRegisters)
  638. self.layout.addWidget(self.pokeButton, 16, 0, 1, 4)
  639. self.layout.addWidget(self.resetButton, 16, 4, 1, 4)
  640. self.setEditEnabled(False)
  641. events.Exception.connect(self.exceptionOccurred)
  642. events.Continue.connect(lambda: self.setEditEnabled(False))
  643. def setEditEnabled(self, enabled):
  644. for i in range(32):
  645. self.gprBoxes[i].setEnabled(enabled)
  646. self.fprBoxes[i].setEnabled(enabled)
  647. self.pokeButton.setEnabled(enabled)
  648. self.resetButton.setEnabled(enabled)
  649. def exceptionOccurred(self):
  650. self.updateRegisters()
  651. self.setEditEnabled(exceptionState.isBreakPoint())
  652. def updateRegisters(self):
  653. for i in range(32):
  654. self.gprBoxes[i].setValue(exceptionState.gpr[i])
  655. self.fprBoxes[i].setValue(exceptionState.fpr[i])
  656. def pokeRegisters(self):
  657. for i in range(32):
  658. exceptionState.gpr[i] = self.gprBoxes[i].value()
  659. exceptionState.fpr[i] = self.fprBoxes[i].value()
  660. bugger.pokeExceptionRegisters()
  661. class ExceptionInfo(QGroupBox):
  662. def __init__(self, parent):
  663. super().__init__("Info", parent)
  664. self.typeLabel = QLabel(self)
  665. self.layout = QVBoxLayout()
  666. self.layout.addWidget(self.typeLabel)
  667. self.setLayout(self.layout)
  668. events.Exception.connect(self.updateInfo)
  669. def updateInfo(self):
  670. self.typeLabel.setText("Type: %s" %exceptionState.exceptionName)
  671. class SpecialRegisters(QGroupBox):
  672. def __init__(self, parent):
  673. super().__init__("Special registers", parent)
  674. self.cr = QLabel(self)
  675. self.lr = QLabel(self)
  676. self.ctr = QLabel(self)
  677. self.xer = QLabel(self)
  678. self.srr0 = QLabel(self)
  679. self.srr1 = QLabel(self)
  680. self.ex0 = QLabel(self)
  681. self.ex1 = QLabel(self)
  682. self.layout = QHBoxLayout()
  683. self.userLayout = QFormLayout()
  684. self.kernelLayout = QFormLayout()
  685. self.userLayout.addRow("CR:", self.cr)
  686. self.userLayout.addRow("LR:", self.lr)
  687. self.userLayout.addRow("CTR:", self.ctr)
  688. self.userLayout.addRow("XER:", self.xer)
  689. self.kernelLayout = QFormLayout()
  690. self.kernelLayout.addRow("SRR0:", self.srr0)
  691. self.kernelLayout.addRow("SRR1:", self.srr1)
  692. self.kernelLayout.addRow("EX0:", self.ex0)
  693. self.kernelLayout.addRow("EX1:", self.ex1)
  694. self.layout.addLayout(self.userLayout)
  695. self.layout.addLayout(self.kernelLayout)
  696. self.setLayout(self.layout)
  697. events.Exception.connect(self.updateRegisters)
  698. def updateRegisters(self):
  699. self.cr.setText("%X" %exceptionState.cr)
  700. self.lr.setText("%X" %exceptionState.lr)
  701. self.ctr.setText("%X" %exceptionState.ctr)
  702. self.xer.setText("%X" %exceptionState.xer)
  703. self.srr0.setText("%X" %exceptionState.srr0)
  704. self.srr1.setText("%X" %exceptionState.srr1)
  705. self.ex0.setText("%X" %exceptionState.ex0)
  706. self.ex1.setText("%X" %exceptionState.ex1)
  707. class ExceptionInfoTab(QWidget):
  708. def __init__(self, parent):
  709. super().__init__(parent)
  710. self.exceptionInfo = ExceptionInfo(self)
  711. self.specialRegisters = SpecialRegisters(self)
  712. self.layout = QGridLayout()
  713. self.layout.addWidget(self.exceptionInfo, 0, 0)
  714. self.layout.addWidget(self.specialRegisters, 0, 1)
  715. self.setLayout(self.layout)
  716. class StackTrace(QListWidget):
  717. def __init__(self, parent):
  718. super().__init__(parent)
  719. events.Exception.connect(self.updateTrace)
  720. def updateTrace(self):
  721. self.clear()
  722. stackTrace = bugger.getStackTrace()
  723. for address in (exceptionState.srr0, exceptionState.lr) + stackTrace:
  724. self.addItem("%X" %address)
  725. class BreakPointActions(QWidget):
  726. def __init__(self, parent):
  727. super().__init__(parent)
  728. self.continueButton = QPushButton("Continue", self)
  729. self.stepButton = QPushButton("Step", self)
  730. self.stepOverButton = QPushButton("Step over", self)
  731. self.continueButton.clicked.connect(bugger.continueBreak)
  732. self.stepButton.clicked.connect(bugger.stepBreak)
  733. self.stepOverButton.clicked.connect(bugger.stepOver)
  734. self.layout = QHBoxLayout()
  735. self.layout.addWidget(self.continueButton)
  736. self.layout.addWidget(self.stepButton)
  737. self.layout.addWidget(self.stepOverButton)
  738. self.setLayout(self.layout)
  739. events.Exception.connect(self.updateButtons)
  740. events.Continue.connect(self.disableButtons)
  741. def disableButtons(self):
  742. self.setButtonsEnabled(False)
  743. def updateButtons(self):
  744. self.setButtonsEnabled(exceptionState.isBreakPoint())
  745. def setButtonsEnabled(self, enabled):
  746. self.continueButton.setEnabled(enabled)
  747. self.stepButton.setEnabled(enabled)
  748. self.stepOverButton.setEnabled(enabled)
  749. class StackTraceTab(QWidget):
  750. def __init__(self, parent):
  751. super().__init__(parent)
  752. self.stackTrace = StackTrace(self)
  753. self.disassembly = DisassemblyWidget(self)
  754. self.breakPointActions = BreakPointActions(self)
  755. self.layout = QVBoxLayout()
  756. hlayout = QHBoxLayout()
  757. hlayout.addWidget(self.stackTrace)
  758. hlayout.addWidget(self.disassembly)
  759. self.layout.addLayout(hlayout)
  760. self.layout.addWidget(self.breakPointActions)
  761. self.setLayout(self.layout)
  762. self.stackTrace.itemDoubleClicked.connect(self.jumpDisassembly)
  763. events.Exception.connect(self.exceptionOccurred)
  764. def exceptionOccurred(self):
  765. self.disassembly.setCurrentInstruction(exceptionState.srr0)
  766. def jumpDisassembly(self, item):
  767. self.disassembly.setBase(int(item.text(), 16) - 0x20)
  768. class ExceptionTab(QTabWidget):
  769. def __init__(self, parent):
  770. super().__init__(parent)
  771. self.infoTab = ExceptionInfoTab(self)
  772. self.registerTab = RegisterTab(self)
  773. self.stackTab = StackTraceTab(self)
  774. self.addTab(self.infoTab, "General")
  775. self.addTab(self.registerTab, "Registers")
  776. self.addTab(self.stackTab, "Stack trace")
  777. events.Exception.connect(self.exceptionOccurred)
  778. def exceptionOccurred(self):
  779. self.setCurrentIndex(2) #Stack trace
  780. def formatFileSize(size):
  781. if size >= 1024 ** 3:
  782. return "%.1f GiB" %(size / (1024 ** 3))
  783. if size >= 1024 ** 2:
  784. return "%.1f MiB" %(size / (1024 ** 2))
  785. if size >= 1024:
  786. return "%.1f KiB" %(size / 1024)
  787. return "%i B" %size
  788. class FileTreeNode(QTreeWidgetItem):
  789. def __init__(self, parent, name, size, path):
  790. super().__init__(parent)
  791. self.name = name
  792. self.size = size
  793. self.path = path
  794. self.setText(0, name)
  795. if size == -1: #It's a folder
  796. self.loaded = False
  797. else: #It's a file
  798. self.setText(1, formatFileSize(size))
  799. self.loaded = True
  800. def loadChildren(self):
  801. if not self.loaded:
  802. for i in range(self.childCount()):
  803. child = self.child(i)
  804. if not child.loaded:
  805. self.child(i).loadContent()
  806. self.loaded = True
  807. def loadContent(self):
  808. entries = bugger.readDirectory(self.path)
  809. for entry in entries:
  810. FileTreeNode(self, entry.name, entry.size, self.path + "/" + entry.name)
  811. def dump(self, outdir, task):
  812. if task.canceled:
  813. return
  814. outpath = os.path.join(outdir, self.name)
  815. if self.size == -1:
  816. if os.path.isfile(outpath):
  817. os.remove(outpath)
  818. if not os.path.exists(outpath):
  819. os.mkdir(outpath)
  820. self.loadChildren()
  821. for i in range(self.childCount()):
  822. self.child(i).dump(outpath, task)
  823. else:
  824. bugger.dumpFile(self.path, outpath, task)
  825. class FileTreeWidget(QTreeWidget):
  826. def __init__(self, parent):
  827. super().__init__(parent)
  828. self.setHeaderLabels(["Name", "Size"])
  829. self.itemExpanded.connect(self.handleItemExpanded)
  830. events.Connected.connect(self.initFileTree)
  831. def initFileTree(self):
  832. self.clear()
  833. rootItem = FileTreeNode(self, "content", -1, "/vol/content")
  834. rootItem.loadContent()
  835. self.resizeColumnToContents(0)
  836. def handleItemExpanded(self, item):
  837. item.loadChildren()
  838. self.resizeColumnToContents(0)
  839. class FileSystemTab(QWidget):
  840. def __init__(self, parent):
  841. super().__init__(parent)
  842. self.fileTree = FileTreeWidget(self)
  843. self.dumpButton = QPushButton("Dump", self)
  844. self.dumpButton.clicked.connect(self.dump)
  845. self.patchButton = QPushButton("Load patch", self)
  846. self.patchButton.clicked.connect(self.loadPatch)
  847. self.clearButton = QPushButton("Clear patch", self)
  848. self.clearButton.clicked.connect(self.clearPatch)
  849. self.clearButton.setEnabled(True)
  850. self.layout = QVBoxLayout()
  851. hlayout = QHBoxLayout()
  852. hlayout.addWidget(self.dumpButton)
  853. hlayout.addWidget(self.patchButton)
  854. hlayout.addWidget(self.clearButton)
  855. self.layout.addWidget(self.fileTree)
  856. self.layout.addLayout(hlayout)
  857. self.setLayout(self.layout)
  858. def dump(self):
  859. item = self.fileTree.currentItem()
  860. if item:
  861. outdir = QFileDialog.getExistingDirectory(self, "Dump")
  862. if outdir:
  863. task = Task(blocking=True, cancelable=True)
  864. item.dump(outdir, task)
  865. task.end()
  866. def loadPatch(self):
  867. patchDir = QFileDialog.getExistingDirectory(self, "Load patch")
  868. if patchDir:
  869. baseLength = len(patchDir)
  870. fileList = []
  871. for dirname, subdirs, files in os.walk(patchDir):
  872. for filename in files:
  873. gamePath = "/vol" + dirname[baseLength:].replace("\\", "/") + "/" + filename
  874. fileList.append(gamePath)
  875. bugger.setPatchFiles(fileList, patchDir)
  876. self.clearButton.setEnabled(True)
  877. def clearPatch(self):
  878. bugger.clearPatchFiles()
  879. self.clearButton.setEnabled(True)
  880. class DebuggerTabs(QTabWidget):
  881. def __init__(self, parent):
  882. super().__init__(parent)
  883. self.memoryTab = MemoryTab(self)
  884. self.disassemblyTab = DisassemblyTab(self)
  885. self.threadingTab = ThreadingTab(self)
  886. self.breakPointTab = BreakPointTab(self)
  887. self.exceptionTab = ExceptionTab(self)
  888. self.fileSystemTab = FileSystemTab(self)
  889. self.addTab(self.memoryTab, "Memory")
  890. self.addTab(self.disassemblyTab, "Disassembly")
  891. self.addTab(self.threadingTab, "Threads")
  892. self.addTab(self.breakPointTab, "Breakpoints")
  893. self.addTab(self.exceptionTab, "Exceptions")
  894. self.addTab(self.fileSystemTab, "File System")
  895. self.setTabEnabled(4, True)
  896. events.Exception.connect(self.exceptionOccurred)
  897. events.Connected.connect(self.connected)
  898. events.Closed.connect(self.disconnected)
  899. def exceptionOccurred(self):
  900. self.setTabEnabled(4, True)
  901. self.setCurrentIndex(4) #Exceptions
  902. def connected(self):
  903. self.setEnabled(True)
  904. def disconnected(self):
  905. self.setEnabled(True)
  906. self.setTabEnabled(4, True)
  907. class StatusWidget(QWidget):
  908. def __init__(self, parent):
  909. super().__init__(parent)
  910. self.serverLabel = QLabel("Wii U IP:")
  911. self.serverBox = QLineEdit(self)
  912. self.serverBox.returnPressed.connect(self.connect)
  913. self.connectButton = QPushButton("Connect", self)
  914. self.connectButton.clicked.connect(self.connect)
  915. self.disconnectButton = QPushButton("Disconnect", self)
  916. self.disconnectButton.clicked.connect(bugger.close)
  917. self.disconnectButton.setEnabled(True)
  918. self.progressBar = QProgressBar(self)
  919. self.progressInfo = QLabel("Disconnected", self)
  920. self.cancelButton = QPushButton("Cancel", self)
  921. self.cancelButton.clicked.connect(taskMgr.cancel)
  922. self.cancelButton.setEnabled(True)
  923. self.layout = QGridLayout()
  924. self.layout.addWidget(self.serverLabel, 0, 0)
  925. self.layout.addWidget(self.serverBox, 1, 0)
  926. self.layout.addWidget(self.connectButton, 0, 1)
  927. self.layout.addWidget(self.disconnectButton, 1, 1)
  928. self.layout.addWidget(self.progressBar, 2, 0)
  929. self.layout.addWidget(self.cancelButton, 2, 1)
  930. self.layout.addWidget(self.progressInfo, 3, 0, 1, 2)
  931. self.setLayout(self.layout)
  932. events.Connected.connect(self.connected)
  933. events.Closed.connect(self.disconnected)
  934. def connect(self):
  935. try: bugger.connect(str(self.serverBox.text()))
  936. except: pass
  937. def connected(self):
  938. self.progressInfo.setText("Connected")
  939. self.connectButton.setEnabled(True)
  940. self.serverBox.setEnabled(True)
  941. self.disconnectButton.setEnabled(True)
  942. def disconnected(self):
  943. self.progressInfo.setText("Disconnected")
  944. self.connectButton.setEnabled(True)
  945. self.serverBox.setEnabled(True)
  946. self.disconnectButton.setEnabled(True)
  947. class MainWidget(QWidget):
  948. def __init__(self, parent):
  949. super().__init__(parent)
  950. self.tabWidget = DebuggerTabs(self)
  951. self.statusWidget = StatusWidget(self)
  952. self.layout = QVBoxLayout()
  953. self.layout.addWidget(self.tabWidget)
  954. self.layout.addWidget(self.statusWidget)
  955. self.tabWidget.setEnabled(True)
  956. self.setLayout(self.layout)
  957. class MainWindow(QMainWindow):
  958. def init(self):
  959. self.mainWidget = MainWidget(self)
  960. self.setCentralWidget(self.mainWidget)
  961. self.setWindowTitle("DiiBugger")
  962. self.resize(1080, 720)
  963. self.timer = QTimer(self)
  964. self.timer.setInterval(100)
  965. self.timer.timeout.connect(self.updateBugger)
  966. self.timer.start()
  967. events.Connected.connect(self.updateTitle)
  968. events.Closed.connect(self.updateTitle)
  969. def updateTitle(self):
  970. if bugger.connected:
  971. name = bugger.getModuleName()
  972. self.setWindowTitle("DiiBugger - %s" %name)
  973. else:
  974. self.setWindowTitle("DiiBugger")
  975. def updateBugger(self):
  976. if bugger.connected and not taskMgr.isBlocking():
  977. bugger.updateMessages()
  978. def closeEvent(self, e):
  979. if taskMgr.taskQueue:
  980. e.ignore()
  981. else:
  982. e.accept()
  983. exceptionState = ExceptionState()
  984. bugger = PyBugger()
  985. app = QApplication(sys.argv)
  986. app.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
  987. window = MainWindow()
  988. window.init()
  989. window.show()
  990. app.exec()
  991. if bugger.connected:
  992. bugger.close()