FlashGBX 4.2__py3-none-any.whl → 4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
FlashGBX/FlashGBX_GUI.py CHANGED
@@ -2,9 +2,10 @@
2
2
  # FlashGBX
3
3
  # Author: Lesserkuma (github.com/lesserkuma)
4
4
 
5
- import sys, os, time, datetime, json, platform, subprocess, requests, webbrowser, pkg_resources, threading, calendar
5
+ import sys, os, time, datetime, json, platform, subprocess, requests, webbrowser, pkg_resources, threading, calendar, queue
6
6
  from .pyside import QtCore, QtWidgets, QtGui, QApplication
7
7
  from PIL.ImageQt import ImageQt
8
+ from PIL import Image
8
9
  from serial import SerialException
9
10
  from .RomFileDMG import RomFileDMG
10
11
  from .RomFileAGB import RomFileAGB
@@ -28,8 +29,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
28
29
  FWUPWIN = None
29
30
  STATUS = {}
30
31
  TEXT_COLOR = (0, 0, 0, 255)
32
+ MSGBOX_QUEUE = queue.Queue()
33
+ MSGBOX_DISPLAYING = False
31
34
 
32
35
  def __init__(self, args):
36
+ sys.excepthook = Util.exception_hook
37
+
33
38
  QtWidgets.QWidget.__init__(self)
34
39
  Util.CONFIG_PATH = args['config_path']
35
40
  Util.APP_PATH = args['app_path']
@@ -199,6 +204,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
199
204
  self.mnuConfig.addAction("Use &No-Intro file names", lambda: self.SETTINGS.setValue("UseNoIntroFilenames", str(self.mnuConfig.actions()[7].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
200
205
  self.mnuConfig.addAction("Automatic cartridge &power off", lambda: [ self.SETTINGS.setValue("AutoPowerOff", str(self.mnuConfig.actions()[8].isChecked()).lower().replace("true", "350").replace("false", "0")), self.SetAutoPowerOff() ])
201
206
  self.mnuConfig.addAction("Skip writing matching ROM chunk&s", lambda: self.SETTINGS.setValue("CompareSectors", str(self.mnuConfig.actions()[9].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
207
+ self.mnuConfig.addAction("Alternative address set mode (can fix or cause write errors)", lambda: self.SETTINGS.setValue("ForceWrPullup", str(self.mnuConfig.actions()[10].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
202
208
  self.mnuConfig.addSeparator()
203
209
  self.mnuConfigReadModeAGB = QtWidgets.QMenu("&Read Method (Game Boy Advance)")
204
210
  self.mnuConfigReadModeAGB.addAction("S&tream", lambda: [ self.SETTINGS.setValue("AGBReadMethod", str(self.mnuConfigReadModeAGB.actions()[1].isChecked()).lower().replace("true", "2")), self.SetAGBReadMethod() ])
@@ -228,6 +234,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
228
234
  self.mnuConfig.actions()[7].setCheckable(True)
229
235
  self.mnuConfig.actions()[8].setCheckable(True)
230
236
  self.mnuConfig.actions()[9].setCheckable(True)
237
+ self.mnuConfig.actions()[10].setCheckable(True)
231
238
  self.mnuConfig.actions()[0].setChecked(self.SETTINGS.value("UpdateCheck") == "enabled")
232
239
  self.mnuConfig.actions()[1].setChecked(self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled") == "enabled")
233
240
  self.mnuConfig.actions()[2].setChecked(self.SETTINGS.value("PreferChipErase", default="disabled") == "enabled")
@@ -238,6 +245,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
238
245
  self.mnuConfig.actions()[7].setChecked(self.SETTINGS.value("UseNoIntroFilenames", default="enabled") == "enabled")
239
246
  self.mnuConfig.actions()[8].setChecked(self.SETTINGS.value("AutoPowerOff", default="350") != "0")
240
247
  self.mnuConfig.actions()[9].setChecked(self.SETTINGS.value("CompareSectors", default="enabled") == "enabled")
248
+ self.mnuConfig.actions()[10].setChecked(self.SETTINGS.value("ForceWrPullup", default="disabled") == "enabled")
241
249
 
242
250
  self.mnuThirdParty = QtWidgets.QMenu("Third Party &Notices")
243
251
  self.mnuThirdParty.addAction("About &Qt", lambda: [ QtWidgets.QMessageBox.aboutQt(None) ])
@@ -299,8 +307,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
299
307
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), config_ret[i][1], QtWidgets.QMessageBox.Ok)
300
308
 
301
309
  QtCore.QTimer.singleShot(1, lambda: [ self.UpdateCheck(), self.FindDevices(port=args["argparsed"].device_port, firstRun=True) ])
310
+ self.MSGBOX_TIMER = QtCore.QTimer()
311
+ self.MSGBOX_TIMER.timeout.connect(self.MsgBoxCheck)
312
+ self.MSGBOX_TIMER.start(200)
313
+
302
314
 
303
-
315
+ def MsgBoxCheck(self):
316
+ if not self.MSGBOX_DISPLAYING and not self.MSGBOX_QUEUE.empty():
317
+ self.MSGBOX_DISPLAYING = True
318
+ msgbox = self.MSGBOX_QUEUE.get()
319
+ Util.dprint(f"Processing Message Box: {msgbox}")
320
+ msgbox.exec()
321
+ self.MSGBOX_DISPLAYING = False
322
+
304
323
  def GuiCreateGroupBoxDMGCartInfo(self):
305
324
  self.grpDMGCartridgeInfo = QtWidgets.QGroupBox("Game Boy Cartridge Information")
306
325
  self.grpDMGCartridgeInfo.setMinimumWidth(400)
@@ -314,7 +333,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
314
333
  self.lblDMGGameNameResult = QtWidgets.QLabel("")
315
334
  rowDMGGameName.addWidget(self.lblDMGGameNameResult)
316
335
  rowDMGGameName.setStretch(0, 9)
317
- rowDMGGameName.setStretch(1, 11)
336
+ rowDMGGameName.setStretch(1, 12)
318
337
  group_layout.addLayout(rowDMGGameName)
319
338
 
320
339
  rowDMGRomTitle = QtWidgets.QHBoxLayout()
@@ -324,7 +343,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
324
343
  self.lblDMGRomTitleResult = QtWidgets.QLabel("")
325
344
  rowDMGRomTitle.addWidget(self.lblDMGRomTitleResult)
326
345
  rowDMGRomTitle.setStretch(0, 9)
327
- rowDMGRomTitle.setStretch(1, 11)
346
+ rowDMGRomTitle.setStretch(1, 12)
328
347
  group_layout.addLayout(rowDMGRomTitle)
329
348
 
330
349
  rowDMGGameCodeRevision = QtWidgets.QHBoxLayout()
@@ -334,7 +353,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
334
353
  self.lblDMGGameCodeRevisionResult = QtWidgets.QLabel("")
335
354
  rowDMGGameCodeRevision.addWidget(self.lblDMGGameCodeRevisionResult)
336
355
  rowDMGGameCodeRevision.setStretch(0, 9)
337
- rowDMGGameCodeRevision.setStretch(1, 11)
356
+ rowDMGGameCodeRevision.setStretch(1, 12)
338
357
  group_layout.addLayout(rowDMGGameCodeRevision)
339
358
 
340
359
  rowDMGHeaderRtc = QtWidgets.QHBoxLayout()
@@ -345,7 +364,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
345
364
  self.lblDMGHeaderRtcResult.mousePressEvent = lambda event: [ self.EditRTC(event) ]
346
365
  rowDMGHeaderRtc.addWidget(self.lblDMGHeaderRtcResult)
347
366
  rowDMGHeaderRtc.setStretch(0, 9)
348
- rowDMGHeaderRtc.setStretch(1, 11)
367
+ rowDMGHeaderRtc.setStretch(1, 12)
349
368
  group_layout.addLayout(rowDMGHeaderRtc)
350
369
 
351
370
  rowDMGHeaderBootlogo = QtWidgets.QHBoxLayout()
@@ -355,7 +374,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
355
374
  self.lblDMGHeaderBootlogoResult = QtWidgets.QLabel("")
356
375
  rowDMGHeaderBootlogo.addWidget(self.lblDMGHeaderBootlogoResult)
357
376
  rowDMGHeaderBootlogo.setStretch(0, 9)
358
- rowDMGHeaderBootlogo.setStretch(1, 11)
377
+ rowDMGHeaderBootlogo.setStretch(1, 12)
359
378
  group_layout.addLayout(rowDMGHeaderBootlogo)
360
379
 
361
380
  rowDMGHeaderROMChecksum = QtWidgets.QHBoxLayout()
@@ -365,7 +384,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
365
384
  self.lblDMGHeaderROMChecksumResult = QtWidgets.QLabel("")
366
385
  rowDMGHeaderROMChecksum.addWidget(self.lblDMGHeaderROMChecksumResult)
367
386
  rowDMGHeaderROMChecksum.setStretch(0, 9)
368
- rowDMGHeaderROMChecksum.setStretch(1, 11)
387
+ rowDMGHeaderROMChecksum.setStretch(1, 12)
369
388
  group_layout.addLayout(rowDMGHeaderROMChecksum)
370
389
 
371
390
  rowDMGHeaderROMSize = QtWidgets.QHBoxLayout()
@@ -376,7 +395,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
376
395
  self.cmbDMGHeaderROMSizeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
377
396
  rowDMGHeaderROMSize.addWidget(self.cmbDMGHeaderROMSizeResult)
378
397
  rowDMGHeaderROMSize.setStretch(0, 9)
379
- rowDMGHeaderROMSize.setStretch(1, 11)
398
+ rowDMGHeaderROMSize.setStretch(1, 12)
380
399
  group_layout.addLayout(rowDMGHeaderROMSize)
381
400
 
382
401
  rowDMGHeaderSaveType = QtWidgets.QHBoxLayout()
@@ -387,7 +406,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
387
406
  self.cmbDMGHeaderSaveTypeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
388
407
  rowDMGHeaderSaveType.addWidget(self.cmbDMGHeaderSaveTypeResult)
389
408
  rowDMGHeaderSaveType.setStretch(0, 9)
390
- rowDMGHeaderSaveType.setStretch(1, 11)
409
+ rowDMGHeaderSaveType.setStretch(1, 12)
391
410
  group_layout.addLayout(rowDMGHeaderSaveType)
392
411
 
393
412
  rowDMGHeaderMapper = QtWidgets.QHBoxLayout()
@@ -398,7 +417,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
398
417
  self.cmbDMGHeaderMapperResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
399
418
  rowDMGHeaderMapper.addWidget(self.cmbDMGHeaderMapperResult)
400
419
  rowDMGHeaderMapper.setStretch(0, 9)
401
- rowDMGHeaderMapper.setStretch(1, 11)
420
+ rowDMGHeaderMapper.setStretch(1, 12)
402
421
  group_layout.addLayout(rowDMGHeaderMapper)
403
422
 
404
423
  rowDMGCartridgeType = QtWidgets.QHBoxLayout()
@@ -428,7 +447,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
428
447
  self.lblAGBGameNameResult = QtWidgets.QLabel("")
429
448
  rowAGBGameName.addWidget(self.lblAGBGameNameResult)
430
449
  rowAGBGameName.setStretch(0, 9)
431
- rowAGBGameName.setStretch(1, 11)
450
+ rowAGBGameName.setStretch(1, 12)
432
451
  group_layout.addLayout(rowAGBGameName)
433
452
 
434
453
  rowAGBRomTitle = QtWidgets.QHBoxLayout()
@@ -438,7 +457,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
438
457
  self.lblAGBRomTitleResult = QtWidgets.QLabel("")
439
458
  rowAGBRomTitle.addWidget(self.lblAGBRomTitleResult)
440
459
  rowAGBRomTitle.setStretch(0, 9)
441
- rowAGBRomTitle.setStretch(1, 11)
460
+ rowAGBRomTitle.setStretch(1, 12)
442
461
  group_layout.addLayout(rowAGBRomTitle)
443
462
 
444
463
  rowAGBHeaderGameCodeRevision = QtWidgets.QHBoxLayout()
@@ -448,7 +467,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
448
467
  self.lblAGBHeaderGameCodeRevisionResult = QtWidgets.QLabel("")
449
468
  rowAGBHeaderGameCodeRevision.addWidget(self.lblAGBHeaderGameCodeRevisionResult)
450
469
  rowAGBHeaderGameCodeRevision.setStretch(0, 9)
451
- rowAGBHeaderGameCodeRevision.setStretch(1, 11)
470
+ rowAGBHeaderGameCodeRevision.setStretch(1, 12)
452
471
  group_layout.addLayout(rowAGBHeaderGameCodeRevision)
453
472
 
454
473
  rowAGBGpioRtc = QtWidgets.QHBoxLayout()
@@ -459,7 +478,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
459
478
  self.lblAGBGpioRtcResult.mousePressEvent = lambda event: [ self.EditRTC(event) ]
460
479
  rowAGBGpioRtc.addWidget(self.lblAGBGpioRtcResult)
461
480
  rowAGBGpioRtc.setStretch(0, 9)
462
- rowAGBGpioRtc.setStretch(1, 11)
481
+ rowAGBGpioRtc.setStretch(1, 12)
463
482
  group_layout.addLayout(rowAGBGpioRtc)
464
483
 
465
484
  rowAGBHeaderBootlogo = QtWidgets.QHBoxLayout()
@@ -469,7 +488,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
469
488
  self.lblAGBHeaderBootlogoResult = QtWidgets.QLabel("")
470
489
  rowAGBHeaderBootlogo.addWidget(self.lblAGBHeaderBootlogoResult)
471
490
  rowAGBHeaderBootlogo.setStretch(0, 9)
472
- rowAGBHeaderBootlogo.setStretch(1, 11)
491
+ rowAGBHeaderBootlogo.setStretch(1, 12)
473
492
  group_layout.addLayout(rowAGBHeaderBootlogo)
474
493
 
475
494
  rowAGBHeaderChecksum = QtWidgets.QHBoxLayout()
@@ -479,7 +498,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
479
498
  self.lblAGBHeaderChecksumResult = QtWidgets.QLabel("")
480
499
  rowAGBHeaderChecksum.addWidget(self.lblAGBHeaderChecksumResult)
481
500
  rowAGBHeaderChecksum.setStretch(0, 9)
482
- rowAGBHeaderChecksum.setStretch(1, 11)
501
+ rowAGBHeaderChecksum.setStretch(1, 12)
483
502
  group_layout.addLayout(rowAGBHeaderChecksum)
484
503
 
485
504
  rowAGBHeaderROMChecksum = QtWidgets.QHBoxLayout()
@@ -489,7 +508,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
489
508
  self.lblAGBHeaderROMChecksumResult = QtWidgets.QLabel("")
490
509
  rowAGBHeaderROMChecksum.addWidget(self.lblAGBHeaderROMChecksumResult)
491
510
  rowAGBHeaderROMChecksum.setStretch(0, 9)
492
- rowAGBHeaderROMChecksum.setStretch(1, 11)
511
+ rowAGBHeaderROMChecksum.setStretch(1, 12)
493
512
  group_layout.addLayout(rowAGBHeaderROMChecksum)
494
513
 
495
514
  rowAGBHeaderROMSize = QtWidgets.QHBoxLayout()
@@ -502,7 +521,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
502
521
  self.cmbAGBHeaderROMSizeResult.setCurrentIndex(self.cmbAGBHeaderROMSizeResult.count() - 1)
503
522
  rowAGBHeaderROMSize.addWidget(self.cmbAGBHeaderROMSizeResult)
504
523
  rowAGBHeaderROMSize.setStretch(0, 9)
505
- rowAGBHeaderROMSize.setStretch(1, 11)
524
+ rowAGBHeaderROMSize.setStretch(1, 12)
506
525
  group_layout.addLayout(rowAGBHeaderROMSize)
507
526
 
508
527
  rowAGBHeaderSaveType = QtWidgets.QHBoxLayout()
@@ -515,7 +534,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
515
534
  self.cmbAGBSaveTypeResult.setCurrentIndex(self.cmbAGBSaveTypeResult.count() - 1)
516
535
  rowAGBHeaderSaveType.addWidget(self.cmbAGBSaveTypeResult)
517
536
  rowAGBHeaderSaveType.setStretch(0, 9)
518
- rowAGBHeaderSaveType.setStretch(1, 11)
537
+ rowAGBHeaderSaveType.setStretch(1, 12)
519
538
  group_layout.addLayout(rowAGBHeaderSaveType)
520
539
 
521
540
  rowAGBCartridgeType = QtWidgets.QHBoxLayout()
@@ -655,23 +674,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
655
674
  print("Error: Failed to check for updates (too many API requests). Try again later.")
656
675
  else:
657
676
  print("Error: Failed to check for updates (HTTP status {:d}).".format(ret.status_code))
658
- else:
659
- update_check = self.SETTINGS.value("UpdateCheck")
660
- if update_check is None or (time.time() > (Util.VERSION_TIMESTAMP + (6*30*24*60*60))):
661
- QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), "Welcome to {:s} {:s} by Lesserkuma!<br><br>".format(APPNAME, VERSION) + "The version update check has been disabled in the options menu and this version is now older than {:d} days. Please regularily check the <a href=\"https://github.com/lesserkuma/FlashGBX/releases/latest\">FlashGBX GitHub page</a> for the latest release notes and updates.".format(int((time.time() - Util.VERSION_TIMESTAMP)/60/60/24)), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
662
677
 
663
678
  def DisconnectDevice(self):
664
679
  try:
665
680
  devname = self.CONN.GetFullNameExtended()
666
681
  self.CONN.Close(cartPowerOff=True)
667
- self.CONN = None
668
- self.DEVICES = {}
669
- self.cmbDevice.clear()
670
682
  print("Disconnected from {:s}".format(devname))
671
683
  except:
672
684
  pass
673
685
 
686
+ self.DEVICES = {}
687
+ self.cmbDevice.clear()
674
688
  self.CONN = None
689
+
675
690
  self.optAGB.setEnabled(False)
676
691
  self.optDMG.setEnabled(False)
677
692
  self.grpDMGCartridgeInfo.setEnabled(False)
@@ -696,6 +711,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
696
711
  self.mnuConfig.actions()[5].setVisible(True)
697
712
  self.mnuConfig.actions()[8].setVisible(True)
698
713
  self.mnuConfig.actions()[9].setVisible(True)
714
+ self.mnuConfig.actions()[10].setVisible(False)
699
715
  self.mnuTools.actions()[2].setEnabled(True)
700
716
  self.mnuConfigReadModeAGB.setEnabled(True)
701
717
 
@@ -710,13 +726,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
710
726
  msg = "This software is being developed by Lesserkuma as a hobby project. There is no affiliation with Nintendo or any other company. This software is provided as-is and the developer is not responsible for any damage that is caused by the use of it. Use at your own risk!<br><br>"
711
727
  msg += f"© 2020–{datetime.datetime.now().year} Lesserkuma<br>"
712
728
  msg += "• Website: <a href=\"https://github.com/lesserkuma/FlashGBX\">https://github.com/lesserkuma/FlashGBX</a><br><br>"
713
- msg += "Acknowledgments and Contributors:<br>2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, olDirdey, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr"
729
+ msg += "Acknowledgments and Contributors:<br>2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, delibird_deals, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, Gahr, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, infinest, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, luxkiller65, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, olDirdey, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr"
714
730
  QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
715
731
 
716
732
  def AboutGameDB(self):
717
733
  msg = f"{APPNAME} uses a game database that is based on the ongoing efforts of the No-Intro project. Visit <a href=\"https://no-intro.org/\">https://no-intro.org/</a> for more information.<br><br>"
718
734
  msg += f"No-Intro databases referenced for this version of {APPNAME}:<br>"
719
- msg += "• Nintendo - Game Boy (20240713-090345)<br>• Nintendo - Game Boy Advance (20240803-104002)<br>• Nintendo - Game Boy Advance (Video) (20240727-194101)<br>• Nintendo - Game Boy Color (20240801-100010)" # No-Intro DBs
735
+ msg += "• Nintendo - Game Boy (20250427-010043)<br>• Nintendo - Game Boy Advance (20250516-204815)<br>• Nintendo - Game Boy Advance (Video) (20241213-211743)<br>• Nintendo - Game Boy Color (20250516-032234)" # No-Intro DBs
720
736
  QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
721
737
 
722
738
  def OpenPath(self, path=None):
@@ -740,26 +756,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
740
756
  if isinstance(event, QtGui.QMouseEvent):
741
757
  if event.button() in (QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.RightButton): return
742
758
 
759
+ device = False
743
760
  try:
744
- Util.dprint("{:s} version: {:s} ({:d})".format(Util.APPNAME, Util.VERSION_PEP440, Util.VERSION_TIMESTAMP))
745
- Util.dprint("Platform: {:s}".format(platform.platform()))
746
- if self.CONN is not None:
747
- Util.dprint("Connected device: {:s}".format(self.CONN.GetFullNameExtended(more=True)))
748
- else:
749
- Util.dprint("No device connected")
750
- Util.dprint("Now writing debug log file")
761
+ device = self.CONN.GetFullNameExtended(more=True)
751
762
  except:
752
763
  pass
764
+
765
+ Util.write_debug_log(device)
753
766
  try:
754
- fn = Util.CONFIG_PATH + "/debug.log"
755
- with open(fn, "wb") as f:
756
- f.write("\n".join(Util.DEBUG_LOG).encode("UTF-8-SIG"))
757
- print("debug.log written")
758
767
  if open_log is True:
768
+ fn = Util.CONFIG_PATH + "/debug.log"
759
769
  self.OpenPath(fn)
760
- return True
761
770
  except:
762
- return False
771
+ pass
763
772
 
764
773
  def ConnectDevice(self):
765
774
  if self.CONN is not None:
@@ -820,6 +829,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
820
829
  self.mnuConfig.actions()[5].setVisible(self.CONN.DEVICE_NAME == "GBxCart RW") # Limit Baud Rate
821
830
  self.mnuConfig.actions()[8].setVisible(self.CONN.CanPowerCycleCart() and self.CONN.CanPowerCycleCart() and self.CONN.FW["fw_ver"] >= 12) # Auto Power Off
822
831
  self.mnuConfig.actions()[9].setVisible(self.CONN.FW["fw_ver"] >= 12) # Skip writing matching ROM chunks
832
+ self.mnuConfig.actions()[10].setVisible(self.CONN.DEVICE_NAME == "Joey Jr") # Force WR Pullup
823
833
  self.mnuConfigReadModeAGB.setEnabled(self.CONN.FW["fw_ver"] >= 12)
824
834
  self.mnuConfigReadModeDMG.setEnabled(self.CONN.FW["fw_ver"] >= 12)
825
835
 
@@ -872,11 +882,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
872
882
  if dev.FirmwareUpdateAvailable():
873
883
  dontShowAgain = str(self.SETTINGS.value("SkipFirmwareUpdate", default="disabled")).lower() == "enabled"
874
884
  if not dontShowAgain or dev.FW_UPDATE_REQ:
885
+ cb = None
875
886
  if dev.FW_UPDATE_REQ is True:
876
887
  text = "A firmware update for your {:s} device is required to use this software. Do you want to update now?".format(dev.GetFullName())
877
888
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes)
878
889
  elif dev.FW_UPDATE_REQ == 2:
879
- text = "Your {:s} device is no longer supported as of FlashGBX v4.0+. You can still use the Firmware Updater, however any other functions are no longer available.\n\nDo you want to run the Firmware Updater now?".format(dev.GetFullName())
890
+ text = "Your {:s} device is no longer supported in this version of FlashGBX due to technical limitations. The last supported version is <a href=\"https://github.com/lesserkuma/FlashGBX/releases/tag/3.37\">FlashGBX v3.37</a>.\n\nYou can still use the Firmware Updater, however any other functions are no longer available.\n\nDo you want to run the Firmware Updater now?".format(dev.GetFullName()).replace("\n", "<br>")
880
891
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes)
881
892
  else:
882
893
  text = "A firmware update for your {:s} device is available. Do you want to update now?".format(dev.GetFullName())
@@ -889,8 +900,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
889
900
  if not Util.DEBUG:
890
901
  self.DisconnectDevice()
891
902
  else:
892
- dontShowAgain = cb.isChecked()
893
- if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
903
+ if cb is not None:
904
+ dontShowAgain = cb.isChecked()
905
+ if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
894
906
  if answer == QtWidgets.QMessageBox.Yes:
895
907
  self.ShowFirmwareUpdateWindow()
896
908
 
@@ -899,8 +911,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
899
911
  if not Util.DEBUG:
900
912
  self.DisconnectDevice()
901
913
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
914
+
915
+ if dev.IsUnregistered():
916
+ try:
917
+ text = dev.GetRegisterInformation()
918
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
919
+ except:
920
+ pass
902
921
 
903
922
  return True
923
+
904
924
  return False
905
925
 
906
926
  def FindDevices(self, connectToFirst=False, port=None, mode=None, firstRun=False):
@@ -911,7 +931,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
911
931
  qt_app.processEvents()
912
932
 
913
933
  messages = []
914
- #last_msg = ""
915
934
 
916
935
  # pylint: disable=global-variable-not-assigned
917
936
  global hw_devices
@@ -933,8 +952,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
933
952
  msg = ret[i][1]
934
953
  if msg in messages: # don’t show the same message twice
935
954
  continue
936
- #else:
937
- # last_msg = msg
938
955
  if status == 3:
939
956
  messages.append(msg)
940
957
  self.CONN = None
@@ -959,7 +976,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
959
976
  msg += message + "\n\n"
960
977
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg[:-2], QtWidgets.QMessageBox.Ok)
961
978
  elif not firstRun:
962
- QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="No compatible devices found. Please ensure the device is connected properly.\n\nTroubleshooting advice:\n- Reconnect the device, try different USB ports/cables, avoid passive USB hubs\n- Use a USB data cable (battery charging cables may not work)\n- Check if the operating system detects the device (if not, reboot your machine)\n- Ensure your user account has permissions to use the device\n- Refer to the device compatibility list on the <a href=\"https://github.com/lesserkuma/FlashGBX/#compatible-cartridge-readerwriter-hardware\">GitHub page</a>".replace("\n", "<br>"), standardButtons=QtWidgets.QMessageBox.Ok).exec()
979
+ msg = \
980
+ "No compatible devices found. Please ensure the device is connected properly.\n\n" \
981
+ "Compatible devices:\n" \
982
+ "- insideGadgets GBxCart RW\n" \
983
+ "- Geeksimon GBFlash\n" \
984
+ "- BennVenn Joey Jr (requires firmware update)\n\n" \
985
+ "Troubleshooting advice:\n" \
986
+ "- Reconnect the device, try different USB ports/cables, avoid passive USB hubs\n" \
987
+ "- Use a USB data cable (battery charging cables may not work)\n" \
988
+ "- Check if the operating system detects the device (if not, reboot your machine)\n" \
989
+ "- Update the firmware through Options → Tools → Firmware Updater"
990
+ if platform.system() == "Darwin":
991
+ msg += "\n   - <b>For Joey Jr on macOS:</b> Use the dedicated <a href=\"https://github.com/lesserkuma/JoeyJr_FWUpdater\">Firmware Updater for Joey Jr</a>"
992
+ elif platform.system() == "Linux":
993
+ msg += "\n- <b>For Linux users:</b> Ensure your user account has permissions to use the device (try adding yourself to user groups “dialout” or “uucp”)"
994
+
995
+ QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg.replace("\n", "<br>"), standardButtons=QtWidgets.QMessageBox.Ok).exec()
963
996
 
964
997
  self.lblDevice.setText("No devices found.")
965
998
  self.lblDevice.setStyleSheet("")
@@ -1080,7 +1113,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1080
1113
  else:
1081
1114
  msg += " This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps. You can also try to change the read mode in the options."
1082
1115
  if self.CONN.GetMode() == "DMG" and self.cmbDMGHeaderMapperResult.currentText() == "MBC1":
1083
- msg += "\n\nIf this is a NP GB-Memory Cartridge, please use the “Retry as G-MMC1” button."
1116
+ msg += "\n\nIf this is a NP GB-Memory Cartridge, try the “Retry as G-MMC1” option."
1084
1117
  button_gmmc1 = msgbox.addButton(" Retry as G-MMC1 ", QtWidgets.QMessageBox.ActionRole)
1085
1118
  msgbox.setText(msg + msg_te)
1086
1119
  msgbox.setIcon(QtWidgets.QMessageBox.Warning)
@@ -1151,22 +1184,24 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1151
1184
 
1152
1185
  dontShowAgainCameraSavePopup = str(self.SETTINGS.value("SkipCameraSavePopup", default="disabled")).lower() == "enabled"
1153
1186
  if not dontShowAgainCameraSavePopup:
1154
- if self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KiB
1155
- cbCameraSavePopup = QtWidgets.QCheckBox("Don’t show this message again", checked=dontShowAgain)
1156
- msgboxCameraPopup = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Would you like to load your save data with the GB Camera Viewer now?")
1157
- msgboxCameraPopup.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
1158
- msgboxCameraPopup.setDefaultButton(QtWidgets.QMessageBox.Yes)
1159
- msgboxCameraPopup.setCheckBox(cbCameraSavePopup)
1160
- answer = msgboxCameraPopup.exec()
1161
- dontShowAgainCameraSavePopup = cbCameraSavePopup.isChecked()
1162
- if dontShowAgainCameraSavePopup: self.SETTINGS.setValue("SkipCameraSavePopup", "enabled")
1163
- if answer == QtWidgets.QMessageBox.Yes:
1164
- self.CAMWIN = None
1165
- self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH)
1166
- self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1167
- self.CAMWIN.setModal(True)
1168
- self.CAMWIN.run()
1169
- return
1187
+ if self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252:
1188
+ # Pocket Camera
1189
+ if self.CONN.INFO["transferred"] == 0x20000 or (self.CONN.INFO["transferred"] == 0x100000 and "Unlicensed Photo!" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]):
1190
+ cbCameraSavePopup = QtWidgets.QCheckBox("Don’t show this message again", checked=dontShowAgain)
1191
+ msgboxCameraPopup = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Would you like to load your save data with the GB Camera Viewer now?")
1192
+ msgboxCameraPopup.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
1193
+ msgboxCameraPopup.setDefaultButton(QtWidgets.QMessageBox.Yes)
1194
+ msgboxCameraPopup.setCheckBox(cbCameraSavePopup)
1195
+ answer = msgboxCameraPopup.exec()
1196
+ dontShowAgainCameraSavePopup = cbCameraSavePopup.isChecked()
1197
+ if dontShowAgainCameraSavePopup: self.SETTINGS.setValue("SkipCameraSavePopup", "enabled")
1198
+ if answer == QtWidgets.QMessageBox.Yes:
1199
+ self.CAMWIN = None
1200
+ self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH)
1201
+ self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1202
+ self.CAMWIN.setModal(True)
1203
+ self.CAMWIN.run()
1204
+ return
1170
1205
 
1171
1206
  if "last_path" in self.CONN.INFO:
1172
1207
  button_open_dir = msgbox.addButton(" Open Fol&der ", QtWidgets.QMessageBox.ActionRole)
@@ -1244,6 +1279,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1244
1279
  self.CONN.INFO["dump_info"]["batteryless_sram"] = temp3
1245
1280
  else:
1246
1281
  self.ReadCartridge(resetStatus=False)
1282
+
1283
+ elif self.CONN.INFO["last_action"] == 6: # Detect Cartridge
1284
+ self.lblStatus4a.setText("Ready.")
1285
+ self.CONN.INFO["last_action"] = 0
1286
+ self.FinishDetectCartridge(self.CONN.INFO["detect_cart"])
1247
1287
 
1248
1288
  else:
1249
1289
  self.lblStatus4a.setText("Ready.")
@@ -1268,6 +1308,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1268
1308
  def CartridgeTypeChanged(self, index):
1269
1309
  self.STATUS["cart_type"] = {}
1270
1310
  if index in (-1, 0): return
1311
+ if "detect_cartridge_args" in self.STATUS: return
1271
1312
  if self.CONN.GetMode() == "DMG":
1272
1313
  cart_types = self.CONN.GetSupportedCartridgesDMG()
1273
1314
  if cart_types[1][index] == "RETAIL": # special keyword
@@ -1375,11 +1416,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1375
1416
  return
1376
1417
 
1377
1418
  if cart_type == 0:
1378
- cart_type = self.DetectCartridge(canSkipMessage=True)
1419
+ if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
1420
+ if self.STATUS["detected_cart_type"] == "":
1421
+ self.STATUS["detected_cart_type"] = "WAITING_FLASH"
1422
+ self.STATUS["detect_cartridge_args"] = { "dpath":path }
1423
+ self.STATUS["can_skip_message"] = True
1424
+ self.DetectCartridge(checkSaveType=False)
1425
+ return
1426
+ cart_type = self.STATUS["detected_cart_type"]
1427
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1428
+
1379
1429
  if cart_type is False: # clicked Cancel button
1380
1430
  return
1381
- elif cart_type is None or cart_type == 0:
1382
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1431
+ elif cart_type is None or cart_type == 0 or not isinstance(cart_type, int):
1432
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1383
1433
  return
1384
1434
 
1385
1435
  if self.CONN.GetMode() == "DMG":
@@ -1387,6 +1437,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1387
1437
  elif self.CONN.GetMode() == "AGB":
1388
1438
  self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
1389
1439
 
1440
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1441
+
1390
1442
  if self.CONN.GetMode() == "DMG":
1391
1443
  self.SetDMGMapperResult(carts[cart_type])
1392
1444
  mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
@@ -1430,8 +1482,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1430
1482
  msg += " You can still give it a try, but it’s possible that it’s too large which may cause the ROM writing to fail."
1431
1483
  answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
1432
1484
  if answer == QtWidgets.QMessageBox.Cancel: return
1433
- #if "mbc" in carts[cart_type]:
1434
- # mbc = carts[cart_type]["mbc"]
1435
1485
 
1436
1486
  override_voltage = False
1437
1487
  if 'voltage_variants' in carts[cart_type] and carts[cart_type]['voltage'] == 3.3:
@@ -1467,7 +1517,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1467
1517
  if not Util.compare_mbc(hdr["mapper_raw"], mbc):
1468
1518
  mbc1 = Util.get_mbc_name(mbc)
1469
1519
  mbc2 = Util.get_mbc_name(hdr["mapper_raw"])
1470
- compatible_mbc = [ "None", "MBC2", "MBC3", "MBC30", "MBC5", "MBC7", "MAC-GBD", "G-MMC1", "HuC-1", "HuC-3" ]
1520
+ compatible_mbc = [ "None", "MBC2", "MBC3", "MBC30", "MBC5", "MBC7", "MAC-GBD", "G-MMC1", "HuC-1", "HuC-3", "Unlicensed MBCX Mapper" ]
1471
1521
  if (mbc2 == "None") or (mbc1 == "G-MMC1" and mbc2 == "MBC1") or (mbc2 == "G-MMC1" and mbc1 == "MBC1"):
1472
1522
  pass
1473
1523
  elif mbc2 != "None" and not (mbc1 in compatible_mbc and mbc2 in compatible_mbc):
@@ -1547,7 +1597,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1547
1597
  pass
1548
1598
 
1549
1599
  flash_offset = 0
1550
-
1600
+ force_wr_pullup = self.SETTINGS.value("ForceWrPullup", default="disabled").lower() == "enabled"
1601
+
1551
1602
  self.grpDMGCartridgeInfo.setEnabled(False)
1552
1603
  self.grpAGBCartridgeInfo.setEnabled(False)
1553
1604
  self.grpActions.setEnabled(False)
@@ -1561,7 +1612,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1561
1612
  verify_write = False
1562
1613
  args = { "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc }
1563
1614
  else:
1564
- args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc, "flash_offset":flash_offset }
1615
+ args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc, "flash_offset":flash_offset, "force_wr_pullup":force_wr_pullup }
1565
1616
  args["compare_sectors"] = self.SETTINGS.value("CompareSectors", default="disabled").lower() == "enabled"
1566
1617
  self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
1567
1618
  #self.CONN._FlashROM(args=args)
@@ -1571,18 +1622,48 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1571
1622
  self.STATUS["last_path"] = path
1572
1623
  self.STATUS["args"] = args
1573
1624
 
1574
- def BackupRAM(self):
1625
+ def BackupRAM(self, dpath=""):
1575
1626
  if not self.CheckDeviceAlive(): return
1576
1627
 
1577
1628
  rtc = False
1578
- add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled")
1579
- path_datetime = ""
1580
- if add_date_time and add_date_time.lower() == "enabled":
1581
- path_datetime = "_{:s}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
1629
+ path = ""
1582
1630
 
1583
- path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS)
1584
- path = os.path.splitext(path)[0]
1585
- path += "{:s}.sav".format(path_datetime)
1631
+ # Detect Cartridge needed?
1632
+ if \
1633
+ (self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1634
+ (self.CONN.GetMode() == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Batteryless SRAM" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) or \
1635
+ (self.CONN.GetMode() == "DMG" and "Unlicensed Photo!" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) \
1636
+ :
1637
+ if self.CONN.GetFWBuildDate() == "": # Legacy Mode
1638
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This feature is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
1639
+ msgbox.exec()
1640
+ return
1641
+
1642
+ if self.CONN.GetMode() == "AGB":
1643
+ cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1644
+ elif self.CONN.GetMode() == "DMG":
1645
+ cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1646
+ if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
1647
+ if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
1648
+ if self.STATUS["detected_cart_type"] == "":
1649
+ self.STATUS["detected_cart_type"] = "WAITING_SAVE_READ"
1650
+ self.STATUS["detect_cartridge_args"] = { "dpath":path }
1651
+ self.STATUS["can_skip_message"] = True
1652
+ self.DetectCartridge(checkSaveType=True)
1653
+ return
1654
+ cart_type = self.STATUS["detected_cart_type"]
1655
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1656
+
1657
+ if cart_type is False: # clicked Cancel button
1658
+ return
1659
+ elif cart_type is None or cart_type == 0 or not isinstance(cart_type, int):
1660
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1661
+ return
1662
+ if self.CONN.GetMode() == "AGB":
1663
+ self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
1664
+ elif self.CONN.GetMode() == "DMG":
1665
+ self.cmbDMGCartridgeTypeResult.setCurrentIndex(cart_type)
1666
+
1586
1667
  cart_type = 0
1587
1668
  if self.CONN.GetMode() == "DMG":
1588
1669
  setting_name = "LastDirSaveDataDMG"
@@ -1594,7 +1675,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1594
1675
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1595
1676
  return
1596
1677
  cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1597
- #save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
1598
1678
 
1599
1679
  elif self.CONN.GetMode() == "AGB":
1600
1680
  setting_name = "LastDirSaveDataAGB"
@@ -1606,13 +1686,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1606
1686
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1607
1687
  return
1608
1688
  cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1609
- #save_size = Util.AGB_Header_Save_Sizes[save_type]
1610
1689
  else:
1611
1690
  return
1612
1691
 
1613
1692
  if not self.CheckHeader(): return
1614
- path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup Save Data", last_dir + "/" + path, "Save Data File (*.sav *.srm *.fla *.eep);;All Files (*.*)")[0]
1615
- if (path == ""): return
1693
+ if dpath == "":
1694
+ path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS)
1695
+ path = os.path.splitext(path)[0]
1696
+
1697
+ add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled")
1698
+ if len(path) > 0 and add_date_time and add_date_time.lower() == "enabled":
1699
+ path += "_{:s}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
1700
+
1701
+ path += ".sav"
1702
+ path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup Save Data", last_dir + "/" + path, "Save Data File (*.sav *.srm *.fla *.eep);;All Files (*.*)")[0]
1703
+ if (path == ""): return
1704
+ else:
1705
+ path = dpath
1616
1706
 
1617
1707
  verify_read = self.SETTINGS.value("VerifyData", default="enabled")
1618
1708
  if verify_read and verify_read.lower() == "enabled":
@@ -1633,26 +1723,22 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1633
1723
  rtc = (answer == QtWidgets.QMessageBox.Yes)
1634
1724
 
1635
1725
  bl_args = {}
1636
- if self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]:
1637
- if self.CONN.GetFWBuildDate() == "": # Legacy Mode
1638
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This feature is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
1639
- msgbox.exec()
1640
- return
1641
- cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1642
- if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
1643
- cart_type = self.DetectCartridge()
1644
- if cart_type is False: # clicked Cancel button
1645
- return
1646
- elif cart_type is None or cart_type == 0:
1647
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1648
- return
1649
- self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
1726
+ if \
1727
+ (self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1728
+ (self.CONN.GetMode() == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Batteryless SRAM" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) \
1729
+ :
1730
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1650
1731
 
1651
1732
  if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
1652
1733
  detected = self.CONN.INFO["dump_info"]["batteryless_sram"]
1653
1734
  else:
1654
1735
  detected = False
1655
- bl_args = self.GetBLArgs(rom_size=Util.AGB_Header_ROM_Sizes_Map[self.cmbAGBHeaderROMSizeResult.currentIndex()], detected=detected)
1736
+
1737
+ if self.CONN.GetMode() == "AGB":
1738
+ rom_size = Util.AGB_Header_ROM_Sizes_Map[self.cmbAGBHeaderROMSizeResult.currentIndex()]
1739
+ elif self.CONN.GetMode() == "DMG":
1740
+ rom_size = Util.DMG_Header_ROM_Sizes_Map[self.cmbDMGHeaderROMSizeResult.currentIndex()]
1741
+ bl_args = self.GetBLArgs(rom_size=rom_size, detected=detected)
1656
1742
  if bl_args is False: return
1657
1743
 
1658
1744
  self.SETTINGS.setValue(setting_name, os.path.dirname(path))
@@ -1678,15 +1764,50 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1678
1764
  self.STATUS["last_path"] = path
1679
1765
  self.STATUS["args"] = args
1680
1766
 
1681
- def WriteRAM(self, dpath="", erase=False, test=False):
1767
+ def WriteRAM(self, dpath="", erase=False, test=False, skip_warning=False):
1682
1768
  if not self.CheckDeviceAlive(): return
1683
1769
  mode = self.CONN.GetMode()
1684
1770
 
1685
- if dpath == "":
1686
- path = Util.GenerateFileName(mode=mode, header=self.CONN.INFO, settings=self.SETTINGS)
1687
- path = os.path.splitext(path)[0]
1688
- path += ".sav"
1689
-
1771
+ path = ""
1772
+ if erase is True:
1773
+ dpath = ""
1774
+
1775
+ # Detect Cartridge needed?
1776
+ if not test and ( \
1777
+ (mode == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1778
+ (mode == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Batteryless SRAM" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) or \
1779
+ (mode == "DMG" and "Unlicensed Photo!" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) \
1780
+ ):
1781
+ if self.CONN.GetFWBuildDate() == "": # Legacy Mode
1782
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This feature is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
1783
+ msgbox.exec()
1784
+ return
1785
+
1786
+ if mode == "AGB":
1787
+ cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1788
+ elif mode == "DMG":
1789
+ cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1790
+ if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
1791
+ if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
1792
+ if self.STATUS["detected_cart_type"] == "":
1793
+ self.STATUS["detected_cart_type"] = "WAITING_SAVE_WRITE"
1794
+ self.STATUS["detect_cartridge_args"] = { "dpath":dpath, "erase":erase }
1795
+ self.STATUS["can_skip_message"] = True
1796
+ self.DetectCartridge(checkSaveType=True)
1797
+ return
1798
+ cart_type = self.STATUS["detected_cart_type"]
1799
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1800
+
1801
+ if cart_type is False: # clicked Cancel button
1802
+ return
1803
+ elif cart_type is None or cart_type == 0 or not isinstance(cart_type, int):
1804
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1805
+ return
1806
+ if mode == "AGB":
1807
+ self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
1808
+ elif mode == "DMG":
1809
+ self.cmbDMGCartridgeTypeResult.setCurrentIndex(cart_type)
1810
+
1690
1811
  if mode == "DMG":
1691
1812
  setting_name = "LastDirSaveDataDMG"
1692
1813
  last_dir = self.SETTINGS.value(setting_name)
@@ -1697,7 +1818,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1697
1818
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1698
1819
  return
1699
1820
  cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1700
- #save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
1701
1821
 
1702
1822
  elif mode == "AGB":
1703
1823
  setting_name = "LastDirSaveDataAGB"
@@ -1708,7 +1828,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1708
1828
  if save_type == 0:
1709
1829
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1710
1830
  return
1711
- #save_size = Util.AGB_Header_Save_Sizes[save_type]
1712
1831
  cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1713
1832
  else:
1714
1833
  return
@@ -1716,14 +1835,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1716
1835
 
1717
1836
  filesize = 0
1718
1837
  if dpath != "":
1719
- text = "The following save data file will now be written to the cartridge:\n" + dpath
1720
- answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
1721
- if answer == QtWidgets.QMessageBox.Cancel: return
1838
+ if not skip_warning:
1839
+ text = "The following save data file will now be written to the cartridge:\n" + dpath
1840
+ answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
1841
+ if answer == QtWidgets.QMessageBox.Cancel: return
1722
1842
  path = dpath
1723
1843
  self.SETTINGS.setValue(setting_name, os.path.dirname(path))
1724
1844
  elif erase:
1725
- answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The save data on your cartridge will now be erased.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
1726
- if answer == QtWidgets.QMessageBox.Cancel: return
1845
+ if not skip_warning:
1846
+ answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The save data on your cartridge will now be erased.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
1847
+ if answer == QtWidgets.QMessageBox.Cancel: return
1727
1848
  elif test:
1728
1849
  path = None
1729
1850
  if self.CONN.GetFWBuildDate() == "": # Legacy Mode
@@ -1736,6 +1857,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1736
1857
  msgbox.exec()
1737
1858
 
1738
1859
  if (mode == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1860
+ (mode == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Batteryless SRAM" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) or \
1861
+ (mode == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Unlicensed Photo!" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) or \
1739
1862
  ("8M DACS" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1740
1863
  (mode == "AGB" and "ereader" in self.CONN.INFO and self.CONN.INFO["ereader"] is True) or \
1741
1864
  (mode == "DMG" and "256M Multi Cart" in self.cmbDMGHeaderMapperResult.currentText() and not self.CONN.CanPowerCycleCart()):
@@ -1744,11 +1867,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1744
1867
  answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge’s save chip will be tested for potential problems as follows:\n- Read the same data multiple times\n- Writing and reading different test patterns\n\nPlease ensure the cartridge pins are freshly cleaned and the save data is backed up before proceeding.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
1745
1868
  if answer == QtWidgets.QMessageBox.Cancel: return
1746
1869
  else:
1870
+ if path == "":
1871
+ path = Util.GenerateFileName(mode=mode, header=self.CONN.INFO, settings=self.SETTINGS)
1872
+ path = os.path.splitext(path)[0]
1873
+ path += ".sav"
1747
1874
  path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav *.srm *.fla *.eep);;All Files (*.*)")[0]
1748
1875
  if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path))
1749
1876
  if (path == ""): return
1750
1877
 
1751
- if not erase and not test:
1878
+ if not erase and not test and len(path) > 0:
1752
1879
  filesize = os.path.getsize(path)
1753
1880
  if filesize == 0 or filesize > 0x200000: # reject too large files to avoid exploding RAM
1754
1881
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The size of this file is not supported.", QtWidgets.QMessageBox.Ok)
@@ -1771,7 +1898,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1771
1898
  buffer = bytearray([0xFF] * 0x20000)
1772
1899
  msg_text = "This {:s} cartridge currently has calibration data in place. It is strongly recommended to keep the existing calibration data.\n\nHow do you want to proceed?".format(cart_name)
1773
1900
  button_overwrite = msgbox.addButton(" &Erase everything ", QtWidgets.QMessageBox.ActionRole)
1774
- erase = False # Don’t just erase everything
1775
1901
  else:
1776
1902
  with open(path, "rb") as f: buffer = bytearray(f.read())
1777
1903
  msg_text = "This {:s} cartridge currently has calibration data in place that is different from this save file’s data. It is strongly recommended to keep the existing calibration data unless you actually need to restore it from a previous backup.\n\nWould you like to keep the existing calibration data, or overwrite it with data from the file you selected?".format(cart_name)
@@ -1794,6 +1920,56 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1794
1920
  answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_text, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
1795
1921
  if answer == QtWidgets.QMessageBox.No: return
1796
1922
 
1923
+ elif mode == "DMG" and self.CONN.INFO["dump_info"]["header"]["mapper_raw"] == 0xFC:
1924
+ if self.CONN.GetFWBuildDate() == "": # Legacy Mode
1925
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This cartridge is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
1926
+ msgbox.exec()
1927
+ return
1928
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="")
1929
+ button_keep = msgbox.addButton(" &Keep existing calibration data ", QtWidgets.QMessageBox.ActionRole)
1930
+ if "Unlicensed Photo!" not in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]:
1931
+ button_reset = msgbox.addButton(" &Force recalibration ", QtWidgets.QMessageBox.ActionRole)
1932
+ else:
1933
+ button_reset = None
1934
+ self.CONN.ReadInfo()
1935
+ cart_name = "Game Boy Camera"
1936
+ if self.CONN.INFO["db"] is not None:
1937
+ cart_name = self.CONN.INFO["db"]["gn"]
1938
+ if not test:
1939
+ if "gbcamera_calibration1" in self.CONN.INFO:
1940
+ if erase:
1941
+ buffer = bytearray([0x00] * 0x20000)
1942
+ if "Unlicensed Photo!" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]:
1943
+ buffer += bytearray([0xFF] * 0xE0000)
1944
+ msg_text = "This {:s} cartridge currently has calibration data in place.\n\nHow would you like to proceed?".format(cart_name)
1945
+ button_overwrite = msgbox.addButton(" &Erase everything ", QtWidgets.QMessageBox.ActionRole)
1946
+ else:
1947
+ with open(path, "rb") as f: buffer = bytearray(f.read())
1948
+ msg_text = "This {:s} cartridge currently has calibration data in place that is different from this save file’s data.\n\nHow would you like to proceed?".format(cart_name)
1949
+ button_overwrite = msgbox.addButton(" &Restore from save data ", QtWidgets.QMessageBox.ActionRole)
1950
+ button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole)
1951
+ msgbox.setText(msg_text)
1952
+ msgbox.setDefaultButton(button_keep)
1953
+ msgbox.setEscapeButton(button_cancel)
1954
+
1955
+ if buffer[0x4FF2:0x5000] != self.CONN.INFO["gbcamera_calibration1"] or buffer[0x11FF2:0x12000] != self.CONN.INFO["gbcamera_calibration2"]:
1956
+ answer = msgbox.exec()
1957
+ if msgbox.clickedButton() == button_cancel:
1958
+ return
1959
+ elif msgbox.clickedButton() == button_keep:
1960
+ buffer[0x4FF2:0x5000] = self.CONN.INFO["gbcamera_calibration1"]
1961
+ buffer[0x11FF2:0x12000] = self.CONN.INFO["gbcamera_calibration2"]
1962
+ elif msgbox.clickedButton() == button_reset:
1963
+ buffer[0x4FF2:0x5000] = bytearray([0xAA] * 0xE)
1964
+ buffer[0x11FF2:0x12000] = bytearray([0xAA] * 0xE)
1965
+ elif msgbox.clickedButton() == button_overwrite:
1966
+ pass
1967
+ else:
1968
+ msg_text = "Warning: This {:s} cartridge may currently have calibration data in place. It is recommended to create a backup of the original save data first and store it in a safe place. That way the calibration data can be restored later.\n\nDo you still want to continue?".format(cart_name)
1969
+ answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_text, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
1970
+ if answer == QtWidgets.QMessageBox.No: return
1971
+
1972
+
1797
1973
  verify_write = self.SETTINGS.value("VerifyData", default="enabled")
1798
1974
  if verify_write and verify_write.lower() == "enabled":
1799
1975
  verify_write = True
@@ -2013,20 +2189,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2013
2189
 
2014
2190
  else:
2015
2191
  bl_args = {}
2016
- if self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]:
2017
- if self.CONN.GetFWBuildDate() == "": # Legacy Mode
2018
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This feature is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
2019
- msgbox.exec()
2020
- return
2021
- cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
2022
- if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
2023
- cart_type = self.DetectCartridge()
2024
- if cart_type is False: # clicked Cancel button
2025
- return
2026
- elif cart_type is None or cart_type == 0:
2027
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
2028
- return
2029
- self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
2192
+ if \
2193
+ (mode == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
2194
+ (mode == "DMG" and self.cmbDMGHeaderSaveTypeResult.currentIndex() < len(Util.DMG_Header_RAM_Sizes) and "Batteryless SRAM" in Util.DMG_Header_RAM_Sizes[self.cmbDMGHeaderSaveTypeResult.currentIndex()]) \
2195
+ :
2196
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
2030
2197
 
2031
2198
  if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
2032
2199
  detected = self.CONN.INFO["dump_info"]["batteryless_sram"]
@@ -2036,6 +2203,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2036
2203
  if bl_args is False: return
2037
2204
 
2038
2205
  args = { "path":path, "cart_type":cart_type, "override_voltage":False, "prefer_chip_erase":False, "fast_read_mode":True, "verify_write":verify_write, "fix_header":False, "fix_bootlogo":False, "mbc":mbc }
2206
+ args.update(bl_args)
2039
2207
  args.update({"bl_save":True, "flash_offset":bl_args["bl_offset"], "flash_size":bl_args["bl_size"]})
2040
2208
  if erase:
2041
2209
  args["path"] = ""
@@ -2043,12 +2211,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2043
2211
  self.STATUS["args"] = args
2044
2212
  self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
2045
2213
  #self.CONN._FlashROM(args=args)
2214
+
2046
2215
  else:
2047
- #cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
2048
2216
  args = { "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase, "verify_write":verify_write, "cart_type":cart_type }
2049
2217
  if buffer is not None:
2050
2218
  args["buffer"] = buffer
2051
2219
  args["path"] = None
2220
+ args["erase"] = False
2052
2221
  self.STATUS["args"] = args
2053
2222
  self.CONN.RestoreRAM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
2054
2223
  #args = { "mode":3, "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase, "verify_write":verify_write }
@@ -2071,11 +2240,18 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2071
2240
  qt_app.processEvents()
2072
2241
 
2073
2242
  def GetBLArgs(self, rom_size, detected=False):
2074
- locs = [ 0x3C0000, 0x7C0000, 0xFC0000, 0x1FC0000 ]
2075
- lens = [ 0x2000, 0x8000, 0x10000, 0x20000 ]
2076
- temp = self.SETTINGS.value("BatterylessSramLocations{:s}".format(self.CONN.GetMode()), "[]")
2243
+ mode = self.CONN.GetMode()
2244
+ if mode == "AGB":
2245
+ locs = [ 0x3C0000, 0x7C0000, 0xFC0000, 0x1FC0000 ]
2246
+ lens = [ 0x2000, 0x8000, 0x10000, 0x20000 ]
2247
+ elif mode == "DMG":
2248
+ locs = [ 0xD0000, 0x100000, 0x110000, 0x1D0000, 0x1E0000, 0x210000, 0x3D0000 ]
2249
+ lens = [ 0x2000, 0x8000, 0x10000, 0x20000 ]
2250
+
2251
+ temp = self.SETTINGS.value("BatterylessSramLocations{:s}".format(mode), "[]")
2077
2252
  loc_index = None
2078
2253
  len_index = None
2254
+ lay_index = None
2079
2255
 
2080
2256
  try:
2081
2257
  temp = json.loads(temp)
@@ -2087,19 +2263,46 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2087
2263
  except:
2088
2264
  pass
2089
2265
 
2266
+ intro_msg = ""
2090
2267
  if detected is not False:
2091
2268
  try:
2092
2269
  loc_index = locs.index(detected["bl_offset"])
2093
2270
  len_index = lens.index(detected["bl_size"])
2094
- intro_msg = "In order to access Batteryless SRAM save data, its ROM location and size must\nbe specified. The previously detected parameters have been pre-selected.\nPlease adjust if necessary, then click “OK” to continue."
2271
+ intro_msg = "In order to access Batteryless SRAM save data, its ROM location and size must be specified.\n\nThe previously detected parameters have been pre-selected. Please adjust if necessary, then click “OK” to continue."
2095
2272
  except:
2096
2273
  detected = False
2097
2274
  if detected is False:
2098
- intro_msg = "In order to access Batteryless SRAM save data, its ROM location and size must\nbe specified.\n\n⚠️ The required parameters could not be auto-detected.\nPlease enter the ROM location and size manually below."
2275
+ intro_msg = "In order to access Batteryless SRAM save data, its ROM location and size must be specified.\n\n"
2276
+ if mode == "AGB":
2277
+ max_size = self.cmbAGBHeaderROMSizeResult.currentText().replace(" ", " ")
2278
+ elif mode == "DMG":
2279
+ max_size = self.cmbDMGHeaderROMSizeResult.currentText().replace(" ", " ")
2280
+ intro_msg2 = "⚠️ The required parameters could not be auto-detected. Please enter the ROM location and size manually below. Note that wrong values can corrupt your game upon writing, so having a full " + max_size + " ROM backup is recommended."
2281
+
2282
+ if mode == "DMG":
2283
+ # Load database of observed configurations from various bootlegs
2284
+ preselect = {}
2285
+ if os.path.exists(Util.CONFIG_PATH + "/db_DMG_bl.json"):
2286
+ with open(Util.CONFIG_PATH + "/db_DMG_bl.json", "r") as f:
2287
+ try:
2288
+ preselect = json.loads(f.read())
2289
+ except Exception as e:
2290
+ print("ERROR: Couldn’t load the database of batteryless SRAM configurations.", e, sep="\n")
2291
+
2292
+ try:
2293
+ if self.CONN.INFO["dump_info"]["header"]["game_title"] in list(preselect.keys()):
2294
+ loc_index = locs.index(preselect[self.CONN.INFO["dump_info"]["header"]["game_title"]][0])
2295
+ len_index = lens.index(preselect[self.CONN.INFO["dump_info"]["header"]["game_title"]][1])
2296
+ lay_index = preselect[self.CONN.INFO["dump_info"]["header"]["game_title"]][2]
2297
+ intro_msg2 = "The required parameters were pre-selected based on the ROM title “" + self.CONN.INFO["dump_info"]["header"]["game_title"] + "”. These may still be inaccurate, so you can adjust them below if necessary. Note that wrong values can corrupt your game when writing, so having a full " + max_size + " ROM backup is recommended."
2298
+ except:
2299
+ pass
2300
+
2301
+ intro_msg += intro_msg2
2099
2302
 
2100
2303
  try:
2101
2304
  if loc_index is None:
2102
- loc_index = locs.index(int(self.SETTINGS.value("BatterylessSramLastLocation{:s}".format(self.CONN.GetMode()))))
2305
+ loc_index = locs.index(int(self.SETTINGS.value("BatterylessSramLastLocation{:s}".format(mode))))
2103
2306
  except:
2104
2307
  pass
2105
2308
 
@@ -2110,7 +2313,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2110
2313
  if l + 0x40000 >= rom_size: break
2111
2314
  loc_index += 1
2112
2315
  if loc_index >= len(locs): loc_index = len(locs) - 1
2113
- if len_index is None: len_index = 2
2316
+ if len_index is None:
2317
+ if mode == "AGB":
2318
+ len_index = 2
2319
+ elif mode == "DMG":
2320
+ len_index = 1
2321
+ if lay_index is None:
2322
+ lay_index = 2
2114
2323
 
2115
2324
  dlg_args = {
2116
2325
  "title":"Batteryless SRAM Parameters",
@@ -2121,6 +2330,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2121
2330
  [ "len", "cmb", "Size:", [ Util.formatFileSize(size=s, asInt=True) for s in lens ], len_index ],
2122
2331
  ]
2123
2332
  }
2333
+ if mode == "DMG":
2334
+ dlg_args["params"].append(
2335
+ [ "layout", "cmb", "Layout:", [ "Continuous", "First half of ROM bank", "Second half of ROM bank" ], lay_index ]
2336
+ )
2337
+
2124
2338
  dlg = UserInputDialog(self, icon=self.windowIcon(), args=dlg_args)
2125
2339
  if dlg.exec_() == 1:
2126
2340
  result = dlg.GetResult()
@@ -2135,10 +2349,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2135
2349
  else:
2136
2350
  bl_args["bl_offset"] = locs[result["loc"].currentIndex()]
2137
2351
  bl_args["bl_size"] = lens[result["len"].currentIndex()]
2352
+ if mode == "DMG":
2353
+ bl_args["bl_layout"] = result["layout"].currentIndex()
2138
2354
 
2139
2355
  locs.append(bl_args["bl_offset"])
2140
- self.SETTINGS.setValue("BatterylessSramLocations{:s}".format(self.CONN.GetMode()), json.dumps(locs))
2141
- self.SETTINGS.setValue("BatterylessSramLastLocation{:s}".format(self.CONN.GetMode()), json.dumps(bl_args["bl_offset"]))
2356
+ self.SETTINGS.setValue("BatterylessSramLocations{:s}".format(mode), json.dumps(locs))
2357
+ self.SETTINGS.setValue("BatterylessSramLastLocation{:s}".format(mode), json.dumps(bl_args["bl_offset"]))
2142
2358
  ret = bl_args
2143
2359
  else:
2144
2360
  ret = False
@@ -2154,7 +2370,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2154
2370
 
2155
2371
  if self.CONN.GetMode() == "DMG":
2156
2372
  mbc = Util.get_mbc_name(Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex()))
2157
- if mbc in ("MBC3", "MBC30"):
2373
+ if mbc in ("MBC3", "MBC30", "Unlicensed MBCX Mapper"):
2158
2374
  dlg_args = {
2159
2375
  "title":"MBC3/MBC30 Real Time Clock Editor",
2160
2376
  "intro":"Enter the number of days, hours, minutes and seconds that passed since the RTC initially started.\n\nPlease note that all values are internal values. The game may use these only as a relative reference.",
@@ -2379,10 +2595,18 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2379
2595
 
2380
2596
  if not self.CheckDeviceAlive(setMode=setTo): return
2381
2597
 
2382
- if self.optDMG.isChecked() and (mode == "AGB" or mode == None):
2383
- self.CONN.SetMode("DMG")
2384
- elif self.optAGB.isChecked() and (mode == "DMG" or mode == None):
2385
- self.CONN.SetMode("AGB")
2598
+ try:
2599
+ if self.optDMG.isChecked() and (mode == "AGB" or mode == None):
2600
+ self.CONN.SetMode("DMG")
2601
+ elif self.optAGB.isChecked() and (mode == "DMG" or mode == None):
2602
+ self.CONN.SetMode("AGB")
2603
+ except BrokenPipeError:
2604
+ msg = "Failed to turn on the cartridge power.\n\nThe “Automatic cartridge power off” setting has therefore been disabled. Please re-connect the device and try again."
2605
+ self.mnuConfig.actions()[5].setChecked(False)
2606
+ self.SETTINGS.setValue("AutoPowerOff", "0")
2607
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
2608
+ self.DisconnectDevice()
2609
+ return False
2386
2610
 
2387
2611
  ok = self.ReadCartridge()
2388
2612
  qt_app.processEvents()
@@ -2412,17 +2636,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2412
2636
  try:
2413
2637
  data = self.CONN.ReadInfo(setPinsAsInputs=True)
2414
2638
  except SerialException:
2639
+ self.LimitBaudRateGBxCartRW()
2415
2640
  self.DisconnectDevice()
2416
2641
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The connection to the device was lost while trying to read the ROM header. This may happen if the inserted cartridge issues a short circuit or its peak power draw is too high.\n\nAs a potential workaround for the latter, you can try hotswapping the cartridge:\n1. Remove the cartridge from the device.\n2. Reconnect the device and select mode.\n3. Then insert the cartridge and click “{:s}”.".format(self.btnHeaderRefresh.text().replace("&", "")), QtWidgets.QMessageBox.Ok)
2417
2642
  return False
2418
2643
 
2419
2644
  if data == False or len(data) == 0:
2645
+ self.LimitBaudRateGBxCartRW()
2420
2646
  self.DisconnectDevice()
2421
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "Invalid response from the device.", QtWidgets.QMessageBox.Ok)
2647
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "Invalid response from the device. Please re-connect the USB cable.", QtWidgets.QMessageBox.Ok)
2422
2648
  return False
2423
2649
 
2424
2650
  if self.CONN.CheckROMStable() is False and resetStatus:
2425
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge and then try again.", QtWidgets.QMessageBox.Ok)
2651
+ try:
2652
+ if data != bytearray(data[0] * len(data)):
2653
+ QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge and then try again.", QtWidgets.QMessageBox.Ok)
2654
+ except:
2655
+ pass
2426
2656
 
2427
2657
  if self.CONN.GetMode() == "DMG":
2428
2658
  self.cmbDMGHeaderMapperResult.clear()
@@ -2447,7 +2677,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2447
2677
  self.lblDMGGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
2448
2678
  temp = data["db"]["gn"]
2449
2679
  self.lblDMGGameNameResult.setText(temp)
2450
- while self.lblDMGGameNameResult.fontMetrics().boundingRect(self.lblDMGGameNameResult.text()).width() > 200:
2680
+ while self.lblDMGGameNameResult.fontMetrics().boundingRect(self.lblDMGGameNameResult.text()).width() > 240:
2451
2681
  temp = temp[:-1]
2452
2682
  self.lblDMGGameNameResult.setText(temp + "…")
2453
2683
  if temp != data["db"]["gn"]:
@@ -2514,10 +2744,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2514
2744
  self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
2515
2745
  self.lblDMGHeaderROMChecksumResult.setText("")
2516
2746
  self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
2517
- # cart_types = self.CONN.GetSupportedCartridgesDMG()
2518
- # for i in range(0, len(cart_types[0])):
2519
- # if "command_set" in cart_types[1][i] and cart_types[1][i]["command_set"] == "BLAZE_XPLODER":
2520
- # self.cmbDMGCartridgeTypeResult.setCurrentIndex(i)
2521
2747
  elif data["mapper_raw"] == 0x205: # Datel
2522
2748
  self.lblDMGHeaderRtcResult.setText("")
2523
2749
  self.lblDMGHeaderBootlogoResult.setText("")
@@ -2536,7 +2762,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2536
2762
  if "logo_sachen" in data:
2537
2763
  data["logo_sachen"].putpalette([ 255, 255, 255, 128, 128, 128 ])
2538
2764
  try:
2539
- self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
2765
+ self.lblDMGHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo_sachen"]))
2540
2766
  except:
2541
2767
  pass
2542
2768
  else:
@@ -2548,7 +2774,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2548
2774
  else:
2549
2775
  data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
2550
2776
  try:
2551
- self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
2777
+ self.lblDMGHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo"]))
2552
2778
  except:
2553
2779
  pass
2554
2780
 
@@ -2569,7 +2795,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2569
2795
  self.lblAGBHeaderGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
2570
2796
  temp = data["db"]["gn"]
2571
2797
  self.lblAGBGameNameResult.setText(temp)
2572
- while self.lblAGBGameNameResult.fontMetrics().boundingRect(self.lblAGBGameNameResult.text()).width() > 200:
2798
+ while self.lblAGBGameNameResult.fontMetrics().boundingRect(self.lblAGBGameNameResult.text()).width() > 240:
2573
2799
  temp = temp[:-1]
2574
2800
  self.lblAGBGameNameResult.setText(temp + "…")
2575
2801
  if temp != data["db"]["gn"]:
@@ -2661,7 +2887,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2661
2887
  else:
2662
2888
  data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
2663
2889
  try:
2664
- self.lblAGBHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
2890
+ self.lblAGBHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo"]))
2665
2891
  except:
2666
2892
  pass
2667
2893
 
@@ -2680,13 +2906,28 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2680
2906
  self.btnRestoreRAM.setEnabled(True)
2681
2907
  self.btnHeaderRefresh.setFocus()
2682
2908
  self.SetProgressBars(min=0, max=100, value=0)
2683
- self.lblStatus4a.setText("Ready.")
2684
2909
  qt_app.processEvents()
2685
2910
 
2686
2911
  if data['game_title'][:11] == "YJencrypted" and resetStatus:
2687
2912
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge may be protected against reading or writing a ROM. If you don’t want to risk this cartridge to render itself unusable, please do not try to write a new ROM to it.", QtWidgets.QMessageBox.Ok)
2688
2913
 
2689
- def DetectCartridge(self, canSkipMessage=False):
2914
+ def LimitBaudRateGBxCartRW(self):
2915
+ if self.CONN.GetName() == "GBxCart RW" and str(self.SETTINGS.value("AutoLimitBaudRate", default="enabled")).lower() == "enabled" and str(self.SETTINGS.value("LimitBaudRate", default="disabled")).lower() == "disabled":
2916
+ Util.dprint("Setting “" + self.mnuConfig.actions()[5].text().replace("&", "") + "” to “enabled”")
2917
+ self.mnuConfig.actions()[5].setChecked(True)
2918
+ self.SETTINGS.setValue("LimitBaudRate", "enabled")
2919
+ Util.dprint("Setting “" + self.mnuConfig.actions()[8].text().replace("&", "") + "” to “0”")
2920
+ self.mnuConfig.actions()[5].setChecked(False)
2921
+ self.SETTINGS.setValue("AutoPowerOff", "0")
2922
+ try:
2923
+ self.CONN.ChangeBaudRate(baudrate=1000000)
2924
+ except:
2925
+ try:
2926
+ self.DisconnectDevice()
2927
+ except:
2928
+ pass
2929
+
2930
+ def DetectCartridge(self, checkSaveType=True):
2690
2931
  if not self.CheckDeviceAlive(): return
2691
2932
  if not self.CONN.CheckROMStable():
2692
2933
  answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge for best results.\n\nContinue anyway?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
@@ -2702,21 +2943,30 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2702
2943
  self.lblStatus2aResult.setText("–")
2703
2944
  self.lblStatus3aResult.setText("–")
2704
2945
  self.lblStatus4aResult.setText("")
2705
- self.lblStatus4a.setText("Analyzing Cartridge...")
2946
+ # self.lblStatus4a.setText("Analyzing Cartridge...")
2706
2947
  self.SetProgressBars(min=0, max=0, value=1)
2707
2948
  qt_app.processEvents()
2708
2949
 
2950
+ if "can_skip_message" not in self.STATUS: self.STATUS["can_skip_message"] = False
2951
+ limitVoltage = str(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled")).lower() == "enabled"
2952
+ self.CONN.DetectCartridge(fncSetProgress=self.PROGRESS.SetProgress, args={"limitVoltage":limitVoltage, "checkSaveType":checkSaveType})
2953
+
2954
+ def FinishDetectCartridge(self, ret):
2955
+ self.lblStatus1aResult.setText("–")
2956
+ self.lblStatus2aResult.setText("–")
2957
+ self.lblStatus3aResult.setText("–")
2958
+
2709
2959
  limitVoltage = str(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled")).lower() == "enabled"
2710
- ret = self.CONN.DetectCartridge(limitVoltage=limitVoltage, checkSaveType=not canSkipMessage)
2711
2960
  if ret is False:
2712
2961
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "An error occured while trying to analyze the cartridge and you may need to physically reconnect the device.\n\nThis cartridge may not be auto-detectable, please select the cartridge type manually.", QtWidgets.QMessageBox.Ok)
2962
+ self.LimitBaudRateGBxCartRW()
2713
2963
  self.DisconnectDevice()
2714
2964
  cart_type = None
2715
2965
  else:
2716
2966
  (header, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, _, flash_id, detected_size) = ret
2717
-
2967
+
2718
2968
  # Save Type
2719
- if not canSkipMessage:
2969
+ if not self.STATUS["can_skip_message"]:
2720
2970
  try:
2721
2971
  if save_type is not None and save_type is not False:
2722
2972
  if self.CONN.GetMode() == "DMG":
@@ -2735,9 +2985,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2735
2985
  supp_cart_types = self.CONN.GetSupportedCartridgesDMG()
2736
2986
  elif self.CONN.GetMode() == "AGB":
2737
2987
  supp_cart_types = self.CONN.GetSupportedCartridgesAGB()
2988
+ else:
2989
+ raise NotImplementedError
2738
2990
  except Exception as e:
2739
2991
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="An unknown error occured. Please try again.\n\n" + str(e), standardButtons=QtWidgets.QMessageBox.Ok)
2740
2992
  msgbox.exec()
2993
+ self.LimitBaudRateGBxCartRW()
2741
2994
  return
2742
2995
 
2743
2996
  try:
@@ -2768,9 +3021,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2768
3021
  # Save Type
2769
3022
  msg_save_type_s = ""
2770
3023
  temp = ""
2771
- if not canSkipMessage and save_type is not False and save_type is not None:
3024
+ if not self.STATUS["can_skip_message"] and save_type is not False and save_type is not None:
2772
3025
  if save_chip is not None:
2773
- temp = "{:s} ({:s})".format(Util.AGB_Header_Save_Types[save_type], save_chip)
3026
+ if save_type == 5 and "Unlicensed" in save_chip and "data" in self.CONN.INFO and self.CONN.INFO["data"] == bytearray([0xFF] * len(self.CONN.INFO["data"])):
3027
+ temp = "{:s} or {:s} ({:s})".format(Util.AGB_Header_Save_Types[4], Util.AGB_Header_Save_Types[5], save_chip)
3028
+ else:
3029
+ temp = "{:s} ({:s})".format(Util.AGB_Header_Save_Types[save_type], save_chip)
2774
3030
  else:
2775
3031
  if self.CONN.GetMode() == "DMG":
2776
3032
  try:
@@ -2866,10 +3122,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2866
3122
  else:
2867
3123
  msg_flash_id_s_limit = ""
2868
3124
  msg_flash_id_s = "<br><b>Flash ID Check{:s}:</b><pre style=\"font-size: 8pt; margin: 0;\">{:s}</pre>".format(msg_flash_id_s_limit, flash_id[:-1])
3125
+ if not is_generic:
2869
3126
  if cfi_s != "":
2870
3127
  msg_cfi_s = "<br><b>Common Flash Interface Data:</b><br>{:s}<br><br>".format(cfi_s.replace("\n", "<br>"))
2871
3128
  else:
2872
- msg_cfi_s = "<br><b>Common Flash Interface Data:</b> No data provided<br><br>"
3129
+ msg_cfi_s = "<br><b>Common Flash Interface Data:</b> Not available<br><br>"
2873
3130
 
2874
3131
  if msg_cart_type_s_detail == "": msg_cart_type_s_detail = msg_cart_type_s
2875
3132
  self.SetProgressBars(min=0, max=100, value=100)
@@ -2916,17 +3173,17 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2916
3173
  msg = "The following cartridge configuration was detected:<br><br>"
2917
3174
  if found_supported:
2918
3175
  dontShowAgain = str(self.SETTINGS.value("SkipAutodetectMessage", default="disabled")).lower() == "enabled"
2919
- if not dontShowAgain or not canSkipMessage:
3176
+ if not dontShowAgain or not self.STATUS["can_skip_message"]:
2920
3177
  temp = "{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_cart_type_s, msg_gbmem)
2921
3178
  temp = temp[:-4]
2922
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=temp)
3179
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s} | {:s}".format(APPNAME, VERSION, self.CONN.GetFullNameLabel()), text=temp)
2923
3180
  msgbox.setTextFormat(QtCore.Qt.RichText)
2924
3181
  button_ok = msgbox.addButton("&OK", QtWidgets.QMessageBox.ActionRole)
2925
3182
  button_details = msgbox.addButton("&Details", QtWidgets.QMessageBox.ActionRole)
2926
3183
  button_cancel = None
2927
3184
  msgbox.setDefaultButton(button_ok)
2928
3185
  cb = QtWidgets.QCheckBox("Always skip this message", checked=False)
2929
- if canSkipMessage:
3186
+ if self.STATUS["can_skip_message"]:
2930
3187
  button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole)
2931
3188
  msgbox.setEscapeButton(button_cancel)
2932
3189
  msgbox.setCheckBox(cb)
@@ -2935,7 +3192,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2935
3192
 
2936
3193
  msgbox.exec()
2937
3194
  dontShowAgain = cb.isChecked()
2938
- if dontShowAgain and canSkipMessage: self.SETTINGS.setValue("SkipAutodetectMessage", "enabled")
3195
+ if dontShowAgain and self.STATUS["can_skip_message"]: self.SETTINGS.setValue("SkipAutodetectMessage", "enabled")
2939
3196
 
2940
3197
  if msgbox.clickedButton() == button_details:
2941
3198
  show_details = True
@@ -2950,10 +3207,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2950
3207
  self.btnHeaderRefresh.setFocus()
2951
3208
  self.SetProgressBars(min=0, max=100, value=0)
2952
3209
  self.lblStatus4a.setText("Ready.")
2953
- return False
3210
+ self.STATUS["can_skip_message"] = False
3211
+ if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
3212
+ return
2954
3213
 
2955
3214
  if not found_supported or show_details is True:
2956
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION))
3215
+ msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s} | {:s}".format(APPNAME, VERSION, self.CONN.GetFullNameLabel()))
2957
3216
  button_ok = msgbox.addButton("&OK", QtWidgets.QMessageBox.ActionRole)
2958
3217
  msgbox.setDefaultButton(button_ok)
2959
3218
  msgbox.setEscapeButton(button_ok)
@@ -2976,7 +3235,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2976
3235
  if answer == QtWidgets.QMessageBox.Yes:
2977
3236
  self.SETTINGS.setValue("AutoDetectLimitVoltage", "disabled")
2978
3237
  self.mnuConfig.actions()[4].setChecked(False)
2979
- return self.DetectCartridge()
3238
+ self.STATUS["can_skip_message"] = False
3239
+ self.DetectCartridge()
3240
+ return
2980
3241
 
2981
3242
  temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_gbmem, msg_fw)
2982
3243
  temp = temp[:-4]
@@ -3006,7 +3267,31 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3006
3267
  self.btnHeaderRefresh.setFocus()
3007
3268
  self.SetProgressBars(min=0, max=100, value=0)
3008
3269
  self.lblStatus4a.setText("Ready.")
3009
- return cart_type
3270
+
3271
+ waiting = None
3272
+ if "detected_cart_type" in self.STATUS and self.STATUS["detected_cart_type"] in ("WAITING_FLASH", "WAITING_SAVE_READ", "WAITING_SAVE_WRITE"):
3273
+ waiting = self.STATUS["detected_cart_type"]
3274
+ self.STATUS["detected_cart_type"] = cart_type
3275
+ self.STATUS["can_skip_message"] = False
3276
+
3277
+ if waiting == "WAITING_FLASH":
3278
+ if "detect_cartridge_args" in self.STATUS:
3279
+ self.FlashROM(dpath=self.STATUS["detect_cartridge_args"]["dpath"])
3280
+ del(self.STATUS["detect_cartridge_args"])
3281
+ else:
3282
+ self.FlashROM()
3283
+ elif waiting == "WAITING_SAVE_READ":
3284
+ if "detect_cartridge_args" in self.STATUS:
3285
+ self.BackupRAM(dpath=self.STATUS["detect_cartridge_args"]["dpath"])
3286
+ del(self.STATUS["detect_cartridge_args"])
3287
+ else:
3288
+ self.BackupRAM()
3289
+ elif waiting == "WAITING_SAVE_WRITE":
3290
+ if "detect_cartridge_args" in self.STATUS:
3291
+ self.WriteRAM(dpath=self.STATUS["detect_cartridge_args"]["dpath"], erase=self.STATUS["detect_cartridge_args"]["erase"], skip_warning=True)
3292
+ del(self.STATUS["detect_cartridge_args"])
3293
+ else:
3294
+ self.WriteRAM()
3010
3295
 
3011
3296
  def WaitProgress(self, args):
3012
3297
  if args["user_action"] == "REINSERT_CART":
@@ -3037,6 +3322,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3037
3322
  self.grpStatus.setTitle("Transfer Status (Write Save Data)")
3038
3323
  elif args["method"] == "SAVE_WRITE_VERIFY":
3039
3324
  self.grpStatus.setTitle("Transfer Status (Verify Save Data)")
3325
+ elif args["method"] == "DETECT_CART":
3326
+ self.grpStatus.setTitle("Transfer Status (Analyze Cartridge)")
3040
3327
 
3041
3328
  if "error" in args:
3042
3329
  self.lblStatus4a.setText("Failed!")
@@ -3049,6 +3336,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3049
3336
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=str(args["error"]), standardButtons=QtWidgets.QMessageBox.Ok)
3050
3337
  if not '\n' in str(args["error"]): msgbox.setTextFormat(QtCore.Qt.RichText)
3051
3338
  msgbox.exec()
3339
+ self.LimitBaudRateGBxCartRW()
3052
3340
  return
3053
3341
 
3054
3342
  self.grpDMGCartridgeInfo.setEnabled(False)
@@ -3120,6 +3408,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3120
3408
  self.lblStatus4aResult.setText("")
3121
3409
  self.btnCancel.setEnabled(args["abortable"])
3122
3410
  self.SetProgressBars(min=0, max=size, value=pos)
3411
+ elif args["action"] == "UPDATE_INFO":
3412
+ self.lblStatus4a.setText(args["text"])
3413
+ self.lblStatus4aResult.setText("")
3414
+ self.btnCancel.setEnabled(args["abortable"])
3415
+ self.SetProgressBars(min=0, max=size, value=pos)
3123
3416
  elif args["action"] == "FINISHED":
3124
3417
  if pos > 0:
3125
3418
  self.lblStatus1aResult.setText(Util.formatFileSize(size=pos))
@@ -3152,16 +3445,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3152
3445
 
3153
3446
  if "info_type" in args.keys() and "info_msg" in args.keys():
3154
3447
  if args["info_type"] == "msgbox_critical":
3155
- Util.dprint("Displaying Message Box:\n----\n{:s} {:s}\n----\n{:s}\n----".format(APPNAME, VERSION, args["info_msg"]))
3156
- self.WriteDebugLog()
3157
3448
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=args["info_msg"], standardButtons=QtWidgets.QMessageBox.Ok)
3449
+ Util.dprint("Queueing Message Box {:s}:\n----\n{:s} {:s}\n----\n{:s}\n----".format(str(msgbox), APPNAME, VERSION, args["info_msg"]))
3158
3450
  if not '\n' in args["info_msg"]: msgbox.setTextFormat(QtCore.Qt.RichText)
3159
- msgbox.exec()
3160
- if "fatal" in args: self.DisconnectDevice()
3451
+ self.MSGBOX_QUEUE.put(msgbox)
3452
+ self.WriteDebugLog()
3453
+ if "fatal" in args:
3454
+ self.LimitBaudRateGBxCartRW()
3455
+ self.DisconnectDevice()
3161
3456
  elif args["info_type"] == "msgbox_information":
3162
3457
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=args["info_msg"], standardButtons=QtWidgets.QMessageBox.Ok)
3458
+ Util.dprint("Queueing Message Box {:s}:\n----\n{:s} {:s}\n----\n{:s}\n----".format(str(msgbox), APPNAME, VERSION, args["info_msg"]))
3163
3459
  if not '\n' in args["info_msg"]: msgbox.setTextFormat(QtCore.Qt.RichText)
3164
- msgbox.exec()
3460
+ #msgbox.exec()
3461
+ self.MSGBOX_QUEUE.put(msgbox)
3165
3462
  elif args["info_type"] == "label":
3166
3463
  self.lblStatus4a.setText(args["info_msg"])
3167
3464
 
@@ -3255,9 +3552,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3255
3552
  if self.CONN.GetMode() == "DMG":
3256
3553
  header = self.CONN.ReadInfo(setPinsAsInputs=True)
3257
3554
  if header["mapper_raw"] == 252: # GBD
3258
- args = { "path":None, "mbc":252, "save_type":4, "rtc":False }
3555
+ args = { "path":None, "mbc":252, "save_type":header["ram_size_raw"], "rtc":False }
3556
+ self.lblStatus4a.setText("Loading data, please wait...")
3557
+ qt_app.processEvents()
3259
3558
  self.CONN.BackupRAM(fncSetProgress=False, args=args)
3260
3559
  data = self.CONN.INFO["data"]
3560
+ self.lblStatus4a.setText("Ready.")
3261
3561
 
3262
3562
  self.CAMWIN = None
3263
3563
  self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=data, config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH)
@@ -3318,6 +3618,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3318
3618
 
3319
3619
  def closeEvent(self, event):
3320
3620
  self.DisconnectDevice()
3621
+
3622
+ self.MSGBOX_TIMER.stop()
3623
+ self.MSGBOX_DISPLAYING = True
3624
+ with self.MSGBOX_QUEUE.mutex:
3625
+ self.MSGBOX_QUEUE.queue.clear()
3321
3626
  event.accept()
3322
3627
 
3323
3628
  def run(self):
@@ -3338,18 +3643,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3338
3643
  except:
3339
3644
  pass
3340
3645
  qt_app.exec()
3341
- # # Taskbar Progress on Windows only
3342
- # try:
3343
- # from PySide6.QtWin import QtWinTaskbarButton, QtWin
3344
- # myappid = 'lesserkuma.flashgbx'
3345
- # QtWin.setAppUserModelId(myappid)
3346
- # taskbar_button = QtWinTaskbarButton()
3347
- # self.TBPROG = taskbar_button.progress()
3348
- # self.TBPROG.setRange(0, 100)
3349
- # taskbar_button.setWindow(self.windowHandle())
3350
- # self.TBPROG.setVisible(False)
3351
- # except ImportError:
3352
- # pass
3353
3646
 
3354
3647
  else: # PySide2
3355
3648
  qt_app.exec_()