This commit is contained in:
Lesserkuma 2023-04-14 19:48:16 +02:00
parent 305e343290
commit c475e722c4
20 changed files with 865 additions and 661 deletions

View File

@ -1,4 +1,14 @@
# Release notes
### v3.25 (released 2023-04-14)
- Bundles GBxCart RW v1.4/v1.4a firmware version R41+L9 (minor improvements)
- Fixed an issue with the Game Boy Camera Album Viewer *(thanks CodyWick13)*
- Switched from PyInstaller to providing Windows Setup/Portable packages with embedded Python runtimes, so FlashGBX can be run from source as well (requires 64-bit Windows installation)
- Added support for DRV with AM29LV160DT and ALTERA CPLD *(thanks Corborg)*
- Verification after writing a ROM is now a lot faster (requires firmware R41+L9)
- Now shows the No-Intro game title in the main window if the cartridge is in the database
- Cleaned up the list of Game Boy mappers in the GUI to only list the unique types; detailed info is still available in dump reports
- Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums
### v3.24 (released 2023-04-09)
- Improved support for Batteryless SRAM save data backup and restore for Game Boy Advance reproduction cartridges *(thanks metroid-maniac and LucentW)*
- When checking for available updates, version information is now read via the GitHub API instead of the PyPI API

View File

@ -157,8 +157,28 @@ def main(portableMode=False):
Util.DEBUG = True
args = {"app_path":app_path, "config_path":config_path, "argparsed":args}
while True:
try:
if not os.path.exists(config_path):
os.mkdir(config_path)
tf = "{:s}/settings.ini".format(config_path)
f = open(tf, "ab")
f.close()
break
except PermissionError:
print("\n{:s}Error: This program has no permission to use the configuration directory “{:s}”!{:s}".format(Util.ANSI.RED, config_path, Util.ANSI.RESET))
if "appdata" in cp and args["argparsed"].cfgdir == "subdir":
answer = input("Use directory “{:s}” instead? [y/N] ".format(cp["appdata"])).strip().lower()
if answer != "y":
return
config_path = cp["appdata"]
args["config_path"] = config_path
continue
else:
input("")
return
args.update(LoadConfig(args))
app = None
exc = None
if not args["argparsed"].cli:

View File

@ -2,7 +2,7 @@
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
import datetime, shutil, platform, os, json, math, traceback, re, time, serial, zipfile
import datetime, shutil, platform, os, math, traceback, re, time, serial, zipfile
try:
# pylint: disable=import-error
import readline
@ -1177,11 +1177,6 @@ class FlashGBX_CLI():
except:
pass
os.unlink(Util.CONFIG_PATH + "/test1.bin")
os.unlink(Util.CONFIG_PATH + "/test2.bin")
os.unlink(Util.CONFIG_PATH + "/test3.bin")
os.unlink(Util.CONFIG_PATH + "/test4.bin")
def UpdateFirmwareGBxCartRW_PrintText(self, text, enableUI=False, setProgress=None):
if setProgress is not None:
self.FWUPD_R = True

View File

@ -94,7 +94,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rowActionsGeneral2.addWidget(self.btnBackupRAM)
self.cmbDMGCartridgeTypeResult.currentIndexChanged.connect(self.CartridgeTypeChanged)
self.cmbHeaderMapperResult.currentIndexChanged.connect(self.DMGMapperTypeChanged)
self.cmbDMGHeaderMapperResult.currentIndexChanged.connect(self.DMGMapperTypeChanged)
rowActionsGeneral3 = QtWidgets.QHBoxLayout()
self.btnFlashROM = QtWidgets.QPushButton("&Write ROM")
@ -274,96 +274,96 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def GuiCreateGroupBoxDMGCartInfo(self):
self.grpDMGCartridgeInfo = QtWidgets.QGroupBox("Game Boy Cartridge Information")
self.grpDMGCartridgeInfo.setMinimumWidth(364)
self.grpDMGCartridgeInfo.setMinimumWidth(375)
group_layout = QtWidgets.QVBoxLayout()
group_layout.setContentsMargins(-1, 5, -1, -1)
rowHeaderTitle = QtWidgets.QHBoxLayout()
lblHeaderTitle = QtWidgets.QLabel("Game Title:")
lblHeaderTitle.setContentsMargins(0, 1, 0, 1)
rowHeaderTitle.addWidget(lblHeaderTitle)
self.lblHeaderTitleResult = QtWidgets.QLabel("")
rowHeaderTitle.addWidget(self.lblHeaderTitleResult)
group_layout.addLayout(rowHeaderTitle)
rowDMGGameName = QtWidgets.QHBoxLayout()
self.lblDMGGameName = QtWidgets.QLabel("Game Name:")
self.lblDMGGameName.setContentsMargins(0, 1, 0, 1)
rowDMGGameName.addWidget(self.lblDMGGameName)
self.lblDMGGameNameResult = QtWidgets.QLabel("")
rowDMGGameName.addWidget(self.lblDMGGameNameResult)
group_layout.addLayout(rowDMGGameName)
rowHeaderRevision = QtWidgets.QHBoxLayout()
self.lblHeaderRevision = QtWidgets.QLabel("Game Code / Revision:")
self.lblHeaderRevision.setContentsMargins(0, 1, 0, 1)
rowHeaderRevision.addWidget(self.lblHeaderRevision)
self.lblHeaderRevisionResult = QtWidgets.QLabel("")
rowHeaderRevision.addWidget(self.lblHeaderRevisionResult)
group_layout.addLayout(rowHeaderRevision)
rowDMGRomTitle = QtWidgets.QHBoxLayout()
self.lblDMGRomTitle = QtWidgets.QLabel("ROM Title:")
self.lblDMGRomTitle.setContentsMargins(0, 1, 0, 1)
rowDMGRomTitle.addWidget(self.lblDMGRomTitle)
self.lblDMGRomTitleResult = QtWidgets.QLabel("")
rowDMGRomTitle.addWidget(self.lblDMGRomTitleResult)
group_layout.addLayout(rowDMGRomTitle)
rowHeaderRtc = QtWidgets.QHBoxLayout()
lblHeaderRtc = QtWidgets.QLabel("Real Time Clock:")
lblHeaderRtc.setContentsMargins(0, 1, 0, 1)
rowHeaderRtc.addWidget(lblHeaderRtc)
self.lblHeaderRtcResult = QtWidgets.QLabel("")
self.lblHeaderRtcResult.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor))
self.lblHeaderRtcResult.setToolTip("This shows the internal register values; in-game clock may use an offset")
rowHeaderRtc.addWidget(self.lblHeaderRtcResult)
group_layout.addLayout(rowHeaderRtc)
rowDMGGameCodeRevision = QtWidgets.QHBoxLayout()
self.lblDMGGameCodeRevision = QtWidgets.QLabel("Game Code and Revision:")
self.lblDMGGameCodeRevision.setContentsMargins(0, 1, 0, 1)
rowDMGGameCodeRevision.addWidget(self.lblDMGGameCodeRevision)
self.lblDMGGameCodeRevisionResult = QtWidgets.QLabel("")
rowDMGGameCodeRevision.addWidget(self.lblDMGGameCodeRevisionResult)
group_layout.addLayout(rowDMGGameCodeRevision)
rowHeaderLogoValid = QtWidgets.QHBoxLayout()
lblHeaderLogoValid = QtWidgets.QLabel("Boot Logo:")
lblHeaderLogoValid.setContentsMargins(0, 1, 0, 1)
rowHeaderLogoValid.addWidget(lblHeaderLogoValid)
self.lblHeaderLogoValidResult = QtWidgets.QLabel("")
rowHeaderLogoValid.addWidget(self.lblHeaderLogoValidResult)
group_layout.addLayout(rowHeaderLogoValid)
rowDMGHeaderRtc = QtWidgets.QHBoxLayout()
lblDMGHeaderRtc = QtWidgets.QLabel("Real Time Clock:")
lblDMGHeaderRtc.setContentsMargins(0, 1, 0, 1)
rowDMGHeaderRtc.addWidget(lblDMGHeaderRtc)
self.lblDMGHeaderRtcResult = QtWidgets.QLabel("")
self.lblDMGHeaderRtcResult.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor))
self.lblDMGHeaderRtcResult.setToolTip("This shows the internal register values; in-game clock may use an offset")
rowDMGHeaderRtc.addWidget(self.lblDMGHeaderRtcResult)
group_layout.addLayout(rowDMGHeaderRtc)
rowHeaderChecksum = QtWidgets.QHBoxLayout()
lblHeaderChecksum = QtWidgets.QLabel("Header Checksum:")
lblHeaderChecksum.setContentsMargins(0, 1, 0, 1)
rowHeaderChecksum.addWidget(lblHeaderChecksum)
self.lblHeaderChecksumResult = QtWidgets.QLabel("")
rowHeaderChecksum.addWidget(self.lblHeaderChecksumResult)
group_layout.addLayout(rowHeaderChecksum)
rowDMGHeaderBootlogo = QtWidgets.QHBoxLayout()
lblDMGHeaderBootlogo = QtWidgets.QLabel("Boot Logo:")
lblDMGHeaderBootlogo.setContentsMargins(0, 1, 0, 1)
rowDMGHeaderBootlogo.addWidget(lblDMGHeaderBootlogo)
self.lblDMGHeaderBootlogoResult = QtWidgets.QLabel("")
rowDMGHeaderBootlogo.addWidget(self.lblDMGHeaderBootlogoResult)
group_layout.addLayout(rowDMGHeaderBootlogo)
rowHeaderROMChecksum = QtWidgets.QHBoxLayout()
lblHeaderROMChecksum = QtWidgets.QLabel("ROM Checksum:")
lblHeaderROMChecksum.setContentsMargins(0, 1, 0, 1)
rowHeaderROMChecksum.addWidget(lblHeaderROMChecksum)
self.lblHeaderROMChecksumResult = QtWidgets.QLabel("")
rowHeaderROMChecksum.addWidget(self.lblHeaderROMChecksumResult)
group_layout.addLayout(rowHeaderROMChecksum)
rowDMGHeaderROMChecksum = QtWidgets.QHBoxLayout()
lblDMGHeaderROMChecksum = QtWidgets.QLabel("ROM Checksum:")
lblDMGHeaderROMChecksum.setContentsMargins(0, 1, 0, 1)
rowDMGHeaderROMChecksum.addWidget(lblDMGHeaderROMChecksum)
self.lblDMGHeaderROMChecksumResult = QtWidgets.QLabel("")
rowDMGHeaderROMChecksum.addWidget(self.lblDMGHeaderROMChecksumResult)
group_layout.addLayout(rowDMGHeaderROMChecksum)
rowHeaderROMSize = QtWidgets.QHBoxLayout()
lblHeaderROMSize = QtWidgets.QLabel("ROM Size:")
rowHeaderROMSize.addWidget(lblHeaderROMSize)
self.cmbHeaderROMSizeResult = QtWidgets.QComboBox()
self.cmbHeaderROMSizeResult.setStyleSheet("combobox-popup: 0;")
self.cmbHeaderROMSizeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowHeaderROMSize.addWidget(self.cmbHeaderROMSizeResult)
group_layout.addLayout(rowHeaderROMSize)
rowDMGHeaderROMSize = QtWidgets.QHBoxLayout()
lblDMGHeaderROMSize = QtWidgets.QLabel("ROM Size:")
rowDMGHeaderROMSize.addWidget(lblDMGHeaderROMSize)
self.cmbDMGHeaderROMSizeResult = QtWidgets.QComboBox()
self.cmbDMGHeaderROMSizeResult.setStyleSheet("combobox-popup: 0;")
self.cmbDMGHeaderROMSizeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowDMGHeaderROMSize.addWidget(self.cmbDMGHeaderROMSizeResult)
group_layout.addLayout(rowDMGHeaderROMSize)
rowHeaderRAMSize = QtWidgets.QHBoxLayout()
lblHeaderRAMSize = QtWidgets.QLabel("Save Type:")
rowHeaderRAMSize.addWidget(lblHeaderRAMSize)
self.cmbHeaderRAMSizeResult = QtWidgets.QComboBox()
self.cmbHeaderRAMSizeResult.setStyleSheet("combobox-popup: 0;")
self.cmbHeaderRAMSizeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowHeaderRAMSize.addWidget(self.cmbHeaderRAMSizeResult)
group_layout.addLayout(rowHeaderRAMSize)
rowDMGHeaderSaveType = QtWidgets.QHBoxLayout()
lblDMGHeaderSaveType = QtWidgets.QLabel("Save Type:")
rowDMGHeaderSaveType.addWidget(lblDMGHeaderSaveType)
self.cmbDMGHeaderSaveTypeResult = QtWidgets.QComboBox()
self.cmbDMGHeaderSaveTypeResult.setStyleSheet("combobox-popup: 0;")
self.cmbDMGHeaderSaveTypeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowDMGHeaderSaveType.addWidget(self.cmbDMGHeaderSaveTypeResult)
group_layout.addLayout(rowDMGHeaderSaveType)
rowHeaderMapper = QtWidgets.QHBoxLayout()
lblHeaderMapper = QtWidgets.QLabel("Mapper Type:")
rowHeaderMapper.addWidget(lblHeaderMapper)
self.cmbHeaderMapperResult = QtWidgets.QComboBox()
self.cmbHeaderMapperResult.setStyleSheet("combobox-popup: 0;")
self.cmbHeaderMapperResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowHeaderMapper.addWidget(self.cmbHeaderMapperResult)
group_layout.addLayout(rowHeaderMapper)
rowDMGHeaderMapper = QtWidgets.QHBoxLayout()
lblDMGHeaderMapper = QtWidgets.QLabel("Mapper Type:")
rowDMGHeaderMapper.addWidget(lblDMGHeaderMapper)
self.cmbDMGHeaderMapperResult = QtWidgets.QComboBox()
self.cmbDMGHeaderMapperResult.setStyleSheet("combobox-popup: 0;")
self.cmbDMGHeaderMapperResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowDMGHeaderMapper.addWidget(self.cmbDMGHeaderMapperResult)
group_layout.addLayout(rowDMGHeaderMapper)
rowCartridgeType = QtWidgets.QHBoxLayout()
lblCartridgeType = QtWidgets.QLabel("Cart:")
rowCartridgeType.addWidget(lblCartridgeType)
rowDMGCartridgeType = QtWidgets.QHBoxLayout()
lblDMGCartridgeType = QtWidgets.QLabel("Cart:")
rowDMGCartridgeType.addWidget(lblDMGCartridgeType)
self.cmbDMGCartridgeTypeResult = QtWidgets.QComboBox()
self.cmbDMGCartridgeTypeResult.setStyleSheet("max-width: 260px;")
self.cmbDMGCartridgeTypeResult.setStyleSheet("combobox-popup: 0;")
self.cmbDMGCartridgeTypeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
rowCartridgeType.addWidget(self.cmbDMGCartridgeTypeResult)
group_layout.addLayout(rowCartridgeType)
rowDMGCartridgeType.addWidget(self.cmbDMGCartridgeTypeResult)
group_layout.addLayout(rowDMGCartridgeType)
self.grpDMGCartridgeInfo.setLayout(group_layout)
@ -371,33 +371,33 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def GuiCreateGroupBoxAGBCartInfo(self):
self.grpAGBCartridgeInfo = QtWidgets.QGroupBox("Game Boy Advance Cartridge Information")
self.grpAGBCartridgeInfo.setMinimumWidth(364)
self.grpAGBCartridgeInfo.setMinimumWidth(375)
group_layout = QtWidgets.QVBoxLayout()
group_layout.setContentsMargins(-1, 5, -1, -1)
rowAGBHeaderTitle = QtWidgets.QHBoxLayout()
lblAGBHeaderTitle = QtWidgets.QLabel("Game Title:")
lblAGBHeaderTitle.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderTitle.addWidget(lblAGBHeaderTitle)
self.lblAGBHeaderTitleResult = QtWidgets.QLabel("")
rowAGBHeaderTitle.addWidget(self.lblAGBHeaderTitleResult)
group_layout.addLayout(rowAGBHeaderTitle)
rowAGBGameName = QtWidgets.QHBoxLayout()
lblAGBGameName = QtWidgets.QLabel("Game Name:")
lblAGBGameName.setContentsMargins(0, 1, 0, 1)
rowAGBGameName.addWidget(lblAGBGameName)
self.lblAGBGameNameResult = QtWidgets.QLabel("")
rowAGBGameName.addWidget(self.lblAGBGameNameResult)
group_layout.addLayout(rowAGBGameName)
rowAGBHeaderCode = QtWidgets.QHBoxLayout()
lblAGBHeaderCode = QtWidgets.QLabel("Game Code:")
lblAGBHeaderCode.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderCode.addWidget(lblAGBHeaderCode)
self.lblAGBHeaderCodeResult = QtWidgets.QLabel("")
rowAGBHeaderCode.addWidget(self.lblAGBHeaderCodeResult)
group_layout.addLayout(rowAGBHeaderCode)
rowAGBRomTitle = QtWidgets.QHBoxLayout()
lblAGBRomTitle = QtWidgets.QLabel("ROM Title:")
lblAGBRomTitle.setContentsMargins(0, 1, 0, 1)
rowAGBRomTitle.addWidget(lblAGBRomTitle)
self.lblAGBRomTitleResult = QtWidgets.QLabel("")
rowAGBRomTitle.addWidget(self.lblAGBRomTitleResult)
group_layout.addLayout(rowAGBRomTitle)
rowAGBHeaderRevision = QtWidgets.QHBoxLayout()
lblAGBHeaderRevision = QtWidgets.QLabel("Revision:")
lblAGBHeaderRevision.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderRevision.addWidget(lblAGBHeaderRevision)
self.lblAGBHeaderRevisionResult = QtWidgets.QLabel("")
rowAGBHeaderRevision.addWidget(self.lblAGBHeaderRevisionResult)
group_layout.addLayout(rowAGBHeaderRevision)
rowAGBHeaderGameCodeRevision = QtWidgets.QHBoxLayout()
lblAGBHeaderGameCodeRevision = QtWidgets.QLabel("Game Code and Revision:")
lblAGBHeaderGameCodeRevision.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderGameCodeRevision.addWidget(lblAGBHeaderGameCodeRevision)
self.lblAGBHeaderGameCodeRevisionResult = QtWidgets.QLabel("")
rowAGBHeaderGameCodeRevision.addWidget(self.lblAGBHeaderGameCodeRevisionResult)
group_layout.addLayout(rowAGBHeaderGameCodeRevision)
rowAGBGpioRtc = QtWidgets.QHBoxLayout()
lblAGBGpioRtc = QtWidgets.QLabel("Real Time Clock:")
@ -405,17 +405,17 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rowAGBGpioRtc.addWidget(lblAGBGpioRtc)
self.lblAGBGpioRtcResult = QtWidgets.QLabel("")
self.lblAGBGpioRtcResult.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor))
self.lblAGBGpioRtcResult.setToolTip(self.lblHeaderRtcResult.toolTip())
self.lblAGBGpioRtcResult.setToolTip(self.lblDMGHeaderRtcResult.toolTip())
rowAGBGpioRtc.addWidget(self.lblAGBGpioRtcResult)
group_layout.addLayout(rowAGBGpioRtc)
rowAGBHeaderLogoValid = QtWidgets.QHBoxLayout()
lblAGBHeaderLogoValid = QtWidgets.QLabel("Boot Logo:")
lblAGBHeaderLogoValid.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderLogoValid.addWidget(lblAGBHeaderLogoValid)
rowAGBHeaderBootlogo = QtWidgets.QHBoxLayout()
lblAGBHeaderBootlogo = QtWidgets.QLabel("Boot Logo:")
lblAGBHeaderBootlogo.setContentsMargins(0, 1, 0, 1)
rowAGBHeaderBootlogo.addWidget(lblAGBHeaderBootlogo)
self.lblAGBHeaderLogoValidResult = QtWidgets.QLabel("")
rowAGBHeaderLogoValid.addWidget(self.lblAGBHeaderLogoValidResult)
group_layout.addLayout(rowAGBHeaderLogoValid)
rowAGBHeaderBootlogo.addWidget(self.lblAGBHeaderLogoValidResult)
group_layout.addLayout(rowAGBHeaderBootlogo)
rowAGBHeaderChecksum = QtWidgets.QHBoxLayout()
lblAGBHeaderChecksum = QtWidgets.QLabel("Header Checksum:")
@ -444,16 +444,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rowAGBHeaderROMSize.addWidget(self.cmbAGBHeaderROMSizeResult)
group_layout.addLayout(rowAGBHeaderROMSize)
rowAGBHeaderRAMSize = QtWidgets.QHBoxLayout()
lblAGBHeaderRAMSize = QtWidgets.QLabel("Save Type:")
rowAGBHeaderRAMSize.addWidget(lblAGBHeaderRAMSize)
rowAGBHeaderSaveType = QtWidgets.QHBoxLayout()
lblAGBHeaderSaveType = QtWidgets.QLabel("Save Type:")
rowAGBHeaderSaveType.addWidget(lblAGBHeaderSaveType)
self.cmbAGBSaveTypeResult = QtWidgets.QComboBox()
self.cmbAGBSaveTypeResult.setStyleSheet("combobox-popup: 0;")
self.cmbAGBSaveTypeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.cmbAGBSaveTypeResult.addItems(Util.AGB_Header_Save_Types)
self.cmbAGBSaveTypeResult.setCurrentIndex(self.cmbAGBSaveTypeResult.count() - 1)
rowAGBHeaderRAMSize.addWidget(self.cmbAGBSaveTypeResult)
group_layout.addLayout(rowAGBHeaderRAMSize)
rowAGBHeaderSaveType.addWidget(self.cmbAGBSaveTypeResult)
group_layout.addLayout(rowAGBHeaderSaveType)
rowAGBCartridgeType = QtWidgets.QHBoxLayout()
lblAGBCartridgeType = QtWidgets.QLabel("Cart:")
@ -487,7 +487,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
return
new_value = str(self.mnuConfig.actions()[0].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")
if new_value == "enabled":
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "Would you like to automatically check for new versions at application startup? This will make use of the GitHub API (<a href=\"https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement\">privacy policy</a>).", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes)
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "Would you like to automatically check for new versions at application startup? This will make use of the GitHub API (<a href=\"https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement\">privacy policy</a>).", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.Yes:
self.SETTINGS.setValue("UpdateCheck", "enabled")
self.mnuConfig.actions()[0].setChecked(True)
@ -544,7 +544,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if msgbox.clickedButton() == button_open:
webbrowser.open(site)
else:
print("This version of {:s} ({:s}) seems to be newer than the latest release ({:s}). Please check for updates manually.".format(APPNAME, VERSION_PEP440, latest_version))
print("This version of {:s} ({:s}) seems to be newer than the latest public release ({:s}).".format(APPNAME, VERSION_PEP440, latest_version))
else:
print("Error: Update check failed due to missing version information in JSON data from GitHub.")
except json.decoder.JSONDecodeError:
@ -602,7 +602,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.SETTINGS.setValue("SkipCameraSavePopup", "disabled")
def OpenPath(self, path=None):
if path is None: path = Util.CONFIG_PATH
if path is None:
path = Util.CONFIG_PATH
self.WriteDebugLogOnShiftKey()
path = 'file://{0:s}'.format(path)
try:
if platform.system() == "Windows":
@ -613,7 +615,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
subprocess.Popen(["xdg-open", path])
except:
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), "The path is:\n{:s}".format(path), QtWidgets.QMessageBox.Ok)
self.WriteDebugLogOnShiftKey()
def WriteDebugLogOnShiftKey(self, event=None):
kbmod = QtWidgets.QApplication.keyboardModifiers()
@ -909,10 +910,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
button_dump_report = msgbox.addButton(" Generate Dump &Report ", QtWidgets.QMessageBox.ActionRole)
button_open_dir = msgbox.addButton(" Open &Directory ", QtWidgets.QMessageBox.ActionRole)
if self.CONN.GetMode() == "DMG":
if self.CONN.INFO["rom_checksum"] == self.CONN.INFO["rom_checksum_calc"]:
self.lblHeaderROMChecksumResult.setText("Valid (0x{:04X})".format(self.CONN.INFO["rom_checksum"]))
self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }")
self.lblDMGHeaderROMChecksumResult.setText("Valid (0x{:04X})".format(self.CONN.INFO["rom_checksum"]))
self.lblDMGHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }")
self.lblStatus4a.setText("Done!")
msg = "The ROM backup is complete and the checksum was verified successfully!"
msgbox.setText(msg + msg_te)
@ -924,15 +927,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msgbox.setText(msg + msg_te)
msgbox.exec()
else:
self.lblHeaderROMChecksumResult.setText("Invalid (0x{:04X}≠0x{:04X})".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["rom_checksum"]))
self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblDMGHeaderROMChecksumResult.setText("Invalid (0x{:04X}≠0x{:04X})".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["rom_checksum"]))
self.lblDMGHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }")
msg = "The ROM was dumped, but the checksum is not correct."
button_gmmc1 = None
if self.CONN.INFO["loop_detected"] is not False:
msg += "\n\nA data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
else:
msg += " This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps."
if self.CONN.GetMode() == "DMG" and (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderMapperResult.currentIndex()])[0] in (1, 2, 3): # If MBC1
if self.CONN.GetMode() == "DMG" and self.cmbDMGHeaderMapperResult.currentText() == "MBC1":
msg += "\n\nIf this is a NP GB-Memory Cartridge, please use the “Retry as G-MMC1” button."
button_gmmc1 = msgbox.addButton(" Retry as G-MMC1 ", QtWidgets.QMessageBox.ActionRole)
msgbox.setText(msg + msg_te)
@ -940,10 +943,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msgbox.exec()
if msgbox.clickedButton() == button_gmmc1:
if self.CheckDeviceAlive():
mappers = list(Util.DMG_Header_Mapper.keys())
for i in range(0, len(mappers)):
if mappers[i] == 0x105:
self.cmbHeaderMapperResult.setCurrentIndex(i)
self.cmbDMGHeaderMapperResult.setCurrentIndex(Util.ConvertMapperToMapperType(0x105)[2])
self.cmbDMGHeaderROMSizeResult.setCurrentIndex(5)
cart_type = 0
cart_types = self.CONN.GetSupportedCartridgesDMG()
for i in range(0, len(cart_types[0])):
@ -966,7 +967,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msgbox.exec()
elif Util.AGB_Global_CRC32 == 0:
self.lblAGBHeaderROMChecksumResult.setText("0x{:06X}".format(self.CONN.INFO["rom_checksum_calc"]))
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblStatus4a.setText("Done!")
msg = "The ROM backup is complete! As there is no known checksum for this ROM in the database, verification was skipped."
if self.CONN.INFO["loop_detected"] is not False:
@ -996,6 +997,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
except Exception as e:
print("ERROR: {:s}".format(str(e)))
self.OpenPath(dumpinfo_file)
elif msgbox.clickedButton() == button_open_dir:
self.OpenPath(os.path.split(dumpinfo_file)[0])
elif self.CONN.INFO["last_action"] == 2: # Backup RAM
self.lblStatus4a.setText("Done!")
@ -1020,8 +1023,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.CAMWIN.run()
return
if "last_path" in self.CONN.INFO:
button_open_dir = msgbox.addButton(" Open &Directory ", QtWidgets.QMessageBox.ActionRole)
msgbox.setText("The save data backup is complete!" + msg_te)
msgbox.exec()
if "last_path" in self.CONN.INFO and msgbox.clickedButton() == button_open_dir:
self.OpenPath(os.path.split(self.CONN.INFO["last_path"])[0])
elif self.CONN.INFO["last_action"] == 3: # Restore RAM
self.lblStatus4a.setText("Done!")
@ -1079,16 +1086,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if index in (-1, 0): return
def SetDMGMapperResult(self, cart_type):
mappers = list(Util.DMG_Header_Mapper.keys())
mbc = 0
if "mbc" in cart_type:
if isinstance(cart_type["mbc"], int):
mbc = cart_type["mbc"]
elif self.cmbHeaderMapperResult.currentIndex() > 0:
mbc = mappers[self.cmbHeaderMapperResult.currentIndex()]
for i in range(0, len(mappers)):
if mbc == mappers[i]:
self.cmbHeaderMapperResult.setCurrentIndex(i)
elif self.cmbDMGHeaderMapperResult.currentIndex() > 0:
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
self.cmbDMGHeaderMapperResult.setCurrentIndex(Util.ConvertMapperToMapperType(mbc)[2])
def CartridgeTypeChanged(self, index):
self.STATUS["cart_type"] = {}
@ -1101,7 +1105,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if "flash_size" in cart_types[1][index] and not "dmg-mmsa-jpn" in cart_types[1][index]:
for i in range(0, len(Util.DMG_Header_ROM_Sizes_Flasher_Map)):
if cart_types[1][index]["flash_size"] == (Util.DMG_Header_ROM_Sizes_Flasher_Map[i]):
self.cmbHeaderROMSizeResult.setCurrentIndex(i)
self.cmbDMGHeaderROMSizeResult.setCurrentIndex(i)
self.STATUS["cart_type"] = cart_types[1][index]
self.SetDMGMapperResult(cart_types[1][index])
@ -1126,7 +1130,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if not self.CheckDeviceAlive(): return
if not self.CheckHeader(): return
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderMapperResult.currentIndex()])[0]
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
rom_size = 0
cart_type = 0
@ -1138,7 +1142,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup ROM", last_dir + "/" + path, "Game Boy ROM File (*.gb *.sgb *.gbc);;All Files (*.*)")[0]
cart_type = self.cmbDMGCartridgeTypeResult.currentIndex()
rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[self.cmbHeaderROMSizeResult.currentIndex()]
rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[self.cmbDMGHeaderROMSizeResult.currentIndex()]
elif self.CONN.GetMode() == "AGB":
setting_name = "LastDirRomAGB"
@ -1152,8 +1156,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if (path == ""): return
self.SETTINGS.setValue(setting_name, os.path.dirname(path))
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.grpDMGCartridgeInfo.setEnabled(False)
self.grpAGBCartridgeInfo.setEnabled(False)
@ -1214,7 +1218,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if self.CONN.GetMode() == "DMG":
self.SetDMGMapperResult(carts[cart_type])
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderMapperResult.currentIndex()])[0]
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
else:
mbc = 0
@ -1291,7 +1295,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if not Util.compare_mbc(hdr["mapper_raw"], mbc):
mbc1 = Util.get_mbc_name(mbc)
mbc2 = Util.get_mbc_name(hdr["mapper_raw"])
compatible_mbc = [ "None", "MBC2", "MBC3", "MBC5", "MBC7", "GBD", "G-MMC1", "HuC-1", "HuC-3" ]
compatible_mbc = [ "None", "MBC2", "MBC3", "MBC30", "MBC5", "MBC7", "MAC-GBD", "G-MMC1", "HuC-1", "HuC-3" ]
if (mbc2 == "None") or (mbc1 == "G-MMC1" and mbc2 == "MBC1") or (mbc2 == "G-MMC1" and mbc1 == "MBC1"):
pass
elif mbc2 != "None" and not (mbc1 in compatible_mbc and mbc2 in compatible_mbc):
@ -1336,14 +1340,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
elif msgbox.clickedButton() == button_2:
pass
# flash_offset = self.PROGRESS.GetLastPosition()
# if flash_offset > 0:
# answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "The previous Write ROM operation seems to have been interrupted. Do you want to resume from address 0x{:X}?".format(flash_offset), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Yes)
# if answer == QtWidgets.QMessageBox.No:
# flash_offset = 0
# elif answer == QtWidgets.QMessageBox.Cancel:
# return
# self.PROGRESS.ResetLastPosition()
flash_offset = 0
self.grpDMGCartridgeInfo.setEnabled(False)
@ -1371,7 +1367,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if not self.CheckDeviceAlive(): return
rtc = False
features = []
add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled")
path_datetime = ""
if add_date_time and add_date_time.lower() == "enabled":
@ -1384,12 +1379,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
setting_name = "LastDirSaveDataDMG"
last_dir = self.SETTINGS.value(setting_name)
if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderMapperResult.currentIndex()])[0]
try:
features = list(Util.DMG_Header_Mapper.keys())[self.cmbHeaderMapperResult.currentIndex()]
except:
features = []
save_type = Util.DMG_Header_RAM_Sizes_Map[self.cmbHeaderRAMSizeResult.currentIndex()]
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
save_type = Util.DMG_Header_RAM_Sizes_Map[self.cmbDMGHeaderSaveTypeResult.currentIndex()]
if save_type == 0:
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
return
@ -1419,8 +1410,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
verify_read = False
rtc = False
if self.CONN.INFO["has_rtc"]: # features in (0x10, 0xFD, 0xFE): # RTC of MBC3, TAMA5, HuC-3
if self.CONN.GetMode() == "DMG" and features == 0x10 and not self.CONN.IsClkConnected():
if self.CONN.INFO["has_rtc"]:
if self.CONN.GetMode() == "DMG" and mbc == 0x10 and not self.CONN.IsClkConnected():
rtc = False
else:
msg = "A Real Time Clock cartridge was detected. Do you want the cartridges Real Time Clock register values also to be saved?"
@ -1479,8 +1470,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def WriteRAM(self, dpath="", erase=False, test=False):
if not self.CheckDeviceAlive(): return
features = 0
if dpath == "":
path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS)
path = os.path.splitext(path)[0]
@ -1490,12 +1479,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
setting_name = "LastDirSaveDataDMG"
last_dir = self.SETTINGS.value(setting_name)
if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation)
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderMapperResult.currentIndex()])[0]
try:
features = list(Util.DMG_Header_Mapper.keys())[self.cmbHeaderMapperResult.currentIndex()]
except:
features = []
save_type = Util.DMG_Header_RAM_Sizes_Map[self.cmbHeaderRAMSizeResult.currentIndex()]
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
save_type = Util.DMG_Header_RAM_Sizes_Map[self.cmbDMGHeaderSaveTypeResult.currentIndex()]
if save_type == 0:
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
return
@ -1560,10 +1545,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rtc = False
rtc_advance = False
if not test and self.CONN.INFO["has_rtc"]: # features in (0x10, 0xFD, 0xFE): # RTC of MBC3, TAMA5, HuC-3
if self.CONN.GetMode() == "DMG" and features == 0x10 and not self.CONN.IsClkConnected():
if not test and self.CONN.INFO["has_rtc"]:
if self.CONN.GetMode() == "DMG" and mbc == 0x10 and not self.CONN.IsClkConnected():
rtc = False
elif (self.CONN.GetMode() == "DMG" and ((features == 0xFD and (filesize == save_size + 0x28 or erase)) or (features == 0xFE and (filesize == save_size + 0xC or erase)) or (self.CONN.IsClkConnected() and features == 0x10 and filesize == save_size + 0x30 or erase))) or \
elif (self.CONN.GetMode() == "DMG" and ((mbc == 0xFD and (filesize == save_size + 0x28 or erase)) or (mbc == 0xFE and (filesize == save_size + 0xC or erase)) or (self.CONN.IsClkConnected() and mbc == 0x10 and filesize == save_size + 0x30 or erase))) or \
(self.CONN.GetMode() == "AGB" and (filesize == save_size + 0x10 or erase)):
msg = "A Real Time Clock cartridge was detected. Do you want the Real Time Clock register values to be also written?"
cb = QtWidgets.QCheckBox("&Adjust RTC", checked=True)
@ -1628,7 +1613,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
test_ok = 0
save1 = bytearray([0])
save2 = bytearray([1])
backup_fn = Util.CONFIG_PATH + "/backup_test_{:s}.sav".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"))
backup_fn = Util.CONFIG_PATH + "/backup_stress_test.bin"
try:
self.lblStatus4a.setText("Testing ({:s} 1/2)...".format(test_patterns_names[0]))
@ -1664,7 +1649,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
except:
save1 = None
if save1 is not None and save1 == save2:
if save1 is not None and save1 != save2:
msg = "Test {:d} ({:s}) failed!\nNote: SRAM requires a working battery to retain save data.\n\nContinue anyway?".format(test_ok+1, test_patterns_names[test_ok])
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.No)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.No:
save1 = None
save2 = None
if save1 is not None:
with open(backup_fn, "wb") as f: f.write(save1)
test_ok += 1
for i in range(0, len(test_patterns)):
@ -1679,7 +1673,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
qt_app.processEvents()
time.sleep(0.02)
t.join()
if i == 0:
if i == 0 \
and not (save1 != save2): # user "continued anyway"
self.CONN.CartPowerOff()
time.sleep(0.5)
self.CONN.CartPowerOn()
@ -1712,10 +1707,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
qt_app.processEvents()
time.sleep(0.02)
t.join()
save3 = self.CONN.INFO["data"]
if save3 == save2:
if os.path.exists(backup_fn):
os.unlink(backup_fn)
time_elapsed = time.time() - time_start
msg_te = "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed, asFloat=True))
@ -1725,6 +1716,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
qt_app.processEvents()
if test_ok == len(test_patterns)+1:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="All tests completed successfully!" + msg_te, standardButtons=QtWidgets.QMessageBox.Ok)
msgbox.exec()
else:
try:
if test_ok == 0:
@ -1734,13 +1726,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
with open("debug_savetest_2.bin", "wb") as f: f.write(readback)
except:
pass
msg = "Test {:d} ({:s}) failed!".format(test_ok+1, test_patterns_names[test_ok])
if test_ok == 0:
if self.CONN.GetMode() == "DMG" or self.CONN.GetMode() == "AGB" and save_type in (3, 7, 8):
msg += "\nNote: SRAM requires a working battery to retain save data."
msg += msg_te
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg, standardButtons=QtWidgets.QMessageBox.Ok)
msgbox.exec()
if test_ok > 0:
msg = "Test {:d} ({:s}) failed!".format(test_ok+1, test_patterns_names[test_ok])
msg += msg_te
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg, standardButtons=QtWidgets.QMessageBox.Ok)
msgbox.exec()
self.grpDMGCartridgeInfo.setEnabled(True)
self.grpAGBCartridgeInfo.setEnabled(True)
@ -1880,7 +1870,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def CheckDeviceAlive(self, setMode=False):
if self.CONN is not None:
mode = self.CONN.GetMode()
if self.CONN.DEVICE is not None:
if self.CONN.DEVICE is None:
self.DisconnectDevice()
else:
if not self.CONN.IsConnected():
self.DisconnectDevice()
self.CONN = None
@ -1995,78 +1987,73 @@ class FlashGBX_GUI(QtWidgets.QWidget):
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)
if self.CONN.GetMode() == "DMG":
self.cmbHeaderMapperResult.clear()
self.cmbHeaderMapperResult.addItems(list(Util.DMG_Header_Mapper.values()))
self.cmbHeaderMapperResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.cmbDMGHeaderMapperResult.clear()
self.cmbDMGHeaderMapperResult.addItems(list(Util.DMG_Mapper_Types.keys()))
self.cmbDMGHeaderMapperResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.cmbDMGCartridgeTypeResult.clear()
self.cmbDMGCartridgeTypeResult.addItems(self.CONN.GetSupportedCartridgesDMG()[0])
self.cmbDMGCartridgeTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
if "flash_type" in data:
self.cmbDMGCartridgeTypeResult.setCurrentIndex(data["flash_type"])
self.cmbHeaderROMSizeResult.clear()
self.cmbHeaderROMSizeResult.addItems(Util.DMG_Header_ROM_Sizes)
self.cmbHeaderROMSizeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.cmbHeaderRAMSizeResult.clear()
self.cmbHeaderRAMSizeResult.addItems(Util.DMG_Header_RAM_Sizes)
self.cmbHeaderRAMSizeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.cmbDMGHeaderROMSizeResult.clear()
self.cmbDMGHeaderROMSizeResult.addItems(Util.DMG_Header_ROM_Sizes)
self.cmbDMGHeaderROMSizeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.cmbDMGHeaderSaveTypeResult.clear()
self.cmbDMGHeaderSaveTypeResult.addItems(Util.DMG_Header_RAM_Sizes)
self.cmbDMGHeaderSaveTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.lblHeaderTitleResult.setText(data['game_title'])
if len(data['game_code']) > 0:
self.lblHeaderRevision.setText("Game Code / Revision:")
self.lblHeaderRevisionResult.setText("{:s}-{:s}".format(data['game_code'], str(data['version'])))
self.lblDMGRomTitleResult.setText(data['game_title'])
self.lblDMGGameCodeRevision.setText("Game Code and Revision:")
if data["db"] is not None:
self.lblDMGGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
temp = data["db"]["gn"]
self.lblDMGGameNameResult.setText(temp)
while self.lblDMGGameNameResult.fontMetrics().boundingRect(self.lblDMGGameNameResult.text()).width() > 170:
temp = temp[:-1]
self.lblDMGGameNameResult.setText(temp + "")
if temp != data["db"]["gn"]:
self.lblDMGGameNameResult.setToolTip(data["db"]["gn"])
else:
self.lblDMGGameNameResult.setToolTip("")
else:
self.lblHeaderRevision.setText("Revision:")
self.lblHeaderRevisionResult.setText(str(data['version']))
self.lblDMGGameNameResult.setText("(Not in database)")
if len(data['game_code']) > 0:
self.lblDMGGameCodeRevisionResult.setText("{:s}-{:s}".format(data["game_code"], str(data["version"])))
else:
self.lblDMGGameCodeRevision.setText("Revision:")
self.lblDMGGameCodeRevisionResult.setText(str(data['version']))
self.lblHeaderRtcResult.setText(data["rtc_string"])
self.lblDMGHeaderRtcResult.setText(data["rtc_string"])
if data['logo_correct']:
self.lblHeaderLogoValidResult.setText("OK")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
if data['logo_correct'] and data['header_checksum_correct']:
self.lblDMGHeaderBootlogoResult.setText("OK")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
else:
self.lblHeaderLogoValidResult.setText("Invalid")
self.lblHeaderLogoValidResult.setStyleSheet("QLabel { color: red; }")
if data['header_checksum_correct']:
self.lblHeaderChecksumResult.setText("Valid (0x{:02X})".format(data['header_checksum']))
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
else:
self.lblHeaderChecksumResult.setText("Invalid (0x{:02X})".format(data['header_checksum']))
self.lblHeaderChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblHeaderROMChecksumResult.setText("0x{:04X}".format(data['rom_checksum']))
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGHeaderBootlogoResult.setText("Invalid")
self.lblDMGHeaderBootlogoResult.setStyleSheet("QLabel { color: red; }")
self.cmbHeaderROMSizeResult.setCurrentIndex(data["rom_size_raw"])
self.lblDMGHeaderROMChecksumResult.setText("0x{:04X}".format(data['rom_checksum']))
self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.cmbDMGHeaderROMSizeResult.setCurrentIndex(data["rom_size_raw"])
for i in range(0, len(Util.DMG_Header_RAM_Sizes_Map)):
if data["ram_size_raw"] == Util.DMG_Header_RAM_Sizes_Map[i]:
self.cmbHeaderRAMSizeResult.setCurrentIndex(i)
i = 0
for k in Util.DMG_Header_Mapper.keys():
if data["mapper_raw"] == k:
self.cmbHeaderMapperResult.setCurrentIndex(i)
if k == 0x06: # MBC2
self.cmbHeaderRAMSizeResult.setCurrentIndex(1)
elif k == 0x22 and data["game_title"] in ("KORO2 KIRBY", "KIRBY TNT"): # MBC7 Kirby
self.cmbHeaderRAMSizeResult.setCurrentIndex(Util.DMG_Header_RAM_Sizes_Map.index(0x101))
elif k == 0x22 and data["game_title"] in ("CMASTER"): # MBC7 Command Master
self.cmbHeaderRAMSizeResult.setCurrentIndex(Util.DMG_Header_RAM_Sizes_Map.index(0x102))
elif k == 0xFD: # TAMA5
self.cmbHeaderRAMSizeResult.setCurrentIndex(Util.DMG_Header_RAM_Sizes_Map.index(0x103))
elif k == 0x20: # MBC6
self.cmbHeaderRAMSizeResult.setCurrentIndex(Util.DMG_Header_RAM_Sizes_Map.index(0x104))
self.cmbDMGHeaderSaveTypeResult.setCurrentIndex(i)
temp = Util.ConvertMapperToMapperType(data["mapper_raw"])
mapper_type = temp[2]
self.cmbDMGHeaderMapperResult.setCurrentIndex(mapper_type)
i += 1
if data['empty'] == True: # defaults
if data['empty_nocart'] == True:
self.lblHeaderTitleResult.setText("(No cartridge connected)")
self.lblDMGGameNameResult.setText("(No cartridge connected)")
else:
self.lblHeaderTitleResult.setText("(No ROM data detected)")
self.lblHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.cmbHeaderROMSizeResult.setCurrentIndex(0)
self.cmbHeaderRAMSizeResult.setCurrentIndex(0)
self.cmbHeaderMapperResult.setCurrentIndex(0)
self.lblDMGGameNameResult.setText("(No ROM data detected)")
self.lblDMGGameNameResult.setStyleSheet("QLabel { color: red; }")
self.cmbDMGHeaderROMSizeResult.setCurrentIndex(0)
self.cmbDMGHeaderSaveTypeResult.setCurrentIndex(0)
self.cmbDMGHeaderMapperResult.setCurrentIndex(0)
else:
self.lblHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGGameNameResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
if data['logo_correct'] and not self.CONN.IsSupportedMbc(data["mapper_raw"]) and resetStatus:
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge uses a mapper that may not be completely supported by {:s} using the current firmware version of the {:s} device. Please check for firmware updates in the Tools menu or the makers website.".format(APPNAME, self.CONN.GetFullName()), QtWidgets.QMessageBox.Ok)
if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU "):
@ -2076,37 +2063,36 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.cmbDMGCartridgeTypeResult.setCurrentIndex(i)
if data["mapper_raw"] == 0x203: # Xploder GB
self.lblHeaderRtcResult.setText("")
self.lblHeaderLogoValidResult.setText("")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderChecksumResult.setText("")
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderROMChecksumResult.setText("")
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGHeaderRtcResult.setText("")
self.lblDMGHeaderBootlogoResult.setText("")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblDMGHeaderROMChecksumResult.setText("")
self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
elif data["mapper_raw"] == 0x205: # Datel Orbit V2
self.lblHeaderRtcResult.setText("")
self.lblHeaderRevisionResult.setText("")
self.lblHeaderLogoValidResult.setText("")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderChecksumResult.setText("")
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderROMChecksumResult.setText("")
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGHeaderRtcResult.setText("")
self.lblDMGHeaderBootlogoResult.setText("")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblDMGGameCodeRevisionResult.setText("")
self.lblDMGGameCodeRevisionResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblDMGHeaderROMChecksumResult.setText("")
self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
elif data["mapper_raw"] == 0x204: # Sachen
self.lblHeaderRtcResult.setText("")
self.lblHeaderRevisionResult.setText("")
self.lblHeaderLogoValidResult.setText("")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblDMGGameNameResult.setText(data["game_title"])
self.lblDMGHeaderRtcResult.setText("")
self.lblDMGRomTitleResult.setText("")
self.lblDMGGameCodeRevisionResult.setText("")
self.lblDMGHeaderBootlogoResult.setText("")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
if "logo_sachen" in data:
data["logo_sachen"].putpalette([ 255, 255, 255, 128, 128, 128 ])
self.lblHeaderLogoValidResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
else:
if "logo" in data:
if data['logo_correct']:
data["logo"].putpalette([ 255, 255, 255, self.TEXT_COLOR[0], self.TEXT_COLOR[1], self.TEXT_COLOR[2] ])
else:
data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
self.lblHeaderLogoValidResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
self.grpAGBCartridgeInfo.setVisible(False)
self.grpDMGCartridgeInfo.setVisible(True)
@ -2119,12 +2105,28 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if "flash_type" in data:
self.cmbAGBCartridgeTypeResult.setCurrentIndex(data["flash_type"])
self.lblAGBHeaderTitleResult.setText(data['game_title'])
self.lblAGBHeaderCodeResult.setText(data['game_code'])
self.lblAGBHeaderRevisionResult.setText(str(data['version']))
self.lblAGBRomTitleResult.setText(data['game_title'])
if data["db"] is not None:
self.lblAGBHeaderGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
temp = data["db"]["gn"]
self.lblAGBGameNameResult.setText(temp)
while self.lblAGBGameNameResult.fontMetrics().boundingRect(self.lblAGBGameNameResult.text()).width() > 170:
temp = temp[:-1]
self.lblAGBGameNameResult.setText(temp + "")
if temp != data["db"]["gn"]:
self.lblAGBGameNameResult.setToolTip(data["db"]["gn"])
else:
self.lblAGBGameNameResult.setToolTip("")
else:
if len(data["game_code"]) > 0:
self.lblAGBHeaderGameCodeRevisionResult.setText("{:s}-{:s}".format(data['game_code'], str(data['version'])))
else:
self.lblAGBHeaderGameCodeRevisionResult.setText("")
self.lblAGBGameNameResult.setText("(Not in database)")
if data['logo_correct']:
self.lblAGBHeaderLogoValidResult.setText("OK")
self.lblAGBHeaderLogoValidResult.setStyleSheet(self.lblAGBHeaderCodeResult.styleSheet())
self.lblAGBHeaderLogoValidResult.setStyleSheet(self.lblAGBRomTitleResult.styleSheet())
else:
self.lblAGBHeaderLogoValidResult.setText("Invalid")
self.lblAGBHeaderLogoValidResult.setStyleSheet("QLabel { color: red; }")
@ -2133,23 +2135,22 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data['header_checksum_correct']:
self.lblAGBHeaderChecksumResult.setText("Valid (0x{:02X})".format(data['header_checksum']))
self.lblAGBHeaderChecksumResult.setStyleSheet(self.lblAGBHeaderCodeResult.styleSheet())
self.lblAGBHeaderChecksumResult.setStyleSheet(self.lblAGBRomTitleResult.styleSheet())
else:
self.lblAGBHeaderChecksumResult.setText("Invalid (0x{:02X})".format(data['header_checksum']))
self.lblAGBHeaderChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setText("Not available")
Util.AGB_Global_CRC32 = 0
db_agb_entry = data["db"]
if db_agb_entry is None:
self.lblAGBHeaderROMChecksumResult.setText("Not in database")
if db_agb_entry != None:
self.cmbAGBHeaderROMSizeResult.setCurrentIndex(Util.AGB_Header_ROM_Sizes_Map.index(db_agb_entry['rs']))
if data["db"] is None:
self.lblAGBHeaderROMChecksumResult.setText("(Not in database)")
if data["db"] != None:
self.cmbAGBHeaderROMSizeResult.setCurrentIndex(Util.AGB_Header_ROM_Sizes_Map.index(data["db"]['rs']))
if data["rom_size_calc"] < 0x400000:
self.lblAGBHeaderROMChecksumResult.setText("In database (0x{:06X})".format(db_agb_entry['rc']))
Util.AGB_Global_CRC32 = db_agb_entry['rc']
self.lblAGBHeaderROMChecksumResult.setText("In database (0x{:06X})".format(data["db"]['rc']))
Util.AGB_Global_CRC32 = data["db"]['rc']
elif data["rom_size"] != 0:
if not data["rom_size"] in Util.AGB_Header_ROM_Sizes_Map:
data["rom_size"] = 0x2000000
@ -2162,21 +2163,21 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data["save_type"] == None:
self.cmbAGBSaveTypeResult.setCurrentIndex(0)
if db_agb_entry != None:
if db_agb_entry['st'] < len(Util.AGB_Header_Save_Types):
self.cmbAGBSaveTypeResult.setCurrentIndex(db_agb_entry['st'])
if data["db"] != None:
if data["db"]['st'] < len(Util.AGB_Header_Save_Types):
self.cmbAGBSaveTypeResult.setCurrentIndex(data["db"]['st'])
if data["dacs_8m"] is True:
self.cmbAGBSaveTypeResult.setCurrentIndex(6)
if data['empty'] == True: # defaults
if data['empty_nocart'] == True:
self.lblAGBHeaderTitleResult.setText("(No cartridge connected)")
self.lblAGBGameNameResult.setText("(No cartridge connected)")
else:
self.lblAGBHeaderTitleResult.setText("(No ROM data detected)")
self.lblAGBHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.lblAGBGameNameResult.setText("(No ROM data detected)")
self.lblAGBGameNameResult.setStyleSheet("QLabel { color: red; }")
self.cmbAGBSaveTypeResult.setCurrentIndex(0)
else:
self.lblAGBHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblAGBGameNameResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
if data['logo_correct'] and data['3d_memory'] is True:
cart_types = self.CONN.GetSupportedCartridgesAGB()
for i in range(0, len(cart_types[0])):
@ -2186,7 +2187,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.grpDMGCartridgeInfo.setVisible(False)
self.grpAGBCartridgeInfo.setVisible(True)
if data['logo_correct'] and isinstance(db_agb_entry, dict) and "rs" in db_agb_entry and db_agb_entry['rs'] == 0x4000000 and not self.CONN.IsSupported3dMemory() and resetStatus:
if data['logo_correct'] and isinstance(data["db"], dict) and "rs" in data["db"] and data["db"]['rs'] == 0x4000000 and not self.CONN.IsSupported3dMemory() and resetStatus:
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge uses a Memory Bank Controller that may not be completely supported by the firmware of the {:s} device. Please check for firmware updates in the Tools menu or the makers website.".format(self.CONN.GetFullName()), QtWidgets.QMessageBox.Ok)
if resetStatus:
@ -2234,7 +2235,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
try:
if save_type is not None and save_type is not False:
if self.CONN.GetMode() == "DMG":
self.cmbHeaderRAMSizeResult.setCurrentIndex(save_type)
self.cmbDMGHeaderSaveTypeResult.setCurrentIndex(save_type)
elif self.CONN.GetMode() == "AGB":
self.cmbAGBSaveTypeResult.setCurrentIndex(save_type)
except:
@ -2272,7 +2273,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
# Messages
# Header
msg_header_s = "<b>Game Title:</b> {:s}<br>".format(header["game_title"])
msg_header_s = "<b>ROM Title:</b> {:s}<br>".format(header["game_title"])
# Save Type
msg_save_type_s = ""
@ -2290,11 +2291,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
try:
if "Batteryless SRAM" in Util.AGB_Header_Save_Types[save_type]:
if save_size == 0:
temp += " (unknown size)<br><b>Possible Batteryless SRAM Location:</b> 0x{:X} (expecting {:s} of SRAM)".format(header["batteryless_sram"]["bl_offset"], Util.formatFileSize(header["batteryless_sram"]["bl_size"], asInt=True))
temp += " (unknown size)<br><b>Batteryless SRAM Location:</b> 0x{:X}0x{:X} ({:s})".format(header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(header["batteryless_sram"]["bl_size"], asInt=True))
elif save_size == header["batteryless_sram"]["bl_size"]:
temp += " ({:s})<br><b>Possible Batteryless SRAM Location:</b> 0x{:X}".format(Util.formatFileSize(save_size, asInt=True), header["batteryless_sram"]["bl_offset"])
temp += " ({:s})<br><b>Batteryless SRAM Location:</b> 0x{:X}0x{:X} ({:s})".format(Util.formatFileSize(save_size, asInt=True), header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(header["batteryless_sram"]["bl_size"], asInt=True))
else:
temp += " ({:s})<br><b>Possible Batteryless SRAM Location:</b> 0x{:X} (expecting {:s} of SRAM)".format(Util.formatFileSize(save_size, asInt=True), header["batteryless_sram"]["bl_offset"], Util.formatFileSize(header["batteryless_sram"]["bl_size"], asInt=True))
temp += " ({:s})<br><b>Batteryless SRAM Location:</b> 0x{:X}0x{:X} ({:s})".format(Util.formatFileSize(save_size, asInt=True), header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(header["batteryless_sram"]["bl_size"], asInt=True))
except:
pass
@ -2684,7 +2685,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if self.CONN.GetMode() == "DMG":
header = self.CONN.ReadInfo(setPinsAsInputs=True)
if header["mapper_raw"] == 252: # GBD
args = { "path":None, "mbc":252, "save_type":128*1024, "rtc":False }
args = { "path":None, "mbc":252, "save_type":4, "rtc":False }
self.CONN.BackupRAM(fncSetProgress=False, args=args)
data = self.CONN.INFO["data"]

View File

@ -39,11 +39,11 @@ class DMG_MBC:
mbc_id = args["mbc"]
if mbc_id in (0x01, 0x02, 0x03): # 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY',
return DMG_MBC1(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x06: # 0x06:'MBC2+SRAM+BATTERY',
elif mbc_id in (0x05, 0x06): # 0x06:'MBC2+SRAM+BATTERY',
return DMG_MBC2(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id in (0x10, 0x13): # 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY',
elif mbc_id in (0x10, 0x11, 0x12, 0x13, 0x110): # 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY',
return DMG_MBC3(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id in (0x19, 0x1B, 0x1C, 0x1E): # 0x19:'MBC5', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY',
elif mbc_id in (0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E): # 0x19:'MBC5', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY',
return DMG_MBC5(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x20: # 0x20:'MBC6+FLASH+SRAM+BATTERY',
return DMG_MBC6(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
@ -53,7 +53,7 @@ class DMG_MBC:
return DMG_MBC1M(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id in (0x0B, 0x0D): # 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY',
return DMG_MMM01(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0xFC: # 0xFC:'GBD+SRAM+BATTERY',
elif mbc_id == 0xFC: # 0xFC:'MAC-GBD+SRAM+BATTERY',
return DMG_GBD(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x105: # 0x105:'G-MMC1+SRAM+BATTERY',
return DMG_GMMC1(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
@ -462,23 +462,6 @@ class DMG_MBC5(DMG_MBC):
]
self.CartWrite(commands)
# def SelectBankROM(self, index):
# dprint(self.GetName(), "|", index)
# if index == 0 or index >= 256:
# commands = [
# [ 0x3000, ((index >> 8) & 0xFF) ],
# [ 0x2100, index & 0xFF ],
# ]
# else:
# commands = [
# [ 0x2100, index & 0xFF ],
# ]
# start_address = 0 if index == 0 else 0x4000
# self.CartWrite(commands)
# return (start_address, self.ROM_BANK_SIZE)
def GetMaxROMSize(self):
return 8*1024*1024
@ -679,7 +662,7 @@ class DMG_MMM01(DMG_MBC):
class DMG_GBD(DMG_MBC5):
def GetName(self):
return "GBD"
return "MAC-GBD"
def GetMaxROMSize(self):
return 1*1024*1024

View File

@ -102,6 +102,7 @@ class RomFileAGB:
if (data["game_title"] == "NGC-HIKARU3" and data["game_code"] == "GHTJ" and data["header_checksum"] == 0xB3):
data["dacs_8m"] = True
data["unchanged"] = data
self.DATA = data
data["db"] = self.GetDatabaseEntry()
return data
@ -115,6 +116,16 @@ class RomFileAGB:
db = json.loads(db)
if data["header_sha1"] in db.keys():
db_entry = db[data["header_sha1"]]
if db_entry["gc"] in ("ZMAJ", "ZMBJ", "ZMDE"):
db_entry["gc"] = "AGS-{:s}".format(db_entry["gc"])
elif db_entry["gc"] == "ZBBJ":
db_entry["gc"] = "NTR-{:s}".format(db_entry["gc"])
elif db_entry["gc"] == "PEAJ":
db_entry["gc"] = "PEC-{:s}".format(db_entry["gc"])
elif db_entry["gc"] in ("PSAJ", "PSAE"):
db_entry["gc"] = "PES-{:s}".format(db_entry["gc"])
else:
db_entry["gc"] = "AGB-{:s}".format(db_entry["gc"])
else:
print("FAIL: Database for Game Boy Advance titles not found at {0:s}/db_AGB.json".format(Util.CONFIG_PATH))
return db_entry

View File

@ -2,7 +2,7 @@
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
import hashlib, re, string, struct, os, json
import hashlib, re, string, struct, os, json, copy
from . import Util
try:
@ -139,8 +139,31 @@ class RomFileDMG:
data["rom_checksum"] = int(256 * buffer[0x14E] + buffer[0x14F])
data["rom_checksum_calc"] = self.CalcChecksumGlobal()
data["rom_checksum_correct"] = data["rom_checksum"] == data["rom_checksum_calc"]
data["unchanged"] = copy.copy(data)
if not unchanged:
# MBC2
if data["mapper_raw"] == 0x06:
data["ram_size_raw"] = 0x100
# MBC30
if data["mapper_raw"] == 0x10 and data["ram_size_raw"] == 0x05:
data["mapper_raw"] += 0x100
# MBC6
if data["mapper_raw"] == 0x20:
data["ram_size_raw"] = 0x104
# MBC7
if data["mapper_raw"] == 0x22 and data["game_title"] in ("KORO2 KIRBY", "KIRBY TNT"):
data["ram_size_raw"] = 0x101
elif data["mapper_raw"] == 0x22 and data["game_title"] == "CMASTER":
data["ram_size_raw"] = 0x102
# TAMA5
if data["mapper_raw"] == 0xFD:
data["ram_size_raw"] = 0x103
# MBC1M
if data["mapper_raw"] == 0x03 and data["game_title"] == "MOMOCOL" and data["header_checksum"] == 0x28 or \
data["mapper_raw"] == 0x01 and data["game_title"] == "BOMCOL" and data["header_checksum"] == 0x86 or \

View File

@ -7,9 +7,6 @@ from .pyside import QtCore, QtWidgets, QtGui
class UserInputDialog(QtWidgets.QDialog):
APP = None
def greetings(self):
print("hi")
def __init__(self, app, icon=None, args=None):
super(UserInputDialog, self).__init__(app)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))

View File

@ -7,9 +7,9 @@ from enum import Enum
# Common constants
APPNAME = "FlashGBX"
VERSION_PEP440 = "3.24"
VERSION_PEP440 = "3.25"
VERSION = "v{:s}".format(VERSION_PEP440)
VERSION_TIMESTAMP = 1680999807
VERSION_TIMESTAMP = 1681494424
DEBUG = False
DEBUG_LOG = []
APP_PATH = ""
@ -23,14 +23,14 @@ AGB_Global_CRC32 = 0
AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS" }
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000 ]
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
DMG_Mapper_Types = { "None":[ 0x00 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x06 ], "MBC3/MBC30":[ 0x10, 0x12, 0x13 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "256M Multi Cart":[ 0x201 ], "Wisdom Tree":[ 0x202 ], "Xploder GB":[ 0x203 ], "Sachen":[ 0x204 ], "Datel Orbit V2":[ 0x205 ] }
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'MAC-GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ] }
DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB" ]
DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A ]
DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]
DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)" ]
DMG_Header_RAM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204 ]
DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000 ] # RAM size in bytes
DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KiB)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)" ]
DMG_Header_RAM_Sizes_Map = [ 0x00, 0x100, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204 ]
DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000 ] # RAM size in bytes
DMG_Header_SGB = { 0x00:'No support', 0x03:'Supported' }
DMG_Header_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' }
@ -57,7 +57,7 @@ class IniSettings():
else:
with open(path, "w+", encoding="UTF-8") as f: f.close()
except:
print("Error accessing the configuration directory or settings file.")
print("Cant access the configuration directory or settings file.")
return
self.FILENAME = path
self.SETTINGS = configparser.RawConfigParser()
@ -66,7 +66,7 @@ class IniSettings():
self.Reload()
except configparser.MissingSectionHeaderError:
print("Resetting invalid configuration file...")
os.unlink(path)
with open(path, "w+", encoding="UTF-8") as f: f.close()
path = ""
if path == "":
@ -460,7 +460,29 @@ def ParseCFI(buffer):
return info
def ConvertMapperToMapperType(mapper_raw):
i = 0
for (k, v) in DMG_Mapper_Types.items():
if i == 0:
retval = (k, v, i)
if mapper_raw in v:
retval = (k, v, i)
break
i += 1
# (string, list of ids, index in DMG_Mapper_Types)
return retval
def ConvertMapperTypeToMapper(mapper_type):
i = 0
for (_, v) in DMG_Mapper_Types.items():
if mapper_type == i:
return v[0]
i += 1
return 0
def GetDumpReport(di, device):
header = di["header"]["unchanged"]
if "db" in di["header"]: header["db"] = di["header"]["db"]
if di["system"] == "DMG":
mode = "DMG"
di["system"] = "Game Boy"
@ -470,7 +492,7 @@ def GetDumpReport(di, device):
di["rom_size"] = "{:,} bytes".format(di["rom_size"])
if di["mapper_type"] in DMG_Header_Mapper:
di["mapper_type"] = "{:s}".format(DMG_Header_Mapper[di["mapper_type"]])
di["mapper_type"] = "{:s}".format(ConvertMapperToMapperType(di["mapper_type"])[0])
else:
di["mapper_type"] = "0x{:02X}".format(di["mapper_type"])
@ -530,41 +552,41 @@ def GetDumpReport(di, device):
"* Cartridge Type: {cart_type:s}\n" \
.format(system=di["system"], rom_size=di["rom_size"], cart_type=di["cart_type"])
di["hdr_logo"] = "OK" if di["header"]["logo_correct"] else "Invalid"
di["header"]["game_title_raw"] = di["header"]["game_title_raw"].replace("\0", "")
di["hdr_logo"] = "OK" if header["logo_correct"] else "Invalid"
header["game_title_raw"] = header["game_title_raw"].replace("\0", "")
if mode == "DMG":
game_title = di["header"]["game_title"]
game_title = header["game_title"]
game_code = ""
if di["header"]["cgb"] in (0xC0, 0x80):
if "game_code" in di["header"] and len(di["header"]["game_code"]) > 0:
game_code = "* Game Code: {:s}\n".format(di["header"]["game_code"])
if header["cgb"] in (0xC0, 0x80):
if "game_code" in header and len(header["game_code"]) > 0:
game_code = "* Game Code: {:s}\n".format(header["game_code"])
di["hdr_target_platform"] = "Game Boy Color"
elif di["header"]["old_lic"] == 0x33 and di["header"]["sgb"] == 0x03:
elif header["old_lic"] == 0x33 and header["sgb"] == 0x03:
di["hdr_target_platform"] = "Super Game Boy"
else:
di["hdr_target_platform"] = "Game Boy"
if di["header"]["old_lic"] == 0x33 and di["header"]["sgb"] == 0x03:
if header["old_lic"] == 0x33 and header["sgb"] == 0x03:
di["hdr_sgb"] = "Supported"
else:
di["hdr_sgb"] = "No support" # (146h=0x{:02X}, 14Bh=0x{:02X})".format(di["header"]["sgb"], di["header"]["old_lic"])
if di["header"]["cgb"] in DMG_Header_CGB:
di["hdr_cgb"] = "{:s}".format(DMG_Header_CGB[di["header"]["cgb"]])
di["hdr_sgb"] = "No support" # (146h=0x{:02X}, 14Bh=0x{:02X})".format(header["sgb"], header["old_lic"])
if header["cgb"] in DMG_Header_CGB:
di["hdr_cgb"] = "{:s}".format(DMG_Header_CGB[header["cgb"]])
else:
di["hdr_cgb"] = "Unknown (0x{:02X})".format(di["header"]["cgb"])
di["hdr_cgb"] = "Unknown (0x{:02X})".format(header["cgb"])
di["hdr_header_checksum"] = "OK (0x{:02X})".format(di["header"]["header_checksum"]) if di["header"]["header_checksum_correct"] else "Invalid (0x{:02X}≠0x{:02X})".format(di["header"]["header_checksum_calc"], di["header"]["header_checksum"])
di["header"]["rom_checksum_calc"] = device.INFO["rom_checksum_calc"]
di["header"]["rom_checksum_correct"] = di["header"]["rom_checksum_calc"] == di["header"]["rom_checksum"]
di["hdr_rom_checksum"] = "OK (0x{:04X})".format(di["header"]["rom_checksum"]) if di["header"]["rom_checksum_correct"] else "Invalid (0x{:04X}≠0x{:04X})".format(di["header"]["rom_checksum_calc"], di["header"]["rom_checksum"])
di["hdr_header_checksum"] = "OK (0x{:02X})".format(header["header_checksum"]) if header["header_checksum_correct"] else "Invalid (0x{:02X}≠0x{:02X})".format(header["header_checksum_calc"], header["header_checksum"])
header["rom_checksum_calc"] = device.INFO["rom_checksum_calc"]
header["rom_checksum_correct"] = header["rom_checksum_calc"] == header["rom_checksum"]
di["hdr_rom_checksum"] = "OK (0x{:04X})".format(header["rom_checksum"]) if header["rom_checksum_correct"] else "Invalid (0x{:04X}≠0x{:04X})".format(header["rom_checksum_calc"], header["rom_checksum"])
di["hdr_rom_size"] = di["header"]["rom_size_raw"]
di["hdr_rom_size"] = header["rom_size_raw"]
if di["hdr_rom_size"] in DMG_Header_ROM_Sizes_Map:
di["hdr_rom_size"] = "{:s} (0x{:02X})".format(DMG_Header_ROM_Sizes[DMG_Header_ROM_Sizes_Map.index(di["hdr_rom_size"])], di["hdr_rom_size"])
else:
di["hdr_rom_size"] = "Unknown (0x{:02X})".format(di["hdr_rom_size"])
di["hdr_save_type"] = di["header"]["ram_size_raw"]
di["hdr_save_type"] = header["ram_size_raw"]
if di["hdr_save_type"] == 0x00:
di["hdr_save_type"] = "No SRAM (0x{:02X})".format(di["hdr_save_type"])
elif di["hdr_save_type"] in DMG_Header_RAM_Sizes_Map:
@ -572,7 +594,7 @@ def GetDumpReport(di, device):
else:
di["hdr_save_type"] = "Unknown (0x{:02X})".format(di["hdr_save_type"])
di["hdr_mapper_type"] = di["header"]["mapper_raw"]
di["hdr_mapper_type"] = header["mapper_raw"]
if di["hdr_mapper_type"] in DMG_Header_Mapper:
di["hdr_mapper_type"] = "{:s} (0x{:02X})".format(DMG_Header_Mapper[di["hdr_mapper_type"]], di["hdr_mapper_type"])
else:
@ -592,7 +614,7 @@ def GetDumpReport(di, device):
"* SRAM Size: {hdr_save_type:s}\n" \
"* Mapper Type: {hdr_mapper_type:s}\n" \
"* Target Platform: {hdr_target_platform:s}\n" \
.format(hdr_game_title=game_title, hdr_game_code=game_code, hdr_revision=str(di["header"]["version"]), hdr_sgb=di["hdr_sgb"], hdr_cgb=di["hdr_cgb"], hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], hdr_rom_checksum=di["hdr_rom_checksum"], hdr_rom_size=di["hdr_rom_size"], hdr_save_type=di["hdr_save_type"], hdr_mapper_type=di["hdr_mapper_type"], hdr_target_platform=di["hdr_target_platform"])
.format(hdr_game_title=game_title, hdr_game_code=game_code, hdr_revision=str(header["version"]), hdr_sgb=di["hdr_sgb"], hdr_cgb=di["hdr_cgb"], hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], hdr_rom_checksum=di["hdr_rom_checksum"], hdr_rom_size=di["hdr_rom_size"], hdr_save_type=di["hdr_save_type"], hdr_mapper_type=di["hdr_mapper_type"], hdr_target_platform=di["hdr_target_platform"])
if "gbmem" in di and di["gbmem"] is not None:
raw_data = ""
for i in range(0, 4):
@ -622,10 +644,22 @@ def GetDumpReport(di, device):
s += "" \
"* GB-Memory Data: {:s}\n" \
.format(raw_data)
if header["db"] is not None and header["db"]["rc"] == di["hash_crc32"]:
db = header["db"]
s += "\n== Database Match ==\n"
if "gn" in db and "ne" in db: s += "* Game Name: {:s} {:s}\n".format(db["gn"], db["ne"])
elif "gn" in db: s += "* Game Name: {:s}\n".format(db["gn"])
if "rg" in db: s += "* Region: {:s}\n".format(db["rg"])
if "lg" in db: s += "* Language(s): {:s}\n".format(db["lg"])
if "rv" in db: s += "* Revision: {:s}\n".format(db["rv"])
if "gc" in db: s += "* Game Code: {:s}\n".format(db["gc"])
if "rc" in db: s += "* ROM CRC32: {:08x}\n".format(db["rc"])
if "rs" in db: s += "* ROM Size: {:s}\n".format(formatFileSize(db["rs"], asInt=True))
if mode == "AGB":
di["header"]["game_code_raw"] = di["header"]["game_code_raw"].replace("\0", "")
di["hdr_header_checksum"] = "OK (0x{:02X})".format(di["header"]["header_checksum"]) if di["header"]["header_checksum_correct"] else "Invalid (0x{:02X}≠0x{:02X})".format(di["header"]["header_checksum_calc"], di["header"]["header_checksum"])
header["game_code_raw"] = header["game_code_raw"].replace("\0", "")
di["hdr_header_checksum"] = "OK (0x{:02X})".format(header["header_checksum"]) if header["header_checksum_correct"] else "Invalid (0x{:02X}≠0x{:02X})".format(header["header_checksum_calc"], header["header_checksum"])
if "agb_savelib" not in di:
di["agb_savelib"] = "None"
elif "SRAM_F_" in di["agb_savelib"]:
@ -652,11 +686,25 @@ def GetDumpReport(di, device):
"* Nintendo Logo: {hdr_logo:s}\n" \
"* Header Checksum: {hdr_header_checksum:s}\n" \
"* Save Type: {agb_savetype}\n" \
.format(hdr_game_title=di["header"]["game_title_raw"], hdr_game_code=di["header"]["game_code_raw"], hdr_revision=str(di["header"]["version"]), hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], agb_savetype=di["agb_savelib"])
.format(hdr_game_title=header["game_title_raw"], hdr_game_code=header["game_code_raw"], hdr_revision=str(header["version"]), hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], agb_savetype=di["agb_savelib"])
if "agb_save_flash_id" in di and di["agb_save_flash_id"] is not None:
s += "" \
"* Save Flash Chip: {agb_save_flash_chip_name:s} (0x{agb_save_flash_chip_id:04X})\n" \
.format(agb_save_flash_chip_name=di["agb_save_flash_id"][1], agb_save_flash_chip_id=di["agb_save_flash_id"][0])
if header["db"] is not None and header["db"]["rc"] == di["hash_crc32"]:
db = header["db"]
s += "\n== Database Match ==\n"
if "gn" in db and "ne" in db: s += "* Game Name: {:s} {:s}\n".format(db["gn"], db["ne"])
elif "gn" in db: s += "* Game Name: {:s}\n".format(db["gn"])
if "rg" in db: s += "* Region: {:s}\n".format(db["rg"])
if "lg" in db: s += "* Language(s): {:s}\n".format(db["lg"])
if "rv" in db: s += "* Revision: {:s}\n".format(db["rv"])
if "gc" in db: s += "* Game Code: {:s}\n".format(db["gc"])
if "rc" in db: s += "* ROM CRC32: {:08x}\n".format(db["rc"])
if "rs" in db: s += "* ROM Size: {:s}\n".format(formatFileSize(db["rs"], asInt=True))
if "st" in db: s += "* Save Type: {:s}\n".format(AGB_Header_Save_Types[db["st"]])
#if "ss" in db: s += "* Save Size: {:s}\n".format(formatFileSize(db["ss"], asInt=True))
return s
@ -717,8 +765,11 @@ def GenerateFileName(mode, header, settings):
path = re.sub(r"[<>:\"/\\|\?\*]", "_", path)
path += "." + path_extension
if fe_ni and header["db"] is not None and (mode != "DMG" or get_mbc_name(header["mapper_raw"]) != "G-MMC1"):
path = "{:s} {:s}.{:s}".format(header["db"]["gn"], header["db"]["ne"], path_extension)
if fe_ni and header["db"] is not None:
if mode == "DMG" and get_mbc_name(header["mapper_raw"]) == "G-MMC1":
path = "{:s}.{:s}".format(header["db"]["gn"], path_extension)
else:
path = "{:s} {:s}.{:s}".format(header["db"]["gn"], header["db"]["ne"], path_extension)
return path

View File

@ -32122,8 +32122,8 @@
"gn": "Daigassou! Band-Brothers - Request Selection",
"ne": "(Japan) (DS Expansion Cartridge)",
"gc": "ZBBJ",
"rc": 3284246738,
"rs": 4194304,
"rc": 927851296,
"rs": 1048576,
"st": 0,
"ss": 0,
"lg": "Ja",
@ -32155,8 +32155,8 @@
"gn": "Nintendo MP3 Player",
"ne": "(Europe) (En,Fr,De,Es,It)",
"gc": "ZMDE",
"rc": 2256268406,
"rs": 4194304,
"rc": 384159986,
"rs": 1048576,
"st": 0,
"ss": 0,
"lg": "En,Fr,De,Es,It",

View File

@ -0,0 +1,96 @@
{
"type":"DMG",
"names":[
"DRV with AM29LV160DT and ALTERA CPLD"
],
"flash_ids":[
[ 0x02, 0x02, 0xC4, 0xC4 ]
],
"voltage":3.3,
"flash_size":0x200000,
"start_addr":0x4000,
"first_bank":0,
"write_pin":"WR",
"sector_size":[
[0x10000, 31],
[0x8000, 1],
[0x2000, 2],
[0x4000, 1]
],
"chip_erase_timeout":120,
"mbc":"manual",
"command_set":"AMD",
"commands":{
"reset":[
[ 0x4000, 0xF0 ]
],
"read_identifier":[
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ 0x4AAA, 0x90 ]
],
"read_cfi":[
[ 0x4AAA, 0x98 ]
],
"chip_erase":[
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ 0x4AAA, 0x80 ],
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ 0x4AAA, 0x10 ]
],
"chip_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ 0, 0xFF, 0xFF ]
],
"sector_erase":[
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ 0x4AAA, 0x80 ],
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ "SA", 0x30 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0xFF, 0xFF ]
],
"buffer_write":[
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ "SA", 0x26 ],
[ "SA", "BS" ],
[ "PA", "PD" ],
[ "SA", 0x2A ]
],
"buffer_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", "PD", 0xFFFF ]
],
"single_write":[
[ 0x4AAA, 0xA9 ],
[ 0x4555, 0x56 ],
[ 0x4AAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -3,7 +3,6 @@
# Author: Lesserkuma (github.com/lesserkuma)
import zipfile, serial, struct, time, random, hashlib, datetime
from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget
try:
from . import Util
except ImportError:
@ -85,255 +84,259 @@ class FirmwareUpdater():
time.sleep(0.2)
return 1
class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None
DEVICE = None
FWUPD = None
DEV_NAME = "GBxCart RW"
FW_VER = ""
PCB_VER = ""
try:
from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget
class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None
DEVICE = None
FWUPD = None
DEV_NAME = "GBxCart RW"
FW_VER = ""
PCB_VER = ""
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle("FlashGBX Firmware Updater for GBxCart RW v1.4")
self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint)
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle("FlashGBX Firmware Updater for GBxCart RW v1.4")
self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint)
self.APP = app
if device is not None:
self.FWUPD = FirmwareUpdater(app_path, device.GetPort())
self.DEV_NAME = device.GetName()
self.FW_VER = device.GetFirmwareVersion(more=True)
self.PCB_VER = device.GetPCBVersion()
self.DEVICE = device
else:
self.APP.QT_APP.processEvents()
text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nOlder GBxCart RW revisions can be updated only after connecting to them first."
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
self.APP = app
if device is not None:
self.FWUPD = FirmwareUpdater(app_path, device.GetPort())
self.DEV_NAME = device.GetName()
self.FW_VER = device.GetFirmwareVersion(more=True)
self.PCB_VER = device.GetPCBVersion()
self.DEVICE = device
else:
self.APP.QT_APP.processEvents()
text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nOlder GBxCart RW revisions can be updated only after connecting to them first."
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
self.FWUPD = FirmwareUpdater(app_path, None)
self.layout = QtWidgets.QGridLayout()
self.layout.setContentsMargins(-1, 8, -1, 8)
self.layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.layout_device = QtWidgets.QVBoxLayout()
# ↓↓↓ Current Device Information
self.grpDeviceInfo = QtWidgets.QGroupBox("Current Firmware")
self.grpDeviceInfo.setMinimumWidth(420)
self.grpDeviceInfoLayout = QtWidgets.QVBoxLayout()
self.grpDeviceInfoLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo1 = QtWidgets.QHBoxLayout()
self.lblDeviceName = QtWidgets.QLabel("Device:")
self.lblDeviceName.setMinimumWidth(120)
self.lblDeviceNameResult = QtWidgets.QLabel("GBxCart RW")
rowDeviceInfo1.addWidget(self.lblDeviceName)
rowDeviceInfo1.addWidget(self.lblDeviceNameResult)
rowDeviceInfo1.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo1)
rowDeviceInfo3 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer.setMinimumWidth(120)
self.lblDeviceFWVerResult = QtWidgets.QLabel("R30+L1")
rowDeviceInfo3.addWidget(self.lblDeviceFWVer)
rowDeviceInfo3.addWidget(self.lblDeviceFWVerResult)
rowDeviceInfo3.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo3)
rowDeviceInfo2 = QtWidgets.QHBoxLayout()
self.lblDevicePCBVer = QtWidgets.QLabel("PCB version:")
self.lblDevicePCBVer.setMinimumWidth(120)
self.optDevicePCBVer14 = QtWidgets.QRadioButton("v1.4")
self.connect(self.optDevicePCBVer14, QtCore.SIGNAL("clicked()"), self.SetPCBVersion)
self.optDevicePCBVer14a = QtWidgets.QRadioButton("v1.4a")
self.connect(self.optDevicePCBVer14a, QtCore.SIGNAL("clicked()"), self.SetPCBVersion)
rowDeviceInfo2.addWidget(self.lblDevicePCBVer)
rowDeviceInfo2.addWidget(self.optDevicePCBVer14)
rowDeviceInfo2.addWidget(self.optDevicePCBVer14a)
rowDeviceInfo2.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo2)
self.grpDeviceInfo.setLayout(self.grpDeviceInfoLayout)
self.layout_device.addWidget(self.grpDeviceInfo)
# ↑↑↑ Current Device Information
# ↓↓↓ Available Firmware Updates
self.grpAvailableFwUpdates = QtWidgets.QGroupBox("Available Firmware")
self.grpAvailableFwUpdates.setMinimumWidth(400)
self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout()
self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo4 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer2 = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer2.setMinimumWidth(120)
self.lblDeviceFWVer2Result = QtWidgets.QLabel("(Please choose the PCB version)")
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2)
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2Result)
rowDeviceInfo4.addStretch(1)
self.grpAvailableFwUpdatesLayout.addLayout(rowDeviceInfo4)
self.rowUpdate = QtWidgets.QHBoxLayout()
self.btnUpdate = QtWidgets.QPushButton("Install Firmware Update")
self.btnUpdate.setMinimumWidth(200)
self.btnUpdate.setContentsMargins(20, 20, 20, 20)
self.connect(self.btnUpdate, QtCore.SIGNAL("clicked()"), lambda: [ self.UpdateFirmware() ])
self.rowUpdate.addStretch()
self.rowUpdate.addWidget(self.btnUpdate)
self.rowUpdate.addStretch()
self.grpAvailableFwUpdatesLayout.addSpacing(3)
self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate)
self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout)
self.layout_device.addWidget(self.grpAvailableFwUpdates)
# ↑↑↑ Available Firmware Updates
self.grpStatus = QtWidgets.QGroupBox("")
self.grpStatusLayout = QtWidgets.QGridLayout()
self.prgStatus = QtWidgets.QProgressBar()
self.prgStatus.setMinimum(0)
self.prgStatus.setMaximum(1000)
self.prgStatus.setValue(0)
self.lblStatus = QtWidgets.QLabel("Ready.")
self.grpStatusLayout.addWidget(self.prgStatus, 1, 0)
self.grpStatusLayout.addWidget(self.lblStatus, 2, 0)
self.grpStatus.setLayout(self.grpStatusLayout)
self.layout_device.addWidget(self.grpStatus)
self.grpFooterLayout = QtWidgets.QHBoxLayout()
self.btnClose = QtWidgets.QPushButton("&Close")
self.connect(self.btnClose, QtCore.SIGNAL("clicked()"), lambda: [ self.reject() ])
self.grpFooterLayout.addStretch()
self.grpFooterLayout.addWidget(self.btnClose)
self.layout_device.addItem(self.grpFooterLayout)
self.layout.addLayout(self.layout_device, 0, 0)
self.setLayout(self.layout)
self.lblDeviceNameResult.setText(self.DEV_NAME)
self.lblDeviceFWVerResult.setText(self.FW_VER)
if self.PCB_VER == "v1.4":
self.optDevicePCBVer14.setChecked(True)
elif self.PCB_VER == "v1.4a":
self.optDevicePCBVer14a.setChecked(True)
self.SetPCBVersion()
def SetPCBVersion(self):
if self.optDevicePCBVer14.isChecked():
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip"
elif self.optDevicePCBVer14a.isChecked():
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4a.zip"
else:
return
with zipfile.ZipFile(file_name) as zip:
with zip.open("fw.ini") as f: ini_file = f.read()
ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
self.OFW_VER = self.INI.GetValue("fw_ver")
self.OFW_BUILDTS = self.INI.GetValue("fw_buildts")
self.OFW_TEXT = self.INI.GetValue("fw_text")
self.lblDeviceFWVer2Result.setText("{:s} ({:s})".format(self.OFW_VER, datetime.datetime.fromtimestamp(int(self.OFW_BUILDTS)).astimezone().replace(microsecond=0).isoformat()))
def run(self):
try:
self.layout.update()
self.layout.activate()
screenGeometry = QDesktopWidget().screenGeometry(self)
x = (screenGeometry.width() - self.width()) / 2
y = (screenGeometry.height() - self.height()) / 2
self.move(x, y)
self.show()
except:
return
def hideEvent(self, event):
if self.DEVICE is None:
self.APP.ConnectDevice()
self.APP.activateWindow()
def reject(self):
if self.CloseDialog():
super().reject()
def CloseDialog(self):
if self.btnClose.isEnabled() is False:
text = "<b>WARNING:</b> If you close this window while a firmware update is still running, it may leave the device in an unbootable state. You can still recover it by running the Firmware Updater again later.<br><br>Are you sure you want to close this window?"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.No)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.No: return False
return True
def UpdateFirmware(self):
if self.optDevicePCBVer14.isChecked():
device_name = "v1.4"
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip"
led = "Done"
elif self.optDevicePCBVer14a.isChecked():
device_name = "v1.4a"
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4a.zip"
led = "Status"
else:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="Please select the PCB version of your GBxCart RW device.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
self.APP.DisconnectDevice()
text = "Please follow these steps to proceed with the firmware update:<ol><li>Disconnect the USB cable of your GBxCart RW {dev_name:s} device.</li><li>On the circuit board of your GBxCart RW {dev_name:s}, press and hold down the small button while connecting the USB cable again.</li><li>Keep the small button held for at least 2 seconds, then let go of it. If done right, the green LED labeled “{led:s}” should remain lit.</li><li>Click OK to continue.</li></ol>".format(dev_name=device_name, led=led)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
msgbox.setTextFormat(QtCore.Qt.TextFormat.RichText)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
self.FWUPD = FirmwareUpdater(app_path, None)
self.layout = QtWidgets.QGridLayout()
self.layout.setContentsMargins(-1, 8, -1, 8)
self.layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.layout_device = QtWidgets.QVBoxLayout()
self.btnUpdate.setEnabled(False)
self.btnClose.setEnabled(False)
self.optDevicePCBVer14.setEnabled(False)
self.optDevicePCBVer14a.setEnabled(False)
while True:
ret = self.FWUPD.WriteFirmware(file_name, self.SetStatus)
if ret == 1:
text = "The firmware update is complete!"
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
self.DEVICE = None
self.reject()
return True
elif ret == 2:
text = "The firmware update has failed. Please try again."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
elif ret == 3:
text = "The firmware update file is corrupted. Please re-install the application."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
# ↓↓↓ Current Device Information
self.grpDeviceInfo = QtWidgets.QGroupBox("Current Firmware")
self.grpDeviceInfo.setMinimumWidth(420)
self.grpDeviceInfoLayout = QtWidgets.QVBoxLayout()
self.grpDeviceInfoLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo1 = QtWidgets.QHBoxLayout()
self.lblDeviceName = QtWidgets.QLabel("Device:")
self.lblDeviceName.setMinimumWidth(120)
self.lblDeviceNameResult = QtWidgets.QLabel("GBxCart RW")
rowDeviceInfo1.addWidget(self.lblDeviceName)
rowDeviceInfo1.addWidget(self.lblDeviceNameResult)
rowDeviceInfo1.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo1)
rowDeviceInfo3 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer.setMinimumWidth(120)
self.lblDeviceFWVerResult = QtWidgets.QLabel("R30+L1")
rowDeviceInfo3.addWidget(self.lblDeviceFWVer)
rowDeviceInfo3.addWidget(self.lblDeviceFWVerResult)
rowDeviceInfo3.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo3)
rowDeviceInfo2 = QtWidgets.QHBoxLayout()
self.lblDevicePCBVer = QtWidgets.QLabel("PCB version:")
self.lblDevicePCBVer.setMinimumWidth(120)
self.optDevicePCBVer14 = QtWidgets.QRadioButton("v1.4")
self.connect(self.optDevicePCBVer14, QtCore.SIGNAL("clicked()"), self.SetPCBVersion)
self.optDevicePCBVer14a = QtWidgets.QRadioButton("v1.4a")
self.connect(self.optDevicePCBVer14a, QtCore.SIGNAL("clicked()"), self.SetPCBVersion)
rowDeviceInfo2.addWidget(self.lblDevicePCBVer)
rowDeviceInfo2.addWidget(self.optDevicePCBVer14)
rowDeviceInfo2.addWidget(self.optDevicePCBVer14a)
rowDeviceInfo2.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo2)
self.grpDeviceInfo.setLayout(self.grpDeviceInfoLayout)
self.layout_device.addWidget(self.grpDeviceInfo)
# ↑↑↑ Current Device Information
# ↓↓↓ Available Firmware Updates
self.grpAvailableFwUpdates = QtWidgets.QGroupBox("Available Firmware")
self.grpAvailableFwUpdates.setMinimumWidth(400)
self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout()
self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo4 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer2 = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer2.setMinimumWidth(120)
self.lblDeviceFWVer2Result = QtWidgets.QLabel("(Please choose the PCB version)")
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2)
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2Result)
rowDeviceInfo4.addStretch(1)
self.grpAvailableFwUpdatesLayout.addLayout(rowDeviceInfo4)
self.rowUpdate = QtWidgets.QHBoxLayout()
self.btnUpdate = QtWidgets.QPushButton("Install Firmware Update")
self.btnUpdate.setMinimumWidth(200)
self.btnUpdate.setContentsMargins(20, 20, 20, 20)
self.connect(self.btnUpdate, QtCore.SIGNAL("clicked()"), lambda: [ self.UpdateFirmware() ])
self.rowUpdate.addStretch()
self.rowUpdate.addWidget(self.btnUpdate)
self.rowUpdate.addStretch()
self.grpAvailableFwUpdatesLayout.addSpacing(3)
self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate)
self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout)
self.layout_device.addWidget(self.grpAvailableFwUpdates)
# ↑↑↑ Available Firmware Updates
self.grpStatus = QtWidgets.QGroupBox("")
self.grpStatusLayout = QtWidgets.QGridLayout()
self.prgStatus = QtWidgets.QProgressBar()
self.prgStatus.setMinimum(0)
self.prgStatus.setMaximum(1000)
self.prgStatus.setValue(0)
self.lblStatus = QtWidgets.QLabel("Ready.")
self.grpStatusLayout.addWidget(self.prgStatus, 1, 0)
self.grpStatusLayout.addWidget(self.lblStatus, 2, 0)
self.grpStatus.setLayout(self.grpStatusLayout)
self.layout_device.addWidget(self.grpStatus)
self.grpFooterLayout = QtWidgets.QHBoxLayout()
self.btnClose = QtWidgets.QPushButton("&Close")
self.connect(self.btnClose, QtCore.SIGNAL("clicked()"), lambda: [ self.reject() ])
self.grpFooterLayout.addStretch()
self.grpFooterLayout.addWidget(self.btnClose)
self.layout_device.addItem(self.grpFooterLayout)
self.layout.addLayout(self.layout_device, 0, 0)
self.setLayout(self.layout)
self.lblDeviceNameResult.setText(self.DEV_NAME)
self.lblDeviceFWVerResult.setText(self.FW_VER)
if self.PCB_VER == "v1.4":
self.optDevicePCBVer14.setChecked(True)
elif self.PCB_VER == "v1.4a":
self.optDevicePCBVer14a.setChecked(True)
self.SetPCBVersion()
def SetPCBVersion(self):
if self.optDevicePCBVer14.isChecked():
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip"
elif self.optDevicePCBVer14a.isChecked():
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4a.zip"
else:
return
with zipfile.ZipFile(file_name) as zip:
with zip.open("fw.ini") as f: ini_file = f.read()
ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
self.OFW_VER = self.INI.GetValue("fw_ver")
self.OFW_BUILDTS = self.INI.GetValue("fw_buildts")
self.OFW_TEXT = self.INI.GetValue("fw_text")
self.lblDeviceFWVer2Result.setText("{:s} ({:s})".format(self.OFW_VER, datetime.datetime.fromtimestamp(int(self.OFW_BUILDTS)).astimezone().replace(microsecond=0).isoformat()))
def run(self):
try:
self.layout.update()
self.layout.activate()
screenGeometry = QDesktopWidget().screenGeometry(self)
x = (screenGeometry.width() - self.width()) / 2
y = (screenGeometry.height() - self.height()) / 2
self.move(x, y)
self.show()
except:
return
def hideEvent(self, event):
if self.DEVICE is None:
self.APP.ConnectDevice()
self.APP.activateWindow()
def reject(self):
if self.CloseDialog():
super().reject()
def CloseDialog(self):
if self.btnClose.isEnabled() is False:
text = "<b>WARNING:</b> If you close this window while a firmware update is still running, it may leave the device in an unbootable state. You can still recover it by running the Firmware Updater again later.<br><br>Are you sure you want to close this window?"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.No)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.No: return False
return True
def UpdateFirmware(self):
if self.optDevicePCBVer14.isChecked():
device_name = "v1.4"
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip"
led = "Done"
elif self.optDevicePCBVer14a.isChecked():
device_name = "v1.4a"
file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4a.zip"
led = "Status"
else:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="Please select the PCB version of your GBxCart RW device.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
self.APP.DisconnectDevice()
text = "Please follow these steps to proceed with the firmware update:<ol><li>Disconnect the USB cable of your GBxCart RW {dev_name:s} device.</li><li>On the circuit board of your GBxCart RW {dev_name:s}, press and hold down the small button while connecting the USB cable again.</li><li>Keep the small button held for at least 2 seconds, then let go of it. If done right, the green LED labeled “{led:s}” should remain lit.</li><li>Click OK to continue.</li></ol>".format(dev_name=device_name, led=led)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
msgbox.setTextFormat(QtCore.Qt.TextFormat.RichText)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
self.btnUpdate.setEnabled(False)
self.btnClose.setEnabled(False)
self.optDevicePCBVer14.setEnabled(False)
self.optDevicePCBVer14a.setEnabled(False)
while True:
ret = self.FWUPD.WriteFirmware(file_name, self.SetStatus)
if ret == 1:
text = "The firmware update is complete!"
def SetStatus(self, text, enableUI=False, setProgress=None):
self.lblStatus.setText("Status: {:s}".format(text))
if setProgress is not None:
self.prgStatus.setValue(setProgress * 10)
if enableUI:
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
self.DEVICE = None
self.reject()
return True
elif ret == 2:
text = "The firmware update has failed. Please try again."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
elif ret == 3:
text = "The firmware update file is corrupted. Please re-install the application."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
def SetStatus(self, text, enableUI=False, setProgress=None):
self.lblStatus.setText("Status: {:s}".format(text))
if setProgress is not None:
self.prgStatus.setValue(setProgress * 10)
if enableUI:
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)
self.optDevicePCBVer14a.setEnabled(True)
self.APP.QT_APP.processEvents()
self.APP.QT_APP.processEvents()
except ImportError:
pass

View File

@ -16,8 +16,8 @@ from . import Util
class GbxDevice:
DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 1
DEVICE_MAX_FW = 8
DEVICE_LATEST_FW_TS = { 4:1619427330, 5:1680535740, 6:1680535740 }
DEVICE_MAX_FW = 9
DEVICE_LATEST_FW_TS = { 4:1619427330, 5:1681395695, 6:1681395696 }
DEVICE_CMD = {
"NULL":0x30,
@ -69,6 +69,7 @@ class GbxDevice:
"AGB_FLASH_WRITE_SHORT":0xD2,
"FLASH_PROGRAM":0xD3,
"CART_WRITE_FLASH_CMD":0xD4,
"CALC_CRC32":0xD5,
}
# \#define VAR(\d+)_([^\t]+)\t+(.+)
DEVICE_VAR = {
@ -280,9 +281,9 @@ class GbxDevice:
def IsSupportedMbc(self, mbc):
if self.CanPowerCycleCart():
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x201, 0x202, 0x203, 0x204, 0x205 )
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205 )
else:
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x202, 0x205 )
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x202, 0x205 )
def IsSupported3dMemory(self):
return True
@ -454,7 +455,11 @@ class GbxDevice:
if self.DEVICE.in_waiting > 1000: dprint("in_waiting = {:d} bytes".format(self.DEVICE.in_waiting))
buffer = self.DEVICE.read(count)
if len(buffer) != count:
dprint("Warning: Received {:d} byte(s) instead of the expected {:d} byte(s)".format(len(buffer), count))
tb_stack = traceback.extract_stack()
stack = tb_stack[len(tb_stack)-2] # caller only
if stack.name == "_read": stack = tb_stack[len(tb_stack)-3]
#traceback.print_stack()
dprint("Warning: Received {:d} byte(s) instead of the expected {:d} byte(s) ({:s}(), line {:d})".format(len(buffer), count, stack.name, stack.lineno))
self.READ_ERRORS += 1
while self.DEVICE.in_waiting > 0:
self.DEVICE.reset_input_buffer()
@ -591,51 +596,27 @@ class GbxDevice:
def CartPowerCycle(self, delay=0.1):
if self.CanPowerCycleCart():
dprint("Power cycling cartridge with a delay of {:.1f} seconds".format(delay))
try:
self.CartPowerOff(delay=delay)
self.CartPowerOn(delay=delay)
if self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_DMG"])
elif self.MODE == "AGB":
self._write(self.DEVICE_CMD["SET_MODE_AGB"])
except:
if self.DEVICE is None:
print("The USB connection was lost after power cycling")
else:
print("An unknown error occured after power cycling")
return False
self.CartPowerOff(delay=delay)
self.CartPowerOn(delay=delay)
if self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_DMG"])
elif self.MODE == "AGB":
self._write(self.DEVICE_CMD["SET_MODE_AGB"])
def CartPowerOff(self, delay=0.1):
if not self.IsConnected(): return
if self.FW["pcb_ver"] in (5, 6):
self._write(self.DEVICE_CMD["OFW_QUERY_CART_PWR"])
if self._read(1) == 1:
dprint("Turning off cartridge power ({:s})".format(self.MODE))
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"])
time.sleep(delay)
while True:
self._write(self.DEVICE_CMD["OFW_QUERY_CART_PWR"])
if self._read(1) == 0: return
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"])
time.sleep(delay)
else:
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"])
def CartPowerOn(self, delay=0.1):
if not self.IsConnected(): return
if self.FW["pcb_ver"] in (5, 6):
self._write(self.DEVICE_CMD["OFW_QUERY_CART_PWR"])
if self._read(1) == 0:
dprint("Turning on cartridge power ({:s})".format(self.MODE))
self._write(self.DEVICE_CMD["OFW_CART_PWR_ON"])
time.sleep(delay)
self._write(self.DEVICE_CMD["OFW_QUERY_CART_PWR"])
while True:
temp = self._read(1)
if temp is False:
self.DEVICE.reset_input_buffer()
self.DEVICE.reset_output_buffer()
return
elif temp == 1:
return
self.DEVICE.reset_input_buffer() # bug workaround
def GetMode(self):
return self.MODE
@ -726,20 +707,21 @@ class GbxDevice:
header[0xD0:0xE0] = self.ReadROM(0x40D0, 0x10)
data = RomFileDMG(header).GetHeader()
_mbc = DMG_MBC().GetInstance(args={"mbc":data["mapper_raw"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
if checkRtc:
data["has_rtc"] = _mbc.HasRTC() is True
if data["has_rtc"] is True:
if _mbc.GetName() == "TAMA5": _mbc.EnableMapper()
_mbc.LatchRTC()
data["rtc_buffer"] = _mbc.ReadRTC()
if _mbc.GetName() == "TAMA5": self._set_fw_variable("DMG_READ_CS_PULSE", 0)
else:
data["has_rtc"] = False
try:
data["rtc_string"] = _mbc.GetRTCString()
except:
data["rtc_string"] = "Invalid data"
data["has_rtc"] = False
data["rtc_string"] = "Not available"
if data["logo_correct"] is True:
_mbc = DMG_MBC().GetInstance(args={"mbc":data["mapper_raw"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
if checkRtc:
data["has_rtc"] = _mbc.HasRTC() is True
if data["has_rtc"] is True:
if _mbc.GetName() == "TAMA5": _mbc.EnableMapper()
_mbc.LatchRTC()
data["rtc_buffer"] = _mbc.ReadRTC()
if _mbc.GetName() == "TAMA5": self._set_fw_variable("DMG_READ_CS_PULSE", 0)
try:
data["rtc_string"] = _mbc.GetRTCString()
except:
data["rtc_string"] = "Invalid data"
elif self.MODE == "AGB":
# Unlock DACS carts on older firmware
@ -2201,7 +2183,31 @@ class GbxDevice:
if (self.MODE == "AGB" and "command_set" in cart_type and cart_type["command_set"] == "3DMEMORY"):
temp = self.ReadROM_3DMemory(address=pos, length=buffer_len, max_length=max_length)
else:
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=skip_init, max_length=max_length)
if self.FW["fw_ver"] >= 9 and "verify_write" in args and (self.MODE != "AGB" or args["verify_base_pos"] > 0xC9):
# Verify mode (by checksum)
dprint("Checksum verification (verify_base_pos=0x{:X}, pos=0x{:X}, pos_total=0x{:X}, buffer_len=0x{:X})".format(args["verify_base_pos"], pos, pos_total, buffer_len))
if self.MODE == "DMG":
self._set_fw_variable("ADDRESS", pos)
elif self.MODE == "AGB":
self._set_fw_variable("ADDRESS", pos >> 1)
self._write(self.DEVICE_CMD["CALC_CRC32"])
self._write(bytearray(struct.pack(">I", buffer_len)))
checksum_expected = zlib.crc32(args["verify_write"][pos_total:pos_total+buffer_len])
checksum_calculated = struct.unpack(">I", self._read(4))[0]
dprint("Expected Checksum: 0x{:X}".format(checksum_expected))
dprint("Calculated Checksum: 0x{:X}".format(checksum_calculated))
if checksum_expected == checksum_calculated:
pos += buffer_len
pos_total += buffer_len
dprint("Verification successful between 0x{:X} and 0x{:X}".format(pos_total-buffer_len, pos_total))
self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total})
continue
else:
dprint("Mismatch during verification between 0x{:X} and 0x{:X}".format(pos_total, pos_total+buffer_len))
return pos_total
else:
# Normal read
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=skip_init, max_length=max_length)
skip_init = True
if len(temp) != buffer_len:
@ -2748,6 +2754,9 @@ class GbxDevice:
self.SetProgress({"action":"UPDATE_POS", "pos":len(buffer)+len(rtc_buffer)})
if args["path"] is not None:
if self.MODE == "DMG" and _mbc.GetName() == "MBC2":
for i in range(0, len(buffer)):
buffer[i] = buffer[i] & 0x0F
file = open(args["path"], "wb")
file.write(buffer)
if rtc_buffer is not None:
@ -3426,6 +3435,7 @@ class GbxDevice:
break
elif buffer_pos == 0 or not self.DEVICE.is_open or self.DEVICE is None:
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"An error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.\n\nTroubleshooting advice:\n- Clean cartridge contacts\n- Avoid passive USB hubs and try different USB ports/cables\n- Check cartridge type selection{:s}".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), errmsg_mbc_selection)})
break
else:
if chip_erase: retry_hp = 0
if "iteration" in self.ERROR_ARGS and self.ERROR_ARGS["iteration"] > 0:
@ -3452,7 +3462,7 @@ class GbxDevice:
self.SetProgress({"action":"ERROR", "abortable":True, "pos":buffer_pos, "text":"Write error! Retrying from 0x{:X}...".format(rev_buffer_pos)})
delay = 0.5 + (100-retry_hp)/50
if self.CanPowerCycleCart():
self.CartPowerOff(0)
self.CartPowerOff()
time.sleep(delay)
self.CartPowerOn()
if self.MODE == "DMG" and _mbc.HasFlashBanks(): _mbc.SelectBankFlash(bank)
@ -3587,7 +3597,10 @@ class GbxDevice:
ret = False
self.SIGNAL = signal
try:
dprint("args:", args)
temp = copy.copy(args)
if "buffer" in temp: temp["buffer"] = "(data)"
dprint("args:", temp)
del(temp)
self.NO_PROG_UPDATE = False
self.READ_ERRORS = 0
if args['mode'] == 1: ret = self._BackupROM(args)

View File

@ -370,7 +370,7 @@ class GbxDevice:
return ["DMG", "AGB"]
def IsSupportedMbc(self, mbc):
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0xFC, 0xFE, 0xFF, 0x101, 0x103 )
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0xFC, 0xFE, 0xFF, 0x101, 0x103, 0x110 )
def IsSupported3dMemory(self):
return False
@ -703,7 +703,7 @@ class GbxDevice:
def EnableRAM(self, mbc=1, enable=True):
if enable:
if mbc in (0x01, 0x02, 0x03, 0x101, 0x103, 0x10, 0x13): # MBC1, MBC1M, MBC3
if mbc in (0x01, 0x02, 0x03, 0x101, 0x103, 0x10, 0x13, 0x110): # MBC1, MBC1M, MBC3, MBC30
self.cart_write(0x6000, 1)
self.cart_write(0x0000, 0x0A, cs=(mbc == 0xFD))
else:
@ -1811,7 +1811,7 @@ class GbxDevice:
self.cart_write(0xA080, 0xC0)
# RTC for MBC3+RTC+SRAM+BATTERY
elif mbc == 0x10 and args["rtc"]:
elif mbc in (0x10, 0x110) and args["rtc"]:
buffer = bytearray()
self.cart_write(0x6000, 0)
self.cart_write(0x6000, 1)

View File

@ -6,7 +6,6 @@
#
from .Util import dprint
# import importlib
try:
import PySide2

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -24,7 +24,7 @@ for [Windows](https://github.com/lesserkuma/FlashGBX/releases), [Linux](https://
## Installing and running
### Windows Binaries
### Windows Packages
Available in the GitHub [Releases](https://github.com/lesserkuma/FlashGBX/releases) section:
@ -72,7 +72,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- MBC7
- MBC1M
- MMM01
- GBD (Game Boy Camera)
- MAC-GBD (Game Boy Camera)
- G-MMC1 (GB Memory)
- M161
- HuC-1
@ -82,6 +82,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- Unlicensed Wisdom Tree Mapper
- Unlicensed Xploder GB Mapper
- Unlicensed Sachen Mapper
- Unlicensed Datel Orbit V2 Mapper
- Game Boy Advance
- All cartridges without memory mapping
@ -158,6 +159,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- DMG-DHCN-20 with MX29LV320ET
- DMG-GBRW-20 with 29LV320ETMI-70G
- DRV with AM29LV160DB and ALTERA CPLD
- DRV with AM29LV160DT and ALTERA CPLD
- ES29LV160_DRV with 29DL32TF-70
- GB-M968 with 29LV160DB
- GB-M968 with M29W160EB
@ -299,7 +301,7 @@ Many different reproduction cartridges share their flash chip command set, so ev
The author would like to thank the following very kind people for their help, contributions or documentation (in alphabetical order):
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, kscheel, kyokohunter, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, Shinichi999, sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, Wkr, x7l7j8cc, xactoes, Zeii, Zelante, zvxr
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, kscheel, kyokohunter, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, Shinichi999, sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, Wkr, x7l7j8cc, xactoes, Zeii, Zelante, zvxr
## DISCLAIMER

View File

@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read(
setuptools.setup(
name="FlashGBX",
version="3.24",
version="3.25",
author="Lesserkuma",
description="Reads and writes Game Boy and Game Boy Advance cartridge data using the GBxCart RW by insideGadgets",
url="https://github.com/lesserkuma/FlashGBX",