diff --git a/.github/FlashGBX_Ubuntu.png b/.github/FlashGBX_Ubuntu.png index f4868c1..b9cbedd 100644 Binary files a/.github/FlashGBX_Ubuntu.png and b/.github/FlashGBX_Ubuntu.png differ diff --git a/.github/FlashGBX_Windows.png b/.github/FlashGBX_Windows.png index 7bbfcec..7fd2ece 100644 Binary files a/.github/FlashGBX_Windows.png and b/.github/FlashGBX_Windows.png differ diff --git a/.github/FlashGBX_macOS.png b/.github/FlashGBX_macOS.png index c501543..d09260f 100644 Binary files a/.github/FlashGBX_macOS.png and b/.github/FlashGBX_macOS.png differ diff --git a/FlashGBX/DataTransfer.py b/FlashGBX/DataTransfer.py index 810617f..9076eb1 100644 --- a/FlashGBX/DataTransfer.py +++ b/FlashGBX/DataTransfer.py @@ -1,42 +1,38 @@ # -*- coding: utf-8 -*- # UTF-8 -import sys +import sys, traceback from PySide2.QtCore import QThread, Signal class DataTransfer(QThread): CONFIG = None - RUNNING = False + FINISHED = False - updateProgress = Signal(object, int, int, float, float, float) + updateProgress = Signal(object) def __init__(self, config=None): QThread.__init__(self) + if config is not None: + self.CONFIG = config + self.FINISHED = False + + def setConfig(self, config): self.CONFIG = config - + self.FINISHED = False + def isRunning(self): - return self.RUNNING + return not self.FINISHED def run(self): try: if self.CONFIG == None: pass - elif self.CONFIG['mode'] == 1: - self.RUNNING = True - self.CONFIG['port']._TransferData(1, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['mbc'], self.CONFIG['rom_banks'], self.CONFIG['agb_rom_size'] ]) - self.RUNNING = False - elif self.CONFIG['mode'] == 2: - self.RUNNING = True - self.CONFIG['port']._TransferData(2, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['mbc'], self.CONFIG['save_type'] ]) - self.RUNNING = False - elif self.CONFIG['mode'] == 3: - self.RUNNING = True - self.CONFIG['port']._TransferData(3, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['mbc'], self.CONFIG['save_type'], self.CONFIG['erase'] ]) - self.RUNNING = False - elif self.CONFIG['mode'] == 4: - self.RUNNING = True - self.CONFIG['port']._TransferData(4, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['cart_type'], self.CONFIG['trim_rom'], self.CONFIG['override_voltage'] ]) - self.RUNNING = False - + + else: + self.FINISHED = False + self.CONFIG['port']._TransferData(self.CONFIG, self.updateProgress) + self.FINISHED = True + except Exception as e: - self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error has occured!\nPlease try to reconnect the hardware and restart the application.\n\n" + str(e), "abortable":False}, 0, 0, 0, 0, 0) - self.RUNNING = False + traceback.print_exc() + self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error has occured!\nPlease try to reconnect the hardware and restart the application.\n\n{:s}: {:s}".format(type(e).__name__, str(e)), "abortable":False}) + self.FINISHED = True diff --git a/FlashGBX/FlashGBX.py b/FlashGBX/FlashGBX.py index 5b09c4a..308eb02 100644 --- a/FlashGBX/FlashGBX.py +++ b/FlashGBX/FlashGBX.py @@ -1,21 +1,23 @@ # -*- coding: utf-8 -*- # UTF-8 -import sys, threading, os, glob, importlib, time, re, json, platform, subprocess, zlib, argparse, math, struct +import sys, threading, os, glob, time, re, json, platform, subprocess, zlib, argparse, math, struct, statistics from PySide2 import QtCore, QtWidgets, QtGui from zipfile import * from datetime import datetime +from .RomFileDMG import * +from .RomFileAGB import * from . import hw_GBxCartRW hw_devices = [hw_GBxCartRW] APPNAME = "FlashGBX" -VERSION = "0.8β" +VERSION = "0.9β" class FlashGBX(QtWidgets.QWidget): global APPNAME, VERSION AGB_Header_ROM_Sizes = [ "4 MB", "8 MB", "16 MB", "32 MB" ] AGB_Header_ROM_Sizes_Map = [ 0x400000, 0x800000, 0x1000000, 0x2000000 ] - AGB_Header_Save_Types = [ "None", "4K EEPROM (512 Bytes)", "64K EEPROM (8 KB)", "256K SRAM (32 KB)", "512K SRAM (64 KB)", "512K FLASH (64 KB)", "1M FLASH (128 KB)" ] + AGB_Header_Save_Types = [ "None", "4K EEPROM (512 Bytes)", "64K EEPROM (8 KB)", "256K SRAM (32 KB)", "512K SRAM (64 KB)", "1M SRAM (128 KB)", "512K FLASH (64 KB)", "1M FLASH (128 KB)" ] AGB_Global_CRC32 = 0 DMG_Header_Features = { 0x00:'ROM ONLY', 0x01:'MBC1', 0x02:'MBC1+RAM', 0x03:'MBC1+RAM+BATTERY', 0x05:'MBC2', 0x06:'MBC2+BATTERY', 0x08:'ROM+RAM', 0x09:'ROM+RAM+BATTERY', 0x0B:'MMM01', 0x0C:'MMM01+RAM', 0x0D:'MMM01+RAM+BATTERY', 0x0F:'MBC3+TIMER+BATTERY', 0x10:'MBC3+TIMER+RAM+BATTERY', 0x11:'MBC3', 0x12:'MBC3+RAM', 0x13:'MBC3+RAM+BATTERY', 0x15:'MBC4', 0x16:'MBC4+RAM', 0x17:'MBC4+RAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+RAM', 0x1B:'MBC5+RAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1D:'MBC5+RUMBLE+RAM', 0x1E:'MBC5+RUMBLE+RAM+BATTERY', 0x20:'MBC6', 0x22:'MBC7+RAM+BATTERY', 0x55:'Game Genie', 0x56:'Game Genie v3.0', 0xFC:'POCKET CAMERA', 0xFD:'BANDAI TAMA5', 0xFE:'HuC3', 0xFF:'HuC1+RAM+BATTERY' } @@ -34,12 +36,30 @@ class FlashGBX(QtWidgets.QWidget): DEVICES = {} FLASHCARTS = { "DMG":{}, "AGB":{} } CONFIG_PATH = "" - TEMPFILE = "" TBPROG = None # Windows 7+ Taskbar Progress Bar + PROGRESS = {} + #DEBUG = [] + + def ReadConfig(self, reset=False): + self.SETTINGS = QtCore.QSettings(self.CONFIG_PATH + "/settings.ini", QtCore.QSettings.IniFormat) + config_version = self.SETTINGS.value("ConfigVersion") + if not os.path.exists(self.CONFIG_PATH): os.makedirs(self.CONFIG_PATH) + fc_files = glob.glob("{0:s}/fc_*.txt".format(self.CONFIG_PATH)) + if config_version is not None and len(fc_files) == 0: + print("FAIL: No flash cartridge type configuration files found in {:s}. Resetting configuration...\n".format(self.CONFIG_PATH)) + self.SETTINGS.clear() + os.rename(self.CONFIG_PATH + "/settings.ini", self.CONFIG_PATH + "/settings.ini_" + datetime.now().strftime("%Y%m%d%H%M%S") + ".bak") + config_version = False # extracts the config.zip again + elif reset: + self.SETTINGS.clear() + print("All configuration has been reset.\n") + + self.SETTINGS.setValue("ConfigVersion", VERSION) + return (config_version, fc_files) def __init__(self, args): app_path = args['app_path'] - config_path = args['config_path'] + self.CONFIG_PATH = args['config_path'] QtWidgets.QWidget.__init__(self) self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }") @@ -48,37 +68,24 @@ class FlashGBX(QtWidgets.QWidget): self.setWindowFlags(self.windowFlags() | QtGui.Qt.MSWindowsFixedSizeDialogHint); # Settings and Config - self.SETTINGS = QtCore.QSettings(config_path + "/config.ini", QtCore.QSettings.IniFormat) - config_version = self.SETTINGS.value("ConfigVersion") - if not os.path.exists(config_path): os.makedirs(config_path) - fc_files = glob.glob("{0:s}/fc_*.txt".format(config_path)) - if config_version is not None and len(fc_files) == 0: - print("FAIL: No flash cartridge type configuration files found. Resetting configuration...\n") - self.SETTINGS.clear() - elif args['argparsed'].reset: - self.SETTINGS.clear() - print("All configuration has been reset.\n") - + (config_version, fc_files) = self.ReadConfig(reset=args['argparsed'].reset) if config_version != VERSION: rf_list = "" if os.path.exists(app_path + "/res/config.zip"): with ZipFile(app_path + "/res/config.zip") as zip: for zfile in zip.namelist(): - if os.path.exists(config_path + "/" + zfile): + if os.path.exists(self.CONFIG_PATH + "/" + zfile): zfile_crc = zip.getinfo(zfile).CRC - with open(config_path + "/" + zfile, "rb") as ofile: buffer = ofile.read() + with open(self.CONFIG_PATH + "/" + zfile, "rb") as ofile: buffer = ofile.read() ofile_crc = zlib.crc32(buffer) & 0xffffffff if zfile_crc == ofile_crc: continue - os.rename(config_path + "/" + zfile, config_path + "/" + zfile + "_" + datetime.now().strftime("%Y%m%d%H%M%S") + ".bak") + os.rename(self.CONFIG_PATH + "/" + zfile, self.CONFIG_PATH + "/" + zfile + "_" + datetime.now().strftime("%Y%m%d%H%M%S") + ".bak") rf_list += zfile + "\n" - zip.extract(zfile, config_path + "/") + zip.extract(zfile, self.CONFIG_PATH + "/") if rf_list != "": QtWidgets.QMessageBox.information(self, APPNAME, "The application was recently updated and some config files have been updated as well. You will find backup copies of them in your configuration directory.\n\nUpdated files:\n" + rf_list[:-1], QtWidgets.QMessageBox.Ok) - fc_files = glob.glob("{0:s}/fc_*.txt".format(config_path)) + fc_files = glob.glob("{0:s}/fc_*.txt".format(self.CONFIG_PATH)) else: - print("ERROR: {:s} not found. This is required to read the flash cartridge type configuration\n".format(app_path + "/res/config.zip")) - - self.SETTINGS.setValue("ConfigVersion", VERSION) - self.CONFIG_PATH = config_path + print("ERROR: {:s} not found. This is required to read the flash cartridge type configuration.\n".format(app_path + "/res/config.zip")) # Read flash cart types for file in fc_files: @@ -88,7 +95,7 @@ class FlashGBX(QtWidgets.QWidget): try: specs = json.loads(specs_int) except: - print("WARNING: Flash chip config file “{:s}” is broken and needs to be fixed before it can be used.".format(os.path.basename(file))) + print("WARNING: Flash chip config file “{:s}” could not be parsed and needs to be fixed before it can be used.".format(os.path.basename(file))) continue for name in specs["names"]: if not specs["type"] in self.FLASHCARTS: continue # only DMG and AGB are supported right now @@ -144,14 +151,11 @@ class FlashGBX(QtWidgets.QWidget): rowActionsGeneral3 = QtWidgets.QHBoxLayout() self.btnFlashROM = QtWidgets.QPushButton("&Flash ROM") self.btnFlashROM.setStyleSheet("min-height: 17px;") - self.mnuFlashROM = QtWidgets.QMenu() - self.mnuFlashROM.addAction("&Complete ROM file", self.FlashROM) - self.mnuFlashROM.addAction("&Trimmed ROM file", lambda: self.FlashROM(trim_rom=True)) - self.btnFlashROM.setMenu(self.mnuFlashROM) + self.connect(self.btnFlashROM, QtCore.SIGNAL("clicked()"), self.FlashROM) rowActionsGeneral3.addWidget(self.btnFlashROM) self.btnRestoreRAM = QtWidgets.QPushButton("Writ&e Save Data") self.mnuRestoreRAM = QtWidgets.QMenu() - self.mnuRestoreRAM.addAction("&Restore save data file", self.WriteRAM) + self.mnuRestoreRAM.addAction("&Restore from save data file", self.WriteRAM) self.mnuRestoreRAM.addAction("&Erase cartridge save data", lambda: self.WriteRAM(erase=True)) self.btnRestoreRAM.setMenu(self.mnuRestoreRAM) self.btnRestoreRAM.setStyleSheet("min-height: 17px;") @@ -178,7 +182,7 @@ class FlashGBX(QtWidgets.QWidget): rowStatus1a.addWidget(self.lblStatus1aResult) grpStatusLayout.addLayout(rowStatus1a) rowStatus2a = QtWidgets.QHBoxLayout() - self.lblStatus2a = QtWidgets.QLabel("Speed:") + self.lblStatus2a = QtWidgets.QLabel("Transfer rate:") rowStatus2a.addWidget(self.lblStatus2a) self.lblStatus2aResult = QtWidgets.QLabel("–") rowStatus2a.addWidget(self.lblStatus2aResult) @@ -200,10 +204,11 @@ class FlashGBX(QtWidgets.QWidget): self.prgStatus = QtWidgets.QProgressBar() self.SetProgressBars(min=0, max=1, value=0) rowStatus2.addWidget(self.prgStatus) - btnText = "&Abort" + btnText = "Stop" self.btnCancel = QtWidgets.QPushButton(btnText) self.btnCancel.setEnabled(False) - btnWidth = self.btnCancel.fontMetrics().boundingRect(btnText).width() + 14 + btnWidth = self.btnCancel.fontMetrics().boundingRect(btnText).width() + 15 + if platform.system() == "Darwin": btnWidth += 12 self.btnCancel.setMaximumWidth(btnWidth) self.connect(self.btnCancel, QtCore.SIGNAL("clicked()"), self.AbortOperation) rowStatus2.addWidget(self.btnCancel) @@ -225,11 +230,25 @@ class FlashGBX(QtWidgets.QWidget): self.layout_devices.addWidget(self.cmbDevice) self.layout_devices.addStretch() - btnText = "C&onfig" + btnText = "C&onfig" self.btnConfig = QtWidgets.QPushButton(btnText) - btnWidth = self.btnConfig.fontMetrics().boundingRect(btnText).width() + 14 + btnWidth = self.btnConfig.fontMetrics().boundingRect(btnText).width() + 24 + if platform.system() == "Darwin": btnWidth += 12 self.btnConfig.setMaximumWidth(btnWidth) - self.connect(self.btnConfig, QtCore.SIGNAL("clicked()"), self.OpenConfigDir) + self.mnuConfig = QtWidgets.QMenu() + self.mnuConfig.addAction("&Append date && time to filename of save data backups", lambda: self.SETTINGS.setValue("SaveFileNameAddDateTime", str(self.mnuConfig.actions()[0].isChecked()).lower().replace("true", "enabled").replace("false", "disabled"))) + self.mnuConfig.addAction("Prefer §or erase over full chip erase when available", lambda: self.SETTINGS.setValue("PreferSectorErase", str(self.mnuConfig.actions()[1].isChecked()).lower().replace("true", "enabled").replace("false", "disabled"))) + self.mnuConfig.addAction("Enable &fast read mode (experimental)", lambda: self.SETTINGS.setValue("FastReadMode", str(self.mnuConfig.actions()[2].isChecked()).lower().replace("true", "enabled").replace("false", "disabled"))) # GBxCart RW + self.mnuConfig.addSeparator() + self.mnuConfig.addAction("Show &configuration directory", self.OpenConfigDir) + self.mnuConfig.actions()[0].setCheckable(True) + self.mnuConfig.actions()[0].setChecked(self.SETTINGS.value("SaveFileNameAddDateTime") == "enabled") + self.mnuConfig.actions()[1].setCheckable(True) + self.mnuConfig.actions()[1].setChecked(self.SETTINGS.value("PreferSectorErase") == "enabled") + self.mnuConfig.actions()[2].setCheckable(True) # GBxCart RW + self.mnuConfig.actions()[2].setChecked(self.SETTINGS.value("FastReadMode") == "enabled") # GBxCart RW + self.btnConfig.setMenu(self.mnuConfig) + self.btnScan = QtWidgets.QPushButton("&Device Scan") self.connect(self.btnScan, QtCore.SIGNAL("clicked()"), self.FindDevices) self.btnConnect = QtWidgets.QPushButton("&Connect") @@ -482,6 +501,7 @@ class FlashGBX(QtWidgets.QWidget): print("Disconnected from {:s}\n".format(devname)) except: pass + self.CONN = None self.btnScan.show() self.optAGB.setEnabled(False) @@ -564,6 +584,7 @@ class FlashGBX(QtWidgets.QWidget): print("Connected to " + dev.GetFullName()) self.grpDMGCartridgeInfo.setEnabled(True) self.grpAGBCartridgeInfo.setEnabled(True) + self.grpActions.setEnabled(True) self.btnCancel.setEnabled(False) self.SetProgressBars(min=0, max=1, value=0) @@ -633,6 +654,7 @@ class FlashGBX(QtWidgets.QWidget): self.CONN.CANCEL = True def FinishOperation(self): + if self.lblStatus2aResult.text() == "Pending...": self.lblStatus2aResult.setText("–") self.lblStatus4aResult.setText("") self.grpDMGCartridgeInfo.setEnabled(True) self.grpAGBCartridgeInfo.setEnabled(True) @@ -655,13 +677,14 @@ class FlashGBX(QtWidgets.QWidget): self.cmbAGBCartridgeTypeResult.setCurrentIndex(t5) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle=APPNAME, text="ROM flashing complete!", standardButtons=QtWidgets.QMessageBox.Ok) msgbox.exec() + #self.CONN.FlashROM(fncSetProgress=self.DEBUG[0], path=self.DEBUG[1], cart_type=self.DEBUG[2], override_voltage=self.DEBUG[3]) elif self.CONN.INFO["last_action"] == 1: # Backup ROM self.CONN.INFO["last_action"] = 0 if self.CONN.GetMode() == "DMG": if self.CONN.INFO["rom_checksum"] == self.CONN.INFO["rom_checksum_calc"]: - self.lblHeaderROMChecksumResult.setText("OK (0x{:04X})".format(self.CONN.INFO["rom_checksum"])) + self.lblHeaderROMChecksumResult.setText("Valid (0x{:04X})".format(self.CONN.INFO["rom_checksum"])) self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }"); self.lblStatus4a.setText("Done!") msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle=APPNAME, text="The ROM was dumped successfully!", standardButtons=QtWidgets.QMessageBox.Ok) @@ -670,14 +693,15 @@ class FlashGBX(QtWidgets.QWidget): 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.lblStatus4a.setText("Done.") - QtWidgets.QMessageBox.warning(self, APPNAME, "The ROM dump is complete, but the checksum is not correct. This may indicate a bad dump, however this is normal for some bootleg cartridges, prototypes, patched games and trimmed ROM files.\nWhen dumping from a flash cartridge, manually selecting MBC5 before dumping may also help.", QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, APPNAME, "The ROM was dumped, but the checksum is not correct. This may indicate a bad dump, however this can be normal for some reproduction cartridges, prototypes and patched games.\nWhen dumping from a flash cartridge, manually selecting MBC5 before dumping may also help.", QtWidgets.QMessageBox.Ok) elif self.CONN.GetMode() == "AGB": if self.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]: - self.lblAGBHeaderROMChecksumResult.setText("OK (0x{:06X})".format(self.AGB_Global_CRC32)) + self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(self.AGB_Global_CRC32)) self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }"); self.lblStatus4a.setText("Done!") msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle=APPNAME, text="The ROM was dumped successfully!", standardButtons=QtWidgets.QMessageBox.Ok) msgbox.exec() + elif self.AGB_Global_CRC32 == 0: self.lblAGBHeaderROMChecksumResult.setText("0x{:06X}".format(self.CONN.INFO["rom_checksum_calc"])) self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet()) @@ -687,16 +711,16 @@ class FlashGBX(QtWidgets.QWidget): self.lblAGBHeaderROMChecksumResult.setText("Invalid (0x{:06X}≠0x{:06X})".format(self.CONN.INFO["rom_checksum_calc"], self.AGB_Global_CRC32)) self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }"); self.lblStatus4a.setText("Done.") - QtWidgets.QMessageBox.warning(self, APPNAME, "The ROM dump is complete, but the checksum doesn’t match the known database entry. This may indicate a bad dump, however this is normal for some bootleg cartridges, prototypes, patched games and trimmed ROM files.", QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, APPNAME, "The ROM was dumped, but the checksum doesn’t match the known database entry. This may indicate a bad dump, however this can be normal for some reproduction cartridges, prototypes and patched games.", QtWidgets.QMessageBox.Ok) elif self.CONN.INFO["last_action"] == 2: # Backup RAM self.lblStatus4a.setText("Done!") self.CONN.INFO["last_action"] = 0 - + elif self.CONN.INFO["last_action"] == 3: # Restore RAM self.lblStatus4a.setText("Done!") self.CONN.INFO["last_action"] = 0 - + else: self.lblStatus4a.setText("Ready.") self.CONN.INFO["last_action"] = 0 @@ -707,6 +731,10 @@ class FlashGBX(QtWidgets.QWidget): cart_type = 0 cart_text = "" + if self.CONN.CheckROMStable() is False: + QtWidgets.QMessageBox.critical(self, APPNAME, "Unstable ROM reading detected. Please make sure you selected the correct mode and that the cartridge contacts are clean.", QtWidgets.QMessageBox.Ok) + return 0 + if self.CONN.GetMode() in self.FLASHCARTS and len(self.FLASHCARTS[self.CONN.GetMode()]) == 0: QtWidgets.QMessageBox.critical(self, APPNAME, "No flash cartridge type configuration files found. Try to restart the application with the “--reset” switch to reset the configuration.", QtWidgets.QMessageBox.Ok) return 0 @@ -722,7 +750,7 @@ class FlashGBX(QtWidgets.QWidget): else: detected = self.CONN.AutoDetectFlash(limitVoltage) if len(detected) == 0: - msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle=APPNAME, text="No pre-configured flash cartridge type was detected. You can still try and manually select one from the list -- look for similar PCB text and/or flash chip markings. However, chances are this cartridge is currently not supported for flashing with " + APPNAME + ".\n\nWould you like " + APPNAME + " to run a Flash ID check? This may help adding support for your flash cartridge in the future.", standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle=APPNAME, text="No pre-configured flash cartridge type was detected. You can still try and manually select one from the list -- look for similar PCB text and/or flash chip markings. However, chances are this cartridge is currently not supported for flashing with " + APPNAME + ".\n\nWould you like " + APPNAME + " to run a flash chip query? This may help adding support for your flash cartridge in the future.", standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if self.CONN.GetMode() == "DMG": msgbox.setCheckBox(cb) answer = msgbox.exec() @@ -732,30 +760,44 @@ class FlashGBX(QtWidgets.QWidget): limitVoltage = False if answer == QtWidgets.QMessageBox.Yes: - check = self.CONN.CheckFlashID(limitVoltage) - if check == "": - QtWidgets.QMessageBox.information(self, APPNAME, "There was no Flash ID response from the cartridge. There probably is no flash chip or it requires unique unlocking and handling.", QtWidgets.QMessageBox.Ok) + (flashid, cfi, cfi_raw) = self.CONN.CheckFlashChip(limitVoltage) + if cfi == "": + QtWidgets.QMessageBox.information(self, APPNAME, "There was no Common Flash Interface (CFI) response from the cartridge. If a flash chip exists on the cartridge PCB, it may be too old or require unique unlocking and handling.", QtWidgets.QMessageBox.Ok) else: - QtWidgets.QMessageBox.information(self, APPNAME, "Here is what the Flash ID check returned:
" + check + "This information along with a good quality picture of the PCB with readable chip markings may help adding support for your flash cartridge. You should be able to copy & paste the text above.", QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.information(self, APPNAME, "Flash chip query result:
" + flashid + "
" + str(cfi) + "This information along with a good quality picture of the PCB with readable chip markings may help adding support for your flash cartridge.", QtWidgets.QMessageBox.Ok) + with open(self.CONFIG_PATH + "/cfi.bin", "wb") as f: f.write(cfi_raw) return 0 else: cart_type = detected[0] if self.CONN.GetMode() == "DMG": cart_types = self.CONN.GetSupportedCartridgesDMG() for i in range(0, len(detected)): - cart_text += cart_types[0][detected[i]] + "\n" + cart_text += "- " + cart_types[0][detected[i]] + "\n" elif self.CONN.GetMode() == "AGB": cart_types = self.CONN.GetSupportedCartridgesAGB() for i in range(0, len(detected)): - cart_text += cart_types[0][detected[i]] + "\n" + cart_text += "- " + cart_types[0][detected[i]] + "\n" if len(detected) == 1: - msg_text = "The following flash cartridge type was detected:\n" + cart_text + "\nIt seems to have a storage capacity of up to {:d} MB.\nOther features (such as save type) have to be manually selected.".format(int(cart_types[1][detected[0]]['flash_size'] / 1024 / 1024)) + msg_text = "The following flash cartridge type was detected:\n" + cart_text + "\nThe supported ROM size is up to {:d} MB.".format(int(cart_types[1][detected[0]]['flash_size'] / 1024 / 1024)) else: - msg_text = "The following flash cartridge type variants were detected:\n" + cart_text + "\nAll from this list should behave identical. The flash chip seems to have a storage capacity of up to {:d} MB.\nOther features (such as save type) have to be manually selected.".format(int(cart_types[1][detected[0]]['flash_size'] / 1024 / 1024)) + msg_text = "The following flash cartridge type variants were detected:\n" + cart_text + "\nAll from this list should work the same. The first name/alias will now be auto-selected.\n\nThe supported ROM size is up to {:d} MB.".format(int(cart_types[1][detected[0]]['flash_size'] / 1024 / 1024)) - if QtWidgets.QMessageBox.Cancel == QtWidgets.QMessageBox.information(self, APPNAME, msg_text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel): - return 0 + msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle=APPNAME, text=msg_text) + button_ok = msgbox.addButton("&OK", QtWidgets.QMessageBox.ActionRole) + button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole) + button_cfi = msgbox.addButton(" Run flash chip &query ", QtWidgets.QMessageBox.ActionRole) + msgbox.setDefaultButton(button_ok) + msgbox.setEscapeButton(button_cancel) + answer = msgbox.exec() + if msgbox.clickedButton() == button_cfi: + (flashid, cfi, cfi_raw) = self.CONN.CheckFlashChip(limitVoltage=limitVoltage, cart_type=cart_types[1][cart_type]) + if cfi == "": + QtWidgets.QMessageBox.information(self, APPNAME, "There was no Common Flash Interface (CFI) response from the cartridge. If a flash chip exists on the cartridge PCB, it may be too old or require unique unlocking and handling.", QtWidgets.QMessageBox.Ok) + else: + QtWidgets.QMessageBox.information(self, APPNAME, "Flash chip query result:
" + flashid + "
" + str(cfi) + "", QtWidgets.QMessageBox.Ok) + with open(self.CONFIG_PATH + "/cfi.bin", "wb") as f: f.write(cfi_raw) + elif msgbox.clickedButton() == button_cancel: return 0 return cart_type @@ -766,19 +808,35 @@ class FlashGBX(QtWidgets.QWidget): cart_type = self.CartridgeTypeAutoDetect() if (cart_type == 1): cart_type = 0 self.cmbDMGCartridgeTypeResult.setCurrentIndex(cart_type) + elif cart_types[1][index] == "RETAIL": # special keyword + pass + else: + for i in range(0, len(self.DMG_Header_ROM_Sizes_Flasher_Map)): + if cart_types[1][index]["flash_size"] == (self.DMG_Header_ROM_Sizes_Flasher_Map[i] * 0x4000): + self.cmbHeaderROMSizeResult.setCurrentIndex(i) + elif self.CONN.GetMode() == "AGB": cart_types = self.CONN.GetSupportedCartridgesAGB() if cart_types[1][index] == "AUTODETECT": # special keyword cart_type = self.CartridgeTypeAutoDetect() if (cart_type == 1): cart_type = 0 self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type) - else: - return + elif cart_types[1][index] == "RETAIL": # special keyword + pass + else: + self.cmbAGBHeaderROMSizeResult.setCurrentIndex(self.AGB_Header_ROM_Sizes_Map.index(cart_types[1][index]["flash_size"])) def BackupROM(self): if not self.CheckDeviceAlive(): return mbc = self.DMG_Header_Features_MBC[self.cmbHeaderFeaturesResult.currentIndex()] rom_banks = self.DMG_Header_ROM_Sizes_Flasher_Map[self.cmbHeaderROMSizeResult.currentIndex()] + + fast_read_mode = self.SETTINGS.value("FastReadMode") + if fast_read_mode and fast_read_mode.lower() == "enabled": + fast_read_mode = True + else: + fast_read_mode = False + rom_size = 0 if self.CONN.GetMode() == "DMG": if mbc == 1 and ("MOMOCOL" in self.lblHeaderTitleResult.text() or "BOMCOL" in self.lblHeaderTitleResult.text()): @@ -815,11 +873,18 @@ class FlashGBX(QtWidgets.QWidget): self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet()) self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet()) - self.CONN.BackupROM(self.setProgress, path, mbc, rom_banks, rom_size) + #self.DEBUG = [ self.SetProgress, path, mbc, rom_banks, rom_size, fast_read_mode ] + self.CONN.BackupROM(fncSetProgress=self.SetProgress, path=path, mbc=mbc, rom_banks=rom_banks, agb_rom_size=rom_size, fast_read_mode=fast_read_mode) - def FlashROM(self, dpath="", trim_rom=False): + def FlashROM(self, dpath=""): if not self.CheckDeviceAlive(): return path = "" + if dpath != "": + text = "The following ROM file will now be written to the flash cartridge:\n" + dpath + answer = QtWidgets.QMessageBox.question(self, APPNAME, text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + if answer == QtWidgets.QMessageBox.Cancel: return + path = dpath + if self.CONN.GetMode() == "DMG": setting_name = "LastDirRomDMG" last_dir = self.SETTINGS.value(setting_name) @@ -844,11 +909,6 @@ class FlashGBX(QtWidgets.QWidget): self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type) if cart_type == 0: return - if dpath != "": - answer = QtWidgets.QMessageBox.question(self, APPNAME, "The following ROM file will now be written to the flash cartridge:\n" + dpath, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) - if answer == QtWidgets.QMessageBox.Cancel: return - path = dpath - while path == "": if self.CONN.GetMode() == "DMG": path = QtWidgets.QFileDialog.getOpenFileName(self, "Flash ROM", last_dir, "Game Boy ROM File (*.gb *.gbc *.sgb *.bin);;All Files (*.*)")[0] @@ -878,7 +938,38 @@ class FlashGBX(QtWidgets.QWidget): override_voltage = 5 elif msgbox.clickedButton() == button_cancel: return - self.CONN.FlashROM(self.setProgress, path=path, cart_type=cart_type, trim_rom=trim_rom, override_voltage=override_voltage) + reverse_sectors = False + if 'sector_reversal' in carts[cart_type]: + msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle=APPNAME, text="The selected flash cartridge type is reported to sometimes have reversed sectors. If the cartridge is not working after flashing, try reversed sectors.") + button_normal = msgbox.addButton("Normal", QtWidgets.QMessageBox.ActionRole) + button_reversed = msgbox.addButton("Reversed", QtWidgets.QMessageBox.ActionRole) + button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole) + msgbox.setDefaultButton(button_normal) + msgbox.setEscapeButton(button_cancel) + answer = msgbox.exec() + if msgbox.clickedButton() == button_reversed: + reverse_sectors = True + elif msgbox.clickedButton() == button_cancel: return + + prefer_sector_erase = False + if 'chip_erase' in carts[cart_type]['commands'] and 'sector_erase' in carts[cart_type]['commands']: + prefer_sector_erase = self.SETTINGS.value("PreferSectorErase") + if prefer_sector_erase and prefer_sector_erase.lower() == "enabled": + prefer_sector_erase = True + + if self.CONN.GetMode() == "DMG": + hdr = RomFileDMG(path).GetHeader() + elif self.CONN.GetMode() == "AGB": + hdr = RomFileAGB(path).GetHeader() + if not hdr["logo_correct"]: + answer = QtWidgets.QMessageBox.warning(self, APPNAME, "Warning: The ROM file you selected may not boot on actual hardware due to invalid logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + if answer == QtWidgets.QMessageBox.Cancel: return + if not hdr["header_checksum_correct"]: + answer = QtWidgets.QMessageBox.warning(self, APPNAME, "Warning: The ROM file you selected may not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).".format(hdr["header_checksum_calc"], hdr["header_checksum"]), QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + if answer == QtWidgets.QMessageBox.Cancel: return + + #self.DEBUG = [ self.SetProgress, path, cart_type, override_voltage ] + self.CONN.FlashROM(fncSetProgress=self.SetProgress, path=path, cart_type=cart_type, override_voltage=override_voltage, prefer_sector_erase=prefer_sector_erase, reverse_sectors=reverse_sectors) buffer = None def BackupRAM(self): @@ -904,18 +995,23 @@ class FlashGBX(QtWidgets.QWidget): features = 0 save_type = self.cmbAGBSaveTypeResult.currentIndex() if save_type == 0: - QtWidgets.QMessageBox.critical(self, APPNAME, "The save type was not selected or auto-detection failed.", QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.warning(self, APPNAME, "The save type was not selected or auto-detection failed.", QtWidgets.QMessageBox.Ok) return else: return - - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav" + + add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime") + if add_date_time and add_date_time.lower() == "enabled": + path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + "_" + datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".sav" + else: + path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav" + path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0] if (path == ""): return self.SETTINGS.setValue(setting_name, os.path.dirname(path)) - self.CONN.BackupRAM(self.setProgress, path, features, save_type) + self.CONN.BackupRAM(fncSetProgress=self.SetProgress, path=path, mbc=features, save_type=save_type) def WriteRAM(self, dpath="", erase=False): if not self.CheckDeviceAlive(): return @@ -943,7 +1039,8 @@ class FlashGBX(QtWidgets.QWidget): return if dpath != "": - answer = QtWidgets.QMessageBox.question(self, APPNAME, "The following save data file will now be written to the cartridge:\n" + dpath, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + text = "The following save data file will now be written to the cartridge:\n" + dpath + answer = QtWidgets.QMessageBox.question(self, APPNAME, text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) if answer == QtWidgets.QMessageBox.Cancel: return path = dpath self.SETTINGS.setValue(setting_name, os.path.dirname(path)) @@ -954,10 +1051,9 @@ class FlashGBX(QtWidgets.QWidget): path = path + ".sav" path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0] if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path)) + if (path == ""): return - if (path == ""): return - - self.CONN.RestoreRAM(self.setProgress, path, features, save_type, erase) + self.CONN.RestoreRAM(fncSetProgress=self.SetProgress, path=path, mbc=features, save_type=save_type, erase=erase) def CheckDeviceAlive(self, setMode=False): if self.CONN is not None: @@ -1075,7 +1171,7 @@ class FlashGBX(QtWidgets.QWidget): self.lblHeaderLogoValidResult.setText("Invalid") self.lblHeaderLogoValidResult.setStyleSheet("QLabel { color: red; }"); if data['header_checksum_correct']: - self.lblHeaderChecksumResult.setText("OK (0x{:02X})".format(data['header_checksum'])) + self.lblHeaderChecksumResult.setText("Valid (0x{:02X})".format(data['header_checksum'])) self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet()) else: self.lblHeaderChecksumResult.setText("Invalid (0x{:02X})".format(data['header_checksum'])) @@ -1123,7 +1219,7 @@ class FlashGBX(QtWidgets.QWidget): self.lblAGBHeader96hResult.setStyleSheet("QLabel { color: red; }"); if data['header_checksum_correct']: - self.lblAGBHeaderChecksumResult.setText("OK (0x{:02X})".format(data['header_checksum'])) + self.lblAGBHeaderChecksumResult.setText("Valid (0x{:02X})".format(data['header_checksum'])) self.lblAGBHeaderChecksumResult.setStyleSheet(self.lblAGBHeaderCodeResult.styleSheet()) else: self.lblAGBHeaderChecksumResult.setText("Invalid (0x{:02X})".format(data['header_checksum'])) @@ -1172,6 +1268,8 @@ class FlashGBX(QtWidgets.QWidget): self.lblStatus3aResult.setText("–") self.lblStatus4a.setText("Ready.") self.FinishOperation() + if self.CONN.CheckROMStable() is False: + QtWidgets.QMessageBox.warning(self, APPNAME, "Unstable ROM reading detected. Please reconnect the device, make sure you selected the correct mode and that the cartridge contacts are clean.", QtWidgets.QMessageBox.Ok) def formatFileSize(self, size): size = size / 1024 @@ -1202,44 +1300,150 @@ class FlashGBX(QtWidgets.QWidget): s = s + "seconds" return s - def setProgress(self, error, cur, max, speed=0, elapsed=0, left=0): - if error != None and type(error) != type({}): + def SetProgress(self, args): + now = time.time() + if args["action"] == "INITIALIZE": + self.PROGRESS["method"] = args["method"] + self.PROGRESS["size"] = args["size"] + if "pos" in args: + self.PROGRESS["pos"] = args["pos"] + else: + self.PROGRESS["pos"] = 0 + if "time_start" in args: + self.PROGRESS["time_start"] = args["time_start"] + else: + self.PROGRESS["time_start"] = now + self.PROGRESS["time_last_emit"] = now + self.PROGRESS["time_last_update_speed"] = now + self.PROGRESS["time_left"] = 0 + self.PROGRESS["speed"] = 0 + self.PROGRESS["speeds"] = [] + self.PROGRESS["bytes_last_update_speed"] = 0 + self.UpdateProgress(self.PROGRESS) + + if args["action"] == "ABORT": + self.UpdateProgress(args) + self.PROGRESS = {} + + elif args["action"] in ("ERASE", "SECTOR_ERASE"): + if "time_start" in self.PROGRESS: + args["time_elapsed"] = now - self.PROGRESS["time_start"] + else: + args["time_elapsed"] = now - args["time_start"] + args["pos"] = 1 + args["size"] = 0 + self.UpdateProgress(args) + + elif self.PROGRESS == {}: + return + + elif args["action"] == "UPDATE_POS": + self.PROGRESS["pos"] = args["pos"] + + #elif args["action"] in ("UPDATE_SPEED"): # so that speed displayed won't drop right after sector erase + # self.PROGRESS["speed_updated"] = True + # self.PROGRESS["time_last_update_speed"] = now + # self.PROGRESS["bytes_last_update_speed"] = self.PROGRESS["pos"] + + elif args["action"] in ("READ", "WRITE"): + if "method" not in self.PROGRESS: return + elif args["action"] == "READ" and self.PROGRESS["method"] in ("SAVE_WRITE", "ROM_WRITE"): return + elif args["action"] == "WRITE" and self.PROGRESS["method"] in ("SAVE_READ", "ROM_READ"): return + + self.PROGRESS["pos"] += args["bytes_added"] + if (now - self.PROGRESS["time_last_emit"]) > 0.05: + self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"] + if (now - self.PROGRESS["time_last_update_speed"]) > 0.25: + time_delta = now - self.PROGRESS["time_last_update_speed"] + pos_delta = self.PROGRESS["pos"] - self.PROGRESS["bytes_last_update_speed"] + if time_delta > 0: + speed = (pos_delta / time_delta) / 1024 + self.PROGRESS["speeds"].append(speed) + if len(self.PROGRESS["speeds"]) > 32: self.PROGRESS["speeds"].pop(0) + self.PROGRESS["speed"] = statistics.median(self.PROGRESS["speeds"]) #(pos_delta / time_delta) / 1024 + self.PROGRESS["time_last_update_speed"] = now + self.PROGRESS["bytes_last_update_speed"] = self.PROGRESS["pos"] + + if "skipping" in args and args["skipping"] is True: + self.PROGRESS["speed"] = 0 + self.PROGRESS["skipping"] = True + else: + self.PROGRESS["skipping"] = False + + if self.PROGRESS["speed"] > 0: + self.PROGRESS["time_left"] = (self.PROGRESS["size"] - self.PROGRESS["pos"]) / 1024 / self.PROGRESS["speed"] + + self.UpdateProgress(self.PROGRESS) + self.PROGRESS["time_last_emit"] = now + + elif args["action"] == "FINISHED": + self.PROGRESS["pos"] = self.PROGRESS["size"] + self.PROGRESS["bytes_last_update_speed"] = self.PROGRESS["size"] + self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"] + self.PROGRESS["time_last_emit"] = now + self.PROGRESS["time_last_update_speed"] = now + self.PROGRESS["time_left"] = 0 + self.PROGRESS["speed"] = (self.PROGRESS["size"] / self.PROGRESS["time_elapsed"]) / 1024 + self.PROGRESS["bytes_last_emit"] = self.PROGRESS["size"] + + if self.PROGRESS["speed"] > self.PROGRESS["size"] / 1024: + self.PROGRESS["speed"] = self.PROGRESS["size"] / 1024 + + self.UpdateProgress(self.PROGRESS) + self.PROGRESS = {} + + def UpdateProgress(self, args): + if args is None: return + + if "error" in args: self.lblStatus4a.setText("Failed!") self.grpDMGCartridgeInfo.setEnabled(True) self.grpAGBCartridgeInfo.setEnabled(True) self.grpActions.setEnabled(True) self.btnCancel.setEnabled(False) - QtWidgets.QMessageBox.critical(self, APPNAME, str(error), QtWidgets.QMessageBox.Ok) + QtWidgets.QMessageBox.critical(self, APPNAME, str(args["error"]), QtWidgets.QMessageBox.Ok) return - + self.grpDMGCartridgeInfo.setEnabled(False) self.grpAGBCartridgeInfo.setEnabled(False) self.grpActions.setEnabled(False) - if (type(error) == type({})): - if error["action"] == "ERASE": + pos = 0 + size = 0 + speed = 0 + elapsed = 0 + left = 0 + if "pos" in args: pos = args["pos"] + if "size" in args: size = args["size"] + if "speed" in args: speed = args["speed"] + if "time_elapsed" in args: elapsed = args["time_elapsed"] + if "time_left" in args: left = args["time_left"] + + if "action" in args: + if args["action"] == "ERASE": self.lblStatus1aResult.setText("Pending...") self.lblStatus2aResult.setText("Pending...") self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) self.lblStatus4a.setText("Erasing flash...") self.lblStatus4aResult.setText("") - self.btnCancel.setEnabled(error["abortable"]) - self.SetProgressBars(min=0, max=max, value=cur) - elif error["action"] == "SECTOR_ERASE": - self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) + self.btnCancel.setEnabled(args["abortable"]) + self.SetProgressBars(min=0, max=size, value=pos) + elif args["action"] == "SECTOR_ERASE": + if elapsed >= 1: + self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) self.lblStatus4a.setText("Erasing sector...") self.lblStatus4aResult.setText("") - self.btnCancel.setEnabled(error["abortable"]) - self.SetProgressBars(min=0, max=max, value=cur, setPause=True) - elif error["action"] == "VERIFY": - self.lblStatus1aResult.setText("") - self.lblStatus2aResult.setText("") - self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) - self.lblStatus4a.setText("Verifying...") + self.btnCancel.setEnabled(args["abortable"]) + self.SetProgressBars(min=0, max=size, value=pos, setPause=True) + elif args["action"] == "ABORTING": + self.lblStatus1aResult.setText("–") + self.lblStatus2aResult.setText("–") + self.lblStatus3aResult.setText("–") + self.lblStatus4a.setText("Stopping... Please wait.") self.lblStatus4aResult.setText("") - self.btnCancel.setEnabled(error["abortable"]) - self.SetProgressBars(min=0, max=max, value=cur) - elif error["action"] == "ABORT": + self.btnCancel.setEnabled(args["abortable"]) + self.SetProgressBars(min=0, max=size, value=pos) + elif args["action"] == "ABORT": wd = 10 while self.CONN.WORKER.isRunning(): time.sleep(0.1) @@ -1253,36 +1457,42 @@ class FlashGBX(QtWidgets.QWidget): self.lblStatus1aResult.setText("–") self.lblStatus2aResult.setText("–") self.lblStatus3aResult.setText("–") - self.lblStatus4a.setText("Aborted.") + self.lblStatus4a.setText("Stopped.") self.lblStatus4aResult.setText("") self.btnCancel.setEnabled(False) self.SetProgressBars(min=0, max=1, value=0) self.btnCancel.setEnabled(False) - if "info_type" in error.keys() and "info_msg" in error.keys(): - if error["info_type"] == "msgbox_critical": - QtWidgets.QMessageBox.critical(self, APPNAME, error["info_msg"], QtWidgets.QMessageBox.Ok) - elif error["info_type"] == "msgbox_information": - QtWidgets.QMessageBox.information(self, APPNAME, error["info_msg"], QtWidgets.QMessageBox.Ok) - elif error["info_type"] == "label": - self.lblStatus4a.setText(error["info_msg"]) + if "info_type" in args.keys() and "info_msg" in args.keys(): + if args["info_type"] == "msgbox_critical": + QtWidgets.QMessageBox.critical(self, APPNAME, args["info_msg"], QtWidgets.QMessageBox.Ok) + elif args["info_type"] == "msgbox_information": + QtWidgets.QMessageBox.information(self, APPNAME, args["info_msg"], QtWidgets.QMessageBox.Ok) + elif args["info_type"] == "label": + self.lblStatus4a.setText(args["info_msg"]) return + else: - self.SetProgressBars(min=0, max=max, value=cur) + self.SetProgressBars(min=0, max=size, value=pos) self.btnCancel.setEnabled(True) - self.lblStatus1aResult.setText(self.formatFileSize(cur)) - if speed == 0: - self.lblStatus2aResult.setText("–") - self.lblStatus4aResult.setText("–") - pass - else: + self.lblStatus1aResult.setText(self.formatFileSize(pos)) + if speed > 0: self.lblStatus2aResult.setText("{:.2f} KB/s".format(speed)) - self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) - self.lblStatus4a.setText("Time left:") - if speed > 0 and left > 0: + else: + self.lblStatus2aResult.setText("Pending...") + if left > 0: self.lblStatus4aResult.setText(self.formatProgressTime(left)) - elif max == cur: + else: + self.lblStatus4aResult.setText("Pending...") + if elapsed > 0: + self.lblStatus3aResult.setText(self.formatProgressTime(elapsed)) + + if speed == 0 and "skipping" in args and args["skipping"] is True: + self.lblStatus4aResult.setText("Pending...") + self.lblStatus4a.setText("Time left:") + + if size == pos: wd = 10 while self.CONN.WORKER.isRunning(): time.sleep(0.1) @@ -1291,7 +1501,7 @@ class FlashGBX(QtWidgets.QWidget): pass self.FinishOperation() - def SetProgressBars(self, min=0, max=100, value=0, setPause=None, setStop=None): + def SetProgressBars(self, min=0, max=100, value=0, setPause=None): self.prgStatus.setMinimum(min) self.prgStatus.setMaximum(max) self.prgStatus.setValue(value) @@ -1374,7 +1584,7 @@ class FlashGBX(QtWidgets.QWidget): # Taskbar Progress on Windows only try: - from PySide2.QtWinExtras import QWinTaskbarProgress, QWinTaskbarButton, QtWin + from PySide2.QtWinExtras import QWinTaskbarButton, QtWin myappid = 'lesserkuma.flashgbx' QtWin.setCurrentProcessExplicitAppUserModelID(myappid) taskbar_button = QWinTaskbarButton() diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py new file mode 100644 index 0000000..b5e906a --- /dev/null +++ b/FlashGBX/Util.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# UTF-8 +import math, time, datetime, copy + +# Utility functions +def bitswap(n, s): + p, q = s + if (((n & (1 << p)) >> p) ^ ((n & (1 << q)) >> q)) == 1: + n ^= (1 << p) + n ^= (1 << q) + return n + +def ParseCFI(buffer): + buffer = copy.copy(buffer) + info = {} + magic = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24])) + if magic != "QRY": # nothing swapped + return False + + try: + info["flash_id"] = buffer[0:8] + info["magic"] = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24])) + + if buffer[0x36] == 0xFF and buffer[0x48] == 0xFF: + print("FAIL: No information about the voltage range found in CFI data.") + try: + with open("./cfi_debug.bin", "wb") as f: f.write(buffer) + except: + pass + return False + + info["vdd_min"] = (buffer[0x36] >> 4) + ((buffer[0x36] & 0x0F) / 10) + info["vdd_max"] = (buffer[0x38] >> 4) + ((buffer[0x38] & 0x0F) / 10) + + if buffer[0x3E] > 0 and buffer[0x3E] < 0xFF: + info["single_write"] = True + info["single_write_time_avg"] = int(math.pow(2, buffer[0x3E])) + info["single_write_time_max"] = int(math.pow(2, buffer[0x46]) * info["single_write_time_avg"]) + else: + info["single_write"] = False + + if buffer[0x40] > 0 and buffer[0x40] < 0xFF: + info["buffer_write"] = True + info["buffer_write_time_avg"] = int(math.pow(2, buffer[0x40])) + info["buffer_write_time_max"] = int(math.pow(2, buffer[0x48]) * info["buffer_write_time_avg"]) + else: + info["buffer_write"] = False + + if buffer[0x42] > 0 and buffer[0x42] < 0xFF: + info["sector_erase"] = True + info["sector_erase_time_avg"] = int(math.pow(2, buffer[0x42])) + info["sector_erase_time_max"] = int(math.pow(2, buffer[0x4A]) * info["sector_erase_time_avg"]) + else: + info["sector_erase"] = False + + if buffer[0x44] > 0 and buffer[0x44] < 0xFF: + info["chip_erase"] = True + info["chip_erase_time_avg"] = int(math.pow(2, buffer[0x44])) + info["chip_erase_time_max"] = int(math.pow(2, buffer[0x4C]) * info["chip_erase_time_avg"]) + else: + info["chip_erase"] = False + + info["tb_boot_sector"] = False + if "{:s}{:s}{:s}".format(chr(buffer[0x80]), chr(buffer[0x82]), chr(buffer[0x84])) == "PRI": + if buffer[0x9E] != 0 and buffer[0x9E] != 0xFF: + temp = { 0x02: 'Bottom Boot Device', 0x03: 'Top Boot Device' } + try: + info["tb_boot_sector"] = "{:s} (0x{:02X})".format(temp[buffer[0x9E]], buffer[0x9E]) + except: + info["tb_boot_sector"] = "0x{:02X}".format(buffer[0x9E]) + elif "{:s}{:s}{:s}".format(chr(buffer[0x214]), chr(buffer[0x216]), chr(buffer[0x218])) == "PRI": + pass # todo + + info["device_size"] = int(math.pow(2, buffer[0x4E])) + info["buffer_size"] = buffer[0x56] << 8 | buffer[0x54] + if info["buffer_size"] > 1: + info["buffer_write"] = True + info["buffer_size"] = int(math.pow(2, info["buffer_size"])) + else: + del(info["buffer_size"]) + info["buffer_write"] = False + info["erase_sector_regions"] = buffer[0x58] + info["erase_sector_blocks"] = [] + total_blocks = 0 + pos = 0 + for i in range(0, min(4, info["erase_sector_regions"])): + b = (buffer[0x5C+(i*8)] << 8 | buffer[0x5A+(i*8)]) + 1 + t = (buffer[0x60+(i*8)] << 8 | buffer[0x5E+(i*8)]) * 256 + total_blocks += b + size = b * t + pos += size + info["erase_sector_blocks"].append([ t, b, size ]) + + except: + print("ERROR: Trying to parse CFI data resulted in an error.") + try: + with open("./cfi_debug.bin", "wb") as f: f.write(buffer) + except: + pass + return False + + return info + +def dprint(*args, **kwargs): + # uncomment for some debug prints + #print(" ".join(map(str, args)), **kwargs) + pass diff --git a/FlashGBX/config/db_AGB.json b/FlashGBX/config/db_AGB.json index f83be0d..9081e90 100644 --- a/FlashGBX/config/db_AGB.json +++ b/FlashGBX/config/db_AGB.json @@ -108,7 +108,7 @@ "rom_size": 16777216, "rom_crc32": 199926516, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW9P" }, "efde0279cc7584ad184ff2f0413cde0a97044d05": { @@ -430,42 +430,42 @@ "rom_size": 16777216, "rom_crc32": 575183996, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW3P" }, "7d8b9ea39fdedcf700635b4f5fa45c1f91458c9e": { "rom_size": 33554432, "rom_crc32": 2496617259, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW4P" }, "b822d712d8ba6800a3e951c90acb5ad6350e8bc8": { "rom_size": 16777216, "rom_crc32": 287824905, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW5P" }, "e500621012c741ecdb24e772ef3782e84f54ea05": { "rom_size": 33554432, "rom_crc32": 743957896, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW7P" }, "03e9c825fce0a38e3e35d977afdf07523704b995": { "rom_size": 33554432, "rom_crc32": 2769150515, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW6P" }, "96f128dcd144f198ad2953d56f2c392187a47a3b": { "rom_size": 16777216, "rom_crc32": 2239209472, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW8P" }, "c4990ad789bb3d6e843bbec6b6fc406243803342": { @@ -920,35 +920,35 @@ "rom_size": 8388608, "rom_crc32": 1727736297, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWRP" }, "8ea95333d61d058b6ae1c0dae6dd0eee2b183619": { "rom_size": 4194304, "rom_crc32": 654118857, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWRE" }, "2bd92f45310e335522a7c7c621737257b81ffe4d": { "rom_size": 4194304, "rom_crc32": 3689877868, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWRE" }, "5f683457960b5963da4e6f5a5fe65a75f96d6454": { "rom_size": 8388608, "rom_crc32": 1510583014, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AW2P" }, "f65375328f67f328a1c9a0d725038b7eaed397f6": { "rom_size": 8388608, "rom_crc32": 1523639665, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AW2E" }, "e1179c2537db6f49e08e7ae9af7bcbf59d264cf9": { @@ -2033,7 +2033,7 @@ "rom_size": 4194304, "rom_crc32": 1634770237, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8YJ" }, "38b849dcb4655fd3b944c78806274cb809881251": { @@ -2264,7 +2264,7 @@ "rom_size": 33554432, "rom_crc32": 2649083144, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BLEJ" }, "0c1eb48f59af56de00fe35a34b3ea3eac8376546": { @@ -2866,7 +2866,7 @@ "rom_size": 8388608, "rom_crc32": 1233404890, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BIXJ" }, "15d8d251505e14c0dd690193b0bad0d375c3769b": { @@ -2908,14 +2908,14 @@ "rom_size": 4194304, "rom_crc32": 2767146645, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "PEAJ" }, "76a27e2dda8bb3ab043296a7bb6b3f21237efac3": { "rom_size": 8388608, "rom_crc32": 1094313923, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "PSAJ" }, "6a7141231603a00a9fe7fa13f5618c22917527f7": { @@ -3356,21 +3356,21 @@ "rom_size": 4194304, "rom_crc32": 2327185344, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ACRP" }, "db67700ddd5348393c71341b137193d35480c5ac": { "rom_size": 4194304, "rom_crc32": 747379546, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ACRJ" }, "b50f162dcd8bcaff4ecb02278938b8de61603efa": { "rom_size": 4194304, "rom_crc32": 92254646, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ACRE" }, "9fd6bed0cfd8e4b5cf90b8dc6f6f34c193bbed40": { @@ -3566,7 +3566,7 @@ "rom_size": 16777216, "rom_crc32": 4222596664, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW5E" }, "b51cbeb1518b0b82f3c6f2793728ee80184a87b8": { @@ -3965,7 +3965,7 @@ "rom_size": 8388608, "rom_crc32": 1029982534, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ADSJ" }, "253b30d9b70b3e65b824af4b576955434d3db3bb": { @@ -4259,7 +4259,7 @@ "rom_size": 4194304, "rom_crc32": 2538008338, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8PJ" }, "4bba99f563f243f718c40f35fa44f0542f62172e": { @@ -4553,7 +4553,7 @@ "rom_size": 33554432, "rom_crc32": 654750464, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BL4E" }, "04d6bacd2461bd5ce713ffc902ac96a5c2d822c4": { @@ -4896,35 +4896,35 @@ "rom_size": 16777216, "rom_crc32": 569332069, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW3J" }, "fdf42901bc017ef20582ddf2ceb4c24c2eab7adf": { "rom_size": 33554432, "rom_crc32": 836178643, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW4J" }, "fa9e9956c3e173dcd6cc362ea18ede82f39d6538": { "rom_size": 33554432, "rom_crc32": 234529778, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BW6J" }, "5a040dd1aa59c7803f878af8494c478d94794854": { "rom_size": 8388608, "rom_crc32": 3116457863, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8DJ" }, "8b8367e29ae016ea891bcc3bbf33c78d95c7b8d9": { "rom_size": 8388608, "rom_crc32": 3345271934, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8DJ" }, "b5974f5c7bbbf3c0ec8b6b34453718546dcba2f8": { @@ -5120,14 +5120,14 @@ "rom_size": 8388608, "rom_crc32": 721909649, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AT2J" }, "aa11560789f6b552d54d91d8bb465b961b5f38dc": { "rom_size": 16777216, "rom_crc32": 3364991648, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BD3J" }, "7276d57ce0cfe49acf9ab3a7205cd62a258b9e3c": { @@ -5365,7 +5365,7 @@ "rom_size": 8388608, "rom_crc32": 2334641511, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "PSAE" }, "e0bf2d5bd19eb96b9ae8836be2f0f120f9ea31a8": { @@ -5547,14 +5547,14 @@ "rom_size": 8388608, "rom_crc32": 1215871978, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGRP" }, "63bfd99df7b32af6c7a5eae7f6c13a1c5f82d3ce": { "rom_size": 8388608, "rom_crc32": 3384571301, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGRE" }, "f91c07931c050b8458489b87996d858c4e81f613": { @@ -5771,7 +5771,7 @@ "rom_size": 16777216, "rom_crc32": 3688657379, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BFTJ" }, "81cf9a21217b807b00c425fc53a6e34abe1e3122": { @@ -6310,21 +6310,21 @@ "rom_size": 16777216, "rom_crc32": 3126714439, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFXP" }, "ecd0098be38419aa812681cc26bcca4f17d386b4": { "rom_size": 16777216, "rom_crc32": 2776301620, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFXJ" }, "ac5e332a98c5038a72e0d3f2104616c71709ddc4": { "rom_size": 16777216, "rom_crc32": 1447421292, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFXE" }, "4a4dafbcd1edd146b68304862843c9c813eda8ca": { @@ -6394,7 +6394,7 @@ "rom_size": 16777216, "rom_crc32": 2684958754, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFWJ" }, "b093af59d8f3614da98141ce4eff9b47d5e4c747": { @@ -6520,21 +6520,21 @@ "rom_size": 8388608, "rom_crc32": 313152076, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFPE" }, "42530d057753c2625cbc245bf06bbde59255a51f": { "rom_size": 8388608, "rom_crc32": 3117400531, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFYE" }, "1b7f8ac8caa64d3ed79a8a23014ef95e61dfdb29": { "rom_size": 8388608, "rom_crc32": 196378243, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AFPJ" }, "b73a6d7027d08cd644d97668181300bdc2de9bd3": { @@ -6737,7 +6737,7 @@ "rom_size": 8388608, "rom_crc32": 3874864597, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AUSJ" }, "523a3765317937f9401700bfccde1ac2992e7aef": { @@ -6807,14 +6807,14 @@ "rom_size": 8388608, "rom_crc32": 3953094417, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ABIJ" }, "cc6693098390dc0379ae7a6751a6044f507dcc8b": { "rom_size": 8388608, "rom_crc32": 3718638686, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AAIJ" }, "2894bc67b2ef8dcd07aa8c8abd0a4944b3fc46b8": { @@ -7227,7 +7227,7 @@ "rom_size": 16777216, "rom_crc32": 1240342493, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BGWJ" }, "1c3162a5f4709ddae88e8affa6649d9ed6dd8c6d": { @@ -7353,7 +7353,7 @@ "rom_size": 16777216, "rom_crc32": 1477050533, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BGJJ" }, "5892f6754a7e2e044634ae270ef24331268c40a6": { @@ -7374,7 +7374,7 @@ "rom_size": 16777216, "rom_crc32": 2633402492, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BGIJ" }, "96061da3d9f9b4f9b239c180ca1ef38620e0246e": { @@ -7402,7 +7402,7 @@ "rom_size": 8388608, "rom_crc32": 2447037544, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8GJ" }, "a264d5d44616a575bdddd5dcb5fbb93ff8b43cb0": { @@ -7479,70 +7479,70 @@ "rom_size": 8388608, "rom_crc32": 4132573537, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSF" }, "93acb70ddd068c802e7f6a298de05f983ab479f3": { "rom_size": 8388608, "rom_crc32": 273931492, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSD" }, "5f8f9cc642860ed147a8362e885194237f1c8447": { "rom_size": 8388608, "rom_crc32": 4078077970, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSI" }, "60676dd068d1263c6bd1fb5fd396e4986ebd388f": { "rom_size": 8388608, "rom_crc32": 3325036758, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSS" }, "f56ebfd0de821b6693ee6e2729e0a5961afc4e7f": { "rom_size": 8388608, "rom_crc32": 3791349992, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSE" }, "fe0a51dd66eb66e8ac51f6ac270d6fc0f379e92d": { "rom_size": 16777216, "rom_crc32": 3649165449, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFD" }, "67f5ab731e0185ce26148407aeb2b11fa1489117": { "rom_size": 16777216, "rom_crc32": 277921075, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFF" }, "91209c3fe5d8de35bf3ccb42d3dd8059c98497fa": { "rom_size": 16777216, "rom_crc32": 2749667139, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFI" }, "a2d69ac459345272b075297443ff26c079ad556a": { "rom_size": 16777216, "rom_crc32": 3996695188, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFS" }, "304742b2b49453595850f3843c6a7348dbf1b607": { "rom_size": 16777216, "rom_crc32": 1617566797, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFE" }, "a10e028b16e9d4d2f383be4e464ca7752b1a3cf3": { @@ -7689,21 +7689,21 @@ "rom_size": 8388608, "rom_crc32": 3130361373, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGXP" }, "3096f813eef237c244f418d32eab7ca5562571be": { "rom_size": 8388608, "rom_crc32": 369689582, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGXJ" }, "cdaf93f1f6038305144cb059a19a8faaf4ef406f": { "rom_size": 8388608, "rom_crc32": 1893416854, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGXE" }, "544f36eea0e566442823aab7cd7b2ab30e1bdfaf": { @@ -7745,7 +7745,7 @@ "rom_size": 4194304, "rom_crc32": 818957017, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGCJ" }, "b00225bf1a120fc55c96749a153260420a6341fa": { @@ -7906,7 +7906,7 @@ "rom_size": 4194304, "rom_crc32": 389704778, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BHAJ" }, "00f9afb55c018786f2c43e5f5e33b5ad299dea99": { @@ -8851,14 +8851,14 @@ "rom_size": 8388608, "rom_crc32": 195395743, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGRJ" }, "d3b08308e192a3ad2afc86f168c474dfc0f13c79": { "rom_size": 8388608, "rom_crc32": 3093551841, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGMJ" }, "eb7cc17dc164f3451e918a80de1e248c43a2ce3a": { @@ -8907,7 +8907,7 @@ "rom_size": 4194304, "rom_crc32": 119099905, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AJUJ" }, "e2c74e2690aab78e0bf7706031d05f66f8ab8212": { @@ -9089,7 +9089,7 @@ "rom_size": 16777216, "rom_crc32": 2192897018, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AKZJ" }, "fe236457f2df5c2fd039d12f8be5fc8f12948f52": { @@ -9124,7 +9124,7 @@ "rom_size": 8388608, "rom_crc32": 1677606398, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AYKE" }, "afdaf47afe088ff9a3f2fed4ce690d62ab60bd61": { @@ -9187,14 +9187,14 @@ "rom_size": 8388608, "rom_crc32": 1155213326, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ATPJ" }, "e4c96c2d2366d42c7a77f5bada0758d68670bef5": { "rom_size": 8388608, "rom_crc32": 2408958547, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ATSJ" }, "f37413a0f76b1b9302fc60e0f0d98099f994d7c7": { @@ -9978,21 +9978,21 @@ "rom_size": 8388608, "rom_crc32": 2381936029, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BLJK" }, "0cf7b20bf2e1c7d5cf03e7196529366030337e32": { "rom_size": 16777216, "rom_crc32": 2554538513, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BLVJ" }, "687bb774170c7480748523f65feabc91354043e2": { "rom_size": 8388608, "rom_crc32": 865403432, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BLJJ" }, "4a40b79fe5c04da1470c711a25186c5a424d8030": { @@ -10559,7 +10559,7 @@ "rom_size": 8388608, "rom_crc32": 1880241488, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AJOJ" }, "8b5bce28d650077e1669ef75169e637698da6e1e": { @@ -10608,7 +10608,7 @@ "rom_size": 8388608, "rom_crc32": 1506535700, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMVJ" }, "c00029f76e7333852fb000388d62b8ecf066658c": { @@ -10643,7 +10643,7 @@ "rom_size": 8388608, "rom_crc32": 999834257, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMCJ" }, "cd1efa43615ae3624cd105f94f99b1e88f1efd09": { @@ -10804,21 +10804,21 @@ "rom_size": 4194304, "rom_crc32": 537024578, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMKP" }, "0813b90ec2623886a1ad21f4b141573d9886c73a": { "rom_size": 4194304, "rom_crc32": 3979439671, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMKE" }, "1df96ece8b773a8fc523f2485321663a34ce2f48": { "rom_size": 4194304, "rom_crc32": 820617165, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMKJ" }, "06a39d45ff867fcc5efef7065d607e01600cba9d": { @@ -10874,21 +10874,21 @@ "rom_size": 16777216, "rom_crc32": 3389197665, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BM5P" }, "470794d23dc8864c2b3b51b01d0cf59c42e52c2b": { "rom_size": 16777216, "rom_crc32": 2083213929, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BM5J" }, "3620ce1ded7045358b144164474d288c67ec42b4": { "rom_size": 16777216, "rom_crc32": 633574609, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BM5E" }, "2c6caf3c5d3ec7c2d909637ed86a59b676630856": { @@ -11161,14 +11161,14 @@ "rom_size": 8388608, "rom_crc32": 356846435, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ANAJ" }, "e7f62f516f8c1905d788e2e5f1b17130aceb6370": { "rom_size": 8388608, "rom_crc32": 3896313814, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVIJ" }, "6f5857fcd4bfbf4faa4663c5264a82e3b227c8c9": { @@ -11574,7 +11574,7 @@ "rom_size": 8388608, "rom_crc32": 1357149096, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BMKJ" }, "0c73cf2187929042bf8a74c80d6a868e347fa156": { @@ -11854,7 +11854,7 @@ "rom_size": 8388608, "rom_crc32": 2381881536, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AMBJ" }, "dcf72caf7848ea983b621021a9a92fa73d7df5c9": { @@ -12169,7 +12169,7 @@ "rom_size": 33554432, "rom_crc32": 1118608569, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A3UJ" }, "52842630235eccf74d1965a9e70f1a70a0c94f78": { @@ -12547,7 +12547,7 @@ "rom_size": 8388608, "rom_crc32": 233354420, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AN2J" }, "978c3add8408279427ec8e7dc40589f3be7e499e": { @@ -12750,7 +12750,7 @@ "rom_size": 4194304, "rom_crc32": 4012099486, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ANBJ" }, "8a6abc8d04a4d69fddc3e2dfc1c0aa4b207d69cf": { @@ -12946,7 +12946,7 @@ "rom_size": 8388608, "rom_crc32": 3748200944, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AICJ" }, "7ddef563f53457341be11ac7035ad04254bc9c27": { @@ -12995,14 +12995,14 @@ "rom_size": 8388608, "rom_crc32": 4220967390, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGSJ" }, "8c354bed050cc53a46105f89bd194a8fa7c361be": { "rom_size": 16777216, "rom_crc32": 2198567263, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGFJ" }, "6cbc3a838f133ecdd612afa6b39da0945ffe7209": { @@ -13562,49 +13562,49 @@ "rom_size": 16777216, "rom_crc32": 1216476152, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPEJ" }, "f5a922fdf81768f73acc848e2a563266f949bf46": { "rom_size": 16777216, "rom_crc32": 3143896567, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRJ" }, "e139f8003de78e59ebcab6cc83fd957bb8c56e1e": { "rom_size": 16777216, "rom_crc32": 991975145, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRJ" }, "d7afa6e7e8f15df9dc871a65d9b900a2303f064a": { "rom_size": 16777216, "rom_crc32": 2376359962, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGJ" }, "d2e246c82af32ecc46ee92813695dc337a27e031": { "rom_size": 16777216, "rom_crc32": 172512619, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGJ" }, "ea91c7159643c8b4d1dc7318e7af59605ac61980": { "rom_size": 8388608, "rom_crc32": 3471394586, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVJ" }, "d8025d0b015dce3bd949f2cb9ed3ecf7a8045e55": { "rom_size": 8388608, "rom_crc32": 4246662008, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPJ" }, "85b5103ec947c18cab67c3648e7475f8f662071f": { @@ -13646,294 +13646,294 @@ "rom_size": 16777216, "rom_crc32": 3509526493, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGD" }, "665c13faf471b2cae7a61f20d93b477e6eb73c55": { "rom_size": 16777216, "rom_crc32": 2353869064, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPES" }, "7584e1cefe1dd47026cbd38399e9b8260a1eb7bd": { "rom_size": 16777216, "rom_crc32": 2668103246, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRS" }, "371f031b7612c8d296981ff8326d62bf52773f74": { "rom_size": 16777216, "rom_crc32": 3112239093, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVS" }, "9927c4fd32f53c3802b7800c6fdd19bae454c85a": { "rom_size": 16777216, "rom_crc32": 3943115215, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVS" }, "281ed96d5850ca56cf5af43115f9586eb93b0797": { "rom_size": 16777216, "rom_crc32": 748756313, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGS" }, "3230710bb0c3b549e7478aa23bc287fa5aa4d246": { "rom_size": 16777216, "rom_crc32": 4073228593, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPS" }, "e8fefcda0a725f23e1aec144c8aa1a07860c4cfb": { "rom_size": 16777216, "rom_crc32": 2689556235, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPS" }, "e0618bd3b4c0986b7f710226d813f8a0de8a4e78": { "rom_size": 16777216, "rom_crc32": 521931003, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPEE" }, "da97b4134145f1bc41245f4be7e56ea7ddb2c9b5": { "rom_size": 16777216, "rom_crc32": 444722911, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRD" }, "62f5e8cf2f83e65df7a5a0e4ab1a84886f2b4838": { "rom_size": 16777216, "rom_crc32": 3716707868, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRE" }, "646d2e75e8e5fcd835bbe62b888595e957b228a4": { "rom_size": 16777216, "rom_crc32": 2230208374, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRE" }, "ad4ce6033af10b253ae57a8aac48bf5fa4f6966f": { "rom_size": 16777216, "rom_crc32": 3600586444, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGE" }, "e8f43d6c5286b626d039621ae3bef5103774329e": { "rom_size": 16777216, "rom_crc32": 3674205420, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGE" }, "7362399e23d685a6fbeaaa989b25fc67b9bd114c": { "rom_size": 16777216, "rom_crc32": 3404239972, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVD" }, "0f0542313b01552f79fe3793c2bed6f8e76f14dc": { "rom_size": 16777216, "rom_crc32": 367125120, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVD" }, "ef59dc2dbbcc1caa4cc50a0155d0491a59d91076": { "rom_size": 16777216, "rom_crc32": 4035010279, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVE" }, "4545c4e0aa9664f38800d881570f2504b7061f20": { "rom_size": 16777216, "rom_crc32": 1633949046, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVE" }, "d8fc86baa6a0da0034ff22d3590ab79596fd866c": { "rom_size": 16777216, "rom_crc32": 2930537446, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVE" }, "aa6d44b020143ea0439b251f6b45b1dfc9aca05e": { "rom_size": 16777216, "rom_crc32": 2965638268, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPD" }, "b1a5c50656fd7f29036d934620515f9ce9e316e0": { "rom_size": 16777216, "rom_crc32": 1875737240, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPD" }, "8155fd52f7b90ef4fe68851aedf95e0dc1d8cfc4": { "rom_size": 16777216, "rom_crc32": 1431170500, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPE" }, "bff91c51da1e93df1661a3e507a8ffd9488b019b": { "rom_size": 16777216, "rom_crc32": 3137264357, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPE" }, "fbd5059e024e18ba4dd8b0b827bdf1b2586ca80c": { "rom_size": 16777216, "rom_crc32": 2630107406, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPE" }, "e3b6f29d1344332b911846e9b0e9f01b6e841340": { "rom_size": 16777216, "rom_crc32": 885645193, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPED" }, "1149712e89c009f49d9e3558f038b536e7c19218": { "rom_size": 16777216, "rom_crc32": 2751319217, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPEF" }, "89d09e60724be1e9ea5533a6cf7f204f09b58d25": { "rom_size": 16777216, "rom_crc32": 1573284086, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRF" }, "860734e3ec2510021d85de67ff2f04e52ba8d735": { "rom_size": 16777216, "rom_crc32": 2677546866, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVF" }, "2353cd6cfdb6eb946f97bc1d1da0e24d5e7ea896": { "rom_size": 16777216, "rom_crc32": 1762644752, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVF" }, "266d59f6664a16a9795de36423a748fa4cb019e8": { "rom_size": 16777216, "rom_crc32": 719622470, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPF" }, "af2873d365fe985449c12b2d69523fa21072e732": { "rom_size": 16777216, "rom_crc32": 897687647, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPF" }, "b32f530c91a07028653afe4878880efaed5ca2e7": { "rom_size": 16777216, "rom_crc32": 3123873251, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGF" }, "0074dc0c470e70829df4a519fb91fdbdba63ed9e": { "rom_size": 16777216, "rom_crc32": 1940332903, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPRI" }, "31a3bc40967c4274d6593342f7bea1dc203225bc": { "rom_size": 16777216, "rom_crc32": 2466637715, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVI" }, "b7c6ed99b3bae979bb8e3a6f45cd8dd686e2b977": { "rom_size": 16777216, "rom_crc32": 3246535081, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXVI" }, "9e7e38766eb5264e0e3a1130653fff5441ca4c33": { "rom_size": 16777216, "rom_crc32": 2695809034, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPEI" }, "4f81d3d5e5a2807e0861eaa795b74609599a187d": { "rom_size": 16777216, "rom_crc32": 379012358, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BPGI" }, "322a018b0bb5060915a3fe2f1472304f7beedba3": { "rom_size": 16777216, "rom_crc32": 1593722003, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPI" }, "f0ec8ae459a004dbd2b7787bd14a4fac1f685a81": { "rom_size": 16777216, "rom_crc32": 209293993, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AXPI" }, "fb62f31654590827a29c864b3e9b3ec89f202b79": { "rom_size": 33554432, "rom_crc32": 438020688, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B24J" }, "7b39c9db54d636058ba85d808aa6db19528572f7": { "rom_size": 33554432, "rom_crc32": 3251736548, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "B24P" }, "6fa0c5f5b8072a607af212f14e90f9696d001101": { "rom_size": 33554432, "rom_crc32": 3708471404, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "B24E" }, "3410b936c4bf86261fdcea3bab522dca1d404260": { @@ -13975,14 +13975,14 @@ "rom_size": 4194304, "rom_crc32": 2402151352, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BIIP" }, "8723e3e1370df22c5ce843d53123f677a4a00a39": { "rom_size": 4194304, "rom_crc32": 47590633, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BIIE" }, "d7ab7617cc018b8e0c0c595d3d2402be39e04186": { @@ -14234,28 +14234,28 @@ "rom_size": 4194304, "rom_crc32": 3397991364, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BPMP" }, "8fce74ae035e146b48b2591176ef5e933535a495": { "rom_size": 4194304, "rom_crc32": 2841245240, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BPMP" }, "0cf932e6c14701381ca80d3bd5991fc6f63b10dc": { "rom_size": 4194304, "rom_crc32": 654761970, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BP4P" }, "7e191f1fc0aa2a274be59d8e8bae111363c8a6bc": { "rom_size": 4194304, "rom_crc32": 990144315, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BP5P" }, "c50fc7fa7ec0c2ce5963ccc16bef5ff9863a8859": { @@ -14794,28 +14794,28 @@ "rom_size": 8388608, "rom_crc32": 1758659130, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ACVE" }, "a58122e23085b7d1b5be8f5637c211a9c3ac1297": { "rom_size": 8388608, "rom_crc32": 3396591994, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ARPE" }, "8f0f3cc537c2334b517a037d450699873edff129": { "rom_size": 8388608, "rom_crc32": 208612123, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ACVJ" }, "09afe0c6126c28ff6f30820f6e93e62a1a6e10ab": { "rom_size": 8388608, "rom_crc32": 1610749340, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ARPJ" }, "32b1ab0a45d87cf45ead7b0b270e13272bec80d5": { @@ -14955,7 +14955,7 @@ "rom_size": 8388608, "rom_crc32": 2789629979, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BR4J" }, "2f6c13a0ac611560507b827abc28dd9005936cc0": { @@ -15039,7 +15039,7 @@ "rom_size": 8388608, "rom_crc32": 3892085200, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A8TJ" }, "7b922bdc8492f805fe4295e6e78b9e6cb27b0dff": { @@ -15179,21 +15179,21 @@ "rom_size": 8388608, "rom_crc32": 4090741305, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASXJ" }, "acb06d19af1ac57fe4bcf418b76921aff64390f9": { "rom_size": 4194304, "rom_crc32": 2762066262, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B3EJ" }, "1cac3488a1b9b21a60c426d95a252fd40676ce10": { "rom_size": 8388608, "rom_crc32": 1984971775, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B3QJ" }, "e96a28cb18b8ae0adaa3d492182fcfa54770502b": { @@ -15494,14 +15494,14 @@ "rom_size": 16777216, "rom_crc32": 3702186101, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BKAJ" }, "0c9f1a9ba635c3ca68a2ec52e7465a630ab893df": { "rom_size": 16777216, "rom_crc32": 2734069612, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "BKAJ" }, "fc8c25cce5ec65d50df16c7befc60e799496a852": { @@ -15830,49 +15830,49 @@ "rom_size": 8388608, "rom_crc32": 1958072425, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AHUP" }, "8ed879bac1b821f47ebe3d63f0ec1cf3ecf8a2fe": { "rom_size": 8388608, "rom_crc32": 1377063121, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AHUJ" }, "74d497de599ba0ddb3357334f609c87804ba9d34": { "rom_size": 8388608, "rom_crc32": 3915102732, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AHUE" }, "9c43149f56c492855e7f2bd1dda26e9d4803ee2e": { "rom_size": 16777216, "rom_crc32": 1441072065, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AU2P" }, "4b5351a7da02570d7d0d0d358a5313f5197656b6": { "rom_size": 16777216, "rom_crc32": 2067376050, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AU2J" }, "55f78ffa7e8e17de6d1935f2a59abc97e0dc55d4": { "rom_size": 16777216, "rom_crc32": 1077469826, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AU2E" }, "817d6cd431e1ced202dfe739e45fa3d5c2f0654e": { "rom_size": 16777216, "rom_crc32": 833757413, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVSJ" }, "57c9b7bb1be6fe4decbd2558f807279b7d552e35": { @@ -16117,21 +16117,21 @@ "rom_size": 33554432, "rom_crc32": 2333853663, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B46E" }, "e3b355e67ace40f28e613845ebdddb9fe9e43838": { "rom_size": 33554432, "rom_crc32": 2281200028, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B4OX" }, "14a80327c4add4a9a60528baa6eea3c81bcfc2b2": { "rom_size": 33554432, "rom_crc32": 3961471017, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B4OE" }, "8981a4eaae2710d081c2bdc082281ffc3e971f34": { @@ -16299,112 +16299,112 @@ "rom_size": 8388608, "rom_crc32": 1647477659, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASOP" }, "4413c9397d7ab229500bbc4247156fb38246e4c8": { "rom_size": 8388608, "rom_crc32": 2241165860, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASOJ" }, "372e1880a0dd1213422422a8e56aac7b8ed01d66": { "rom_size": 8388608, "rom_crc32": 1599152675, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASOJ" }, "b37ebb42082531a8b19b4135b418b1a0f379777e": { "rom_size": 8388608, "rom_crc32": 1677135832, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASOE" }, "6a7f68a89eb6949e631fc79f15d58be9c343ef76": { "rom_size": 16777216, "rom_crc32": 2303760529, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A2NP" }, "8063f0416fa8b6c0308f16ca8b0b75a98f2b6e7b": { "rom_size": 16777216, "rom_crc32": 1362625791, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A2NJ" }, "b86c0231782e6f5c94b97e75920950074bcd50c8": { "rom_size": 16777216, "rom_crc32": 2130634743, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A2NE" }, "75ec7cfe6fd4e9715b753c8a9fcab3d0466a1450": { "rom_size": 16777216, "rom_crc32": 1542992982, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B3SP" }, "54c6f80b4a54bb9af3086879a65a3ca33476109b": { "rom_size": 16777216, "rom_crc32": 1131803094, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B3SJ" }, "87d0b8c6967ad71f1cfaf11d4a68ba2ca4efbf8d": { "rom_size": 16777216, "rom_crc32": 1239262694, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B3SE" }, "9581bf4e75e5c410d716dfcb002e70a28af036f3": { "rom_size": 16777216, "rom_crc32": 3505803557, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BSBP" }, "ca289d003d6cf14882b9b0f6409ae929f89aef71": { "rom_size": 16777216, "rom_crc32": 1929751600, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BSBJ" }, "5425170401c21236ff5bb6e473be425bf2118d3e": { "rom_size": 16777216, "rom_crc32": 2664028271, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BSBE" }, "600900657db867a7b1dd7e02af6de839d69301e0": { "rom_size": 8388608, "rom_crc32": 1144361342, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A3VP" }, "3a8b0121284aee9880877be82bc1190e2102ed2b": { "rom_size": 8388608, "rom_crc32": 1135997287, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A86J" }, "6a4e23f1e3ddc9aed119b4fac1624e0ac3bb847a": { "rom_size": 8388608, "rom_crc32": 142165827, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A3VE" }, "db78ad7cd77b4215f7be5683a07b397c2f4bc2bb": { @@ -17412,49 +17412,49 @@ "rom_size": 4194304, "rom_crc32": 1516725284, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4J" }, "264cd0336a8f4c657b5d2e8b9aedfd2017f64e16": { "rom_size": 4194304, "rom_crc32": 2065320994, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4J" }, "042a2fc1b3846798ce0549afa32a5a3e8313613e": { "rom_size": 4194304, "rom_crc32": 4090000134, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4J" }, "55c16e8dcab8c0c07fde1afa0ff996ec9ef64dbf": { "rom_size": 8388608, "rom_crc32": 2374282492, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4P" }, "3f8e097a2db67aac369cfdba6ff35faf22079179": { "rom_size": 8388608, "rom_crc32": 924065586, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4P" }, "0bde2155cd2ccd33327ad77491f90116587d210a": { "rom_size": 4194304, "rom_crc32": 2756421535, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4E" }, "d403fd1341f13106d47baa56a4f22e8380962041": { "rom_size": 4194304, "rom_crc32": 2296033919, "savesize": 131072, - "savetype": 6, + "savetype": 7, "code": "AX4E" }, "8d904c5caddcc4b8a4698477362b16a9fd7be742": { @@ -17531,63 +17531,63 @@ "rom_size": 16777216, "rom_crc32": 3182980266, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AOGP" }, "2e52ca55e7a2f54398b07c8e34f39c450f943497": { "rom_size": 8388608, "rom_crc32": 1964146372, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AOGJ" }, "db641133a065909dff04b7b8b2c44221c7c58852": { "rom_size": 16777216, "rom_crc32": 1491624550, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AOGE" }, "e2aff59f5a90503067ec8c9a76980abd141157c1": { "rom_size": 16777216, "rom_crc32": 2785574927, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B2RJ" }, "18f7eb3d4e411eee51b00c371cf0a2ef6c756239": { "rom_size": 16777216, "rom_crc32": 720241536, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B2RE" }, "55d018e29781c15aded399619c3540ee1a1708f7": { "rom_size": 8388608, "rom_crc32": 795776904, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ASRJ" }, "a4bb144d278b6a9ec0d5bec39ff11adb2f3c4b7a": { "rom_size": 8388608, "rom_crc32": 4021571863, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A6SJ" }, "bced30c579c29b2b49fe8f163d30090fec83a906": { "rom_size": 16777216, "rom_crc32": 3377921335, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "B6JJ" }, "7972f2b5220d443bac5fc5a96dd9eee7002aa864": { "rom_size": 8388608, "rom_crc32": 3749679548, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AJ9J" }, "90b3f47c4786796dc677df692cf1bdb060b6bc5a": { @@ -17678,28 +17678,28 @@ "rom_size": 16777216, "rom_crc32": 295743305, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVSY" }, "352da67011a8dbd93dc26bfd41bd467e1fd54cd8": { "rom_size": 16777216, "rom_crc32": 1591836369, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVSX" }, "5cff665bd7d417b3b626a33e7f4d790a3ee91251": { "rom_size": 16777216, "rom_crc32": 2296793738, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVSP" }, "dc9e56b67bfc972e49f963acecf96ed2f88f4c1a": { "rom_size": 16777216, "rom_crc32": 2132716661, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AVSE" }, "b54424b04c68dff800d4e3bed36fe9aa12c926ab": { @@ -17727,14 +17727,14 @@ "rom_size": 8388608, "rom_crc32": 1716114126, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ATOE" }, "db411340c20b2eaf91c602814b7273904094d692": { "rom_size": 8388608, "rom_crc32": 23570552, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ATOJ" }, "f30595d0c10c2991edbef187bebe6b369d1a7ca1": { @@ -18007,7 +18007,7 @@ "rom_size": 8388608, "rom_crc32": 3710143642, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "ATIJ" }, "052d411a16e5cd61e5f702ed5a97104a0a993cba": { @@ -18553,7 +18553,7 @@ "rom_size": 4194304, "rom_crc32": 3754462526, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A2YE" }, "fb56c6cff2d21ecc0471cacf5e18f59f5a480b1d": { @@ -18644,14 +18644,14 @@ "rom_size": 8388608, "rom_crc32": 3962723474, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BTRJ" }, "8e26a7ba6c1687473f84f8be0256bdd5c0365af8": { "rom_size": 8388608, "rom_crc32": 2514507925, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BTRE" }, "eec0a1975c68ed9f18329c6e91c13b46d536882e": { @@ -18749,7 +18749,7 @@ "rom_size": 4194304, "rom_crc32": 1518237227, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BIIJ" }, "4a13a4198da97326c7d05146277bb17c55fde103": { @@ -18987,7 +18987,7 @@ "rom_size": 8388608, "rom_crc32": 3224864199, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BUTJ" }, "afced08761c65e939d8274b9ab96bb851133438f": { @@ -19043,14 +19043,14 @@ "rom_size": 33554432, "rom_crc32": 3150758653, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BOCJ" }, "ffb1229bf6d8c549e13e0f0af4e89ceed68be483": { "rom_size": 33554432, "rom_crc32": 2335387835, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "BOCE" }, "4de78b608d2dad0b08d0719f7daf6a6267f3f89b": { @@ -19449,21 +19449,21 @@ "rom_size": 4194304, "rom_crc32": 4128770805, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWPJ" }, "d99e6c8498bb7b4e7706524a618a46134122bd73": { "rom_size": 4194304, "rom_crc32": 2679418634, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWPJ" }, "4379f548feea37e96d360084215d39a407c4bffb": { "rom_size": 4194304, "rom_crc32": 1524261110, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AWPJ" }, "21a4580b592356d6f539e23add8be0ad5bcba542": { @@ -19841,21 +19841,21 @@ "rom_size": 8388608, "rom_crc32": 3996221289, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AYDP" }, "cb6c0e3dde6d76ffc21ee33e343beb9cfdc1c4dc": { "rom_size": 8388608, "rom_crc32": 1370709639, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AYDJ" }, "6fa5952ed82ced10cb525ed693c4a63b4c8a0f0e": { "rom_size": 8388608, "rom_crc32": 2338196007, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AYDE" }, "28685df31700adc7c7c1f0db3bd436eba00fdba6": { @@ -20072,7 +20072,7 @@ "rom_size": 8388608, "rom_crc32": 2683237997, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AZEJ" }, "714f2321b7fb6e6854e196edd3ec26eaa73348e6": { @@ -20114,14 +20114,14 @@ "rom_size": 4194304, "rom_crc32": 3542133093, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AGTJ" }, "60a0a115869886ed4973fad07975c7410fc29c64": { "rom_size": 8388608, "rom_crc32": 646945918, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "A2ZJ" }, "95b3a7696b8d1bcb21a2e341a818dcec3851db3a": { @@ -20142,7 +20142,7 @@ "rom_size": 4194304, "rom_crc32": 3732675304, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AZTJ" }, "a811e4f0b26f498f61f1a5241fd608755241afac": { @@ -20240,14 +20240,14 @@ "rom_size": 8388608, "rom_crc32": 2536806112, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AZEP" }, "ac4d50a5223e1905a281bdcf3c787794aac0adc6": { "rom_size": 8388608, "rom_crc32": 606356501, "savesize": 65536, - "savetype": 5, + "savetype": 6, "code": "AZEE" }, "b4fb4232bfb6a420bcc3c424b9cb5c33fdd97383": { diff --git a/FlashGBX/config/fc_AGB_29LV128DT.txt b/FlashGBX/config/fc_AGB_29LV128DT.txt new file mode 100644 index 0000000..94da201 --- /dev/null +++ b/FlashGBX/config/fc_AGB_29LV128DT.txt @@ -0,0 +1,53 @@ +{ + "type":"AGB", + "names":[ + "AGB-E08-09 with 29LV128DTMC-90Q" + ], + "flash_ids":[ + [ 0xC1, 0x00, 0x7D, 0x22 ] + ], + "voltage":3.3, + "flash_size":0x1000000, + "sector_size":[ + [0x10000, 255], + [0x02000, 8] + ], + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x90 ] + ], + "sector_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ "SA", 0x30 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0xFFFF, 0xFFFF ] + ], + "single_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_AGB_M36L0R705.txt b/FlashGBX/config/fc_AGB_M36L0R7050T.txt similarity index 85% rename from FlashGBX/config/fc_AGB_M36L0R705.txt rename to FlashGBX/config/fc_AGB_M36L0R7050T.txt index c7bba8d..c96118f 100644 --- a/FlashGBX/config/fc_AGB_M36L0R705.txt +++ b/FlashGBX/config/fc_AGB_M36L0R7050T.txt @@ -1,9 +1,11 @@ { "type":"AGB", "names":[ - "4455_4400_4000_4350_36L0R_V3 with M36L0R705" + "4455_4400_4000_4350_36L0R_V3 with M36L0R7050T", + "4050_4400_4000_4350_36L0R_V5 with M36L0R7050T" ], "flash_ids":[ + [ 0x20, 0x00, 0xC4, 0x88 ], [ 0x20, 0x00, 0xC4, 0x88 ] ], "voltage":3.3, diff --git a/FlashGBX/config/fc_AGB_M36L0R8060B.txt b/FlashGBX/config/fc_AGB_M36L0R8060B.txt new file mode 100644 index 0000000..8bbb049 --- /dev/null +++ b/FlashGBX/config/fc_AGB_M36L0R8060B.txt @@ -0,0 +1,86 @@ +{ + "type":"AGB", + "names":[ + "36L0R8-39VF512 with M36L0R8060B", + "36L0R8-39VF512 with M36L0R8060B" + ], + "flash_ids":[ + [ 0x20, 0x00, 0x0D, 0x88 ], + [ 0x8A, 0x00, 0x10, 0x88 ] + ], + "voltage":3.3, + "flash_size":0x2000000, + "sector_size":[ + [0x08000, 4], + [0x20000, 255] + ], + "commands":{ + "reset":[ + [ 0, 0xFF ], + [ 0x100000, 0xFF ], + [ 0x200000, 0xFF ], + [ 0x300000, 0xFF ], + [ 0x400000, 0xFF ], + [ 0x500000, 0xFF ], + [ 0x600000, 0xFF ], + [ 0x700000, 0xFF ], + [ 0x800000, 0xFF ], + [ 0x900000, 0xFF ], + [ 0xA00000, 0xFF ], + [ 0xB00000, 0xFF ], + [ 0xC00000, 0xFF ], + [ 0xD00000, 0xFF ], + [ 0xE00000, 0xFF ], + [ 0xF00000, 0xFF ], + [ 0x1000000, 0xFF ], + [ 0x1100000, 0xFF ], + [ 0x1200000, 0xFF ], + [ 0x1300000, 0xFF ], + [ 0x1400000, 0xFF ], + [ 0x1500000, 0xFF ], + [ 0x1600000, 0xFF ], + [ 0x1700000, 0xFF ], + [ 0x1800000, 0xFF ], + [ 0x1900000, 0xFF ], + [ 0x1A00000, 0xFF ], + [ 0x1B00000, 0xFF ], + [ 0x1C00000, 0xFF ], + [ 0x1D00000, 0xFF ], + [ 0x1E00000, 0xFF ], + [ 0x1F00000, 0xFF ] + ], + "read_identifier":[ + [ 0, 0x90 ] + ], + "sector_erase":[ + [ "SA", 0x60 ], + [ "SA", 0xD0 ], + [ "SA", 0x20 ], + [ "SA", 0xD0 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ] + ], + "buffer_write":[ + [ "SA", 0x60 ], + [ "SA", 0xD0 ], + [ "SA", 0xE8 ], + [ "SA", "BS" ], + [ "PA", "PD" ], + [ "SA", 0xD0 ], + [ "SA", 0xFF ] + ], + "buffer_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_AGB_M36L0R8060T.txt b/FlashGBX/config/fc_AGB_M36L0R8060T.txt new file mode 100644 index 0000000..4c14c74 --- /dev/null +++ b/FlashGBX/config/fc_AGB_M36L0R8060T.txt @@ -0,0 +1,86 @@ +{ + "type":"AGB", + "names":[ + "4050_4400_4000_4350_36L0R_V5 with M36L0R8060T", + "36L0R8-39VF512 with M36L0R8060T" + ], + "flash_ids":[ + [ 0x20, 0x00, 0x0E, 0x88 ], + [ 0x20, 0x00, 0x0E, 0x88 ] + ], + "voltage":3.3, + "flash_size":0x2000000, + "sector_size":[ + [0x20000, 255], + [0x08000, 4] + ], + "commands":{ + "reset":[ + [ 0, 0xFF ], + [ 0x100000, 0xFF ], + [ 0x200000, 0xFF ], + [ 0x300000, 0xFF ], + [ 0x400000, 0xFF ], + [ 0x500000, 0xFF ], + [ 0x600000, 0xFF ], + [ 0x700000, 0xFF ], + [ 0x800000, 0xFF ], + [ 0x900000, 0xFF ], + [ 0xA00000, 0xFF ], + [ 0xB00000, 0xFF ], + [ 0xC00000, 0xFF ], + [ 0xD00000, 0xFF ], + [ 0xE00000, 0xFF ], + [ 0xF00000, 0xFF ], + [ 0x1000000, 0xFF ], + [ 0x1100000, 0xFF ], + [ 0x1200000, 0xFF ], + [ 0x1300000, 0xFF ], + [ 0x1400000, 0xFF ], + [ 0x1500000, 0xFF ], + [ 0x1600000, 0xFF ], + [ 0x1700000, 0xFF ], + [ 0x1800000, 0xFF ], + [ 0x1900000, 0xFF ], + [ 0x1A00000, 0xFF ], + [ 0x1B00000, 0xFF ], + [ 0x1C00000, 0xFF ], + [ 0x1D00000, 0xFF ], + [ 0x1E00000, 0xFF ], + [ 0x1F00000, 0xFF ] + ], + "read_identifier":[ + [ 0, 0x90 ] + ], + "sector_erase":[ + [ "SA", 0x60 ], + [ "SA", 0xD0 ], + [ "SA", 0x20 ], + [ "SA", 0xD0 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ] + ], + "buffer_write":[ + [ "SA", 0x60 ], + [ "SA", 0xD0 ], + [ "SA", 0xE8 ], + [ "SA", "BS" ], + [ "PA", "PD" ], + [ "SA", 0xD0 ], + [ "SA", 0xFF ] + ], + "buffer_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0x80, 0xFFFF ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_AGB_MSP55LV128.txt b/FlashGBX/config/fc_AGB_MSP55LV128.txt index 4c1fe00..0eb1d04 100644 --- a/FlashGBX/config/fc_AGB_MSP55LV128.txt +++ b/FlashGBX/config/fc_AGB_MSP55LV128.txt @@ -9,7 +9,6 @@ "voltage":3.3, "flash_size":0x1000000, "sector_size":0x10000, - "chip_erase_timeout":75, "commands":{ "reset":[ [ 0, 0xF0 ] @@ -19,22 +18,6 @@ [ 0x555, 0x56 ], [ 0xAAA, 0x90 ] ], - "chip_erase":[ - [ 0xAAA, 0xA9 ], - [ 0x555, 0x56 ], - [ 0xAAA, 0x80 ], - [ 0xAAA, 0xA9 ], - [ 0x555, 0x56 ], - [ 0xAAA, 0x10 ] - ], - "chip_erase_wait_for":[ - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ 0, 0xFFFF, 0xFFFF ] - ], "sector_erase":[ [ 0xAAA, 0xA9 ], [ 0x555, 0x56 ], @@ -51,10 +34,26 @@ [ null, null, null ], [ "SA", 0xFFFF, 0xFFFF ] ], + "buffer_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 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":[ - [ 0x555, 0xA9 ], - [ 0x2AA, 0x56 ], - [ 0x555, 0xA0 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0xA0 ], [ "PA", "PD" ] ], "single_write_wait_for":[ diff --git a/FlashGBX/config/fc_AGB_MSP55LV128M.txt b/FlashGBX/config/fc_AGB_MSP55LV128M.txt index df78372..8da6dd3 100644 --- a/FlashGBX/config/fc_AGB_MSP55LV128M.txt +++ b/FlashGBX/config/fc_AGB_MSP55LV128M.txt @@ -2,11 +2,19 @@ "type":"AGB", "names":[ "AGB-E05-01 with MSP55LV128M", - "BX2006_0106_NEW with S29GL128N10TFI01" + "AGB-E05-01 with GL128S", + "BX2006_0106_NEW with S29GL128N10TFI01", + "BX2006_TSOP_64BALL with GL128S", + "BX2006_TSOPBGA_0106 with M29W640GB6AZA6", + "AGB-E05-02 with M29W128GH" ], "flash_ids":[ [ 0x02, 0x00, 0x7D, 0x22 ], - [ 0x02, 0x00, 0x7D, 0x22 ] + [ 0x02, 0x00, 0x7D, 0x22 ], + [ 0x02, 0x00, 0x7D, 0x22 ], + [ 0x02, 0x00, 0x7D, 0x22 ], + [ 0x20, 0x00, 0x7D, 0x22 ], + [ 0x20, 0x00, 0x7D, 0x22 ] ], "voltage":3.3, "flash_size":0x1000000, @@ -21,22 +29,6 @@ [ 0x555, 0x56 ], [ 0xAAA, 0x90 ] ], - "chip_erase":[ - [ 0xAAA, 0xA9 ], - [ 0x555, 0x56 ], - [ 0xAAA, 0x80 ], - [ 0xAAA, 0xA9 ], - [ 0x555, 0x56 ], - [ 0xAAA, 0x10 ] - ], - "chip_erase_wait_for":[ - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ null, null, null ], - [ 0, 0xFFFF, 0xFFFF ] - ], "sector_erase":[ [ 0xAAA, 0xA9 ], [ 0x555, 0x56 ], @@ -53,10 +45,26 @@ [ null, null, null ], [ "SA", 0xFFFF, 0xFFFF ] ], + "buffer_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 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":[ - [ 0x555, 0xA9 ], - [ 0x2AA, 0x56 ], - [ 0x555, 0xA0 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0xA0 ], [ "PA", "PD" ] ], "single_write_wait_for":[ diff --git a/FlashGBX/config/fc_AGB_Nintendo_E201850.txt b/FlashGBX/config/fc_AGB_Nintendo_E201850.txt index 3064190..3ee23be 100644 --- a/FlashGBX/config/fc_AGB_Nintendo_E201850.txt +++ b/FlashGBX/config/fc_AGB_Nintendo_E201850.txt @@ -12,7 +12,10 @@ "wait_read_status_register":true, "commands":{ "reset":[ - [ 0, 0xFF ] + [ 0, 0xFF ], + [ 0x400000, 0xFF ], + [ 0x800000, 0xFF ], + [ 0xC00000, 0xFF ] ], "read_status_register":[ [ 0, 0x70 ] diff --git a/FlashGBX/config/fc_DMG_29DL161TD-90.txt b/FlashGBX/config/fc_DMG_29DL161TD-90.txt index 902c4e7..264ea0e 100644 --- a/FlashGBX/config/fc_DMG_29DL161TD-90.txt +++ b/FlashGBX/config/fc_DMG_29DL161TD-90.txt @@ -1,20 +1,20 @@ { "type":"DMG", "names":[ - "SD007_48BALL_64M_V3 with 29DL161TD-90", - "SD007_48BALL_64M_V5 with 29DL163BD-90", - "SD007_48BALL_64M_V6 with 29DL163BD-90" + "SD007_48BALL_64M_V3 with 29DL161TD-90" ], "flash_ids":[ - [ 0x04, 0x04, 0x35, 0x35 ], - [ 0x04, 0x04, 0x2B, 0x2B ], - [ 0x04, 0x04, 0x2B, 0x2B ] + [ 0x04, 0x04, 0x35, 0x35 ] ], "voltage":3.3, "flash_size":0x200000, "start_addr":0, "first_bank":1, "write_pin":"WR", + "sector_size":[ + [0x10000, 31], + [0x2000, 8] + ], "chip_erase_timeout":120, "commands":{ "reset":[ @@ -41,6 +41,22 @@ [ null, null, null ], [ 0, 0xFF, 0xFF ] ], + "sector_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ "SA", 0x30 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ 0, 0xFF, 0xFF ] + ], "single_write":[ [ 0xAAA, 0xA9 ], [ 0x555, 0x56 ], diff --git a/FlashGBX/config/fc_DMG_29DL163BD-90.txt b/FlashGBX/config/fc_DMG_29DL163BD-90.txt new file mode 100644 index 0000000..add6f96 --- /dev/null +++ b/FlashGBX/config/fc_DMG_29DL163BD-90.txt @@ -0,0 +1,75 @@ +{ + "type":"DMG", + "names":[ + "SD007_48BALL_64M_V5 with 29DL163BD-90", + "SD007_48BALL_64M_V6 with 29DL163BD-90" + ], + "flash_ids":[ + [ 0x04, 0x04, 0x2B, 0x2B ], + [ 0x04, 0x04, 0x2B, 0x2B ] + ], + "voltage":3.3, + "flash_size":0x200000, + "start_addr":0, + "first_bank":1, + "write_pin":"WR", + "sector_size":[ + [0x2000, 8], + [0x10000, 31] + ], + "chip_erase_timeout":120, + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x90 ] + ], + "chip_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 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":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ "SA", 0x30 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ 0, 0xFF, 0xFF ] + ], + "single_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_DMG_29DL32TF-70.txt b/FlashGBX/config/fc_DMG_29DL32TF-70.txt index 5b6cdd7..23f444c 100644 --- a/FlashGBX/config/fc_DMG_29DL32TF-70.txt +++ b/FlashGBX/config/fc_DMG_29DL32TF-70.txt @@ -11,6 +11,11 @@ "start_addr":0, "first_bank":1, "write_pin":"WR", + "sector_size":[ + [0x2000, 8], + [0x10000, 31] + ], + "sector_reversal":true, "chip_erase_timeout":50, "commands":{ "reset":[ @@ -37,6 +42,22 @@ [ null, null, null ], [ 0, 0xFF, 0xFF ] ], + "sector_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 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 ] + ], "single_write":[ [ 0xAAA, 0xA9 ], [ 0x555, 0x56 ], diff --git a/FlashGBX/config/fc_DMG_36VF3204.txt b/FlashGBX/config/fc_DMG_36VF3204.txt index 6604abf..1447f0e 100644 --- a/FlashGBX/config/fc_DMG_36VF3204.txt +++ b/FlashGBX/config/fc_DMG_36VF3204.txt @@ -1,10 +1,16 @@ { "type":"DMG", "names":[ - "SD007_48BALL_64M_V5 with 36VF3204" + "SD007_48BALL_64M_V5 with 36VF3204", + "SD007_48BALL_64M_V6 with 36VF3204", + "SD007_TSOP_48BALL with 36VF3204", + "SD007_48BALL_64M_V5 with 36VF3203" ], "flash_ids":[ - [ 0xBF, 0x00, 0x53, 0x73 ] + [ 0xBF, 0x00, 0x53, 0x73 ], + [ 0xBF, 0x00, 0x53, 0x73 ], + [ 0xBF, 0x00, 0x53, 0x73 ], + [ 0xBF, 0x00, 0x54, 0x73 ] ], "voltage":3.3, "voltage_variants":true, diff --git a/FlashGBX/config/fc_DMG_AM29F016_AUDIO.txt b/FlashGBX/config/fc_DMG_AM29F016_AUDIO.txt index 1212349..0f15d7f 100644 --- a/FlashGBX/config/fc_DMG_AM29F016_AUDIO.txt +++ b/FlashGBX/config/fc_DMG_AM29F016_AUDIO.txt @@ -1,7 +1,7 @@ { "type":"DMG", "names":[ - "AM29F016/AM29F016B (DIY cart) @ AUDIO" + "DIY cart with AM29F016/AM29F016B @ AUDIO" ], "flash_ids":[ [ 0x01, 0xAD, 0x00, 0x00 ] diff --git a/FlashGBX/config/fc_DMG_AM29F016_WR.txt b/FlashGBX/config/fc_DMG_AM29F016_WR.txt index 7c5f6e5..9fc42c8 100644 --- a/FlashGBX/config/fc_DMG_AM29F016_WR.txt +++ b/FlashGBX/config/fc_DMG_AM29F016_WR.txt @@ -1,7 +1,7 @@ { "type":"DMG", "names":[ - "AM29F016/AM29F016B (DIY cart) @ WR" + "DIY cart with AM29F016/AM29F016B @ WR" ], "flash_ids":[ [ 0x01, 0xAD, 0x00, 0x00 ] diff --git a/FlashGBX/config/fc_DMG_AM29F032_AUDIO.txt b/FlashGBX/config/fc_DMG_AM29F032_AUDIO.txt index 03b8833..9033175 100644 --- a/FlashGBX/config/fc_DMG_AM29F032_AUDIO.txt +++ b/FlashGBX/config/fc_DMG_AM29F032_AUDIO.txt @@ -1,7 +1,7 @@ { "type":"DMG", "names":[ - "AM29F032/AM29F032B (DIY cart) @ AUDIO" + "DIY cart with AM29F032/AM29F032B @ AUDIO" ], "flash_ids":[ [ 0x04, 0xD4, 0x00, 0x00 ] diff --git a/FlashGBX/config/fc_DMG_AM29F032_WR.txt b/FlashGBX/config/fc_DMG_AM29F032_WR.txt index fa420d8..dd2c258 100644 --- a/FlashGBX/config/fc_DMG_AM29F032_WR.txt +++ b/FlashGBX/config/fc_DMG_AM29F032_WR.txt @@ -1,7 +1,7 @@ { "type":"DMG", "names":[ - "AM29F032/AM29F032B (DIY cart) @ WR" + "DIY cart with AM29F032/AM29F032B @ WR" ], "flash_ids":[ [ 0x04, 0xD4, 0x00, 0x00 ] diff --git a/FlashGBX/config/fc_DMG_AM29LV160MB.txt b/FlashGBX/config/fc_DMG_AM29LV160MB.txt index 0dc1436..1e07be1 100644 --- a/FlashGBX/config/fc_DMG_AM29LV160MB.txt +++ b/FlashGBX/config/fc_DMG_AM29LV160MB.txt @@ -1,10 +1,14 @@ { "type":"DMG", "names":[ - "SD007_BV5_V3 with AM29LV160MB" + "SD007_BV5_V3 with AM29LV160MB", + "SD007_TSOP_48BALL with L160DB12VI", + "SD007_TSOP_48BALL with AM29LV160DT" ], "flash_ids":[ - [ 0x02, 0x02, 0x4A, 0x4A ] + [ 0x02, 0x02, 0x4A, 0x4A ], + [ 0x02, 0x02, 0x4A, 0x4A ], + [ 0x02, 0x02, 0xC4, 0xC4 ] ], "voltage":5, "flash_size":0x200000, diff --git a/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt b/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt index bb124dd..116c55f 100644 --- a/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt +++ b/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt @@ -2,9 +2,11 @@ "type":"DMG", "names":[ "SD007_48BALL_64M with GL032M11BAIR4", - "S29GL032 (no PCB text)" + "SD007_48BALL_64M_V2 with GL032M11BAIR4", + "S29GL032N90T and ALTERA CPLD (no PCB text)" ], "flash_ids":[ + [ 0x02, 0x02, 0x7D, 0x7D ], [ 0x02, 0x02, 0x7D, 0x7D ], [ 0x02, 0x02, 0x7D, 0x7D ] ], @@ -13,23 +15,27 @@ "start_addr":0x4000, "first_bank":0, "write_pin":"WR", + "sector_size":[ + [0x2000, 8], + [0x10000, 63] + ], "chip_erase_timeout":60, "commands":{ "reset":[ - [ 0x7000, 0xF0 ] + [ 0x4000, 0xF0 ] ], "read_identifier":[ - [ 0x7AAA, 0xA9 ], - [ 0x7555, 0x56 ], - [ 0x7AAA, 0x90 ] + [ 0x4AAA, 0xA9 ], + [ 0x4555, 0x56 ], + [ 0x4AAA, 0x90 ] ], "chip_erase":[ - [ 0x7AAA, 0xA9 ], - [ 0x7555, 0x56 ], - [ 0x7AAA, 0x80 ], - [ 0x7AAA, 0xA9 ], - [ 0x7555, 0x56 ], - [ 0x7AAA, 0x10 ] + [ 0x4AAA, 0xA9 ], + [ 0x4555, 0x56 ], + [ 0x4AAA, 0x80 ], + [ 0x4AAA, 0xA9 ], + [ 0x4555, 0x56 ], + [ 0x4AAA, 0x10 ] ], "chip_erase_wait_for":[ [ null, null, null ], @@ -39,10 +45,26 @@ [ 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 ], + [ 0, 0xFF, 0xFF ] + ], "single_write":[ - [ 0x7AAA, 0xA9 ], - [ 0x7555, 0x56 ], - [ 0x7AAA, 0xA0 ], + [ 0x4AAA, 0xA9 ], + [ 0x4555, 0x56 ], + [ 0x4AAA, 0xA0 ], [ "PA", "PD" ] ], "single_write_wait_for":[ diff --git a/FlashGBX/config/fc_DMG_HY29LV160TT.txt b/FlashGBX/config/fc_DMG_HY29LV160TT.txt index 7da0494..988dbeb 100644 --- a/FlashGBX/config/fc_DMG_HY29LV160TT.txt +++ b/FlashGBX/config/fc_DMG_HY29LV160TT.txt @@ -1,10 +1,16 @@ { "type":"DMG", "names":[ - "SD007_BV5_V2 with HY29LV160TT" + "SD007_BV5_V2 with HY29LV160TT", + "SD007_BV5_V3 with HY29LV160BT", + "SD007_BV5_V3 with 29LV160BE-90PFTN", + "SD007_TSOP_48BALL with M29W160ET70ZA6" ], "flash_ids":[ - [ 0xAE, 0xAE, 0xC4, 0xC4 ] + [ 0xAE, 0xAE, 0xC4, 0xC4 ], + [ 0xAE, 0xAE, 0x4A, 0x4A ], + [ 0x04, 0x04, 0x4A, 0x4A ], + [ 0x20, 0x20, 0xC4, 0xC4 ] ], "voltage":3.3, "flash_size":0x200000, diff --git a/FlashGBX/config/fc_DMG_M29W160EB.txt b/FlashGBX/config/fc_DMG_M29W160EB.txt index 40be9d7..4b43a5d 100644 --- a/FlashGBX/config/fc_DMG_M29W160EB.txt +++ b/FlashGBX/config/fc_DMG_M29W160EB.txt @@ -11,6 +11,12 @@ "start_addr":0, "first_bank":1, "write_pin":"WR", + "sector_size":[ + [0x4000, 1], + [0x2000, 2], + [0x8000, 1], + [0x10000, 31] + ], "chip_erase_timeout":30, "commands":{ "reset":[ @@ -37,6 +43,22 @@ [ null, null, null ], [ 0, 0xFF, 0xFF ] ], + "sector_erase":[ + [ 0xAAA, 0xAA ], + [ 0x555, 0x55 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xAA ], + [ 0x555, 0x55 ], + [ "SA", 0x30 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0xFF, 0xFF ] + ], "single_write":[ [ 0xAAA, 0xAA ], [ 0x555, 0x55 ], diff --git a/FlashGBX/config/fc_DMG_M29W640.txt b/FlashGBX/config/fc_DMG_M29W640.txt index 7cc8ef8..b05d343 100644 --- a/FlashGBX/config/fc_DMG_M29W640.txt +++ b/FlashGBX/config/fc_DMG_M29W640.txt @@ -11,6 +11,10 @@ "start_addr":0, "first_bank":1, "write_pin":"WR", + "sector_size":[ + [0x02000, 8], + [0x10000, 127] + ], "chip_erase_timeout":70, "commands":{ "reset":[ @@ -37,6 +41,22 @@ [ null, null, null ], [ 0, 0xFF, 0xFF ] ], + "sector_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 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 ] + ], "single_write":[ [ 0xAAA, 0xA9 ], [ 0x555, 0x56 ], diff --git a/FlashGBX/config/fc_DMG_MX29LV320ABTC.txt b/FlashGBX/config/fc_DMG_MX29LV320ABTC.txt index 330b9e2..cddd06a 100644 --- a/FlashGBX/config/fc_DMG_MX29LV320ABTC.txt +++ b/FlashGBX/config/fc_DMG_MX29LV320ABTC.txt @@ -1,10 +1,12 @@ { "type":"DMG", "names":[ - "GB-M968 with MX29LV320ABTC" + "GB-M968 with MX29LV320ABTC", + "SD007_BV5_DRV with M29W320DT" ], "flash_ids":[ - [ 0xC2, 0xC2, 0xA8, 0xA8 ] + [ 0xC2, 0xC2, 0xA8, 0xA8 ], + [ 0x20, 0x20, 0xCA, 0xCA ] ], "voltage":3.3, "flash_size":0x400000, diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index 561e597..9dd5fa9 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -2,15 +2,16 @@ # UTF-8 import serial, os, serial.tools.list_ports from serial import SerialException -import sys, time, re, glob, math, struct, json, statistics, traceback, zlib, random +import sys, time, re, glob, math, struct, json, traceback, zlib, copy from .DataTransfer import * from .RomFileDMG import * from .RomFileAGB import * +from .Util import * class GbxDevice: DEVICE_NAME = "GBxCart RW" - DEVICE_MIN_FW = 17 - DEVICE_MAX_FW = 19 + DEVICE_MIN_FW = 19 + DEVICE_MAX_FW = 23 DEVICE_CMD = { "CART_MODE":'C', @@ -19,6 +20,7 @@ class GbxDevice: # GB/GBC defines/commands "SET_START_ADDRESS":'A', "READ_ROM_RAM":'R', + "READ_ROM_4000H":'Q', "WRITE_RAM":'W', "SET_BANK":'B', "GB_CART_MODE":'G', @@ -36,6 +38,7 @@ class GbxDevice: "FLASH_FOUND_ATMEL":3, "GBA_READ_ROM":'r', "GBA_READ_ROM_256BYTE":'j', + "GBA_READ_ROM_8000H":'Z', "GBA_READ_SRAM":'m', "GBA_WRITE_SRAM":'w', "GBA_WRITE_ONE_BYTE_SRAM":'o', @@ -70,6 +73,8 @@ class GbxDevice: "GBA_FLASH_WRITE_64BYTE_SWAPPED_D0D1":'q', "GBA_FLASH_WRITE_256BYTE_SWAPPED_D0D1":'t', "GBA_FLASH_WRITE_256BYTE":'f', + "GBA_FLASH_WRITE_BUFFERED_256BYTE":'c', + "GBA_FLASH_WRITE_BUFFERED_256BYTE_SWAPPED_D0D1":'d', "GBA_FLASH_WRITE_INTEL_64BYTE":'l', "GBA_FLASH_WRITE_INTEL_64BYTE_WORD":'u', "GBA_FLASH_WRITE_INTEL_INTERLEAVED_256BYTE":'v', @@ -86,6 +91,7 @@ class GbxDevice: "READ_PCB_VERSION":'h', "VOLTAGE_3_3V":'3', "VOLTAGE_5V":'5', + "SET_PINS_AS_INPUTS":':', "RESET_AVR":'*', "RESET_VALUE":0x7E5E1, "XMAS_LEDS":'#', @@ -93,10 +99,7 @@ class GbxDevice: "READ_BUFFER":0 } PCB_VERSIONS = {1:'v1.0', 2:'v1.1', 4:'v1.3', 90:'XMAS', 100:'Mini'} - SUPPORTED_CARTS = { - "DMG":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" }, - "AGB":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" } - } + SUPPORTED_CARTS = {} FW = [] MODE = None @@ -105,6 +108,9 @@ class GbxDevice: WORKER = None INFO = { "last_action":None } CANCEL = False + CANCEL_ARGS = {} + SIGNAL = None + POS = 0 def __init__(self): pass @@ -150,9 +156,7 @@ class GbxDevice: self.PORT = ports[i] # Load Flash Cartridge Handlers - for mode in flashcarts.keys(): - for key in sorted(flashcarts[mode].keys(), key=str.casefold): - self.SUPPORTED_CARTS[mode][key] = flashcarts[mode][key] + self.UpdateFlashCarts(flashcarts) # Stop after first found device break @@ -164,13 +168,27 @@ class GbxDevice: conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)]) continue - conn_msg.append([0, "NOTE: This is an unofficial tool for GBxCart RW. Visit https://www.gbxcart.com/ for officially supported options."]) + #conn_msg.append([0, "NOTE: This is a third party tool for GBxCart RW by insideGadgets. Visit https://www.gbxcart.com/ for more information."]) return conn_msg + def UpdateFlashCarts(self, flashcarts): + self.SUPPORTED_CARTS = { + "DMG":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" }, + "AGB":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" } + } + for mode in flashcarts.keys(): + for key in sorted(flashcarts[mode].keys(), key=str.casefold): + self.SUPPORTED_CARTS[mode][key] = flashcarts[mode][key] + def IsConnected(self): if self.DEVICE is None: return False if not self.DEVICE.isOpen(): return False try: + while self.DEVICE.in_waiting > 0: + print("Clearing input buffer... ({:d})".format(self.DEVICE.in_waiting)) + self.DEVICE.reset_input_buffer() + time.sleep(1) + self.DEVICE.reset_output_buffer() self.LoadFirmwareVersion() return True except SerialException as e: @@ -237,6 +255,11 @@ class GbxDevice: def GetSupportedCartridgesAGB(self): return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values())) + def SetProgress(self, args): + if self.CANCEL and args["action"] != "ABORT": return + if args["action"] == "UPDATE_POS": self.POS = args["pos"] + if self.SIGNAL: self.SIGNAL.emit(args) + def wait_for_ack(self): buffer = self.read(1) timeout = 5 @@ -250,7 +273,7 @@ class GbxDevice: return False else: print("Retrying...") - time.sleep(0.2) + time.sleep(0.5) buffer = self.read(1) return True @@ -262,12 +285,16 @@ class GbxDevice: if length <= 64: buffer = self.DEVICE.read(readlen) if len(buffer) != readlen: - print("read(): Failed to receive {:d} byte(s) from the device. Discarding {:d} byte(s).".format(readlen, len(buffer))) + dprint("read(): Received {:d} byte(s) instead of the expected {:d} bytes.".format(len(buffer), readlen)) self.write('0') - self.DEVICE.reset_input_buffer() + time.sleep(1) + while self.DEVICE.in_waiting > 0: + self.DEVICE.reset_input_buffer() + time.sleep(1) self.DEVICE.reset_output_buffer() return False + self.SetProgress({"action":"READ", "bytes_added":len(buffer)}) if length == 1: return buffer else: @@ -277,18 +304,42 @@ class GbxDevice: self.write('1') return buffer[:length] + elif length == 0x10000 or length == 0x4000: + mbuffer = bytearray() + for i in range(0, length, 64): + buffer = self.DEVICE.read(readlen) + if len(buffer) != readlen: + dprint("read(): Received {:d} byte(s) instead of the expected {:d} bytes during iteration {:d}.".format(len(buffer), readlen, i)) + self.write('0') + time.sleep(1) + while self.DEVICE.in_waiting > 0: + self.DEVICE.reset_input_buffer() + time.sleep(1) + self.DEVICE.reset_output_buffer() + return False + + mbuffer += buffer + self.SetProgress({"action":"READ", "bytes_added":len(buffer)}) + + self.write('0') + return mbuffer + else: # length > 64 mbuffer = bytearray() for i in range(0, length, 64): buffer = self.DEVICE.read(readlen) if len(buffer) != readlen: - print("read(): Failed to receive {:d} byte(s) from the device during iteration {:d}. Discarding {:d} byte(s).".format(readlen, i, len(buffer))) + dprint("read(): Received {:d} byte(s) instead of the expected {:d} bytes during iteration {:d}.".format(len(buffer), readlen, i)) self.write('0') - self.DEVICE.reset_input_buffer() + time.sleep(1) + while self.DEVICE.in_waiting > 0: + self.DEVICE.reset_input_buffer() + time.sleep(1) self.DEVICE.reset_output_buffer() return False mbuffer += buffer + self.SetProgress({"action":"READ", "bytes_added":len(buffer)}) if not (i + 64 >= length): self.write('1') @@ -302,33 +353,49 @@ class GbxDevice: data = bytearray(data, 'ascii') self.DEVICE.write(data) self.DEVICE.flush() - if wait_for_ack: return self.wait_for_ack() def ReadROM(self, offset, length, set_address=True): buffer = False - lives = 10 + lives = 5 + dprint("ReadROM(offset=0x{:X}, length=0x{:X}, set_address={:s})".format(offset, length, str(set_address))) while buffer == False: if self.MODE == "DMG": - if set_address: - self.set_number(offset, self.DEVICE_CMD['SET_START_ADDRESS']) - self.set_mode(self.DEVICE_CMD['READ_ROM_RAM']) + if length == 0x4000: + if set_address: + self.set_number(offset, self.DEVICE_CMD['SET_START_ADDRESS']) + self.set_mode(self.DEVICE_CMD['READ_ROM_4000H']) + else: + if set_address: + self.set_number(offset, self.DEVICE_CMD['SET_START_ADDRESS']) + self.set_mode(self.DEVICE_CMD['READ_ROM_RAM']) buffer = self.read(length, last=True) elif self.MODE == "AGB": - if set_address: - self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS']) - self.set_mode(self.DEVICE_CMD['GBA_READ_ROM']) + if length == 0x10000: + if set_address: + self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS']) + self.set_mode(self.DEVICE_CMD['GBA_READ_ROM_8000H']) + else: + if set_address: + self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS']) + self.set_mode(self.DEVICE_CMD['GBA_READ_ROM']) buffer = self.read(length, last=True) if buffer == False: - lives -= 1 if lives == 0: self.CANCEL = True - print("\nCouldn’t recover from the error. Please try again from the beginning.") + self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"A critical error occured while trying to read the cartridge ROM. Please try again from the beginning."} + print(" Giving up.", flush=True) return False - print("\nStarting over segment at 0x{:06X}...".format(offset)) + elif lives != 5: + print("", flush=True) + print("Failed to receive data at 0x{:X}. Retrying...".format(self.POS), end="", flush=True) set_address = True + lives -= 1 + + if lives < 5: + print(" OK!", flush=True) return buffer @@ -350,8 +417,8 @@ class GbxDevice: time.sleep(0.001) ack = self.wait_for_ack() if ack == False: - print("Can’t continue. Please try again.") self.CANCEL = True + self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"A critical error occured while trying to write the ROM. Please try again from the beginning."} return False def gb_flash_write_address_byte(self, address, data): @@ -362,8 +429,8 @@ class GbxDevice: self.write(buffer) ack = self.wait_for_ack() if ack == False: - print("Can’t continue. Please try again.") self.CANCEL = True + self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"A critical error occured while trying to write a byte. Please try again from the beginning."} return False def gbx_flash_write_data_bytes(self, command, data): @@ -382,6 +449,7 @@ class GbxDevice: time.sleep(0.005) def set_mode(self, command): + dprint("set_mode(command={:s})".format(str(command))) buffer = format(command, 's') self.write(buffer) @@ -394,13 +462,12 @@ class GbxDevice: time.sleep(0.3) def EnableRAM(self, mbc=1, enable=True): - #self.ReadROM(0, 64) if enable: - if mbc <= 4: - self.set_bank(0x6000, 1) + if mbc <= 4: self.set_bank(0x6000, 1) self.set_bank(0x0000, 0x0A) else: self.set_bank(0x0000, 0x00) + if mbc <= 4: self.set_bank(0x6000, 0) time.sleep(0.2) def SetBankROM(self, bank, mbc=5): @@ -422,7 +489,7 @@ class GbxDevice: def SetBankRAM(self, bank): self.set_bank(0x4000, (bank & 0xFF)) - def ReadFlashSaveID(self): + def ReadFlashSaveMakerID(self): makers = { 0x1F:"ATMEL", 0xBF:"SST/SANYO", 0xC2:"MACRONIX", 0x32:"PANASONIC", 0x62:"SANYO" } self.set_mode(self.DEVICE_CMD['GBA_FLASH_READ_ID']) time.sleep(0.02) @@ -440,10 +507,11 @@ class GbxDevice: self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) self.MODE = "AGB" self.set_number(0, self.DEVICE_CMD['SET_START_ADDRESS']) - + def AutoDetectFlash(self, limitVoltage=False): flash_types = [] flash_type = 0 + flash_id = None if limitVoltage: self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) @@ -451,6 +519,9 @@ class GbxDevice: supported_carts = list(self.SUPPORTED_CARTS['DMG'].values()) for f in range(2, len(supported_carts)): flashcart_meta = supported_carts[f] + if flash_id is not None: + if flash_id not in flashcart_meta["flash_ids"]: + continue self.set_mode(self.DEVICE_CMD['GB_CART_MODE']) if "flash_commands_on_bank_1" in flashcart_meta: @@ -477,14 +548,17 @@ class GbxDevice: for i in range(0, len(flashcart_meta["commands"]["reset"])): self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - # Read Identifier + # Read Flash ID / Electronic Signature if "read_identifier" in flashcart_meta["commands"]: for i in range(0, len(flashcart_meta["commands"]["read_identifier"])): self.gbx_flash_write_address_byte(flashcart_meta["commands"]["read_identifier"][i][0], flashcart_meta["commands"]["read_identifier"][i][1]) + buffer = self.ReadROM(0, 64) flash_id_found = False for i in range(0, len(flashcart_meta["flash_ids"])): - flash_id = list(self.ReadROM(0, 64)[0:len(flashcart_meta["flash_ids"][i])]) - if not len(flashcart_meta["flash_ids"][i]) == 0 and flash_id in flashcart_meta["flash_ids"]: flash_id_found = True + id = list(buffer[0:len(flashcart_meta["flash_ids"][i])]) + if id in flashcart_meta["flash_ids"]: + flash_id = id + flash_id_found = True if not flash_id_found and len(flashcart_meta["flash_ids"]) > 0: pass @@ -504,6 +578,9 @@ class GbxDevice: supported_carts = list(self.SUPPORTED_CARTS['AGB'].values()) for f in range(2, len(supported_carts)): flashcart_meta = supported_carts[f] + if flash_id is not None: + if flash_id not in flashcart_meta["flash_ids"]: + continue # Unlock Flash if "unlock" in flashcart_meta["commands"]: @@ -519,14 +596,17 @@ class GbxDevice: for i in range(0, len(flashcart_meta["commands"]["reset"])): self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - # Read Identifier + # Read Flash ID / Electronic Signature if "read_identifier" in flashcart_meta["commands"]: for i in range(0, len(flashcart_meta["commands"]["read_identifier"])): self.gbx_flash_write_address_byte(flashcart_meta["commands"]["read_identifier"][i][0], flashcart_meta["commands"]["read_identifier"][i][1]) + buffer = self.ReadROM(0, 64) flash_id_found = False for i in range(0, len(flashcart_meta["flash_ids"])): - flash_id = list(self.ReadROM(0, 64)[0:len(flashcart_meta["flash_ids"][i])]) - if flash_id in flashcart_meta["flash_ids"]: flash_id_found = True + id = list(buffer[0:len(flashcart_meta["flash_ids"][i])]) + if id in flashcart_meta["flash_ids"]: + flash_id = id + flash_id_found = True if not flash_id_found and len(flashcart_meta["flash_ids"]) > 0: pass @@ -541,92 +621,155 @@ class GbxDevice: return flash_types - def CheckFlashID(self, limitVoltage=False): + def CheckFlashChip(self, limitVoltage=False, cart_type=None): if limitVoltage: self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) flash_id_lines = [] + flash_commands = [ + { 'read_cfi':[[0x555, 0x98]], 'read_identifier':[[ 0x555, 0xAA ], [ 0x2AA, 0x55 ], [ 0x555, 0x90 ]], 'reset':[[ 0x0, 0xF0 ]] }, + { 'read_cfi':[[0x5555, 0x98]], 'read_identifier':[[ 0x5555, 0xAA ], [ 0x2AAA, 0x55 ], [ 0x5555, 0x90 ]], 'reset':[[ 0x0, 0xF0 ]] }, + { 'read_cfi':[[0xAA, 0x98]], 'read_identifier':[[ 0xAAA, 0xAA ], [ 0x555, 0x55 ], [ 0xAAA, 0x90 ]], 'reset':[[ 0x0, 0xF0 ]] }, + { 'read_cfi':[[0xAAA, 0x98]], 'read_identifier':[[ 0xAAA, 0xAA ], [ 0x555, 0x55 ], [ 0xAAA, 0x90 ]], 'reset':[[ 0x0, 0xF0 ]] }, + { 'read_cfi':[[0xAAAA, 0x98]], 'read_identifier':[[ 0xAAAA, 0xAA ], [ 0x5555, 0x55 ], [ 0xAAAA, 0x90 ]], 'reset':[[ 0x0, 0xF0 ]] }, + { 'read_cfi':[[0x4555, 0x98]], 'read_identifier':[[ 0x4555, 0xAA ], [ 0x4AAA, 0x55 ], [ 0x4555, 0x90 ]], 'reset':[[ 0x4000, 0xF0 ]] }, + { 'read_cfi':[[0x7555, 0x98]], 'read_identifier':[[ 0x7555, 0xAA ], [ 0x7AAA, 0x55 ], [ 0x7555, 0x90 ]], 'reset':[[ 0x7000, 0xF0 ]] }, + { 'read_cfi':[[0x4AAA, 0x98]], 'read_identifier':[[ 0x4AAA, 0xAA ], [ 0x4555, 0x55 ], [ 0x4AAA, 0x90 ]], 'reset':[[ 0x4000, 0xF0 ]] }, + { 'read_cfi':[[0x7AAA, 0x98]], 'read_identifier':[[ 0x7AAA, 0xAA ], [ 0x7555, 0x55 ], [ 0x7AAA, 0x90 ]], 'reset':[[ 0x7000, 0xF0 ]] }, + { 'read_cfi':[[0, 0x98]], 'read_identifier':[[ 0, 0x90 ]], 'reset':[[ 0, 0xFF ]] }, + ] + + check_buffer = self.ReadROM(0, 0x200) + d_swap = None + cfi_info = "" + rom_string = "" + for j in range(0, 8): + rom_string += "{:02X} ".format(check_buffer[j]) + rom_string += "\n" + cfi = {'raw':b''} + if self.MODE == "DMG": - ret_rom = "[ ROM ] " - methods = [ - {'we':'WR', 'read_identifier':[[0x555, 0xAA], [0x2AA, 0x55], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0x555, 0xA9], [0x2AA, 0x56], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0xAAA, 0xAA], [0x555, 0x55], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0xAAA, 0xA9], [0x555, 0x56], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0xAA, 0x98]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0x5555, 0xAA], [0x2AAA, 0x55], [0x5555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0x5555, 0xA9], [0x2AAA, 0x56], [0x5555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'WR', 'read_identifier':[[0x7AAA, 0xA9], [0x7555, 0x56], [0x7AAA, 0x90]], 'reset':[[0x7000, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0x555, 0xAA], [0x2AA, 0x55], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0x555, 0xA9], [0x2AA, 0x56], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0xAAA, 0xAA], [0x555, 0x55], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0xAAA, 0xA9], [0x555, 0x56], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0x5555, 0xAA], [0x2AAA, 0x55], [0x5555, 0x90]], 'reset':[[0, 0xF0]]}, - {'we':'AUDIO', 'read_identifier':[[0x5555, 0xA9], [0x2AAA, 0x56], [0x5555, 0x90]], 'reset':[[0, 0xF0]]}, - ] - - self.set_mode(self.DEVICE_CMD['GB_CART_MODE']) - if not limitVoltage: self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) - rom = self.ReadROM(0, 64)[0:8] - for i in range(0, len(methods)): - method = methods[i] - self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN']) - self.set_mode(self.DEVICE_CMD['WE_AS_' + method['we'] + '_PIN']) - for i in range(0, len(method['read_identifier'])): - self.gbx_flash_write_address_byte(method['read_identifier'][i][0], method["read_identifier"][i][1]) - flash_id = self.ReadROM(0, 64)[0:8] + rom_string = "[ ROM ] " + rom_string + we_pins = [ "WR", "AUDIO" ] + else: + rom_string = "[ ROM ] " + rom_string + we_pins = [ False ] + + for we in we_pins: + if "method" in cfi: break + for method in flash_commands: + if self.MODE == "DMG": + self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN']) + self.set_mode(self.DEVICE_CMD['WE_AS_' + we + '_PIN']) + for i in range(0, len(method['reset'])): self.gbx_flash_write_address_byte(method['reset'][i][0], method['reset'][i][1]) - if rom == flash_id: continue - - method_string = "[" + method['we'].ljust(5) + "/{:4X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1]) - flash_id_lines.append([method_string, flash_id]) - - elif self.MODE == "AGB": - ret_rom = "[ ROM ] " - methods = [ - {'read_identifier':[[0x555, 0xAA], [0x2AA, 0x55], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'read_identifier':[[0x555, 0xA9], [0x2AA, 0x56], [0x555, 0x90]], 'reset':[[0, 0xF0]]}, - {'read_identifier':[[0xAAA, 0xAA], [0x555, 0x55], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'read_identifier':[[0xAAA, 0xA9], [0x555, 0x56], [0xAAA, 0x90]], 'reset':[[0, 0xF0]]}, - {'read_identifier':[[0, 0x90]], 'reset':[[0, 0xFF]]}, - ] - - self.set_mode(self.DEVICE_CMD['GBA_CART_MODE']) - self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) - rom = self.ReadROM(0, 64)[0:8] - for i in range(0, len(methods)): - method = methods[i] - for i in range(0, len(method['read_identifier'])): - self.gbx_flash_write_address_byte(method['read_identifier'][i][0], method["read_identifier"][i][1]) - flash_id = self.ReadROM(0, 64)[0:8] + for i in range(0, len(method['read_cfi'])): + self.gbx_flash_write_address_byte(method['read_cfi'][i][0], method["read_cfi"][i][1]) + buffer = self.ReadROM(0, 0x400) for i in range(0, len(method['reset'])): self.gbx_flash_write_address_byte(method['reset'][i][0], method['reset'][i][1]) - if rom == flash_id: continue + if buffer == check_buffer: continue - method_string = "[{:6X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1]) - flash_id_lines.append([method_string, flash_id]) + magic = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24])) + if magic == "QRY": # nothing swapped + d_swap = ( 0, 0 ) + elif magic == "RQZ": # D0D1 swapped + d_swap = ( 0, 1 ) + if d_swap is not None: + for i in range(0, len(buffer)): + buffer[i] = bitswap(buffer[i], d_swap) + + cfi_parsed = ParseCFI(buffer) + dprint(cfi_parsed) + if cfi_parsed != False: + cfi = cfi_parsed + cfi["raw"] = buffer + #cfi["sha1"] = hashlib.sha1(buffer).hexdigest() + cfi["bytes"] = "" + for i in range(0, 0x400): + cfi["bytes"] += "{:02X}".format(buffer[i]) + if self.MODE == "DMG": cfi["we"] = we + cfi["method_id"] = flash_commands.index(method) + + if d_swap is not None: + for k, v in method.items(): + for c in range(0, len(method[k])): + if isinstance(method[k][c][1], int): + method[k][c][1] = bitswap(method[k][c][1], d_swap) + + # Flash ID + for i in range(0, len(method['read_identifier'])): + self.gbx_flash_write_address_byte(method['read_identifier'][i][0], method["read_identifier"][i][1]) + flash_id = self.ReadROM(0, 64)[0:8] + if self.MODE == "DMG": + method_string = "[" + we.ljust(5) + "/{:4X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1]) + else: + method_string = "[{:6X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1]) + line_exists = False + for i in range(0, len(flash_id_lines)): + if method_string == flash_id_lines[i][0]: line_exists = True + if not line_exists: flash_id_lines.append([method_string, flash_id]) + for i in range(0, len(method['reset'])): + self.gbx_flash_write_address_byte(method['reset'][i][0], method['reset'][i][1]) + + cfi["method"] = method + + if cart_type is not None: # reset cartridge if method is known + flashcart_meta = copy.deepcopy(cart_type) + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - ret = "" + if "method" in cfi: + s = "" + if d_swap is not None and d_swap != ( 0, 0 ): s += "Swapped pins: {:s}\n".format(str(d_swap)) + s += "Device size: 0x{:07X} ({:.2f} MB)\n".format(cfi["device_size"], cfi["device_size"] / 1024 / 1024) + s += "Voltage: {:.1f}–{:.1f} V\n".format(cfi["vdd_min"], cfi["vdd_max"]) + s += "Single write: {:s}\n".format(str(cfi["single_write"])) + if "buffer_size" in cfi: + s += "Buffered write: {:s} ({:d} Bytes)\n".format(str(cfi["buffer_write"]), cfi["buffer_size"]) + else: + s += "Buffered write: {:s}\n".format(str(cfi["buffer_write"])) + if cfi["chip_erase"]: s += "Chip erase: {:d}–{:d} ms\n".format(cfi["chip_erase_time_avg"], cfi["chip_erase_time_max"]) + if cfi["sector_erase"]: s += "Sector erase: {:d}–{:d} ms\n".format(cfi["sector_erase_time_avg"], cfi["sector_erase_time_max"]) + if cfi["tb_boot_sector"] is not False: s += "Top/Bottom flags: {:s}\n".format(str(cfi["tb_boot_sector"])) + pos = 0 + oversize = False + s = s[:-1] + for i in range(0, cfi['erase_sector_regions']): + esb = cfi['erase_sector_blocks'][i] + s += "\nRegion {:d}: 0x{:07X}–0x{:07X} @ 0x{:X} Bytes × {:d}".format(i+1, pos, pos+esb[2]-1, esb[0], esb[1]) + if oversize: s += " (alt)" + pos += esb[2] + if pos >= cfi['device_size']: + pos = 0 + oversize = True + #s += "\nSHA-1: {:s}".format(cfi["sha1"]) + cfi_info = s + + flash_id = "" for i in range(0, len(flash_id_lines)): - ret += flash_id_lines[i][0] + " " + flash_id += flash_id_lines[i][0] + " " for j in range(0, 8): - ret += "{:02X} ".format(flash_id_lines[i][1][j]) - ret += "\n" + flash_id += "{:02X} ".format(flash_id_lines[i][1][j]) + flash_id += "\n" - if ret != "": - for j in range(0, 8): - ret_rom += "{:02X} ".format(rom[j]) - ret_rom += "\n" - ret = ret_rom + ret + if flash_id != "": + flash_id = rom_string + flash_id - if limitVoltage and self.MODE == "DMG": - self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) - - return ret + return (flash_id, cfi_info, cfi["raw"]) + + def CheckROMStable(self): + self.ReadROM(0, 64) + buffer = self.ReadROM(0, 0x180) + time.sleep(0.1) + if buffer != self.ReadROM(0, 0x180): + return False + return True def ReadInfo(self): - if not self.IsConnected() and not self.Initialize(): raise Exception("Couldn’t access the the device.") + if not self.IsConnected(): raise Exception("Couldn’t access the the device.") data = {} header = self.ReadROM(0x000000, 0x000180) @@ -647,51 +790,70 @@ class GbxDevice: self.INFO["flash_type"] = 0 self.INFO["last_action"] = 0 + self.set_mode(self.DEVICE_CMD['SET_PINS_AS_INPUTS']) return data - - def BackupROM(self, fncSetProgress, path="ROM.gb", mbc=0x00, rom_banks=512, agb_rom_size=0): - config = { 'mode':1, 'port':self, 'path':path, 'mbc':mbc, 'rom_banks':rom_banks, 'agb_rom_size':agb_rom_size } - self.WORKER = DataTransfer(config) - self.WORKER.updateProgress.connect(fncSetProgress) - self.WORKER.start() - - def BackupRAM(self, fncSetProgress, path="ROM.sav", mbc=0x00, save_type=0): - config = { 'mode':2, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type } - self.WORKER = DataTransfer(config) - self.WORKER.updateProgress.connect(fncSetProgress) - self.WORKER.start() - - def RestoreRAM(self, fncSetProgress, path="ROM.sav", mbc=0x00, save_type=0, erase=False): - config = { 'mode':3, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':erase } - self.WORKER = DataTransfer(config) - self.WORKER.updateProgress.connect(fncSetProgress) - self.WORKER.start() - - def FlashROM(self, fncSetProgress, path="ROM.gb", cart_type=0, trim_rom=False, override_voltage=False): - config = { 'mode':4, 'port':self, 'path':path, 'cart_type':cart_type, 'trim_rom':trim_rom, 'override_voltage':override_voltage } - self.WORKER = DataTransfer(config) - self.WORKER.updateProgress.connect(fncSetProgress) + + def BackupROM(self, fncSetProgress=None, path="ROM.gb", mbc=0x00, rom_banks=512, agb_rom_size=0, start_addr=0, fast_read_mode=False): + config = { 'mode':1, 'port':self, 'path':path, 'mbc':mbc, 'rom_banks':rom_banks, 'agb_rom_size':agb_rom_size, 'start_addr':start_addr, 'fast_read_mode':fast_read_mode } + if self.WORKER is None: + self.WORKER = DataTransfer(config) + self.WORKER.updateProgress.connect(fncSetProgress) + else: + self.WORKER.setConfig(config) self.WORKER.start() - def _TransferData(self, mode, signal, args): # called by thread - if not self.IsConnected() and not self.Initialize(): raise Exception("Couldn’t access the the device.") - - path = args[0] + def BackupRAM(self, fncSetProgress=None, path="ROM.sav", mbc=0x00, save_type=0): + config = { 'mode':2, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type } + if self.WORKER is None: + self.WORKER = DataTransfer(config) + self.WORKER.updateProgress.connect(fncSetProgress) + else: + self.WORKER.setConfig(config) + self.WORKER.start() + + def RestoreRAM(self, fncSetProgress=None, path="", mbc=0x00, save_type=0, erase=False): + config = { 'mode':3, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':erase } + if self.WORKER is None: + self.WORKER = DataTransfer(config) + self.WORKER.updateProgress.connect(fncSetProgress) + else: + self.WORKER.setConfig(config) + self.WORKER.start() + + def FlashROM(self, fncSetProgress=None, path="", cart_type=0, override_voltage=False, buffer=bytearray(), start_addr=0, prefer_sector_erase=False, reverse_sectors=False): + config = { 'mode':4, 'port':self, 'path':path, 'cart_type':cart_type, 'override_voltage':override_voltage, 'start_addr':start_addr, 'buffer':buffer, 'prefer_sector_erase':prefer_sector_erase, 'reverse_sectors':reverse_sectors } + if self.WORKER is None: + self.WORKER = DataTransfer(config) + self.WORKER.updateProgress.connect(fncSetProgress) + else: + self.WORKER.setConfig(config) + self.WORKER.start() + + def _TransferData(self, args, signal): # called by thread + if not self.IsConnected(): raise Exception("Couldn’t access the the device.") + self.SIGNAL = signal + mode = args["mode"] if self.INFO == None: self.ReadInfo() + path = args["path"] self.INFO["last_path"] = path self.INFO["last_action"] = mode - measure_points = [] time_start = time.time() bank_size = 0x4000 # main work if mode == 1: # Backup ROM + fast_read_mode = args["fast_read_mode"] + buffer_len = 0x1000 if self.MODE == "DMG": - mbc = args[1] - bank_count = args[2] + mbc = args["mbc"] + bank_count = args["rom_banks"] + if fast_read_mode: + buffer_len = 0x4000 rom_size = bank_count * bank_size elif self.MODE == "AGB": - rom_size = args[3] + rom_size = args["agb_rom_size"] + if fast_read_mode: + buffer_len = 0x10000 if rom_size == 0: rom_size = 32 * 1024 * 1024 @@ -700,16 +862,11 @@ class GbxDevice: startAddr = 0 currAddr = 0 recvBytes = 0 - speed = 0 - time_left = 0 - - signal.emit(None, 0, rom_size, 0, 0, 0) - last_emit = time.time() try: file = open(path, "wb") except PermissionError as e: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"FlashGBX doesn’t have permission to access this file for writing:\n" + path, "abortable":False}, 0, 0, 0, 0, 0) + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"FlashGBX doesn’t have permission to access this file for writing:\n" + path, "abortable":False}) return False if self.MODE == "DMG": @@ -718,17 +875,29 @@ class GbxDevice: endAddr = rom_size bank_count = 1 + # Read a bit before actually dumping (fixes some bootlegs) + self.ReadROM(0, 64) + + self.SetProgress({"action":"INITIALIZE", "method":"ROM_READ", "size":rom_size}) + for bank in range(0, bank_count): if self.MODE == "DMG": if bank > 0: startAddr = bank_size endAddr = startAddr + bank_size self.SetBankROM(bank, mbc) + #self.SetProgress({"action":"UPDATE_SPEED", "abortable":False}) - buffer_len = 0x1000 # debug todo for currAddr in range(startAddr, endAddr, buffer_len): if self.CANCEL: - signal.emit({"action":"ABORT", "abortable":False}, 0, 0, 0, 0, 0) + cancel_args = {"action":"ABORT", "abortable":False} + cancel_args.update(self.CANCEL_ARGS) + self.CANCEL_ARGS = {} + self.SetProgress(cancel_args) + try: + file.close() + except: + pass return if currAddr == startAddr: @@ -737,37 +906,13 @@ class GbxDevice: buffer = self.ReadROM(currAddr, buffer_len, False) if buffer == False: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An I/O error occured. Please try again from the beginning.", "abortable":False}, 0, 0, 0, 0, 0) - return False + self.CANCEL = True + continue data_dump.extend(buffer) file.write(buffer) recvBytes += buffer_len - - # Report back to GUI - if recvBytes % buffer_len == 0: - now = time.time() - measure_points.append(now - last_emit) - last_emit = now - if len(measure_points) >= 16: - time_delta = statistics.mean(measure_points) - speed = buffer_len / time_delta - time_elapsed = time.time() - time_start - time_left = (time_elapsed / (recvBytes/rom_size)) - time_elapsed - if len(measure_points) >= 512: - measure_points = measure_points[-512:] - - if not recvBytes == rom_size: signal.emit(None, recvBytes, rom_size, speed/1024, time.time()-time_start, time_left) - - # check for flashgbx trimmed rom - if not os.path.splitext(path)[0].endswith("_notrimfix"): # debug - rom_size = len(data_dump) - trm_index = data_dump.find(b'TRIMFLASHGBX') - if trm_index != -1: - free_byte = data_dump[trm_index-4] - data_dump = data_dump[0:trm_index-4] + bytes([free_byte] * (rom_size - (trm_index-4))) - file.seek(0) - file.write(data_dump) + self.SetProgress({"action":"UPDATE_POS", "pos":recvBytes}) file.close() @@ -783,7 +928,7 @@ class GbxDevice: chk = zlib.crc32(data_dump) & 0xffffffff self.INFO["rom_checksum_calc"] = chk - signal.emit(None, recvBytes, rom_size, speed/1024, time.time()-time_start, time_left) + self.SetProgress({"action":"FINISHED"}) ######################################### @@ -795,8 +940,8 @@ class GbxDevice: startAddr = 0 if self.MODE == "DMG": bank_size = 0x2000 - mbc = args[1] - save_size = args[2] + mbc = args["mbc"] + save_size = args["save_type"] bank_count = int(max(save_size, bank_size) / bank_size) self.EnableRAM(mbc=mbc, enable=True) transfer_size = 64 @@ -804,7 +949,7 @@ class GbxDevice: elif self.MODE == "AGB": bank_size = 0x10000 - save_type = args[2] + save_type = args["save_type"] bank_count = 1 transfer_size = 64 eeprom_size = 0 @@ -831,10 +976,15 @@ class GbxDevice: save_size = 64 * 1024 read_command = 'GBA_READ_SRAM' write_command = 'GBA_WRITE_SRAM' - elif save_type == 5: # FLASH 64 KB + elif save_type == 5: # SRAM 128 KB + save_size = 128 * 1024 + read_command = 'GBA_READ_SRAM' + write_command = 'GBA_WRITE_SRAM' + bank_count = 2 + elif save_type == 6: # FLASH 64 KB save_size = 64 * 1024 read_command = 'GBA_READ_SRAM' - elif save_type == 6: # FLASH 128 KB + elif save_type == 7: # FLASH 128 KB save_size = 128 * 1024 read_command = 'GBA_READ_SRAM' bank_count = 2 @@ -842,11 +992,11 @@ class GbxDevice: return # Get Save Flash Manufacturer - flash_id = None - if save_type == 5 or save_type == 6: - flash_id = self.ReadFlashSaveID() - if flash_id == "ATMEL": - print("NOTE: For save data this cartridge uses an ATMEL chip which is untested.") + maker_id = None + if save_type == 6 or save_type == 7: + maker_id = self.ReadFlashSaveMakerID() + if maker_id == "ATMEL": + print("NOTE: For save data, this cartridge uses an ATMEL chip which is untested.") transfer_size = 128 # Prepare some stuff @@ -854,36 +1004,37 @@ class GbxDevice: try: file = open(path, "wb") except PermissionError as e: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"FlashGBX doesn’t have permission to access this file for writing:\n" + path, "abortable":False}, 0, 0, 0, 0, 0) + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"FlashGBX doesn’t have permission to access this file for writing:\n" + path, "abortable":False}) return False + self.SetProgress({"action":"INITIALIZE", "method":"SAVE_READ", "size":save_size}) elif mode == 3: # Restore - if args[3]: # Erase + if args["erase"]: # Erase data_import = save_size * b'\xFF' else: with open(path, "rb") as file: data_import = file.read() if save_size > len(data_import): data_import += b'\xFF' * (save_size - len(data_import)) + self.SetProgress({"action":"INITIALIZE", "method":"SAVE_WRITE", "size":save_size}) currAddr = 0 pos = 0 - speed = 0 - time_left = 0 - signal.emit(None, 0, save_size, 0, 0, 0) for bank in range(0, bank_count): if self.MODE == "DMG": endAddr = startAddr + bank_size if endAddr > (startAddr + save_size): endAddr = startAddr + save_size self.SetBankRAM(bank) - + elif self.MODE == "AGB": endAddr = startAddr + min(save_size, bank_size) if endAddr > (startAddr + save_size): endAddr = startAddr + save_size if save_type == 1 or save_type == 2: # EEPROM self.set_number(eeprom_size, self.DEVICE_CMD['GBA_SET_EEPROM_SIZE']) - elif (save_type == 5 or save_type == 6) and bank > 0: # FLASH + elif (save_type == 5) and bank > 0: # 1M SRAM + self.gbx_flash_write_address_byte(0x1000000, bank) + elif (save_type == 6 or save_type == 7) and bank > 0: # FLASH self.set_number(bank, self.DEVICE_CMD['GBA_FLASH_SET_BANK']) self.set_number(startAddr, self.DEVICE_CMD['SET_START_ADDRESS']) @@ -898,7 +1049,10 @@ class GbxDevice: if bank > 0: self.set_number(0, self.DEVICE_CMD['GBA_FLASH_SET_BANK']) self.set_mode(self.DEVICE_CMD['READ_ROM_RAM']) self.ReadInfo() - signal.emit({"action":"ABORT", "abortable":False}, 0, 0, 0, 0, 0) + cancel_args = {"action":"ABORT", "abortable":False} + cancel_args.update(self.CANCEL_ARGS) + self.CANCEL_ARGS = {} + self.SetProgress(cancel_args) return if mode == 2: # Backup @@ -912,7 +1066,7 @@ class GbxDevice: self.set_mode(self.DEVICE_CMD[read_command]) buffer = self.read(buffer_len, last) if buffer == False: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Backup failed, please try again.", "abortable":False}, 0, 0, 0, 0, 0) + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Backup failed, please try again.", "abortable":False}) return False data_dump.extend(buffer) @@ -924,20 +1078,25 @@ class GbxDevice: self.gbx_flash_write_data_bytes("WRITE_RAM", data) elif self.MODE == "AGB": - if save_type == 5 or save_type == 6: # FLASH - if flash_id == "ATMEL": + if save_type == 6 or save_type == 7: # FLASH + if maker_id == "ATMEL": self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_ATMEL", data) else: if (currAddr % 4096 == 0): self.set_number(sector, self.DEVICE_CMD['GBA_FLASH_4K_SECTOR_ERASE']) self.wait_for_ack() sector += 1 + lives = 50 while True: # wait for 0xFF (= erase done) self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) self.set_mode(self.DEVICE_CMD[read_command]) buffer = self.read(buffer_len, last=True) if buffer[0] == 0xFF: break time.sleep(0.01) + lives -= 1 + if lives == 0: + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Writing the flash save data failed. Please make sure you selected the correct save type.", "abortable":False}) + return False self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_BYTE", data) @@ -945,125 +1104,146 @@ class GbxDevice: else: # EEPROM / SRAM self.gbx_flash_write_data_bytes(write_command, data) + self.SetProgress({"action":"WRITE", "bytes_added":len(data)}) self.wait_for_ack() pos += buffer_len + self.SetProgress({"action":"UPDATE_POS", "pos":pos}) - if not pos == save_size: signal.emit(None, pos, save_size, speed/1024, time.time()-time_start, 0) + #if not pos == save_size: signal.emit(None, pos, save_size, speed/1024, time.time()-time_start, 0) self.INFO["transfered"] = pos if self.MODE == "DMG": self.EnableRAM(mbc=mbc, enable=False) elif self.MODE == "AGB": - if bank > 0: self.set_number(0, self.DEVICE_CMD['GBA_FLASH_SET_BANK']) + if bank > 0: + if (save_type == 5) and bank > 0: # 1M SRAM + self.gbx_flash_write_address_byte(0x1000000, 0) + elif (save_type == 6 or save_type == 7) and bank > 0: # FLASH + self.set_number(0, self.DEVICE_CMD['GBA_FLASH_SET_BANK']) if mode == 2: file.close() self.INFO["last_action"] = mode - signal.emit(None, pos, pos, speed/1024, time.time()-time_start, 0) + self.SetProgress({"action":"FINISHED"}) ######################################### elif mode == 4: # Flash ROM - with open(path, "rb") as file: data_import = file.read() - - if len(data_import) < 0x8000: # smallest flashable ROM is 32 KB - data_import += b'\xFF' * (0x8000 - len(data_import)) - - if args[2]: # trim_rom - free_byte = data_import[len(data_import)-1] - if free_byte == 0xFF: # or free_byte == 0x00: - marker = bytes([free_byte] * 4) + b'TRIMFLASHGBX' # a trim marker, so dumps will still be valid - for i in range(len(data_import)-1, -1, -1): - if data_import[i] != free_byte: break - rom_size_trimmed = i - rom_size_trimmed += len(marker) - rom_size_trimmed = 0x1000 * (math.ceil(rom_size_trimmed / 0x1000)) - if rom_size_trimmed < len(data_import): - data_import = data_import[0:rom_size_trimmed-len(marker)] + marker - if self.MODE == "DMG": supported_carts = list(self.SUPPORTED_CARTS['DMG'].values()) elif self.MODE == "AGB": supported_carts = list(self.SUPPORTED_CARTS['AGB'].values()) + + if not isinstance(args["cart_type"], dict): + cart_type = "RETAIL" + for i in range(0, len(supported_carts)): + if i == args["cart_type"]: cart_type = supported_carts[i] + if cart_type == "RETAIL" or cart_type == "AUTODETECT": return False # Generic ROM Cartridge is not flashable + else: + cart_type = args["cart_type"] + + if path != "": + with open(path, "rb") as file: data_import = file.read() + else: + data_import = args["buffer"] + self._FlashROM(buffer=data_import, cart_type=cart_type, voltage=args["override_voltage"], start_addr=args["start_addr"], signal=signal, prefer_sector_erase=args["prefer_sector_erase"], reverse_sectors=args["reverse_sectors"]) + + # Reset pins to avoid save data loss + self.set_mode(self.DEVICE_CMD['SET_PINS_AS_INPUTS']) + + ####################################################################################################################################### + + def _FlashROM(self, buffer=bytearray(), start_addr=0, cart_type=None, voltage=3.3, signal=None, prefer_sector_erase=False, reverse_sectors=False): + if not self.IsConnected(): raise Exception("Couldn’t access the the device.") + if self.INFO == None: self.ReadInfo() + self.INFO["last_action"] = 4 + bank_size = 0x4000 + time_start = time.time() + + data_import = copy.copy(buffer) + if start_addr > 0: + data_import = (b'\xFF' * start_addr) + data_import + + if self.MODE == "DMG": + supported_carts = list(self.SUPPORTED_CARTS['DMG'].values()) + elif self.MODE == "AGB": + supported_carts = list(self.SUPPORTED_CARTS['AGB'].values()) - cart_type = "RETAIL" - for i in range(0, len(supported_carts)): - if i == args[1]: cart_type = supported_carts[i] - if cart_type == "RETAIL" or cart_type == "AUTODETECT": return False # Generic ROM Cartridge is not flashable + if cart_type == "RETAIL" or cart_type == "AUTODETECT": return False # Generic ROM Cartridge is not flashable + flashcart_meta = copy.deepcopy(cart_type) + + # Reverse sectors if requested + if reverse_sectors: flashcart_meta['sector_size'].reverse() + + # Firmware check R23+ + if (int(self.FW[0]) < 23) and self.MODE == "AGB" and "buffer_write" in flashcart_meta["commands"] and flashcart_meta["commands"]["buffer_write"] == [[0xAAA, 0xA9], [0x555, 0x56], ['SA', 0x26], ['SA', 'BS'], ['PA', 'PD'], ['SA', 0x2A]]: + print("NOTE: Update your GBxCart RW firmware to version R23 or higher for a better transfer rate with this cartridge.") + del flashcart_meta["commands"]["buffer_write"] + # Firmware check R23+ + + # Set Voltage + if voltage == 3.3: + self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) + elif voltage == 5: + self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) + elif flashcart_meta["voltage"] == 3.3: + self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) + elif flashcart_meta["voltage"] == 5: + self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) + + if self.MODE == "DMG": + self.set_mode(self.DEVICE_CMD['GB_CART_MODE']) + if "flash_commands_on_bank_1" in flashcart_meta and flashcart_meta["flash_commands_on_bank_1"]: + self.set_mode(self.DEVICE_CMD['GB_FLASH_BANK_1_COMMAND_WRITES']) + dprint("Setting GB_FLASH_BANK_1_COMMAND_WRITES...") - flashcart_meta = cart_type.copy() - - signal.emit(None, 0, len(data_import), 0, 0, 0) - time_start = time.time() - speed = 0 - time_left = 0 - - # Set Voltage - if args[3] == 3.3: - self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) - elif args[3] == 5: - self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) - elif flashcart_meta["voltage"] == 3.3: - self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V']) - elif flashcart_meta["voltage"] == 5: - self.set_mode(self.DEVICE_CMD['VOLTAGE_5V']) - - if self.MODE == "DMG": - self.set_mode(self.DEVICE_CMD['GB_CART_MODE']) - if "flash_commands_on_bank_1" in flashcart_meta: - self.set_mode(self.DEVICE_CMD['GB_FLASH_BANK_1_COMMAND_WRITES']) - - self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN']) - if flashcart_meta["write_pin"] == "WR": - self.set_mode(self.DEVICE_CMD['WE_AS_WR_PIN']) - elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"): - self.set_mode(self.DEVICE_CMD['WE_AS_AUDIO_PIN']) + self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN']) + if flashcart_meta["write_pin"] == "WR": + self.set_mode(self.DEVICE_CMD['WE_AS_WR_PIN']) + dprint("Setting WE_AS_WR_PIN...") + elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"): + self.set_mode(self.DEVICE_CMD['WE_AS_AUDIO_PIN']) + dprint("Setting WE_AS_AUDIO_PIN...") - if "single_write" in flashcart_meta["commands"] and len(flashcart_meta["commands"]["single_write"]) == 4: - # Submit flash program commands to firmware - self.set_mode(self.DEVICE_CMD['GB_FLASH_PROGRAM_METHOD']) - for i in range(0, 3): - self.write(bytearray(format(flashcart_meta["commands"]["single_write"][i][0], "x"), "ascii") + b'\x00', True) - self.write(bytearray(format(flashcart_meta["commands"]["single_write"][i][1], "x"), "ascii") + b'\x00', True) - - elif self.MODE == "AGB": - # Read a bit of ROM before starting - self.ReadROM(0, 64) - - # Unlock Flash - if "unlock" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["unlock"])): - addr = flashcart_meta["commands"]["unlock"][i][0] - data = flashcart_meta["commands"]["unlock"][i][1] - count = flashcart_meta["commands"]["unlock"][i][2] - for j in range(0, count): - self.gbx_flash_write_address_byte(addr, data) - - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - - # Read Identifier - if "read_identifier" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["read_identifier"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["read_identifier"][i][0], flashcart_meta["commands"]["read_identifier"][i][1]) - flash_id_found = False - for i in range(0, len(flashcart_meta["flash_ids"])): - flash_id = list(self.ReadROM(0, 64)[0:len(flashcart_meta["flash_ids"][i])]) - if flash_id in flashcart_meta["flash_ids"]: flash_id_found = True - if not flash_id_found and len(flashcart_meta["flash_ids"]) > 0: - pass - - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - - # Chip Erase - if "chip_erase" in flashcart_meta["commands"] and "sector_erase" not in flashcart_meta["commands"]: - signal.emit({"action":"ERASE", "abortable":False}, 1, 0, 0, time.time()-time_start, 0) + if "single_write" in flashcart_meta["commands"] and len(flashcart_meta["commands"]["single_write"]) == 4: + # Submit flash program commands to firmware + dprint("Setting GB_FLASH_PROGRAM_METHOD...") + self.set_mode(self.DEVICE_CMD['GB_FLASH_PROGRAM_METHOD']) + for i in range(0, 3): + dprint("single_write_command(",i,"):", hex(flashcart_meta["commands"]["single_write"][i][0]), "=", hex(flashcart_meta["commands"]["single_write"][i][1])) + self.write(bytearray(format(flashcart_meta["commands"]["single_write"][i][0], "x"), "ascii") + b'\x00', True) + self.write(bytearray(format(flashcart_meta["commands"]["single_write"][i][1], "x"), "ascii") + b'\x00', True) + + elif self.MODE == "AGB": + # Read a bit of ROM before starting + self.ReadROM(0, 64) + + # Unlock Flash + if "unlock" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["unlock"])): + addr = flashcart_meta["commands"]["unlock"][i][0] + data = flashcart_meta["commands"]["unlock"][i][1] + count = flashcart_meta["commands"]["unlock"][i][2] + for j in range(0, count): + self.gbx_flash_write_address_byte(addr, data) + + # Reset Flash + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) + + # Chip Erase + chip_erase = False + if "chip_erase" in flashcart_meta["commands"]: + if "sector_erase" in flashcart_meta["commands"] and prefer_sector_erase is True: + chip_erase = False + elif "chip_erase_treshold" in flashcart_meta: + if len(data_import) > flashcart_meta["chip_erase_treshold"] or "sector_erase" not in flashcart_meta["commands"]: + chip_erase = True + else: + chip_erase = True + self.SetProgress({"action":"ERASE", "time_start":time_start, "abortable":False}) for i in range(0, len(flashcart_meta["commands"]["chip_erase"])): addr = flashcart_meta["commands"]["chip_erase"][i][0] data = flashcart_meta["commands"]["chip_erase"][i][1] @@ -1074,8 +1254,8 @@ class GbxDevice: data = flashcart_meta["commands"]["chip_erase_wait_for"][i][1] timeout = flashcart_meta["chip_erase_timeout"] while True: - signal.emit({"action":"ERASE", "abortable":False}, 1, 0, 0, time.time()-time_start, 0) - if "wait_read_status_register" in flashcart_meta and flashcart_meta["wait_read_status_register"] == True: + self.SetProgress({"action":"ERASE", "time_start":time_start, "abortable":False}) + if "wait_read_status_register" in flashcart_meta and flashcart_meta["wait_read_status_register"]: for j in range(0, len(flashcart_meta["commands"]["read_status_register"])): sr_addr = flashcart_meta["commands"]["read_status_register"][j][0] sr_data = flashcart_meta["commands"]["read_status_register"][j][1] @@ -1083,241 +1263,327 @@ class GbxDevice: wait_for = self.ReadROM(addr, 64) wait_for = ((wait_for[1] << 8 | wait_for[0]) & flashcart_meta["commands"]["chip_erase_wait_for"][i][2]) if wait_for == data: break - time.sleep(1) - timeout -= 1 - if timeout < 1: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the flash chip timed out. Please make sure the correct flash cartridge type is selected.", "abortable":False}, 0, 0, 0, 0, 0) + time.sleep(0.5) + timeout -= 0.5 + if timeout <= 0: + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the flash chip timed out. Please make sure the correct flash cartridge type is selected.", "abortable":False}) return False - - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - self.set_number(0, self.DEVICE_CMD['SET_START_ADDRESS']) + # Reset Flash + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - # Write Flash - pos = 0 + dprint("Chip erase took {:d} seconds".format(math.ceil(time.time() - time_start))) + + self.set_number(0, self.DEVICE_CMD['SET_START_ADDRESS']) + + # Write Flash + pos = 0 + currAddr = 0 + skipping = False # skips if block is full of 0xFF + if self.MODE == "DMG": + if "first_bank" in flashcart_meta: first_bank = flashcart_meta["first_bank"] + if "start_addr" in flashcart_meta: currAddr = flashcart_meta["start_addr"] + endAddr = 0x7FFF + bank_count = math.ceil(len(data_import) / bank_size) + if start_addr == 0: self.SetBankROM(0) + elif self.MODE == "AGB": currAddr = 0 - if self.MODE == "DMG": - if "first_bank" in flashcart_meta: first_bank = flashcart_meta["first_bank"] - if "start_addr" in flashcart_meta: currAddr = flashcart_meta["start_addr"] - endAddr = 0x7FFF - bank_count = math.ceil(len(data_import) / bank_size) - self.SetBankROM(0) - elif self.MODE == "AGB": - currAddr = 0 - endAddr = len(data_import) - first_bank = 0 - bank_count = 1 - - time_start = time.time() - last_emit = time.time() - - sectCount = None - currSect = 0 + endAddr = len(data_import) + first_bank = 0 + bank_count = 1 + + self.SetProgress({"action":"INITIALIZE", "time_start":time_start, "method":"ROM_WRITE", "size":len(data_import)}) + + currSect = 0 + + # Fast Forward + if start_addr > 0: + first_bank = math.floor(start_addr / bank_size) + self.SetBankROM(first_bank) + offset = start_addr % bank_size + currAddr = bank_size + offset if "sector_erase" in flashcart_meta["commands"]: - if isinstance(flashcart_meta["sector_size"], list): - sector_size = flashcart_meta["sector_size"][currSect][0] - else: - sector_size = flashcart_meta["sector_size"] + while pos < start_addr: + dprint("* currSect:",currSect) + dprint("sector_size:", hex(flashcart_meta["sector_size"][currSect][0])) + dprint("start_addr:",hex(start_addr),", pos:",hex(pos)) + pos += flashcart_meta["sector_size"][currSect][0] + flashcart_meta["sector_size"][currSect][1] -= 1 + if flashcart_meta["sector_size"][currSect][1] == 0: + currSect += 1 + dprint("currSect:", currSect, end=", ") + dprint(flashcart_meta["sector_size"]) + dprint(flashcart_meta["sector_size"][currSect][1]) + + sector_count = None + if "sector_erase" in flashcart_meta["commands"]: + if isinstance(flashcart_meta["sector_size"], list): + sector_size = flashcart_meta["sector_size"][currSect][0] + else: + sector_size = flashcart_meta["sector_size"] + dprint("sector_size:", sector_size) + + dprint("start_addr:", hex(start_addr)) + dprint("first_bank:", first_bank, ", bank_count:", bank_count) + dprint("currAddr:", hex(currAddr), ", endAddr:", hex(endAddr)) + dprint("pos:", hex(pos)) + + if first_bank == bank_count: first_bank -= 1 # dirty hack so that <32 KB works too + for bank in range(first_bank, bank_count): + dprint("BANK {:d}".format(bank)) + if self.MODE == "DMG": + if bank > first_bank: currAddr = bank_size + self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) - for bank in range(first_bank, bank_count): - if self.MODE == "DMG": - if bank > first_bank: currAddr = bank_size - self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) + while (currAddr < endAddr): + if pos >= len(data_import): break + if self.CANCEL: + # Reset Flash + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) + cancel_args = {"action":"ABORT", "abortable":False} + cancel_args.update(self.CANCEL_ARGS) + self.CANCEL_ARGS = {} + self.SetProgress(cancel_args) + return - while (currAddr < endAddr): - if pos == len(data_import): break - if self.CANCEL: + if self.MODE == "DMG": + # Change Bank + if (currAddr == bank_size): # Reset Flash if "reset" in flashcart_meta["commands"]: for i in range(0, len(flashcart_meta["commands"]["reset"])): self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - signal.emit({"action":"ABORT", "abortable":False}, 0, 0, 0, 0, 0) - return + self.SetBankROM(bank) + time.sleep(0.05) + #self.SetProgress({"action":"UPDATE_SPEED", "abortable":False}) + + # Sector Erase (if supported) + if "sector_erase" in flashcart_meta["commands"] and not chip_erase: + if isinstance(flashcart_meta["sector_size"], list): + if sector_count == None: + sector_count = flashcart_meta["sector_size"][currSect][1] + if sector_count == 0: + if ((currSect+1) != len(flashcart_meta["sector_size"])): + currSect += 1 + sector_count = flashcart_meta["sector_size"][currSect][1] - if self.MODE == "DMG": - # Change Bank - if (currAddr == bank_size): - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - self.SetBankROM(bank) - time.sleep(0.05) - - # Sector Erase (if supported) - if "sector_erase" in flashcart_meta["commands"]: - if isinstance(flashcart_meta["sector_size"], list): - if sectCount == None: - sectCount = flashcart_meta["sector_size"][currSect][1] - if sectCount == 0: - if ((currSect+1) != len(flashcart_meta["sector_size"])): - currSect += 1 - sectCount = flashcart_meta["sector_size"][currSect][1] + if pos % sector_size == 0: + self.SetProgress({"action":"SECTOR_ERASE", "time_start":time.time(), "abortable":True}) - if pos % sector_size == 0: - signal.emit({"action":"SECTOR_ERASE", "abortable":False}, 1, 0, 0, time.time()-time_start, 0) - - # Update sector size if required - if "sector_erase" in flashcart_meta["commands"]: - if isinstance(flashcart_meta["sector_size"], list): - sector_size = flashcart_meta["sector_size"][currSect][0] - - for i in range(0, len(flashcart_meta["commands"]["sector_erase"])): - addr = flashcart_meta["commands"]["sector_erase"][i][0] - data = flashcart_meta["commands"]["sector_erase"][i][1] + # Update sector size if changed + if "sector_erase" in flashcart_meta["commands"]: + if isinstance(flashcart_meta["sector_size"], list): + sector_size = flashcart_meta["sector_size"][currSect][0] + dprint("\n* sector_count:", sector_count, "sector_size:", hex(sector_size), "pos:",hex(pos)) + + for i in range(0, len(flashcart_meta["commands"]["sector_erase"])): + addr = flashcart_meta["commands"]["sector_erase"][i][0] + data = flashcart_meta["commands"]["sector_erase"][i][1] + if addr == "SA": addr = currAddr + if addr == "SA+1": addr = currAddr + 1 + if addr == "SA+2": addr = currAddr + 2 + if addr == "SA+0x4000": addr = currAddr + 0x4000 + if addr == "SA+0x7000": addr = currAddr + 0x7000 + if not addr == None: + self.gbx_flash_write_address_byte(addr, data) + dprint("SE {:08X}={:X}".format(addr, data)) + if flashcart_meta["commands"]["sector_erase_wait_for"][i][0] != None: + addr = flashcart_meta["commands"]["sector_erase_wait_for"][i][0] + data = flashcart_meta["commands"]["sector_erase_wait_for"][i][1] if addr == "SA": addr = currAddr if addr == "SA+1": addr = currAddr + 1 if addr == "SA+2": addr = currAddr + 2 - if not addr == None: - self.gbx_flash_write_address_byte(addr, data) - if flashcart_meta["commands"]["sector_erase_wait_for"][i][0] != None: - addr = flashcart_meta["commands"]["sector_erase_wait_for"][i][0] - data = flashcart_meta["commands"]["sector_erase_wait_for"][i][1] - if addr == "SA": addr = currAddr - if addr == "SA+1": addr = currAddr + 1 - if addr == "SA+2": addr = currAddr + 2 - time.sleep(0.05) - timeout = 50 - while True: - if "wait_read_status_register" in flashcart_meta and flashcart_meta["wait_read_status_register"] == True: - for j in range(0, len(flashcart_meta["commands"]["read_status_register"])): - sr_addr = flashcart_meta["commands"]["read_status_register"][j][0] - sr_data = flashcart_meta["commands"]["read_status_register"][j][1] - self.gbx_flash_write_address_byte(sr_addr, sr_data) - wait_for = self.ReadROM(currAddr, 64) - wait_for = ((wait_for[1] << 8 | wait_for[0]) & flashcart_meta["commands"]["sector_erase_wait_for"][i][2]) - time.sleep(0.1) - timeout -= 1 - if timeout < 1: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing a flash chip sector timed out. Please make sure the correct flash cartridge type is selected.", "abortable":False}, 0, 0, 0, 0, 0) - return False - if wait_for == data: break - - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - - if self.MODE == "DMG": - self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) - elif self.MODE == "AGB": - self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) - - if sectCount is not None: - sectCount -= 1 - - # Write data (with special firmware acceleration if available) - if "buffer_write" in flashcart_meta["commands"]: + if addr == "SA+0x4000": addr = currAddr + 0x4000 + if addr == "SA+0x7000": addr = currAddr + 0x7000 + time.sleep(0.05) + timeout = 50 + while True: + if "wait_read_status_register" in flashcart_meta and flashcart_meta["wait_read_status_register"] == True: + for j in range(0, len(flashcart_meta["commands"]["read_status_register"])): + sr_addr = flashcart_meta["commands"]["read_status_register"][j][0] + sr_data = flashcart_meta["commands"]["read_status_register"][j][1] + self.gbx_flash_write_address_byte(sr_addr, sr_data) + dprint("SE_SR {:08X}={:X}".format(addr, data)) + wait_for = self.ReadROM(currAddr, 64) + wait_for = ((wait_for[1] << 8 | wait_for[0]) & flashcart_meta["commands"]["sector_erase_wait_for"][i][2]) + dprint("SE_SR {:X}=={:X}?".format(wait_for, data)) + time.sleep(0.1) + timeout -= 1 + if timeout < 1: + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing a flash chip sector timed out. Please make sure the correct flash cartridge type is selected.", "abortable":False}) + return False + if wait_for == data: break + + # Reset Flash + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) + if self.MODE == "DMG": - # BUNG Doctor GB Card 64M - if flashcart_meta["commands"]["buffer_write"] == [['SA', 232], ['SA', 'BS'], ['PA', 'PD'], ['SA', 208]]: - data = data_import[pos:pos+32] + self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) + elif self.MODE == "AGB": + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + + if sector_count is not None: + sector_count -= 1 + + #self.SetProgress({"action":"UPDATE_SPEED", "abortable":False}) + + # Write data (with special firmware acceleration if available) + if "buffer_write" in flashcart_meta["commands"]: + if self.MODE == "DMG": + # BUNG Doctor GB Card 64M + if flashcart_meta["commands"]["buffer_write"] == [['SA', 0xE8], ['SA', 'BS'], ['PA', 'PD'], ['SA', 0xD0]]: + data = data_import[pos:pos+32] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False self.gbx_flash_write_data_bytes("GB_FLASH_WRITE_INTEL_BUFFERED_32BYTE", data) self.wait_for_ack() - currAddr += 32 - pos += 32 - - else: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}, 0, 0, 0, 0, 0) - return False - # TODO + currAddr += 32 + pos += 32 - elif self.MODE == "AGB": - # Flash2Advance 256M - if flashcart_meta["commands"]["buffer_write"] == [['SA', 232], ['SA+2', 232], ['SA', 'BS'], ['SA+2', 'BS'], ['PA', 'PD'], ['SA', 208], ['SA+2', 208], [None, None], [None, None]]: - data = data_import[pos:pos+256] + else: + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}) + return False + # TODO + + elif self.MODE == "AGB": + # Flash2Advance 256M + if flashcart_meta["commands"]["buffer_write"] == [['SA', 0xE8], ['SA+2', 0xE8], ['SA', 'BS'], ['SA+2', 'BS'], ['PA', 'PD'], ['SA', 0xD0], ['SA+2', 0xD0], [None, None], [None, None]]: + data = data_import[pos:pos+256] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_INTEL_INTERLEAVED_256BYTE", data) self.wait_for_ack() - currAddr += 256 - pos += 256 - - # 256L30B - elif flashcart_meta["commands"]["buffer_write"] == [['SA', 96], ['SA', 208], ['SA', 232], ['SA', 'BS'], ['PA', 'PD'], ['SA', 208], ['SA', 255]]: - if "single_write_7FC0_to_7FFF" in flashcart_meta and int(currAddr % 0x8000) in range(0x7FC0, 0x7FFF): - for i in range(0, len(flashcart_meta["commands"]["single_write"])): - addr = flashcart_meta["commands"]["single_write"][i][0] - data = flashcart_meta["commands"]["single_write"][i][1] - if addr == "PA": addr = int(currAddr) - if data == "PD": data = struct.unpack('H', data_import[pos:pos+2])[0] - self.gbx_flash_write_address_byte(addr, data) - currAddr += 2 - pos += 2 - - if int(currAddr % 0x8000) == 0: - self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) - - else: - data = data_import[pos:pos+64] - self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_INTEL_64BYTE", data) - self.wait_for_ack() - currAddr += 64 - pos += 64 - - else: - signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}, 0, 0, 0, 0, 0) - return False - # TODO - - elif "single_write" in flashcart_meta["commands"]: - if self.MODE == "DMG": - data = data_import[pos:pos+64] - if "pulse_reset_after_write" in flashcart_meta: - self.gbx_flash_write_data_bytes("GB_FLASH_WRITE_64BYTE_PULSE_RESET", data) - else: - self.gbx_flash_write_data_bytes("GB_FLASH_WRITE_64BYTE", data) - self.wait_for_ack() - currAddr += 64 - pos += 64 + currAddr += 256 + pos += 256 - elif self.MODE == "AGB": - # MSP55LV128M - if flashcart_meta["commands"]["single_write"] == [[1365, 169], [682, 86], [1365, 160], ['PA', 'PD']]: - data = data_import[pos:pos+256] - self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_256BYTE_SWAPPED_D0D1", data) - self.wait_for_ack() - currAddr += 256 - pos += 256 - - # E201850 and E201868 - elif flashcart_meta["commands"]["single_write"] == [[0, 112], [0, 16], ['PA', 'PD']] and (([ 0xB0, 0x00, 0xE2, 0x00 ] in flashcart_meta["flash_ids"]) or ([ 0xB0, 0x00, 0xB0, 0x00 ] in flashcart_meta["flash_ids"])): - data = data_import[pos:pos+64] - self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_SHARP_64BYTE", data) - self.wait_for_ack() - currAddr += 64 - pos += 64 - - else: # super slow + # 256L30B etc. + elif flashcart_meta["commands"]["buffer_write"] == [['SA', 0x60], ['SA', 0xD0], ['SA', 0xE8], ['SA', 'BS'], ['PA', 'PD'], ['SA', 0xD0], ['SA', 0xFF]]: + if "single_write_7FC0_to_7FFF" in flashcart_meta and flashcart_meta["single_write_7FC0_to_7FFF"] and int(currAddr % 0x8000) in range(0x7FC0, 0x7FFF): for i in range(0, len(flashcart_meta["commands"]["single_write"])): addr = flashcart_meta["commands"]["single_write"][i][0] data = flashcart_meta["commands"]["single_write"][i][1] if addr == "PA": addr = int(currAddr) if data == "PD": data = struct.unpack('H', data_import[pos:pos+2])[0] self.gbx_flash_write_address_byte(addr, data) - currAddr += 2 pos += 2 + data = bytearray([0] * 2) + + if int(currAddr % 0x8000) == 0: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + + else: + data = data_import[pos:pos+64] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False + self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_INTEL_64BYTE", data) + self.wait_for_ack() + + currAddr += 64 + pos += 64 + + # MSP55LV128M etc. + elif flashcart_meta["commands"]["buffer_write"] == [[0xAAA, 0xA9], [0x555, 0x56], ['SA', 0x26], ['SA', 'BS'], ['PA', 'PD'], ['SA', 0x2A]]: + data = data_import[pos:pos+256] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False + self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_BUFFERED_256BYTE_SWAPPED_D0D1", data) + self.wait_for_ack() + currAddr += 256 + pos += 256 + + else: + dprint(flashcart_meta["commands"]["buffer_write"]) + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}) + return False + # TODO + + elif "single_write" in flashcart_meta["commands"]: + if self.MODE == "DMG": + data = data_import[pos:pos+64] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False + if "pulse_reset_after_write" in flashcart_meta and flashcart_meta["pulse_reset_after_write"]: + self.gbx_flash_write_data_bytes("GB_FLASH_WRITE_64BYTE_PULSE_RESET", data) + else: + self.gbx_flash_write_data_bytes("GB_FLASH_WRITE_64BYTE", data) + if not self.wait_for_ack(): + self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Timeout error.", "abortable":False}) + return False + currAddr += 64 + pos += 64 - # Report back to GUI - if pos % 4096 == 0: - now = time.time() - measure_points.append(now - last_emit) - last_emit = now - if len(measure_points) >= 16: - time_delta = statistics.median(measure_points) - speed = 4096 / time_delta - time_elapsed = time.time() - time_start - time_left = (time_elapsed / (pos/len(data_import))) - time_elapsed - if len(measure_points) >= 512: - measure_points = measure_points[-512:] - - if not pos == len(data_import): signal.emit(None, pos, len(data_import), speed/1024, time.time()-time_start, time_left) - - # Reset Flash - if "reset" in flashcart_meta["commands"]: - for i in range(0, len(flashcart_meta["commands"]["reset"])): - self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) - - signal.emit(None, pos, len(data_import), speed/1024, time.time()-time_start, time_left) + elif self.MODE == "AGB": + # MSP55LV128 etc. + if flashcart_meta["commands"]["single_write"] == [[0xAAA, 0xA9], [0x555, 0x56], [0xAAA, 0xA0], ['PA', 'PD']]: + data = data_import[pos:pos+256] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False + self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_256BYTE_SWAPPED_D0D1", data) + self.wait_for_ack() + currAddr += 256 + pos += 256 + + # E201850 and E201868 + elif flashcart_meta["commands"]["single_write"] == [[0, 0x70], [0, 0x10], ['PA', 'PD']] and (([ 0xB0, 0x00, 0xE2, 0x00 ] in flashcart_meta["flash_ids"]) or ([ 0xB0, 0x00, 0xB0, 0x00 ] in flashcart_meta["flash_ids"])): + data = data_import[pos:pos+64] + if data == bytearray([0xFF] * len(data)): + skipping = True + else: + if skipping: + self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS']) + skipping = False + self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_SHARP_64BYTE", data) + self.wait_for_ack() + currAddr += 64 + pos += 64 + + else: # super slow + for i in range(0, len(flashcart_meta["commands"]["single_write"])): + addr = flashcart_meta["commands"]["single_write"][i][0] + data = flashcart_meta["commands"]["single_write"][i][1] + if addr == "PA": addr = int(currAddr) + if data == "PD": data = struct.unpack('H', data_import[pos:pos+2])[0] + self.gbx_flash_write_address_byte(addr, data) + + currAddr += 2 + pos += 2 + + self.SetProgress({"action":"WRITE", "bytes_added":len(data), "skipping":skipping}) + + # Reset Flash + if "reset" in flashcart_meta["commands"]: + for i in range(0, len(flashcart_meta["commands"]["reset"])): + self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1]) + + self.SetProgress({"action":"FINISHED"}) diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index b5ea944..2a6b0be 100644 Binary files a/FlashGBX/res/config.zip and b/FlashGBX/res/config.zip differ diff --git a/README.md b/README.md index 479cc21..25c6325 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,12 @@ by Lesserkuma - Backup, restore and erase save data from Game Boy and Game Boy Advance game cartridges - Backup ROM data from Game Boy and Game Boy Advance game cartridges - Write new ROMs to a wide variety of Game Boy and Game Boy Advance flash cartridges -- Many flash cartridges can be auto-detected -- A Flash ID check can be performed for unsupported flash cartridges +- Many reproduction cartridges and flash cartridges can be auto-detected +- A flash chip query can be performed for unsupported flash cartridges ### Confirmed working reader/writer hardware -- [insideGadgets GBxCart RW v1.3 and v1.3 Pro](https://www.gbxcart.com/) with firmware versions from R17 up to R19 (other hardware revisions and firmware versions may also work, but are untested) +- [insideGadgets GBxCart RW v1.3 and v1.3 Pro](https://www.gbxcart.com/) with firmware versions from R19 up to R23 (other hardware revisions and firmware versions may also work, but are untested) ### Currently supported flash cartridges @@ -38,44 +38,63 @@ by Lesserkuma - Game Boy Advance - - Flash2Advance 256M (non-ultra variant) + - Flash2Advance 256M (non-ultra variant, with 2× 28F128J3A150) - Nintendo AGB Cartridge 128M Flash S, E201850 - Nintendo AGB Cartridge 256M Flash S, E201868 -### Currently supported bootleg cartridges +### Currently supported and tested reproduction cartridges - Game Boy - ES29LV160_DRV with 29DL32TF-70 - GB-M968 with M29W160EB *(thanks RevZ)* - GB-M968 with MX29LV320ABTC - - S29GL032 (no PCB text) + - ALTERA CPLD and S29GL032N90T (no PCB text) - SD007_48BALL_64M with GL032M11BAIR4 *(thanks RevZ)* - SD007_48BALL_64M with M29W640 + - SD007_48BALL_64M_V2 with GL032M11BAIR4 - SD007_48BALL_64M_V3 with 29DL161TD-90 + - SD007_48BALL_64M_V5 with 36VF3203 - SD007_48BALL_64M_V5 with 36VF3204 + - SD007_48BALL_64M_V6 with 36VF3204 - SD007_48BALL_64M_V6 with 29DL163BD-90 *(thanks LovelyA72)* + - SD007_BV5_DRV with M29W320DT *(thanks Frost Clock)* + - SD007_BV5_V3 with 29LV160BE-90PFTN *(thanks LucentW)* + - SD007_BV5_V3 with HY29LV160BT-70 *(thanks LucentW)* - SD007_BV5_V2 with HY29LV160TT *(thanks RevZ)* - SD007_BV5_V2 with MX29LV320BTC *(thanks RevZ)* - SD007_BV5_V3 with AM29LV160MB *(thanks RevZ)* + - SD007_TSOP_48BALL with 36VF3204 + - SD007_TSOP_48BALL with AM29LV160DT *(thanks marv17)* + - SD007_TSOP_48BALL with L160DB12VI *(thanks marv17)* - Game Boy Advance - 28F256L03B-DRV with 256L30B - - 4455_4400_4000_4350_36L0R_V3 with M36L0R705 + - 36L0R8-39VF512 with M36L0R8060B *(thanks LucentW)* + - 36L0R8-39VF512 with M36L0R8060T *(thanks AndehX)* + - 4050_4400_4000_4350_36L0R_V5 with M36L0R7050T + - 4050_4400_4000_4350_36L0R_V5 with M36L0T8060T + - 4050_4400_4000_4350_36L0R_V5 with M36L0R8060T + - 4455_4400_4000_4350_36L0R_V3 with M36L0R7050T + - AGB-E05-01 with GL128S - AGB-E05-01 with MSP55LV128M + - AGB-E05-02 with M29W128GH *(thanks marv17)* + - AGB-E08-09 with 29LV128DTMC-90Q *(thanks LucentW)* - AGB-SD-E05 with MSP55LV128 *(thanks RevZ)* - BX2006_0106_NEW with S29GL128N10TFI01 *(thanks litlemoran)* + - BX2006_TSOP_64BALL with GL128S + - BX2006_TSOPBGA_0106 with M29W640GB6AZA6 -Many different bootleg cartridges share their flash chip command set, so even if yours is not on this list, it may still work fine or even be detected as another one. Support for more cartridges can also be added by creating external config files that include the necessary flash chip commands. +Many different reproduction cartridges share their flash chip command set, so even if yours is not on this list, it may still work fine or even be auto-detected as another one. Support for more cartridges can also be added by creating external config files that include the necessary flash chip commands. ## Installing and running The application should work on pretty much every operating system that supports Qt-GUI applications built using [Python 3](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/) and [pyserial](https://pypi.org/project/pyserial/) packages. -If you have Python and pip installed, you can use `pip install FlashGBX` to download and install the application. +If you have Python and pip installed, you can use `pip install FlashGBX` to download and install the application. Then use `python -m FlashGBX` to run it. -To run it in portable mode, you can also download the source code archive and call `python run.py` after installing the prerequisites yourself. +To run FlashGBX in portable mode, you can also download the source code archive and call `python run.py` after installing the prerequisites yourself. ### Windows binaries @@ -88,12 +107,16 @@ These executables have been created using *PyInstaller* and *Inno Setup*. ### Troubleshooting -* If something doesn’t work as expected during data transfer, first try to clean the game cartridge contacts, wiggle the cartridge a bit and reconnect the USB device. +* If something doesn’t work as expected, first try to clean the game cartridge contacts (best with IPA 99%+ on a Q-tip) and reconnect the device. * On Linux systems, you may run into a *Permission Error* problem when trying to connect to USB devices without *sudo* privileges. To grant yourself the necessary permissions temporarily, you can run `sudo chmod 0666 /dev/ttyUSB0` (replace with actual device path) before running the app. For a permanent solution, add yourself to the usergroup that has access to serial devices by default (e.g. *dialout* on Debian-based distros; `sudo adduser $USER dialout`) and then reboot the system. * On some Linux systems, you may need the *XCB Xinerama package* if you see an error regarding failed Qt platform plugin initialization. You can install it with `sudo apt install libxcb-xinerama0` etc. +* For save data backup/restore on Game Boy Advance reproduction cartridges, depending on how it was built, you may have to manually select the save type for it to work properly. + +* The save data backup/restore feature may not work on certain reproduction cartridges with batteryless-patched ROMs. As those cartridges use the same flash chip for both ROM and save data storage, a full ROM backup will usually include the save data. Also, when flashing a new unpatched ROM to a cartridge like this, the game may not be able to save progress without soldering in a battery. + ## DISCLAIMER This software is provided as-is and the developer is not responsible for any damage that is caused by the use of it. Use at your own risk! @@ -103,16 +126,20 @@ This software is provided as-is and the developer is not responsible for any dam The author would like to thank the following very kind people for their help and contributions (in alphabetical order): - AlexiG (GBxCart RW hardware, bug reports, flash chip info) -- AndehX (app icon) +- AndehX (app icon, flash chip info) +- easthighNerd (feature suggestions) +- Frost Clock (flash chip info) - JFox (help with properly packaging the app for pip) - julgr (macOS help, testing) - litlemoran (flash chip info) - LovelyA72 (flash chip info) +- LucentW (flash chip info, testing) +- marv17 (flash chip info, testing) - RevZ (Linux help, testing, bug reports, flash chip info) ## Changes -### v0.7β (2020-09-25) +### v0.7β (released 2020-09-25) - First started tracking changes - Added a way to launch the flash cartridge type auto-detection process from the type list @@ -121,24 +148,24 @@ The author would like to thank the following very kind people for their help and - Fixed config file for 4455_4400_4000_4350_36L0R_V3 with M36L0R705 (sector size map was incomplete) - Renamed some labels of flash cartridge types - Made config files UTF-8 compatible -- File open and save dialogs will now remember the last used directory for ROM files and save data files respectively (stored in config.ini in AppData/Roaming), clearable with the `--reset` switch or by editing/deleting config.ini +- File open and save dialogs will now remember the last used directory for ROM files and save data files respectively (stored in settings.ini in AppData/Roaming), clearable with the `--reset` switch or by editing/deleting settings.ini - Added CRC32 checksum comparison for Game Boy Advance games by using a database based on header SHA1 hashes - Fixed a bug with save data restore and improved save data backup speed - Fixed a few status message errors - Added a warning when changing platforms -- Made some messages suppressible (stored in config.ini) +- Made some messages suppressible (stored in settings.ini) - Added detection of ungraceful device disconnects when starting a new task, with optional automatic reconnect - First public beta release -### v0.8β (2020-10-03) +### v0.8β (released 2020-10-03) - Added support for the DIY cart with AM29F016/AM29F016B with AUDIO as WE *(thanks AndehX)* - Renamed `VIN` to `AUDIO` in config files and the command line switch `--resetconfig` to `--reset` - Added experimental support for GBxCart RW revisions other than v1.3 and fixed a crash when connecting to unknown revisions of the GBxCart RW - The app is now available as a package and can be installed directly through *pip* *(thanks JFox)* - Changed the way configuration files are stored (for details call with `--help` command line switch) -- Added the option to write an automatically trimmed ROM file which can reduce flashing time, especially in Game Boy Advance mode (note that not all ROMs can be trimmed) -- When dumping a flash cartridge that has been flashed with a trimmed ROM, the ROM will be fixed so checksums will still match up (can be disabled for debugging by adding `_notrimfix` to the file name) +- ~~Added the option to write an automatically trimmed ROM file which can reduce flashing time, especially in Game Boy Advance mode (note that not all ROMs can be trimmed)~~ +- ~~When dumping a flash cartridge that has been flashed with a trimmed ROM, the ROM will be fixed so checksums will still match up (can be disabled for debugging by adding `_notrimfix` to the file name)~~ - Added a button that opens a file browser to the currently used config directory for easy access - Added the option to erase/wipe the save data on a cartridge - Rearranged some buttons on the main window so that the newly added button popup menus don’t block anything @@ -146,9 +173,45 @@ The author would like to thank the following very kind people for their help and - Rewrote some of the save data handling for Game Boy Advance cartridges to speed transfers up a bit - Fixed a couple of instability issues that used to caused timeouts on macOS *(thanks julgr)* - Confirmed support for SD007_48BALL_64M_V5 with 29DL163BD-90 *(thanks julgr)* -- Added an option for flashing at 5V for a few flash cartridge types that sometimes require this; if you think your cartridge is affected, you can add `"voltage_variants":true,` to the config file +- Added an option for flashing at 5V for a few flash cartridge types that sometimes require this; if you think your cartridge is affected, you can add `"voltage_variants":true,` to the config file for it - Fixed support for Windows 7 when using pre-compiled exe file packages - Reduced size and decompression time of the pre-compiled exe file packages by excluding unnecessary DLL files - Fixed a timing issue that could sometimes cause a loss of save data when hot-swapping Game Boy cartridges - Added some warnings that help with troubleshooting, for example that manually setting the feature box to a MBC5 option may be necessary for a clean dump when dumping ROMs from flash cartridges - Added taskbar progress visualization on Windows systems + +### v0.9β (released 2020-12-17) + +- Confirmed support for BX2006_TSOP_64BALL with GL128S +- Confirmed support for SD007_48BALL_64M_V2 with GL032M11BAIR4 +- Added support for 4050_4400_4000_4350_36L0R_V5 with M36L0R8060T/M36L0T8060T +- Rewrote parts of the GBxCart RW interface code +- Removed the option to trim ROM files and will now instead just skip writing empty chunks of data +- Fixed config files for MSP55LV128 and MSP55LV128M flash chips +- Confirmed support for SD007_48BALL_64M_V6 with 36VF3204 +- Confirmed support for SD007_TSOP_48BALL with 36VF3204 +- Added the option to add date and time to suggested filenames for save data backups *(thanks easthighNerd)* +- Added a check and warning for unstable ROM readings +- Added Common Flash Interface query for both unknown and known flash cartridge types +- Added support for 36L0R8-39VF512 with M36L0R8060B *(thanks LucentW)* +- Added support for another version of 36L0R8-39VF512 with M36L0R8060B/M36L0R8060T *(thanks AndehX)* +- Added support for AGB-E05-02 with M29W128GH *(thanks marv17)* +- Added backup and restore of 1M SRAM save data in GBA mode +- Confirmed support for BX2006_TSOPBGA_0106 with M29W640GB6AZA6 *(thanks LucentW)* +- Confirmed support for AGB-E05-01 with GL128S +- Improved writing speed for MSP55LV128M, S29GL128 and similar flash chips (requires GBxCart RW firmware R23 or higher) +- Before flashing a ROM it will now be checked if its logo data and header checksum are valid and a warning will be shown if not +- Added support for SD007_BV5_V3 with 29LV160BE-90PFTN *(thanks LucentW)* +- Added support for SD007_BV5_V3 with HY29LV160BT *(thanks LucentW)* +- Added support for SD007_48BALL_64M_V5 with 36VF3203 *(thanks LucentW)* +- Added support for SD007_TSOP_48BALL with M29W160ET70ZA6 *(thanks LucentW)* +- Added support for AGB-E08-09 with 29LV128DTMC-90Q *(thanks LucentW)* +- Confirmed support for SD007_TSOP_48BALL with L160DB12VI *(thanks marv17)* +- Added support for SD007_TSOP_48BALL with AM29LV160DT *(thanks marv17)* +- Added support for SD007_BV5_DRV with M29W320DT *(thanks Frost Clock)* +- Added experimental *fast read mode* support for GBxCart RW v1.3 with firmware R19+ (about 20% faster) +- Bumped the required minimum firmware version of GBxCart RW v1.3 to R19 +- Confirmed support for 4050_4400_4000_4350_36L0R_V5 with M36L0R7050T +- Added the option to enable the preference of sector erase over chip erase when flashing a ROM (this can improve flashing speed for ROMs smaller than the flash chip capacity) +- Some flash chips may have reversed sectors despite shared flash ID; if you think your cartridge is affected, you can add `"sector_reversal":true,` to its config file for a prompt upon flashing +- Renamed config.ini to settings.ini to avoid confusion with the term “config file” diff --git a/run.py b/run.py index b971c51..03875a3 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ -# Note: This file runs FlashGBX in portable mode and is also used to build the portable Windows archive. +# Note: This file runs FlashGBX in portable mode. from FlashGBX import FlashGBX FlashGBX.main(portableMode=True) diff --git a/setup.py b/setup.py index 5a72bb0..f00901f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read( setuptools.setup( name="FlashGBX", - version="0.8b0", + version="0.9b0", author="Lesserkuma", description="A GUI application that can read and write Game Boy and Game Boy Advance cartridge data. Currently supports the GBxCart RW hardware device by insideGadgets.", url="https://github.com/lesserkuma/FlashGBX",