diff --git a/CHANGES.md b/CHANGES.md index e531120..81dc2f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ # Release notes +### v3.31 (released 2023-06-18) +- Improved support for 32 MiB cartridges that have one of the EEPROM save types (fixes both backups and writes of B3CJ, B53E, B53P, BBAE, BBAP, BC2J, BFRP, BH3E, BH3P, BH8E, BJPP, BU7E, BU7P, BX3E, BX3P, BYUE, BYUJ, BYUP) +- Confirmed support for BGA64B-71-TV-DEEP with 256M29EML *(thanks Leitplanke)* +- Updated the Game Boy Advance lookup databases for save types, ROM sizes and checksums +- Minor bug fixes and improvements + ### v3.30 (released 2023-06-06) - Improved auto-detection of official GBA Video cartridges with the 3D Memory mapper including those that are less than 64 MiB - Added support for Unknown 29LV320 variant (no PCB text) *(thanks Zoo)* diff --git a/FlashGBX/FlashGBX_CLI.py b/FlashGBX/FlashGBX_CLI.py index 9305481..563400f 100644 --- a/FlashGBX/FlashGBX_CLI.py +++ b/FlashGBX/FlashGBX_CLI.py @@ -315,31 +315,32 @@ class FlashGBX_CLI(): else: msg = "The ROM was dumped, but the checksum is not correct." if self.CONN.INFO["loop_detected"] is not False: - msg += "\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)) + msg += "\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(size=self.CONN.INFO["loop_detected"], asInt=True)) else: msg += "\nThis may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps." print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET)) elif self.CONN.GetMode() == "AGB": print("CRC32: {:08x}".format(self.CONN.INFO["file_crc32"])) print("SHA-1: {:s}\n".format(self.CONN.INFO["file_sha1"])) - if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]: - print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET)) - elif Util.AGB_Global_CRC32 == 0: + if "db" in self.CONN.INFO and self.CONN.INFO["db"] is not None: + if self.CONN.INFO["db"]["rc"] == self.CONN.INFO["file_crc32"]: + print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET)) + else: + msg = "The ROM backup is complete, but the checksum doesn’t match the known database entry." + if self.CONN.INFO["loop_detected"] is not False: + msg += "\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(size=self.CONN.INFO["loop_detected"], asInt=True)) + else: + msg += "\nThis may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps." + print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET)) + else: 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: - msg += "\nNOTE: A 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)) - print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET)) - else: - msg = "The ROM backup is complete, but the checksum doesn’t match the known database entry." - if self.CONN.INFO["loop_detected"] is not False: - msg += "\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 += "\nThis may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps." + msg += "\nNOTE: A 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(size=self.CONN.INFO["loop_detected"], asInt=True)) print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET)) elif self.CONN.INFO["last_action"] == 2: # Backup RAM self.CONN.INFO["last_action"] = 0 - if not "debug" in self.ARGS and self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KB: # 128 KB + if not "debug" in self.ARGS and self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KiB: # 128 KiB answer = input("Would you like to extract Game Boy Camera pictures to “{:s}” now? [Y/n]: ".format(Util.formatPathOS(os.path.abspath(os.path.splitext(self.CONN.INFO["last_path"])[0]), end_sep=True) + "IMG_PC**.{:s}".format(self.ARGS["argparsed"].gbcamera_outfile_format))).strip().lower() if answer != "n": pc = PocketCamera() @@ -532,19 +533,19 @@ class FlashGBX_CLI(): bad_read = True s += "ROM Checksum: " - Util.AGB_Global_CRC32 = 0 + #Util.AGB_Global_CRC32 = 0 db_agb_entry = data["db"] if db_agb_entry != None: if data["rom_size_calc"] < 0x400000: s += "In database (0x{:06X})\n".format(db_agb_entry['rc']) - Util.AGB_Global_CRC32 = db_agb_entry['rc'] - s += "ROM Size: {:d} MB\n".format(int(db_agb_entry['rs']/1024/1024)) + #Util.AGB_Global_CRC32 = db_agb_entry['rc'] + s += "ROM Size: {:d} MiB\n".format(int(db_agb_entry['rs']/1024/1024)) data['rom_size'] = db_agb_entry['rs'] elif data["rom_size"] != 0: s += "Not in database\n" if not data["rom_size"] in Util.AGB_Header_ROM_Sizes_Map: data["rom_size"] = 0x2000000 - s += "ROM Size: {:d} MB\n".format(int(data["rom_size"]/1024/1024)) + s += "ROM Size: {:d} MiB\n".format(int(data["rom_size"]/1024/1024)) else: s += "Not in database\n" s += "ROM Size: Not detected\n" @@ -640,7 +641,7 @@ class FlashGBX_CLI(): if "flash_size" in supp_cart_types[1][cart_type_id]: size = supp_cart_types[1][cart_type_id]["flash_size"] - msg_flash_size_s = "ROM Size: {:s}\n".format(Util.formatFileSize(size, asInt=True)) + msg_flash_size_s = "ROM Size: {:s}\n".format(Util.formatFileSize(size=size, asInt=True)) if self.CONN.GetMode() == "DMG": if "mbc" in supp_cart_types[1][cart_type_id]: @@ -724,7 +725,7 @@ class FlashGBX_CLI(): try: rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[header["rom_size_raw"]] except: - print("{:s}Couldn’t determine ROM size, will use 8 MB. It can also be manually set with the “--dmg-romsize” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET)) + print("{:s}Couldn’t determine ROM size, will use 8 MiB. It can also be manually set with the “--dmg-romsize” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET)) rom_size = 8 * 1024 * 1024 else: sizes = [ "auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb" ] @@ -849,10 +850,10 @@ class FlashGBX_CLI(): try: if os.path.getsize(path) > 0x10000000: # reject too large files to avoid exploding RAM - print("{:s}ROM files bigger than 256 MB are not supported.{:s}".format(ANSI.RED, ANSI.RESET)) + print("{:s}ROM files bigger than 256 MiB are not supported.{:s}".format(ANSI.RED, ANSI.RESET)) return elif os.path.getsize(path) < 0x400: - print("{:s}ROM files smaller than 1 KB are not supported.{:s}".format(ANSI.RED, ANSI.RESET)) + print("{:s}ROM files smaller than 1 KiB are not supported.{:s}".format(ANSI.RED, ANSI.RESET)) return #with open(path, "rb") as file: buffer = bytearray(file.read()) @@ -866,7 +867,7 @@ class FlashGBX_CLI(): rom_size = os.stat(path).st_size if "flash_size" in carts[cart_type]: if rom_size > carts[cart_type]['flash_size']: - msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(carts[cart_type]['flash_size']), Util.formatFileSize(os.path.getsize(path), roundUp=True)) + msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(size=carts[cart_type]['flash_size']), Util.formatFileSize(size=os.path.getsize(path))) msg += " You can still give it a try, but it’s possible that it’s too large which may cause the ROM writing to fail." print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET)) answer = input("Do you want to continue? [y/N]: ").strip().lower() @@ -1187,9 +1188,9 @@ class FlashGBX_CLI(): found_length = len(test2) - found_offset if self.CONN.GetMode() == "DMG": - print("\n{:s}Done! The writable save data size is {:s} out of {:s} checked.{:s}".format(ANSI.GREEN, Util.formatFileSize(found_length), Util.formatFileSize(Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]), ANSI.RESET)) + print("\n{:s}Done! The writable save data size is {:s} out of {:s} checked.{:s}".format(ANSI.GREEN, Util.formatFileSize(size=found_length), Util.formatFileSize(size=Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]), ANSI.RESET)) elif self.CONN.GetMode() == "AGB": - print("\n{:s}Done! The writable save data size using save type “{:s}” is {:s}.{:s}".format(ANSI.GREEN, Util.AGB_Header_Save_Types[save_type], Util.formatFileSize(found_length), ANSI.RESET)) + print("\n{:s}Done! The writable save data size using save type “{:s}” is {:s}.{:s}".format(ANSI.GREEN, Util.AGB_Header_Save_Types[save_type], Util.formatFileSize(size=found_length), ANSI.RESET)) try: (_, _, cfi) = self.CONN.CheckFlashChip(limitVoltage=False) diff --git a/FlashGBX/FlashGBX_GUI.py b/FlashGBX/FlashGBX_GUI.py index 9612c43..9803f77 100644 --- a/FlashGBX/FlashGBX_GUI.py +++ b/FlashGBX/FlashGBX_GUI.py @@ -511,7 +511,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.SETTINGS.setValue("UpdateCheck", "disabled") if update_check and update_check.lower() == "enabled": print("") - url = "https://api.github.com/repos/lesserkuma/FlashGBX/releases?per_page=1" + url = "https://api.github.com/repos/lesserkuma/FlashGBX/releases/latest" site = "https://github.com/lesserkuma/FlashGBX/releases/latest" try: ret = requests.get(url, allow_redirects=True, timeout=1.5) @@ -527,7 +527,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): if ret is not False and ret.status_code == 200: ret = ret.content try: - ret = json.loads(ret)[0] + ret = json.loads(ret) if 'tag_name' in ret: latest_version = str(ret['tag_name']) if pkg_resources.parse_version(latest_version) == pkg_resources.parse_version(VERSION_PEP440): @@ -933,7 +933,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): 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)) + 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(size=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 self.cmbDMGHeaderMapperResult.currentText() == "MBC1": @@ -959,35 +959,36 @@ class FlashGBX_GUI(QtWidgets.QWidget): QtCore.QTimer.singleShot(1, lambda: [ self.CONN.BackupROM(fncSetProgress=self.PROGRESS.SetProgress, args=self.STATUS["args"]) ]) return elif self.CONN.GetMode() == "AGB": - if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]: - self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(Util.AGB_Global_CRC32)) - self.lblAGBHeaderROMChecksumResult.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) - msgbox.exec() - elif Util.AGB_Global_CRC32 == 0: - self.lblAGBHeaderROMChecksumResult.setText("0x{:06X}".format(self.CONN.INFO["rom_checksum_calc"])) + if "db" in self.CONN.INFO and self.CONN.INFO["db"] is not None: + if self.CONN.INFO["db"]["rc"] == self.CONN.INFO["file_crc32"]: + self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(self.CONN.INFO["db"]["rc"])) + self.lblAGBHeaderROMChecksumResult.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) + msgbox.exec() + else: + self.lblAGBHeaderROMChecksumResult.setText("Invalid (0x{:06X}≠0x{:06X})".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["db"]["rc"])) + self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }") + self.lblStatus4a.setText("Done.") + msg = "The ROM backup is complete, but the checksum doesn’t match the known database entry." + 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(size=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." + msgbox.setText(msg + msg_te) + msgbox.setIcon(QtWidgets.QMessageBox.Warning) + msgbox.exec() + else: + self.lblAGBHeaderROMChecksumResult.setText("0x{:06X}".format(self.CONN.INFO["file_crc32"])) 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: - msg += "\n\nNOTE: A 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)) + msg += "\n\nNOTE: A 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(size=self.CONN.INFO["loop_detected"], asInt=True)) msgbox.setIcon(QtWidgets.QMessageBox.Warning) msgbox.setText(msg + msg_te) msgbox.exec() - else: - self.lblAGBHeaderROMChecksumResult.setText("Invalid (0x{:06X}≠0x{:06X})".format(self.CONN.INFO["rom_checksum_calc"], Util.AGB_Global_CRC32)) - self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }") - self.lblStatus4a.setText("Done.") - msg = "The ROM backup is complete, but the checksum doesn’t match the known database entry." - 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." - msgbox.setText(msg + msg_te) - msgbox.setIcon(QtWidgets.QMessageBox.Warning) - msgbox.exec() if msgbox.clickedButton() == button_dump_report: if not (dump_report is not False and dumpinfo_file != "" and temp is True): @@ -1007,7 +1008,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): dontShowAgainCameraSavePopup = str(self.SETTINGS.value("SkipCameraSavePopup", default="disabled")).lower() == "enabled" if not dontShowAgainCameraSavePopup: - if self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KB + if self.CONN.GetMode() == "DMG" and self.CONN.INFO["mapper_raw"] == 252 and self.CONN.INFO["transferred"] == 131072: # Pocket Camera / 128 KiB cbCameraSavePopup = QtWidgets.QCheckBox("Don’t show this message again", checked=dontShowAgain) msgboxCameraPopup = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Would you like to load your save data with the GB Camera Viewer now?") msgboxCameraPopup.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) @@ -1055,14 +1056,14 @@ class FlashGBX_GUI(QtWidgets.QWidget): elif self.CONN.GetMode() == "AGB": cart_types = self.CONN.GetSupportedCartridgesAGB()[0] cart_type_str = " ({:s})".format(cart_types[self.CONN.INFO["dump_info"]["cart_type"]]) - msg_v += "\n\nTips:\n- Clean cartridge contacts\n- Check soldering if it’s a DIY cartridge\n- Avoid passive USB hubs and try different USB ports/cables\n- Check cartridge type selection{:s}\n- Check cartridge ROM storage size (at least {:s} is required)".format(cart_type_str, Util.formatFileSize(self.CONN.INFO["verify_error_params"]["rom_size"])) + msg_v += "\n\nTips:\n- Clean cartridge contacts\n- Check soldering if it’s a DIY cartridge\n- Avoid passive USB hubs and try different USB ports/cables\n- Check cartridge type selection{:s}\n- Check cartridge ROM storage size (at least {:s} is required)".format(cart_type_str, Util.formatFileSize(size=self.CONN.INFO["verify_error_params"]["rom_size"])) if "mapper_selection_type" in self.CONN.INFO["verify_error_params"]: if self.CONN.INFO["verify_error_params"]["mapper_selection_type"] == 1: # manual msg_v += "\n- Check mapper type used: {:s} (manual selection)".format(self.CONN.INFO["verify_error_params"]["mapper_name"]) elif self.CONN.INFO["verify_error_params"]["mapper_selection_type"] == 2: # forced by cart type msg_v += "\n- Check mapper type used: {:s} (forced by selected cartridge type)".format(self.CONN.INFO["verify_error_params"]["mapper_name"]) if self.CONN.INFO["verify_error_params"]["rom_size"] > self.CONN.INFO["verify_error_params"]["mapper_max_size"]: - msg_v += "\n- Check mapper type ROM size limit: likely up to {:s}".format(Util.formatFileSize(self.CONN.INFO["verify_error_params"]["mapper_max_size"])) + msg_v += "\n- Check mapper type ROM size limit: likely up to {:s}".format(Util.formatFileSize(size=self.CONN.INFO["verify_error_params"]["mapper_max_size"])) msg_v += "\n\nDo you want to try and write the sectors again that failed verification?" answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_v, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) @@ -1132,7 +1133,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): if cart_types[1][index] == "RETAIL": # special keyword pass else: - if "flash_size" in cart_types[1][index]: + if "flash_size" in cart_types[1][index] and cart_types[1][index]["flash_size"] in Util.AGB_Header_ROM_Sizes_Map: self.cmbAGBHeaderROMSizeResult.setCurrentIndex(Util.AGB_Header_ROM_Sizes_Map.index(cart_types[1][index]["flash_size"])) self.STATUS["cart_type"] = cart_types[1][index] @@ -1260,7 +1261,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The selected ROM file is empty.", QtWidgets.QMessageBox.Ok) return if os.path.getsize(path) > 0x10000000: # reject too large files to avoid exploding RAM - QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "ROM files bigger than 256 MB are not supported.", QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "ROM files bigger than 256 MiB are not supported.", QtWidgets.QMessageBox.Ok) return with open(path, "rb") as file: @@ -1273,7 +1274,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): rom_size = os.stat(path).st_size if "flash_size" in carts[cart_type]: if rom_size > carts[cart_type]['flash_size']: - msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(carts[cart_type]['flash_size']), Util.formatFileSize(os.path.getsize(path), roundUp=True)) + msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(size=carts[cart_type]['flash_size']), Util.formatFileSize(size=os.path.getsize(path))) msg += " You can still give it a try, but it’s possible that it’s too large which may cause the ROM writing to fail." answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if answer == QtWidgets.QMessageBox.Cancel: return @@ -1514,7 +1515,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): if save_type == 0: QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok) return - save_size = Util.AGB_Header_Save_Sizes[save_type] + #save_size = Util.AGB_Header_Save_Sizes[save_type] else: return if not self.CheckHeader(): return @@ -1908,7 +1909,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): "params": [ # ID, Type, Value(s), Default Index [ "loc", "cmb_e", "Location:", [ "0x{:X}".format(l) for l in locs ], loc_index ], - [ "len", "cmb", "Size:", [ Util.formatFileSize(s, asInt=True) for s in lens ], len_index ], + [ "len", "cmb", "Size:", [ Util.formatFileSize(size=s, asInt=True) for s in lens ], len_index ], ] } dlg = UserInputDialog(self, icon=self.windowIcon(), args=dlg_args) @@ -2208,7 +2209,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet()) self.lblAGBHeaderROMChecksumResult.setText("Not available") - Util.AGB_Global_CRC32 = 0 if data["db"] is None: self.lblAGBHeaderROMChecksumResult.setText("(Not in database)") @@ -2216,7 +2216,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): 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(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 @@ -2355,11 +2354,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)
Batteryless SRAM Location: 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)) + temp += " (unknown size)
Batteryless SRAM Location: 0x{:X}–0x{:X} ({:s})".format(header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(size=header["batteryless_sram"]["bl_size"], asInt=True)) elif save_size == header["batteryless_sram"]["bl_size"]: - temp += " ({:s})
Batteryless SRAM Location: 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)) + temp += " ({:s})
Batteryless SRAM Location: 0x{:X}–0x{:X} ({:s})".format(Util.formatFileSize(size=save_size, asInt=True), header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(size=header["batteryless_sram"]["bl_size"], asInt=True)) else: - temp += " ({:s})
Batteryless SRAM Location: 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)) + temp += " ({:s})
Batteryless SRAM Location: 0x{:X}–0x{:X} ({:s})".format(Util.formatFileSize(size=save_size, asInt=True), header["batteryless_sram"]["bl_offset"], header["batteryless_sram"]["bl_offset"]+header["batteryless_sram"]["bl_size"]-1, Util.formatFileSize(size=header["batteryless_sram"]["bl_size"], asInt=True)) except: pass @@ -2391,7 +2390,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): if "flash_size" in supp_cart_types[1][cart_type_id]: size = supp_cart_types[1][cart_type_id]["flash_size"] - msg_flash_size_s = "ROM Size: {:s}
".format(Util.formatFileSize(size, asInt=True)) + msg_flash_size_s = "ROM Size: {:s}
".format(Util.formatFileSize(size=size, asInt=True)) if self.CONN.GetMode() == "DMG": if "mbc" in supp_cart_types[1][cart_type_id]: @@ -2675,7 +2674,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.SetProgressBars(min=0, max=size, value=pos) elif args["action"] == "FINISHED": if pos > 0: - self.lblStatus1aResult.setText(Util.formatFileSize(pos)) + self.lblStatus1aResult.setText(Util.formatFileSize(size=pos)) self.FinishOperation() elif args["action"] == "ABORT": wd = 10 @@ -2727,7 +2726,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.btnCancel.setEnabled(args["abortable"]) else: self.btnCancel.setEnabled(True) - self.lblStatus1aResult.setText("{:s}".format(Util.formatFileSize(pos))) + self.lblStatus1aResult.setText("{:s}".format(Util.formatFileSize(size=pos))) if speed > 0: self.lblStatus2aResult.setText("{:.2f} KiB/s".format(speed)) else: diff --git a/FlashGBX/Mapper.py b/FlashGBX/Mapper.py index 0a438f6..154751a 100644 --- a/FlashGBX/Mapper.py +++ b/FlashGBX/Mapper.py @@ -148,15 +148,9 @@ class DMG_MBC: 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 ], - ] + commands = [ + [ 0x2100, index & 0xFF ], + ] start_address = 0 if index == 0 else 0x4000 @@ -459,6 +453,18 @@ class DMG_MBC5(DMG_MBC): ] self.CartWrite(commands) + def SelectBankROM(self, index): + dprint(self.GetName(), "|", index) + commands = [ + [ 0x3000, ((index >> 8) & 0xFF) ], + [ 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 @@ -703,15 +709,10 @@ class DMG_GMMC1(DMG_MBC5): def SelectBankROM(self, index): dprint(self.GetName(), "|", index) - if index == 0 or index >= 256: - commands = [ - [ 0x2100, index & 0xFF ], - ] - else: - commands = [ - [ 0x2100, index & 0xFF ], - ] - + commands = [ + [ 0x2100, index & 0xFF ], + ] + start_address = 0 if index == 0 else 0x4000 self.CartWrite(commands) diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py index d13351a..a676875 100644 --- a/FlashGBX/Util.py +++ b/FlashGBX/Util.py @@ -7,9 +7,9 @@ from enum import Enum # Common constants APPNAME = "FlashGBX" -VERSION_PEP440 = "3.30" +VERSION_PEP440 = "3.31" VERSION = "v{:s}".format(VERSION_PEP440) -VERSION_TIMESTAMP = 1686057604 +VERSION_TIMESTAMP = 1687091551 DEBUG = False DEBUG_LOG = [] APP_PATH = "" @@ -19,7 +19,6 @@ AGB_Header_ROM_Sizes = [ "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 AGB_Header_ROM_Sizes_Map = [ 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000 ] AGB_Header_Save_Types = [ "None", "4K EEPROM (512 Bytes)", "64K EEPROM (8 KiB)", "256K SRAM/FRAM (32 KiB)", "512K FLASH (64 KiB)", "1M FLASH (128 KiB)", "8M DACS (1 MiB)", "Unlicensed 512K SRAM (64 KiB)", "Unlicensed 1M SRAM (128 KiB)", "Unlicensed Batteryless SRAM" ] AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1048576, 65536, 131072, 0 ] -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 ] @@ -282,30 +281,26 @@ def isx2bin(buffer): break return data_output[:temp] -def roundup(x): - # https://stackoverflow.com/questions/50405017/ - d = 10 ** 2 - if x < 0: - return math.floor(x * d) / d - else: - return math.ceil(x * d) / d +def round2(num, decimals=2): + x = (pow(10, decimals)) + return int(num * x) / x -def formatFileSize(size, asInt=False, roundUp=False, nobr=True): +def formatFileSize(size, asInt=False, nobr=True): space = " " if nobr else " " if size == 1: return "{:d}{:s}Byte".format(size, space) elif size < 1024: return "{:d}{:s}Bytes".format(size, space) elif size < 1024 * 1024: - val = size/1024 - if roundUp: val = roundup(val) + val = size / 1024 + val = round2(val) if asInt: return "{:d}{:s}KiB".format(int(val), space) else: return "{:.1f}{:s}KiB".format(val, space) else: - val = size/1024/1024 - if roundUp: val = roundup(val) + val = size / 1024 / 1024 + val = round2(val) if asInt: return "{:d}{:s}MiB".format(int(val), space) else: @@ -509,7 +504,7 @@ def GetDumpReport(di, device): di["file_name"] = "" else: di["file_name"] = os.path.split(di["file_name"])[1] - di["file_size"] = "{:s} ({:d} bytes)".format(formatFileSize(di["file_size"]), di["file_size"]) + di["file_size"] = "{:s} ({:d} bytes)".format(formatFileSize(size=di["file_size"]), di["file_size"]) s = "" \ "= FlashGBX Dump Report =\n" \ @@ -659,7 +654,7 @@ def GetDumpReport(di, device): timestamp=di["gbmem_parsed"][i]["timestamp"], kiosk_id=di["gbmem_parsed"][i]["kiosk_id"], location="0x{:06X}–0x{:06X}".format(di["gbmem_parsed"][i]["rom_offset"], di["gbmem_parsed"][i]["rom_offset"]+di["gbmem_parsed"][i]["rom_size"]-1), - size="{:s} ({:d} bytes)".format(formatFileSize(di["gbmem_parsed"][i]["rom_size"]), di["gbmem_parsed"][i]["rom_size"]), + size="{:s} ({:d} bytes)".format(formatFileSize(size=di["gbmem_parsed"][i]["rom_size"]), di["gbmem_parsed"][i]["rom_size"]), ) if "crc32" in di["gbmem_parsed"][i]: s += "* CRC32: {:08x}\n".format(di["gbmem_parsed"][i]["crc32"]) if "md5" in di["gbmem_parsed"][i]: s += "* MD5: {:s}\n".format(di["gbmem_parsed"][i]["md5"]) @@ -702,7 +697,7 @@ def GetDumpReport(di, device): 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 "rs" in db: s += "* ROM Size: {:s}\n".format(formatFileSize(size=db["rs"], asInt=True)) if mode == "AGB": header["game_code_raw"] = header["game_code_raw"].replace("\0", "␀") @@ -738,7 +733,10 @@ def GetDumpReport(di, device): 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 "eeprom_data" in di: + s += "* EEPROM area: {:s}…\n".format(''.join(format(x, '02X') for x in di["eeprom_data"])) + if header["db"] is not None and header["db"]["rc"] == di["hash_crc32"]: db = header["db"] s += "\n== Database Match ==\n" @@ -749,10 +747,10 @@ def GetDumpReport(di, device): 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 "rs" in db: s += "* ROM Size: {:s}\n".format(formatFileSize(size=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)) - + #if "ss" in db: s += "* Save Size: {:s}\n".format(formatFileSize(size=db["ss"], asInt=True)) + return s def GenerateFileName(mode, header, settings=None): diff --git a/FlashGBX/config/db_AGB.json b/FlashGBX/config/db_AGB.json index 68fd86e..0ec5f24 100644 --- a/FlashGBX/config/db_AGB.json +++ b/FlashGBX/config/db_AGB.json @@ -17310,7 +17310,7 @@ "gn": "Crash & Spyro Superpack - Spyro Orange - The Cortex Conspiracy + Crash Bandicoot Purple - Ripto's Rampage", "ne": "(USA)", "gc": "B53E", - "rc": 1497403502, + "rc": 2608249099, "rs": 33554432, "st": 2, "ss": 8192, @@ -17321,7 +17321,7 @@ "gn": "Crash & Spyro Super Pack Volume 3", "ne": "(Europe) (En,Fr,De,Es,It)", "gc": "B53P", - "rc": 84981519, + "rc": 3341196906, "rs": 33554432, "st": 2, "ss": 8192, @@ -18678,7 +18678,7 @@ "gn": "Shamu's Deep Sea Adventures", "ne": "(USA)", "gc": "BBAE", - "rc": 1318332139, + "rc": 2127263017, "rs": 33554432, "st": 1, "ss": 512, @@ -19107,7 +19107,7 @@ "gn": "Crayon Shin-chan - Densetsu o Yobu Omake no Miyako Shockgaan!", "ne": "(Japan)", "gc": "BC2J", - "rc": 1170752614, + "rc": 1391748094, "rs": 33554432, "st": 2, "ss": 8192, @@ -21162,7 +21162,7 @@ "gn": "My Animal Centre in Africa", "ne": "(Europe) (En,Fr,De,Es,It)", "gc": "BFRP", - "rc": 381739048, + "rc": 3572943181, "rs": 33554432, "st": 2, "ss": 8192, @@ -21869,7 +21869,7 @@ "gn": "Happy Feet", "ne": "(USA) (En,Fr)", "gc": "BH3E", - "rc": 1747326927, + "rc": 620782579, "rs": 33554432, "st": 1, "ss": 512, @@ -21880,7 +21880,7 @@ "gn": "Happy Feet", "ne": "(Europe) (En,Fr,De,Es,It)", "gc": "BH3P", - "rc": 2052265788, + "rc": 3093642841, "rs": 33554432, "st": 1, "ss": 512, @@ -23247,7 +23247,7 @@ "gn": "Harry Potter Collection", "ne": "(Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da)", "gc": "BJPP", - "rc": 4289328379, + "rc": 1329185140, "rs": 33554432, "st": 2, "ss": 8192, @@ -28788,7 +28788,7 @@ "gn": "Legend of Spyro, The - The Eternal Night", "ne": "(USA) (En,Fr)", "gc": "BU7E", - "rc": 3173470694, + "rc": 2373452324, "rs": 33554432, "st": 1, "ss": 512, @@ -28799,7 +28799,7 @@ "gn": "Legend of Spyro, The - The Eternal Night", "ne": "(Europe) (En,Fr,De,Es,It,Nl)", "gc": "BU7P", - "rc": 2477884243, + "rc": 2750337169, "rs": 33554432, "st": 1, "ss": 512, @@ -29804,7 +29804,7 @@ "gn": "2 in 1 Game Pack - Spider-Man + Spider-Man 2", "ne": "(USA) (En,Fr,De+En,Fr,De,Es)", "gc": "BX3E", - "rc": 182674303, + "rc": 985411773, "rs": 33554432, "st": 1, "ss": 512, @@ -29815,7 +29815,7 @@ "gn": "2 in 1 Game Pack - Spider-Man & Spider-Man 2", "ne": "(Europe) (En,Fr,De+En,Fr,De,Es,It)", "gc": "BX3P", - "rc": 3610436909, + "rc": 3882727151, "rs": 33554432, "st": 1, "ss": 512, @@ -30355,7 +30355,7 @@ "gn": "Yggdra Union - We'll Never Fight Alone", "ne": "(USA)", "gc": "BYUE", - "rc": 1006266615, + "rc": 2226480748, "rs": 33554432, "st": 2, "ss": 8192, @@ -30366,7 +30366,7 @@ "gn": "Yggdra Union", "ne": "(Japan)", "gc": "BYUJ", - "rc": 2825880650, + "rc": 3209929682, "rs": 33554432, "st": 2, "ss": 8192, @@ -30377,7 +30377,7 @@ "gn": "Yggdra Union - We'll Never Fight Alone", "ne": "(Europe)", "gc": "BYUP", - "rc": 1041101104, + "rc": 691132072, "rs": 33554432, "st": 2, "ss": 8192, diff --git a/FlashGBX/config/fc_AGB_MSP55LV128M.txt b/FlashGBX/config/fc_AGB_MSP55LV128M.txt index 47e9769..38248ab 100644 --- a/FlashGBX/config/fc_AGB_MSP55LV128M.txt +++ b/FlashGBX/config/fc_AGB_MSP55LV128M.txt @@ -9,7 +9,8 @@ "AGB-E05-02 with M29W128FH", "2006_TSOP_64BALL_6106 with W29GL128SH9B", "AGB-E05-02 with JS28F128", - "AGB-E05-03H with M29W128GH" + "AGB-E05-03H with M29W128GH", + "BGA64B-71-TV-DEEP with 256M29EML" ], "flash_ids":[ [ 0x02, 0x00, 0x7D, 0x22 ], @@ -20,7 +21,8 @@ [ 0x20, 0x00, 0x7D, 0x22 ], [ 0xEF, 0x00, 0x7D, 0x22 ], [ 0x8A, 0x00, 0x7D, 0x22 ], - [ 0x20, 0x00, 0x7D, 0x22 ] + [ 0x20, 0x00, 0x7D, 0x22 ], + [ 0x8A, 0x00, 0x7D, 0x22 ] ], "voltage":3.3, "flash_size":0x1000000, diff --git a/FlashGBX/fw_GBxCartRW_v1_4.py b/FlashGBX/fw_GBxCartRW_v1_4.py index 57ad10e..4d2b32f 100644 --- a/FlashGBX/fw_GBxCartRW_v1_4.py +++ b/FlashGBX/fw_GBxCartRW_v1_4.py @@ -110,7 +110,7 @@ try: self.DEVICE = device else: self.APP.QT_APP.processEvents() - text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4/v1.4a 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." + 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() @@ -148,7 +148,7 @@ try: 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.optDevicePCBVer14a = QtWidgets.QRadioButton("v1.4a/b") self.connect(self.optDevicePCBVer14a, QtCore.SIGNAL("clicked()"), self.SetPCBVersion) rowDeviceInfo2.addWidget(self.lblDevicePCBVer) rowDeviceInfo2.addWidget(self.optDevicePCBVer14) @@ -217,7 +217,7 @@ try: self.lblDeviceFWVerResult.setText(self.FW_VER) if self.PCB_VER == "v1.4": self.optDevicePCBVer14.setChecked(True) - elif self.PCB_VER == "v1.4a": + elif self.PCB_VER == "v1.4a/b": self.optDevicePCBVer14a.setChecked(True) self.SetPCBVersion() @@ -275,7 +275,7 @@ try: file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip" led = "Done" elif self.optDevicePCBVer14a.isChecked(): - device_name = "v1.4a" + device_name = "v1.4a/b" file_name = self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4a.zip" led = "Status" else: diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index 092930b..5ae8908 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -92,7 +92,7 @@ class GbxDevice: "AGB_READ_METHOD":[8, 0x0C], } - PCB_VERSIONS = {4:'v1.3', 5:'v1.4', 6:'v1.4a', 101:'Mini v1.0d'} + PCB_VERSIONS = {4:'v1.3', 5:'v1.4', 6:'v1.4a/b', 101:'Mini v1.0d'} ACTIONS = {"ROM_READ":1, "SAVE_READ":2, "SAVE_WRITE":3, "ROM_WRITE":4, "ROM_WRITE_VERIFY":4, "SAVE_WRITE_VERIFY":3} SUPPORTED_CARTS = {} @@ -114,7 +114,8 @@ class GbxDevice: FAST_READ = False SKIPPING = False BAUDRATE = 1000000 - MAX_BUFFER_LEN = 0x800 + MAX_BUFFER_READ = 0x2000 + MAX_BUFFER_WRITE = 0x400 DEVICE_TIMEOUT = 0.75 WRITE_DELAY = False READ_ERRORS = 0 @@ -172,14 +173,16 @@ class GbxDevice: elif self.FW["fw_ts"] > self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]: conn_msg.append([0, "Note: The GBxCart RW device on port " + ports[i] + " is running a firmware version that is newer than what this version of FlashGBX was developed to work with, so errors may occur."]) - if self.FW["pcb_ver"] not in (4, 5, 6, 101): # only the v1.3, v1.4, v1.4a, Mini v1.1 PCB revisions are supported + if self.FW["pcb_ver"] not in (4, 5, 6, 101): # only the v1.3, v1.4, v1.4a/b, Mini v1.1 PCB revisions are supported dev.close() self.DEVICE = None continue elif self.FW["pcb_ver"] in (5, 6, 101) and self.BAUDRATE > 1000000: - self.MAX_BUFFER_LEN = 0x2000 + self.MAX_BUFFER_READ = 0x2000 + self.MAX_BUFFER_WRITE = 0x400 else: - self.MAX_BUFFER_LEN = 0x1000 + self.MAX_BUFFER_READ = 0x1000 + self.MAX_BUFFER_WRITE = 0x100 conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"]) @@ -383,7 +386,7 @@ class GbxDevice: return (None, fw_GBxCartRW_v1_3.FirmwareUpdaterWindow) except: return False - elif self.FW["pcb_ver"] in (5, 6): # v1.4 / v1.4a + elif self.FW["pcb_ver"] in (5, 6): # v1.4 / v1.4a/b try: from . import fw_GBxCartRW_v1_4 return (fw_GBxCartRW_v1_4.FirmwareUpdater, fw_GBxCartRW_v1_4.FirmwareUpdaterWindow) @@ -613,7 +616,7 @@ class GbxDevice: self._write(self.DEVICE_CMD["OFW_CART_PWR_ON"]) time.sleep(delay) self.DEVICE.reset_input_buffer() # bug workaround - + def GetMode(self): return self.MODE @@ -1017,7 +1020,7 @@ class GbxDevice: addr_shift = batteryless_loader[base_addr - 3] << 1 address = (addr_value >> addr_rotate_right) | (addr_value << (32 - addr_rotate_right)) & 0xFFFFFFFF address = (address << addr_shift) - if address < 32*1024*1024: + if address < 32*1024*1024 and address > 0x1000: bl_offset = address dprint("Detected Chinese bootleg Batteryless SRAM ROM") else: @@ -1338,12 +1341,8 @@ class GbxDevice: self.NO_PROG_UPDATE = npu - def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False): + def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False, max_length=MAX_BUFFER_WRITE): length = len(buffer) - if self.FW["pcb_ver"] not in (5, 6, 101) or self.BAUDRATE == 1000000: - max_length = 256 - else: - max_length = 1024 num = math.ceil(length / max_length) dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s)".format(length, num)) if length == 0: @@ -2153,7 +2152,7 @@ class GbxDevice: dprint("Pullups disabled") buffer = bytearray(size) - max_length = self.MAX_BUFFER_LEN + max_length = self.MAX_BUFFER_READ dprint("Max buffer size: 0x{:X}".format(max_length)) if self.FAST_READ is True: if is_3dmemory: @@ -2275,7 +2274,7 @@ class GbxDevice: else: dprint("Failed to receive 0x{:X} bytes from the device at position 0x{:X}. Decreasing maximum transfer buffer size to 0x{:X}.".format(buffer_len, pos_temp, max_length >> 1)) max_length >>= 1 - self.MAX_BUFFER_LEN = max_length + self.MAX_BUFFER_READ = max_length err_text += "\nBuffer size adjusted to {:d} bytes...".format(max_length) if ".dev" in Util.VERSION_PEP440 and not Util.DEBUG: print(err_text) @@ -2322,8 +2321,6 @@ class GbxDevice: bank += 1 - if file is not None: file.close() - if "verify_write" in args: return min(pos_total, len(args["verify_write"])) @@ -2353,23 +2350,16 @@ class GbxDevice: if "gbmem_parsed" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["gbmem_parsed"]) # Calculate Global Checksum - self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF - self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest() - self.INFO["file_sha256"] = hashlib.sha256(buffer).hexdigest() - self.INFO["file_md5"] = hashlib.md5(buffer).hexdigest() - self.INFO["dump_info"]["hash_crc32"] = self.INFO["file_crc32"] - self.INFO["dump_info"]["hash_sha1"] = self.INFO["file_sha1"] - self.INFO["dump_info"]["hash_sha256"] = self.INFO["file_sha256"] - self.INFO["dump_info"]["hash_md5"] = self.INFO["file_md5"] if self.MODE == "DMG": if _mbc.GetName() == "MMM01": self.INFO["dump_info"]["header"] = RomFileDMG(buffer[-0x8000:-0x8000+0x180]).GetHeader(unchanged=True) else: self.INFO["dump_info"]["header"] = RomFileDMG(buffer[:0x180]).GetHeader() - chk = _mbc.CalcChecksum(buffer) + #chk = _mbc.CalcChecksum(buffer) + self.INFO["rom_checksum_calc"] = _mbc.CalcChecksum(buffer) elif self.MODE == "AGB": self.INFO["dump_info"]["header"] = RomFileAGB(buffer[:0x180]).GetHeader() - chk = self.INFO["file_crc32"] + #chk = self.INFO["file_crc32"] temp_ver = "N/A" ids = [ b"SRAM_", b"EEPROM_V", b"FLASH_V", b"FLASH512_V", b"FLASH1M_V", b"AGB_8MDACS_DL_V" ] @@ -2390,8 +2380,23 @@ class GbxDevice: print("Error querying the flash save chip.") self.DEVICE.reset_input_buffer() self.DEVICE.reset_output_buffer() - - self.INFO["rom_checksum_calc"] = chk + + if "EEPROM" in temp_ver and len(buffer) == 0x2000000: + padding_byte = buffer[0x1FFFEFF] + dprint("Replacing unmapped ROM data of cartridge (32 MiB ROM + EEPROM save type) with the original padding byte of 0x{:02X}.".format(padding_byte)) + self.INFO["dump_info"]["eeprom_data"] = buffer[0x1FFFF00:0x1FFFF10] + buffer[0x1FFFF00:0x2000000] = bytearray([padding_byte] * 0x100) + file.seek(0x1FFFF00) + file.write(buffer[0x1FFFF00:0x2000000]) + + self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF + self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest() + self.INFO["file_sha256"] = hashlib.sha256(buffer).hexdigest() + self.INFO["file_md5"] = hashlib.md5(buffer).hexdigest() + self.INFO["dump_info"]["hash_crc32"] = self.INFO["file_crc32"] + self.INFO["dump_info"]["hash_sha1"] = self.INFO["file_sha1"] + self.INFO["dump_info"]["hash_sha256"] = self.INFO["file_sha256"] + self.INFO["dump_info"]["hash_md5"] = self.INFO["file_md5"] # Check for ROM loops self.INFO["loop_detected"] = False @@ -2404,6 +2409,8 @@ class GbxDevice: else: break + if file is not None: file.close() + # ↓↓↓ Switch to first ROM bank if self.MODE == "DMG": if _mbc.ResetBeforeBankChange(0) is True: @@ -2959,6 +2966,20 @@ class GbxDevice: data_import += bytearray([0xFF] * (0x400 - len(data_import))) if len(data_import) % 0x8000 > 0: data_import += bytearray([0xFF] * (0x8000 - len(data_import) % 0x8000)) + + # Skip writing the last 256 bytes of 32 MiB ROMs with EEPROM save type + if self.MODE == "AGB" and len(data_import) == 0x2000000: + temp_ver = "N/A" + ids = [ b"SRAM_", b"EEPROM_V", b"FLASH_V", b"FLASH512_V", b"FLASH1M_V", b"AGB_8MDACS_DL_V" ] + for id in ids: + temp_pos = data_import.find(id) + if temp_pos > 0: + temp_ver = data_import[temp_pos:temp_pos+0x20] + temp_ver = temp_ver[:temp_ver.index(0x00)].decode("ascii", "replace") + break + if "EEPROM" in temp_ver: + print("Note: The last 256 bytes of this 32 MiB ROM will not be written as this area is reserved by the EEPROM save type.") + data_import = data_import[:0x1FFFF00] # Fix header if "fix_header" in args and args["fix_header"]: @@ -3096,7 +3117,7 @@ class GbxDevice: else: errmsg_mbc_selection += "\n- Check mapper type used: {:s} (forced by selected cartridge type)".format(_mbc.GetName()) if len(data_import) > _mbc.GetMaxROMSize(): - errmsg_mbc_selection += "\n- Check mapper type ROM size limit: likely up to {:s}".format(Util.formatFileSize(_mbc.GetMaxROMSize())) + errmsg_mbc_selection += "\n- Check mapper type ROM size limit: likely up to {:s}".format(Util.formatFileSize(size=_mbc.GetMaxROMSize())) elif self.MODE == "AGB": self._write(self.DEVICE_CMD["SET_MODE_AGB"]) @@ -3303,7 +3324,7 @@ class GbxDevice: if len(sector_offsets) > 0: flash_capacity = sector_offsets[-1][0] + sector_offsets[-1][1] if flash_capacity < len(data_import) and not (flashcart.SupportsChipErase() and args["prefer_chip_erase"]): - self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"There are not enough flash sectors available to write this ROM. The maximum capacity is {:s}.".format(Util.formatFileSize(flash_capacity, asInt=False)), "abortable":False}) + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"There are not enough flash sectors available to write this ROM. The maximum capacity is {:s}.".format(Util.formatFileSize(size=flash_capacity, asInt=False)), "abortable":False}) return False sector_offsets_hash = base64.urlsafe_b64encode(hashlib.sha1(str(sector_offsets).encode("UTF-8")).digest()).decode("ASCII", "ignore")[:4] @@ -3533,7 +3554,12 @@ class GbxDevice: elif command_set_type == "DATEL_ORBITV2": status = self.WriteROM_DMG_DatelOrbitV2(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank) else: - status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING), rumble_stop=rumble) + max_buffer_write = self.MAX_BUFFER_WRITE + if (len(data_import) == 0x1FFFF00) and (buffer_pos+buffer_len > len(data_import)): + # 32 MiB ROM + EEPROM cart + max_buffer_write = 256 + buffer_len = (buffer_pos+buffer_len - len(data_import)) + status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING), rumble_stop=rumble, max_length=max_buffer_write) if status is False or se_ret is False: self.CANCEL = True @@ -3553,7 +3579,7 @@ class GbxDevice: else: retry_hp -= 10 if retry_hp <= 0: - 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\n- Check cartridge ROM storage size (at least {:s} is required){:s}".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), Util.formatFileSize(len(data_import), asInt=False), errmsg_mbc_selection), "abortable":False}) + 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\n- Check cartridge ROM storage size (at least {:s} is required){:s}".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), Util.formatFileSize(size=len(data_import), asInt=False), errmsg_mbc_selection), "abortable":False}) continue rev_buffer_pos = sector_offsets[sector_pos - 1][0] @@ -3600,13 +3626,6 @@ class GbxDevice: if status is not False: bank += 1 - if delta_state_new is not None and not chip_erase: - try: - with open(json_file, "wb") as f: - f.write(json.dumps(delta_state_new).encode("UTF-8-SIG")) - except PermissionError: - print("Error: Couldn’t update write-protected file “{:s}”".format(json_file)) - self.SetProgress({"action":"UPDATE_POS", "pos":len(data_import)}) # ↑↑↑ Flash write @@ -3677,6 +3696,13 @@ class GbxDevice: verified = False # ↑↑↑ Flash verify + if delta_state_new is not None and not chip_erase and not "broken_sectors" in self.INFO: + try: + with open(json_file, "wb") as f: + f.write(json.dumps(delta_state_new).encode("UTF-8-SIG")) + except PermissionError: + print("Error: Couldn’t update write-protected file “{:s}”".format(json_file)) + # ↓↓↓ Switch to first ROM bank if self.MODE == "DMG": if _mbc.ResetBeforeBankChange(0) is True: diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index 5349e72..f77d836 100644 Binary files a/FlashGBX/res/config.zip and b/FlashGBX/res/config.zip differ diff --git a/README.md b/README.md index c654453..b930c04 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed - B104 with MSP55LV128 - B11 with 26L6420MC-90 - B54 with MX29LV320ET + - BGA64B-71-TV-DEEP with 256M29EML - BX2006_0106_NEW with S29GL128N10TFI01 - BX2006_TSOP_64BALL with GL128S - BX2006_TSOP_64BALL with GL256S @@ -307,7 +308,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, CodyWick13, Corborg, Cristóbal, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, KOOORAY, kscheel, kyokohunter, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, wickawack, Wkr, x7l7j8cc, xactoes, yosoo, Zeii, Zelante, Zoo, zvxr +2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, Cristóbal, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, wickawack, Wkr, x7l7j8cc, xactoes, yosoo, Zeii, Zelante, Zoo, zvxr ## DISCLAIMER diff --git a/setup.py b/setup.py index f4ddd37..04d6209 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read( setuptools.setup( name="FlashGBX", - version="3.30", + version="3.31", 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",