FlashGBX 4.3__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,6 +29,8 @@ 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):
33
36
  sys.excepthook = Util.exception_hook
@@ -201,6 +204,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
201
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")))
202
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() ])
203
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")))
204
208
  self.mnuConfig.addSeparator()
205
209
  self.mnuConfigReadModeAGB = QtWidgets.QMenu("&Read Method (Game Boy Advance)")
206
210
  self.mnuConfigReadModeAGB.addAction("S&tream", lambda: [ self.SETTINGS.setValue("AGBReadMethod", str(self.mnuConfigReadModeAGB.actions()[1].isChecked()).lower().replace("true", "2")), self.SetAGBReadMethod() ])
@@ -230,6 +234,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
230
234
  self.mnuConfig.actions()[7].setCheckable(True)
231
235
  self.mnuConfig.actions()[8].setCheckable(True)
232
236
  self.mnuConfig.actions()[9].setCheckable(True)
237
+ self.mnuConfig.actions()[10].setCheckable(True)
233
238
  self.mnuConfig.actions()[0].setChecked(self.SETTINGS.value("UpdateCheck") == "enabled")
234
239
  self.mnuConfig.actions()[1].setChecked(self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled") == "enabled")
235
240
  self.mnuConfig.actions()[2].setChecked(self.SETTINGS.value("PreferChipErase", default="disabled") == "enabled")
@@ -240,6 +245,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
240
245
  self.mnuConfig.actions()[7].setChecked(self.SETTINGS.value("UseNoIntroFilenames", default="enabled") == "enabled")
241
246
  self.mnuConfig.actions()[8].setChecked(self.SETTINGS.value("AutoPowerOff", default="350") != "0")
242
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")
243
249
 
244
250
  self.mnuThirdParty = QtWidgets.QMenu("Third Party &Notices")
245
251
  self.mnuThirdParty.addAction("About &Qt", lambda: [ QtWidgets.QMessageBox.aboutQt(None) ])
@@ -301,8 +307,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
301
307
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), config_ret[i][1], QtWidgets.QMessageBox.Ok)
302
308
 
303
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
+
304
314
 
305
-
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
+
306
323
  def GuiCreateGroupBoxDMGCartInfo(self):
307
324
  self.grpDMGCartridgeInfo = QtWidgets.QGroupBox("Game Boy Cartridge Information")
308
325
  self.grpDMGCartridgeInfo.setMinimumWidth(400)
@@ -657,23 +674,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
657
674
  print("Error: Failed to check for updates (too many API requests). Try again later.")
658
675
  else:
659
676
  print("Error: Failed to check for updates (HTTP status {:d}).".format(ret.status_code))
660
- else:
661
- update_check = self.SETTINGS.value("UpdateCheck")
662
- if update_check is None or (time.time() > (Util.VERSION_TIMESTAMP + (6*30*24*60*60))):
663
- 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)
664
677
 
665
678
  def DisconnectDevice(self):
666
679
  try:
667
680
  devname = self.CONN.GetFullNameExtended()
668
681
  self.CONN.Close(cartPowerOff=True)
669
- self.CONN = None
670
- self.DEVICES = {}
671
- self.cmbDevice.clear()
672
682
  print("Disconnected from {:s}".format(devname))
673
683
  except:
674
684
  pass
675
685
 
686
+ self.DEVICES = {}
687
+ self.cmbDevice.clear()
676
688
  self.CONN = None
689
+
677
690
  self.optAGB.setEnabled(False)
678
691
  self.optDMG.setEnabled(False)
679
692
  self.grpDMGCartridgeInfo.setEnabled(False)
@@ -698,6 +711,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
698
711
  self.mnuConfig.actions()[5].setVisible(True)
699
712
  self.mnuConfig.actions()[8].setVisible(True)
700
713
  self.mnuConfig.actions()[9].setVisible(True)
714
+ self.mnuConfig.actions()[10].setVisible(False)
701
715
  self.mnuTools.actions()[2].setEnabled(True)
702
716
  self.mnuConfigReadModeAGB.setEnabled(True)
703
717
 
@@ -712,13 +726,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
712
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>"
713
727
  msg += f"© 2020–{datetime.datetime.now().year} Lesserkuma<br>"
714
728
  msg += "• Website: <a href=\"https://github.com/lesserkuma/FlashGBX\">https://github.com/lesserkuma/FlashGBX</a><br><br>"
715
- 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"
716
730
  QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
717
731
 
718
732
  def AboutGameDB(self):
719
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>"
720
734
  msg += f"No-Intro databases referenced for this version of {APPNAME}:<br>"
721
- msg += "• Nintendo - Game Boy (20241003-140822)<br>• Nintendo - Game Boy Advance (20241102-092521)<br>• Nintendo - Game Boy Advance (Video) (20241021-195439)<br>• Nintendo - Game Boy Color (20241105-004534)" # 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
722
736
  QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
723
737
 
724
738
  def OpenPath(self, path=None):
@@ -815,6 +829,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
815
829
  self.mnuConfig.actions()[5].setVisible(self.CONN.DEVICE_NAME == "GBxCart RW") # Limit Baud Rate
816
830
  self.mnuConfig.actions()[8].setVisible(self.CONN.CanPowerCycleCart() and self.CONN.CanPowerCycleCart() and self.CONN.FW["fw_ver"] >= 12) # Auto Power Off
817
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
818
833
  self.mnuConfigReadModeAGB.setEnabled(self.CONN.FW["fw_ver"] >= 12)
819
834
  self.mnuConfigReadModeDMG.setEnabled(self.CONN.FW["fw_ver"] >= 12)
820
835
 
@@ -872,7 +887,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
872
887
  text = "A firmware update for your {:s} device is required to use this software. Do you want to update now?".format(dev.GetFullName())
873
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)
874
889
  elif dev.FW_UPDATE_REQ == 2:
875
- 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>")
876
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)
877
892
  else:
878
893
  text = "A firmware update for your {:s} device is available. Do you want to update now?".format(dev.GetFullName())
@@ -916,7 +931,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
916
931
  qt_app.processEvents()
917
932
 
918
933
  messages = []
919
- #last_msg = ""
920
934
 
921
935
  # pylint: disable=global-variable-not-assigned
922
936
  global hw_devices
@@ -938,8 +952,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
938
952
  msg = ret[i][1]
939
953
  if msg in messages: # don’t show the same message twice
940
954
  continue
941
- #else:
942
- # last_msg = msg
943
955
  if status == 3:
944
956
  messages.append(msg)
945
957
  self.CONN = None
@@ -964,7 +976,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
964
976
  msg += message + "\n\n"
965
977
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg[:-2], QtWidgets.QMessageBox.Ok)
966
978
  elif not firstRun:
967
- 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>\n- Update the firmware manually through Options → Tools → Firmware Updater".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()
968
996
 
969
997
  self.lblDevice.setText("No devices found.")
970
998
  self.lblDevice.setStyleSheet("")
@@ -1156,22 +1184,24 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1156
1184
 
1157
1185
  dontShowAgainCameraSavePopup = str(self.SETTINGS.value("SkipCameraSavePopup", default="disabled")).lower() == "enabled"
1158
1186
  if not dontShowAgainCameraSavePopup:
1159
- if self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KiB
1160
- cbCameraSavePopup = QtWidgets.QCheckBox("Don’t show this message again", checked=dontShowAgain)
1161
- 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?")
1162
- msgboxCameraPopup.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
1163
- msgboxCameraPopup.setDefaultButton(QtWidgets.QMessageBox.Yes)
1164
- msgboxCameraPopup.setCheckBox(cbCameraSavePopup)
1165
- answer = msgboxCameraPopup.exec()
1166
- dontShowAgainCameraSavePopup = cbCameraSavePopup.isChecked()
1167
- if dontShowAgainCameraSavePopup: self.SETTINGS.setValue("SkipCameraSavePopup", "enabled")
1168
- if answer == QtWidgets.QMessageBox.Yes:
1169
- self.CAMWIN = None
1170
- self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH)
1171
- self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1172
- self.CAMWIN.setModal(True)
1173
- self.CAMWIN.run()
1174
- 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
1175
1205
 
1176
1206
  if "last_path" in self.CONN.INFO:
1177
1207
  button_open_dir = msgbox.addButton(" Open Fol&der ", QtWidgets.QMessageBox.ActionRole)
@@ -1278,6 +1308,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1278
1308
  def CartridgeTypeChanged(self, index):
1279
1309
  self.STATUS["cart_type"] = {}
1280
1310
  if index in (-1, 0): return
1311
+ if "detect_cartridge_args" in self.STATUS: return
1281
1312
  if self.CONN.GetMode() == "DMG":
1282
1313
  cart_types = self.CONN.GetSupportedCartridgesDMG()
1283
1314
  if cart_types[1][index] == "RETAIL": # special keyword
@@ -1397,7 +1428,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1397
1428
 
1398
1429
  if cart_type is False: # clicked Cancel button
1399
1430
  return
1400
- elif cart_type is None or cart_type == 0:
1431
+ elif cart_type is None or cart_type == 0 or not isinstance(cart_type, int):
1401
1432
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1402
1433
  return
1403
1434
 
@@ -1451,8 +1482,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1451
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."
1452
1483
  answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
1453
1484
  if answer == QtWidgets.QMessageBox.Cancel: return
1454
- #if "mbc" in carts[cart_type]:
1455
- # mbc = carts[cart_type]["mbc"]
1456
1485
 
1457
1486
  override_voltage = False
1458
1487
  if 'voltage_variants' in carts[cart_type] and carts[cart_type]['voltage'] == 3.3:
@@ -1568,7 +1597,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1568
1597
  pass
1569
1598
 
1570
1599
  flash_offset = 0
1571
-
1600
+ force_wr_pullup = self.SETTINGS.value("ForceWrPullup", default="disabled").lower() == "enabled"
1601
+
1572
1602
  self.grpDMGCartridgeInfo.setEnabled(False)
1573
1603
  self.grpAGBCartridgeInfo.setEnabled(False)
1574
1604
  self.grpActions.setEnabled(False)
@@ -1582,7 +1612,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1582
1612
  verify_write = False
1583
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 }
1584
1614
  else:
1585
- 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 }
1586
1616
  args["compare_sectors"] = self.SETTINGS.value("CompareSectors", default="disabled").lower() == "enabled"
1587
1617
  self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
1588
1618
  #self.CONN._FlashROM(args=args)
@@ -1592,18 +1622,48 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1592
1622
  self.STATUS["last_path"] = path
1593
1623
  self.STATUS["args"] = args
1594
1624
 
1595
- def BackupRAM(self):
1625
+ def BackupRAM(self, dpath=""):
1596
1626
  if not self.CheckDeviceAlive(): return
1597
1627
 
1598
1628
  rtc = False
1599
- add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled")
1600
- path_datetime = ""
1601
- if add_date_time and add_date_time.lower() == "enabled":
1602
- path_datetime = "_{:s}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
1629
+ path = ""
1603
1630
 
1604
- path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS)
1605
- path = os.path.splitext(path)[0]
1606
- 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
+
1607
1667
  cart_type = 0
1608
1668
  if self.CONN.GetMode() == "DMG":
1609
1669
  setting_name = "LastDirSaveDataDMG"
@@ -1615,7 +1675,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1615
1675
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1616
1676
  return
1617
1677
  cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1618
- #save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
1619
1678
 
1620
1679
  elif self.CONN.GetMode() == "AGB":
1621
1680
  setting_name = "LastDirSaveDataAGB"
@@ -1627,13 +1686,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1627
1686
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1628
1687
  return
1629
1688
  cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1630
- #save_size = Util.AGB_Header_Save_Sizes[save_type]
1631
1689
  else:
1632
1690
  return
1633
1691
 
1634
1692
  if not self.CheckHeader(): return
1635
- path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup Save Data", last_dir + "/" + path, "Save Data File (*.sav *.srm *.fla *.eep);;All Files (*.*)")[0]
1636
- 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
1637
1706
 
1638
1707
  verify_read = self.SETTINGS.value("VerifyData", default="enabled")
1639
1708
  if verify_read and verify_read.lower() == "enabled":
@@ -1654,37 +1723,22 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1654
1723
  rtc = (answer == QtWidgets.QMessageBox.Yes)
1655
1724
 
1656
1725
  bl_args = {}
1657
- 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()]:
1658
- if self.CONN.GetFWBuildDate() == "": # Legacy Mode
1659
- 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)
1660
- msgbox.exec()
1661
- return
1662
- cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1663
- if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
1664
- if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
1665
- if self.STATUS["detected_cart_type"] == "":
1666
- self.STATUS["detected_cart_type"] = "WAITING_SAVE_READ"
1667
- self.STATUS["detect_cartridge_args"] = { "dpath":path }
1668
- self.STATUS["can_skip_message"] = True
1669
- self.DetectCartridge(checkSaveType=True)
1670
- return
1671
- cart_type = self.STATUS["detected_cart_type"]
1672
- if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1673
-
1674
- if cart_type is False: # clicked Cancel button
1675
- return
1676
- elif cart_type is None or cart_type == 0:
1677
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
1678
- return
1679
- self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
1680
-
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
+ :
1681
1730
  if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
1682
1731
 
1683
1732
  if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
1684
1733
  detected = self.CONN.INFO["dump_info"]["batteryless_sram"]
1685
1734
  else:
1686
1735
  detected = False
1687
- 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)
1688
1742
  if bl_args is False: return
1689
1743
 
1690
1744
  self.SETTINGS.setValue(setting_name, os.path.dirname(path))
@@ -1714,13 +1768,46 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1714
1768
  if not self.CheckDeviceAlive(): return
1715
1769
  mode = self.CONN.GetMode()
1716
1770
 
1717
- if erase is True: dpath = ""
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)
1718
1810
 
1719
- if dpath == "":
1720
- path = Util.GenerateFileName(mode=mode, header=self.CONN.INFO, settings=self.SETTINGS)
1721
- path = os.path.splitext(path)[0]
1722
- path += ".sav"
1723
-
1724
1811
  if mode == "DMG":
1725
1812
  setting_name = "LastDirSaveDataDMG"
1726
1813
  last_dir = self.SETTINGS.value(setting_name)
@@ -1731,7 +1818,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1731
1818
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1732
1819
  return
1733
1820
  cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
1734
- #save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
1735
1821
 
1736
1822
  elif mode == "AGB":
1737
1823
  setting_name = "LastDirSaveDataAGB"
@@ -1742,7 +1828,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1742
1828
  if save_type == 0:
1743
1829
  QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
1744
1830
  return
1745
- #save_size = Util.AGB_Header_Save_Sizes[save_type]
1746
1831
  cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
1747
1832
  else:
1748
1833
  return
@@ -1772,6 +1857,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1772
1857
  msgbox.exec()
1773
1858
 
1774
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 \
1775
1862
  ("8M DACS" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
1776
1863
  (mode == "AGB" and "ereader" in self.CONN.INFO and self.CONN.INFO["ereader"] is True) or \
1777
1864
  (mode == "DMG" and "256M Multi Cart" in self.cmbDMGHeaderMapperResult.currentText() and not self.CONN.CanPowerCycleCart()):
@@ -1780,11 +1867,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1780
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)
1781
1868
  if answer == QtWidgets.QMessageBox.Cancel: return
1782
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"
1783
1874
  path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav *.srm *.fla *.eep);;All Files (*.*)")[0]
1784
1875
  if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path))
1785
1876
  if (path == ""): return
1786
1877
 
1787
- if not erase and not test:
1878
+ if not erase and not test and len(path) > 0:
1788
1879
  filesize = os.path.getsize(path)
1789
1880
  if filesize == 0 or filesize > 0x200000: # reject too large files to avoid exploding RAM
1790
1881
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The size of this file is not supported.", QtWidgets.QMessageBox.Ok)
@@ -1807,7 +1898,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1807
1898
  buffer = bytearray([0xFF] * 0x20000)
1808
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)
1809
1900
  button_overwrite = msgbox.addButton(" &Erase everything ", QtWidgets.QMessageBox.ActionRole)
1810
- erase = False # Don’t just erase everything
1811
1901
  else:
1812
1902
  with open(path, "rb") as f: buffer = bytearray(f.read())
1813
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)
@@ -1830,6 +1920,56 @@ class FlashGBX_GUI(QtWidgets.QWidget):
1830
1920
  answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_text, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
1831
1921
  if answer == QtWidgets.QMessageBox.No: return
1832
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
+
1833
1973
  verify_write = self.SETTINGS.value("VerifyData", default="enabled")
1834
1974
  if verify_write and verify_write.lower() == "enabled":
1835
1975
  verify_write = True
@@ -2049,30 +2189,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2049
2189
 
2050
2190
  else:
2051
2191
  bl_args = {}
2052
- 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()]:
2053
- if self.CONN.GetFWBuildDate() == "": # Legacy Mode
2054
- 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)
2055
- msgbox.exec()
2056
- return
2057
- cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
2058
- if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
2059
- if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
2060
- if self.STATUS["detected_cart_type"] == "":
2061
- self.STATUS["detected_cart_type"] = "WAITING_SAVE_WRITE"
2062
- self.STATUS["detect_cartridge_args"] = { "dpath":path, "erase":erase }
2063
- self.STATUS["can_skip_message"] = True
2064
- self.DetectCartridge(checkSaveType=True)
2065
- return
2066
- cart_type = self.STATUS["detected_cart_type"]
2067
- if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
2068
-
2069
- if cart_type is False: # clicked Cancel button
2070
- return
2071
- elif cart_type is None or cart_type == 0:
2072
- QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
2073
- return
2074
- self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
2075
-
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
+ :
2076
2196
  if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
2077
2197
 
2078
2198
  if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
@@ -2083,6 +2203,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2083
2203
  if bl_args is False: return
2084
2204
 
2085
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)
2086
2207
  args.update({"bl_save":True, "flash_offset":bl_args["bl_offset"], "flash_size":bl_args["bl_size"]})
2087
2208
  if erase:
2088
2209
  args["path"] = ""
@@ -2090,12 +2211,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2090
2211
  self.STATUS["args"] = args
2091
2212
  self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
2092
2213
  #self.CONN._FlashROM(args=args)
2214
+
2093
2215
  else:
2094
- #cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
2095
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 }
2096
2217
  if buffer is not None:
2097
2218
  args["buffer"] = buffer
2098
2219
  args["path"] = None
2220
+ args["erase"] = False
2099
2221
  self.STATUS["args"] = args
2100
2222
  self.CONN.RestoreRAM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
2101
2223
  #args = { "mode":3, "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase, "verify_write":verify_write }
@@ -2118,11 +2240,18 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2118
2240
  qt_app.processEvents()
2119
2241
 
2120
2242
  def GetBLArgs(self, rom_size, detected=False):
2121
- locs = [ 0x3C0000, 0x7C0000, 0xFC0000, 0x1FC0000 ]
2122
- lens = [ 0x2000, 0x8000, 0x10000, 0x20000 ]
2123
- 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), "[]")
2124
2252
  loc_index = None
2125
2253
  len_index = None
2254
+ lay_index = None
2126
2255
 
2127
2256
  try:
2128
2257
  temp = json.loads(temp)
@@ -2139,15 +2268,41 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2139
2268
  try:
2140
2269
  loc_index = locs.index(detected["bl_offset"])
2141
2270
  len_index = lens.index(detected["bl_size"])
2142
- 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."
2143
2272
  except:
2144
2273
  detected = False
2145
2274
  if detected is False:
2146
- 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
2147
2302
 
2148
2303
  try:
2149
2304
  if loc_index is None:
2150
- 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))))
2151
2306
  except:
2152
2307
  pass
2153
2308
 
@@ -2158,7 +2313,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2158
2313
  if l + 0x40000 >= rom_size: break
2159
2314
  loc_index += 1
2160
2315
  if loc_index >= len(locs): loc_index = len(locs) - 1
2161
- 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
2162
2323
 
2163
2324
  dlg_args = {
2164
2325
  "title":"Batteryless SRAM Parameters",
@@ -2169,6 +2330,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2169
2330
  [ "len", "cmb", "Size:", [ Util.formatFileSize(size=s, asInt=True) for s in lens ], len_index ],
2170
2331
  ]
2171
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
+
2172
2338
  dlg = UserInputDialog(self, icon=self.windowIcon(), args=dlg_args)
2173
2339
  if dlg.exec_() == 1:
2174
2340
  result = dlg.GetResult()
@@ -2183,10 +2349,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2183
2349
  else:
2184
2350
  bl_args["bl_offset"] = locs[result["loc"].currentIndex()]
2185
2351
  bl_args["bl_size"] = lens[result["len"].currentIndex()]
2352
+ if mode == "DMG":
2353
+ bl_args["bl_layout"] = result["layout"].currentIndex()
2186
2354
 
2187
2355
  locs.append(bl_args["bl_offset"])
2188
- self.SETTINGS.setValue("BatterylessSramLocations{:s}".format(self.CONN.GetMode()), json.dumps(locs))
2189
- 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"]))
2190
2358
  ret = bl_args
2191
2359
  else:
2192
2360
  ret = False
@@ -2433,7 +2601,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2433
2601
  elif self.optAGB.isChecked() and (mode == "DMG" or mode == None):
2434
2602
  self.CONN.SetMode("AGB")
2435
2603
  except BrokenPipeError:
2436
- msg = "Failed to turn on the cartridge power.\n\nAs a workaround, try to disable the \"Automatic cartridge power off\" setting and then hotplug the cartridge after clicking \"Refresh\"."
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")
2437
2607
  QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
2438
2608
  self.DisconnectDevice()
2439
2609
  return False
@@ -2466,13 +2636,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2466
2636
  try:
2467
2637
  data = self.CONN.ReadInfo(setPinsAsInputs=True)
2468
2638
  except SerialException:
2639
+ self.LimitBaudRateGBxCartRW()
2469
2640
  self.DisconnectDevice()
2470
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)
2471
2642
  return False
2472
2643
 
2473
2644
  if data == False or len(data) == 0:
2645
+ self.LimitBaudRateGBxCartRW()
2474
2646
  self.DisconnectDevice()
2475
- 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)
2476
2648
  return False
2477
2649
 
2478
2650
  if self.CONN.CheckROMStable() is False and resetStatus:
@@ -2572,10 +2744,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2572
2744
  self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
2573
2745
  self.lblDMGHeaderROMChecksumResult.setText("")
2574
2746
  self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
2575
- # cart_types = self.CONN.GetSupportedCartridgesDMG()
2576
- # for i in range(0, len(cart_types[0])):
2577
- # if "command_set" in cart_types[1][i] and cart_types[1][i]["command_set"] == "BLAZE_XPLODER":
2578
- # self.cmbDMGCartridgeTypeResult.setCurrentIndex(i)
2579
2747
  elif data["mapper_raw"] == 0x205: # Datel
2580
2748
  self.lblDMGHeaderRtcResult.setText("")
2581
2749
  self.lblDMGHeaderBootlogoResult.setText("")
@@ -2594,7 +2762,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2594
2762
  if "logo_sachen" in data:
2595
2763
  data["logo_sachen"].putpalette([ 255, 255, 255, 128, 128, 128 ])
2596
2764
  try:
2597
- self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
2765
+ self.lblDMGHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo_sachen"]))
2598
2766
  except:
2599
2767
  pass
2600
2768
  else:
@@ -2606,7 +2774,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2606
2774
  else:
2607
2775
  data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
2608
2776
  try:
2609
- self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
2777
+ self.lblDMGHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo"]))
2610
2778
  except:
2611
2779
  pass
2612
2780
 
@@ -2719,7 +2887,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2719
2887
  else:
2720
2888
  data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
2721
2889
  try:
2722
- self.lblAGBHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
2890
+ self.lblAGBHeaderBootlogoResult.setPixmap(Util.bitmap2pixmap(data["logo"]))
2723
2891
  except:
2724
2892
  pass
2725
2893
 
@@ -2743,6 +2911,22 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2743
2911
  if data['game_title'][:11] == "YJencrypted" and resetStatus:
2744
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)
2745
2913
 
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
+
2746
2930
  def DetectCartridge(self, checkSaveType=True):
2747
2931
  if not self.CheckDeviceAlive(): return
2748
2932
  if not self.CONN.CheckROMStable():
@@ -2775,11 +2959,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2775
2959
  limitVoltage = str(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled")).lower() == "enabled"
2776
2960
  if ret is False:
2777
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()
2778
2963
  self.DisconnectDevice()
2779
2964
  cart_type = None
2780
2965
  else:
2781
2966
  (header, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, _, flash_id, detected_size) = ret
2782
-
2967
+
2783
2968
  # Save Type
2784
2969
  if not self.STATUS["can_skip_message"]:
2785
2970
  try:
@@ -2805,6 +2990,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2805
2990
  except Exception as e:
2806
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)
2807
2992
  msgbox.exec()
2993
+ self.LimitBaudRateGBxCartRW()
2808
2994
  return
2809
2995
 
2810
2996
  try:
@@ -2837,7 +3023,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2837
3023
  temp = ""
2838
3024
  if not self.STATUS["can_skip_message"] and save_type is not False and save_type is not None:
2839
3025
  if save_chip is not None:
2840
- 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)
2841
3030
  else:
2842
3031
  if self.CONN.GetMode() == "DMG":
2843
3032
  try:
@@ -2933,10 +3122,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2933
3122
  else:
2934
3123
  msg_flash_id_s_limit = ""
2935
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:
2936
3126
  if cfi_s != "":
2937
3127
  msg_cfi_s = "<br><b>Common Flash Interface Data:</b><br>{:s}<br><br>".format(cfi_s.replace("\n", "<br>"))
2938
3128
  else:
2939
- 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>"
2940
3130
 
2941
3131
  if msg_cart_type_s_detail == "": msg_cart_type_s_detail = msg_cart_type_s
2942
3132
  self.SetProgressBars(min=0, max=100, value=100)
@@ -2986,7 +3176,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
2986
3176
  if not dontShowAgain or not self.STATUS["can_skip_message"]:
2987
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)
2988
3178
  temp = temp[:-4]
2989
- 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)
2990
3180
  msgbox.setTextFormat(QtCore.Qt.RichText)
2991
3181
  button_ok = msgbox.addButton("&OK", QtWidgets.QMessageBox.ActionRole)
2992
3182
  button_details = msgbox.addButton("&Details", QtWidgets.QMessageBox.ActionRole)
@@ -3022,7 +3212,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3022
3212
  return
3023
3213
 
3024
3214
  if not found_supported or show_details is True:
3025
- 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()))
3026
3216
  button_ok = msgbox.addButton("&OK", QtWidgets.QMessageBox.ActionRole)
3027
3217
  msgbox.setDefaultButton(button_ok)
3028
3218
  msgbox.setEscapeButton(button_ok)
@@ -3091,7 +3281,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3091
3281
  else:
3092
3282
  self.FlashROM()
3093
3283
  elif waiting == "WAITING_SAVE_READ":
3094
- self.BackupRAM()
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()
3095
3289
  elif waiting == "WAITING_SAVE_WRITE":
3096
3290
  if "detect_cartridge_args" in self.STATUS:
3097
3291
  self.WriteRAM(dpath=self.STATUS["detect_cartridge_args"]["dpath"], erase=self.STATUS["detect_cartridge_args"]["erase"], skip_warning=True)
@@ -3142,6 +3336,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3142
3336
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=str(args["error"]), standardButtons=QtWidgets.QMessageBox.Ok)
3143
3337
  if not '\n' in str(args["error"]): msgbox.setTextFormat(QtCore.Qt.RichText)
3144
3338
  msgbox.exec()
3339
+ self.LimitBaudRateGBxCartRW()
3145
3340
  return
3146
3341
 
3147
3342
  self.grpDMGCartridgeInfo.setEnabled(False)
@@ -3250,16 +3445,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3250
3445
 
3251
3446
  if "info_type" in args.keys() and "info_msg" in args.keys():
3252
3447
  if args["info_type"] == "msgbox_critical":
3253
- Util.dprint("Displaying Message Box:\n----\n{:s} {:s}\n----\n{:s}\n----".format(APPNAME, VERSION, args["info_msg"]))
3254
- self.WriteDebugLog()
3255
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"]))
3256
3450
  if not '\n' in args["info_msg"]: msgbox.setTextFormat(QtCore.Qt.RichText)
3257
- msgbox.exec()
3258
- 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()
3259
3456
  elif args["info_type"] == "msgbox_information":
3260
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"]))
3261
3459
  if not '\n' in args["info_msg"]: msgbox.setTextFormat(QtCore.Qt.RichText)
3262
- msgbox.exec()
3460
+ #msgbox.exec()
3461
+ self.MSGBOX_QUEUE.put(msgbox)
3263
3462
  elif args["info_type"] == "label":
3264
3463
  self.lblStatus4a.setText(args["info_msg"])
3265
3464
 
@@ -3353,9 +3552,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3353
3552
  if self.CONN.GetMode() == "DMG":
3354
3553
  header = self.CONN.ReadInfo(setPinsAsInputs=True)
3355
3554
  if header["mapper_raw"] == 252: # GBD
3356
- 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()
3357
3558
  self.CONN.BackupRAM(fncSetProgress=False, args=args)
3358
3559
  data = self.CONN.INFO["data"]
3560
+ self.lblStatus4a.setText("Ready.")
3359
3561
 
3360
3562
  self.CAMWIN = None
3361
3563
  self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=data, config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH)
@@ -3416,6 +3618,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
3416
3618
 
3417
3619
  def closeEvent(self, event):
3418
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()
3419
3626
  event.accept()
3420
3627
 
3421
3628
  def run(self):