This commit is contained in:
Lesserkuma 2023-01-16 22:38:23 +01:00
parent 3b360b28d0
commit 288dbf8216
35 changed files with 681 additions and 112 deletions

BIN
.github/01.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 50 KiB

BIN
.github/02.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -1,6 +1,19 @@
# Release notes
### v3.21 (released 2023-01-16)
- Bundles GBxCart RW v1.4/v1.4a firmware version R39+L8 (adds support for insideGadgets WonderSwan and Game Gear flash carts)
- Added support for SD007_48BALL_SOP28 with M29W320ET *(thanks DevDavisNunez)*
- Added support for the BennVenn MBC3000 RTC cart *(thanks LucentW)*
- Added support for Flash2Advance Ultra 256M with 8× 3204C3B100 *(thanks djeddit)*
- Added support for SD007_T40_64BALL_SOJ28 with 29LV016T *(thanks Stitch)*
- Confirmed support for SD007_T40_64BALL_S71_TV_TS28 with TC58FVB016FT-85 *(thanks edo999)*
- Added support for F864-3 with M36L0R7050B *(thanks s1cp)*
- Allowed for switching between different Write Enable pins during chip erase and sector erase command sequences via a third parameter (“WR” or “AUDIO”) *(thanks ALXCO-Hardware for the suggestion)*
- Added support for the Squareboi 4 MB (2× 2 MB) cart *(thanks ALXCO-Hardware)*
- Added an option to limit the baud rate for GBxCart RW v1.4/v1.4a
- Minor bug fixes and improvements *(thanks gboh, Grender and orangeglo)*
### v3.20 (released 2022-11-30)
- Bundles GBxCart RW v1.4/v1.4a firmware version R38+L8
- Bundles GBxCart RW v1.4/v1.4a firmware version R38+L8 (minor improvements)
- Will now retry failed flash sector writes a few times before stopping the process (requires firmware version L1+)
- Added delta ROM writing (only write the difference between two ROMs); requires both the original <name>.<ext> ROM file and the changed <name>.delta.<ext> ROM file in the same directory (requires firmware version L1+) *(thanks djeddit for the suggestion)*
- Fixed support for Flash2Advance Ultra 64M with 2× 28F320C3B
@ -234,7 +247,7 @@
- Added support for SD007_TSOP_48BALL_V10 with M29W320DT *(thanks Jayro)*
- Fixed a problem of reading from a certain type of cartridge that uses the GL256S flash chip *(thanks marv17)*
- Added support for B11 with 26L6420MC-90 *(thanks dyf2007)*
- Added support for DIY carts with MBC3 and MX29LV640 *(thanks eveningmoose)*
- Added support for DIY carts with MX29LV640 *(thanks eveningmoose)*
### v2.1 (released 2021-05-05)
- Fixed support for SD007_TSOP_29LV017D with L017D70VC *(thanks marv17 and 90sFlav)*

View File

@ -3,6 +3,7 @@
# Author: Lesserkuma (github.com/lesserkuma)
import traceback
from serial import SerialException
from . import pyside as PySide2
class DataTransfer(PySide2.QtCore.QThread):
@ -25,6 +26,8 @@ class DataTransfer(PySide2.QtCore.QThread):
return not self.FINISHED
def run(self):
tb = ""
error = None
try:
if self.CONFIG == None:
pass
@ -34,7 +37,19 @@ class DataTransfer(PySide2.QtCore.QThread):
self.CONFIG['port'].TransferData(self.CONFIG, self.updateProgress)
self.FINISHED = True
except SerialException as e:
if "GetOverlappedResult failed" in e.args[0]:
self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The USB connection was lost during a transfer. Try different USB cables, reconnect the device, restart the software and try again.", "abortable":False})
self.FINISHED = True
return
tb = traceback.format_exc()
error = e
except Exception as e:
traceback.print_exc()
self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An unresolvable error has occured. See console output for more information. Reconnect the device, restart the software and try again.\n\n{:s}: {:s}".format(type(e).__name__, str(e)), "abortable":False})
tb = traceback.format_exc()
error = e
if error is not None:
print(tb)
self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An unresolvable error has occured. See console output for more information. Reconnect the device, restart the software and try again.\n\n{:s}: {:s}".format(type(error).__name__, str(error)), "abortable":False})
self.FINISHED = True

View File

@ -372,6 +372,7 @@ class FlashGBX_CLI():
self.CONN.INFO["last_action"] = 0
def FindDevices(self, port=None):
# pylint: disable=global-variable-not-assigned
global hw_devices
for hw_device in hw_devices:
dev = hw_device.GbxDevice()
@ -607,7 +608,7 @@ class FlashGBX_CLI():
Util.AGB_Global_CRC32 = 0
db_agb_entry = None
if os.path.exists("{0:s}/db_AGB.json".format(self.CONFIG_PATH)):
with open("{0:s}/db_AGB.json".format(self.CONFIG_PATH)) as f:
with open("{0:s}/db_AGB.json".format(self.CONFIG_PATH), encoding="UTF-8") as f:
db_agb = f.read()
db_agb = json.loads(db_agb)
if data["header_sha1"] in db_agb.keys():

View File

@ -200,7 +200,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.mnuConfig.addAction("Prefer full &chip erase over sector erase when both available", lambda: self.SETTINGS.setValue("PreferChipErase", str(self.mnuConfig.actions()[2].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
self.mnuConfig.addAction("&Verify data after writing", lambda: self.SETTINGS.setValue("VerifyWrittenData", str(self.mnuConfig.actions()[3].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
self.mnuConfig.addAction("&Limit voltage to 3.3V when detecting Game Boy flash cartridges", lambda: self.SETTINGS.setValue("AutoDetectLimitVoltage", str(self.mnuConfig.actions()[4].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
self.mnuConfig.addAction("Always &generate ROM dump reports", lambda: self.SETTINGS.setValue("GenerateDumpReports", str(self.mnuConfig.actions()[5].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
self.mnuConfig.addAction("Limit &baud rate to 1Mbps for GBxCart RW v1.4 devices", lambda: [ self.SETTINGS.setValue("LimitBaudRate", str(self.mnuConfig.actions()[5].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")), self.SetLimitBaudRate() ])
self.mnuConfig.addAction("Always &generate ROM dump reports", lambda: self.SETTINGS.setValue("GenerateDumpReports", str(self.mnuConfig.actions()[6].isChecked()).lower().replace("true", "enabled").replace("false", "disabled")))
self.mnuConfig.addSeparator()
self.mnuConfig.addAction("Re-&enable suppressed messages", self.ReEnableMessages)
self.mnuConfig.addSeparator()
@ -211,12 +212,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.mnuConfig.actions()[3].setCheckable(True)
self.mnuConfig.actions()[4].setCheckable(True)
self.mnuConfig.actions()[5].setCheckable(True)
self.mnuConfig.actions()[6].setCheckable(True)
self.mnuConfig.actions()[0].setChecked(self.SETTINGS.value("UpdateCheck") == "enabled")
self.mnuConfig.actions()[1].setChecked(self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled") == "enabled")
self.mnuConfig.actions()[2].setChecked(self.SETTINGS.value("PreferChipErase", default="disabled") == "enabled")
self.mnuConfig.actions()[3].setChecked(self.SETTINGS.value("VerifyWrittenData", default="enabled") == "enabled")
self.mnuConfig.actions()[4].setChecked(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled") == "enabled")
self.mnuConfig.actions()[5].setChecked(self.SETTINGS.value("GenerateDumpReports", default="disabled") == "enabled")
self.mnuConfig.actions()[5].setChecked(self.SETTINGS.value("LimitBaudRate", default="disabled") == "enabled")
self.mnuConfig.actions()[6].setChecked(self.SETTINGS.value("GenerateDumpReports", default="disabled") == "enabled")
self.btnConfig.setMenu(self.mnuConfig)
@ -264,7 +267,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def GuiCreateGroupBoxDMGCartInfo(self):
self.grpDMGCartridgeInfo = QtWidgets.QGroupBox("Game Boy Cartridge Information")
self.grpDMGCartridgeInfo.setMinimumWidth(352)
self.grpDMGCartridgeInfo.setMinimumWidth(364)
group_layout = QtWidgets.QVBoxLayout()
group_layout.setContentsMargins(-1, 5, -1, -1)
@ -361,7 +364,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
def GuiCreateGroupBoxAGBCartInfo(self):
self.grpAGBCartridgeInfo = QtWidgets.QGroupBox("Game Boy Advance Cartridge Information")
self.grpAGBCartridgeInfo.setMinimumWidth(352)
self.grpAGBCartridgeInfo.setMinimumWidth(364)
group_layout = QtWidgets.QVBoxLayout()
group_layout.setContentsMargins(-1, 5, -1, -1)
@ -459,6 +462,17 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.grpAGBCartridgeInfo.setLayout(group_layout)
return self.grpAGBCartridgeInfo
def SetLimitBaudRate(self):
if not self.CheckDeviceAlive(): return
mode = self.CONN.GetMode()
limit_baudrate = self.SETTINGS.value("LimitBaudRate")
if limit_baudrate == "enabled":
self.CONN.ChangeBaudRate(baudrate=1000000)
else:
self.CONN.ChangeBaudRate(baudrate=1700000)
self.DisconnectDevice()
self.FindDevices(connectToFirst=True, mode=mode)
def UpdateCheck(self):
update_check = self.SETTINGS.value("UpdateCheck")
if update_check is None:
@ -614,7 +628,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.CONN = None
if self.cmbDevice.count() == 0: self.lblDevice.setText("No connection.")
return False
elif isinstance(ret, list):
for i in range(0, len(ret)):
status = ret[i][0]
@ -637,6 +650,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
return False
if dev.IsConnected():
dev.SetWriteDelay(enable=str(self.SETTINGS.value("WriteDelay", default="disabled")).lower() == "enabled")
qt_app.processEvents()
self.CONN = dev
self.optDMG.setAutoExclusive(False)
@ -722,6 +736,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
messages = []
last_msg = ""
# pylint: disable=global-variable-not-assigned
global hw_devices
for hw_device in hw_devices:
dev = hw_device.GbxDevice()
@ -1178,6 +1194,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if not just_erase:
self.SETTINGS.setValue(setting_name, os.path.dirname(path))
if os.path.getsize(path) == 0:
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The selected ROM file is empty.", QtWidgets.QMessageBox.Ok)
return
if os.path.getsize(path) > 0x10000000: # reject too large files to avoid exploding RAM
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "ROM files bigger than 256 MB are not supported.", QtWidgets.QMessageBox.Ok)
return
@ -1234,7 +1253,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
mbc2 = Util.get_mbc_name(hdr["mapper_raw"])
compatible_mbc = [ "None", "MBC2", "MBC3", "MBC5", "MBC7", "GBD", "G-MMC1", "HuC-1", "HuC-3" ]
if mbc2 == "None":
mbc = 0x19 # MBC5
pass
elif mbc2 != "None" and not (mbc1 in compatible_mbc and mbc2 in compatible_mbc):
if "mbc" in carts[cart_type] and carts[cart_type]["mbc"] == "manual":
msg_text = "The ROM file you selected uses a different mapper type than your current selection. What mapper should be used when writing the ROM?\n\nSelected mapper type: {:s}\nROM mapper type: {:s}".format(mbc1, mbc2)
@ -1251,6 +1270,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
elif msgbox.clickedButton() == button_2:
mbc = hdr["mapper_raw"]
else:
if mbc1 == "None": mbc1 = "None/Unknown"
msg_text = "Warning: The ROM file you selected uses a different mapper type than your cartridge type. The ROM file may be incompatible with your cartridge.\n\nCartridge mapper type: {:s}\nROM mapper type: {:s}".format(mbc1, mbc2)
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
if answer == QtWidgets.QMessageBox.Cancel: return
@ -1416,7 +1436,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if not erase:
filesize = os.path.getsize(path)
if filesize > 0x200000: # reject too large files to avoid exploding RAM
if filesize == 0 or filesize > 0x200000: # reject too large files to avoid exploding RAM
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The size of this file is not supported.", QtWidgets.QMessageBox.Ok)
return
@ -1795,7 +1815,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
db_agb_entry = None
if os.path.exists("{0:s}/db_AGB.json".format(self.CONFIG_PATH)):
with open("{0:s}/db_AGB.json".format(self.CONFIG_PATH)) as f:
with open("{0:s}/db_AGB.json".format(self.CONFIG_PATH), encoding="UTF-8") as f:
db_agb = f.read()
db_agb = json.loads(db_agb)
if data["header_sha1"] in db_agb.keys():
@ -2413,5 +2433,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
qt_app.exec_() # PySide2
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
qt_app = QApplication(sys.argv)
qt_app.setApplicationName(APPNAME)

View File

@ -13,19 +13,24 @@ class Flashcart:
CART_READ_FNCPTR = None
CART_POWERCYCLE_FNCPTR = None
PROGRESS_FNCPTR = None
SET_WE_PIN_WR = None
SET_WE_PIN_AUDIO = None
DEFAULT_WE = None
SECTOR_COUNT = 0
SECTOR_POS = 0
SECTOR_MAP = None
CFI = None
LAST_SR = 0x00
def __init__(self, config=None, cart_write_fncptr=None, cart_write_fast_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, progress_fncptr=None):
def __init__(self, config=None, cart_write_fncptr=None, cart_write_fast_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, progress_fncptr=None, set_we_pin_wr=None, set_we_pin_audio=None):
if config is None: config = {}
self.CART_WRITE_FNCPTR = cart_write_fncptr
self.CART_WRITE_FAST_FNCPTR = cart_write_fast_fncptr
self.CART_READ_FNCPTR = cart_read_fncptr
self.CART_POWERCYCLE_FNCPTR = cart_powercycle_fncptr
self.PROGRESS_FNCPTR = progress_fncptr
self.SET_WE_PIN_WR = set_we_pin_wr
self.SET_WE_PIN_AUDIO = set_we_pin_audio
self.CONFIG = config
if "command_set" in config:
self.CONFIG["_command_set"] = config["command_set"]
@ -33,6 +38,8 @@ class Flashcart:
self.CONFIG["_command_set"] = "INTEL"
else:
self.CONFIG["_command_set"] = ""
if "write_pin" in config:
self.DEFAULT_WE = config["write_pin"]
def CartRead(self, address, length=0):
if length == 0:
@ -42,16 +49,16 @@ class Flashcart:
length = 1
return self.CART_READ_FNCPTR(address, length)
def CartWrite(self, commands, flashcart=True, sram=False):
if "command_set" in self.CONFIG and self.CONFIG["command_set"] in ("GBMEMORY", "DMG-MBC5-32M-FLASH"): flashcart = False
dprint(commands, flashcart, sram)
if flashcart and not sram:
def CartWrite(self, commands, fast_write=True, sram=False):
if "command_set" in self.CONFIG and self.CONFIG["command_set"] in ("GBMEMORY", "DMG-MBC5-32M-FLASH"): fast_write = False
dprint(commands, fast_write, sram)
if fast_write and not sram:
self.CART_WRITE_FAST_FNCPTR(commands, flashcart=True)
else:
for command in commands:
address = command[0]
value = command[1]
self.CART_WRITE_FNCPTR(address, value, flashcart=flashcart, sram=sram)
self.CART_WRITE_FNCPTR(address, value, flashcart=fast_write, sram=sram)
def GetCommandSetType(self):
return self.CONFIG["_command_set"].upper()
@ -158,7 +165,7 @@ class Flashcart:
dprint("Reading 0x{:X} bytes from cartridge at 0x{:X} = {:s}".format(command[1], command[0], str(temp)))
time.sleep(0.001)
if "unlock" in self.CONFIG["commands"]:
self.CartWrite(self.CONFIG["commands"]["unlock"])
self.CartWrite(self.CONFIG["commands"]["unlock"], fast_write=False)
time.sleep(0.001)
def Reset(self, full_reset=False, max_address=0x2000000):
@ -172,7 +179,7 @@ class Flashcart:
if j >= max_address: break
dprint("reset_every @ 0x{:X}".format(j))
for command in self.CONFIG["commands"]["reset"]:
self.CartWrite([[j, command[1]]])
self.CartWrite([[j + command[0], command[1]]])
time.sleep(0.01)
elif "reset" in self.CONFIG["commands"]:
self.CartWrite(self.CONFIG["commands"]["reset"])
@ -285,8 +292,23 @@ class Flashcart:
for i in range(0, len(self.CONFIG["commands"]["chip_erase"])):
addr = self.CONFIG["commands"]["chip_erase"][i][0]
data = self.CONFIG["commands"]["chip_erase"][i][1]
if len(self.CONFIG["commands"]["chip_erase"][i]) > 2:
we = self.CONFIG["commands"]["chip_erase"][i][2]
else:
we = None
if not addr == None:
if we == "WR":
self.SET_WE_PIN_WR()
elif we == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartWrite([[addr, data]])
if we is not None:
if self.DEFAULT_WE == "WR":
self.SET_WE_PIN_WR()
elif self.DEFAULT_WE == "AUDIO":
self.SET_WE_PIN_AUDIO()
time.sleep(0.1)
if self.CONFIG["commands"]["chip_erase_wait_for"][i][0] != None:
addr = self.CONFIG["commands"]["chip_erase_wait_for"][i][0]
@ -298,7 +320,18 @@ class Flashcart:
for j in range(0, len(self.CONFIG["commands"]["read_status_register"])):
#sr_addr = self.CONFIG["commands"]["read_status_register"][j][0]
sr_data = self.CONFIG["commands"]["read_status_register"][j][1]
if we == "WR":
self.SET_WE_PIN_WR()
elif we == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartWrite([[addr, sr_data]])
if we is not None:
if self.DEFAULT_WE == "WR":
self.SET_WE_PIN_WR()
elif self.DEFAULT_WE == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartRead(addr, 2) # dummy read (fixes some bootlegs)
wait_for = struct.unpack("<H", self.CartRead(addr, 2))[0]
self.LAST_SR = wait_for
@ -320,13 +353,28 @@ class Flashcart:
for i in range(0, len(self.CONFIG["commands"]["sector_erase"])):
addr = self.CONFIG["commands"]["sector_erase"][i][0]
data = self.CONFIG["commands"]["sector_erase"][i][1]
if len(self.CONFIG["commands"]["sector_erase"][i]) > 2:
we = self.CONFIG["commands"]["sector_erase"][i][2]
else:
we = None
if addr == "SA": addr = pos
if addr == "SA+1": addr = pos + 1
if addr == "SA+2": addr = pos + 2
if addr == "SA+0x4000": addr = pos + 0x4000
if addr == "SA+0x7000": addr = pos + 0x7000
if not addr == None:
if we == "WR":
self.SET_WE_PIN_WR()
elif we == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartWrite([[addr, data]])
if we is not None:
if self.DEFAULT_WE == "WR":
self.SET_WE_PIN_WR()
elif self.DEFAULT_WE == "AUDIO":
self.SET_WE_PIN_AUDIO()
if self.CONFIG["commands"]["sector_erase_wait_for"][i][0] != None:
addr = self.CONFIG["commands"]["sector_erase_wait_for"][i][0]
data = self.CONFIG["commands"]["sector_erase_wait_for"][i][1]
@ -342,7 +390,18 @@ class Flashcart:
for j in range(0, len(self.CONFIG["commands"]["read_status_register"])):
sr_addr = self.CONFIG["commands"]["read_status_register"][j][0]
sr_data = self.CONFIG["commands"]["read_status_register"][j][1]
if we == "WR":
self.SET_WE_PIN_WR()
elif we == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartWrite([[sr_addr, sr_data]])
if we is not None:
if self.DEFAULT_WE == "WR":
self.SET_WE_PIN_WR()
elif self.DEFAULT_WE == "AUDIO":
self.SET_WE_PIN_AUDIO()
self.CartRead(addr, 2) # dummy read (fixes some bootlegs)
temp = self.CartRead(addr, 2)
if len(temp) != 2:

View File

@ -517,7 +517,7 @@ class DMG_MBC6(DMG_MBC):
sr = self.CartRead(0x4000)
dprint("Status Register Check: 0x{:X} == 0x80? {:s}".format(sr, str(sr == 0x80)))
if sr == 0x80: break
time.sleep(0.0001)
time.sleep(0.01)
def GetFlashID(self):
self.EnableFlash(enable=True)

View File

@ -7,7 +7,7 @@ from enum import Enum
# Common constants
APPNAME = "FlashGBX"
VERSION_PEP440 = "3.20"
VERSION_PEP440 = "3.21"
VERSION = "v{:s}".format(VERSION_PEP440)
DEBUG = False
DEBUG_LOG = []
@ -21,7 +21,7 @@ AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000 ]
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
DMG_Mapper_Types = { "None":[ 0x00 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x06 ], "MBC3":[ 0x10, 0x13 ], "MBC5":[ 0x19, 0x1B, 0x1C, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "256M Multi Cart":[ 0x201 ], "Wisdom Tree":[ 0x202 ], "Xploder GB":[ 0x203 ], "Sachen":[ 0x204 ], "Datel Orbit V2":[ 0x205 ] }
DMG_Mapper_Types = { "None":[ 0x00 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x06 ], "MBC3":[ 0x10, 0x13 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "256M Multi Cart":[ 0x201 ], "Wisdom Tree":[ 0x202 ], "Xploder GB":[ 0x203 ], "Sachen":[ 0x204 ], "Datel Orbit V2":[ 0x205 ] }
DMG_Header_ROM_Sizes = [ "32 KB", "64 KB", "128 KB", "256 KB", "512 KB", "1 MB", "2 MB", "4 MB", "8 MB", "16 MB", "32 MB" ]
DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A ]
DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]

View File

@ -20222,11 +20222,32 @@
"st": 3,
"gc": "RZWJ"
},
"1b58262c2bea83608faeb85db1ef1d9423b09b1b": {
"rc": 3149310304,
"rs": 2097152,
"ss": 512,
"st": 1,
"gc": "TCHK"
},
"569244c74f0f1c6d9bc1efefb6a2a1d0a5bdd77b": {
"rc": 3494379487,
"rs": 8388608,
"ss": 0,
"st": 0,
"ss": 512,
"st": 1,
"gc": "TCHK"
},
"5dd63ac864ec954cb3ed58a51b34bec1eaa17cda": {
"rc": 778659949,
"rs": 2097152,
"ss": 512,
"st": 1,
"gc": "TCHK"
},
"72ba2ff35d246d36482381588b80c8b2cddae76f": {
"rc": 2528463141,
"rs": 1326620,
"ss": 512,
"st": 1,
"gc": "TCHK"
},
"3fc931b3150d91bca7b4d525fc2894814d51a08c": {

View File

@ -0,0 +1,56 @@
{
"type":"AGB",
"names":[
"Action Replay Ultimate Codes (SST39VF800A)"
],
"flash_ids":[
[ 0xBF, 0x00, 0x81, 0x27 ]
],
"voltage":3.3,
"flash_size":0x100000,
"chip_erase_timeout":10,
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAAA, 0xAA ],
[ 0x5555, 0x55 ],
[ 0xAAAA, 0x90 ]
],
"read_cfi":[
[ 0xAAAA, 0xAA ],
[ 0x5555, 0x55 ],
[ 0xAAAA, 0x98 ]
],
"chip_erase":[
[ 0xAAAA, 0xAA ],
[ 0x5555, 0x55 ],
[ 0xAAAA, 0x80 ],
[ 0xAAAA, 0xAA ],
[ 0x5555, 0x55 ],
[ 0xAAAA, 0x10 ]
],
"chip_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ 0, 0xFF, 0xFF ]
],
"single_write":[
[ 0xAAAA, 0xAA ],
[ 0x5555, 0x55 ],
[ 0xAAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -14,15 +14,7 @@
"commands":{
"unlock":[
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x1234, 5 ],
[ 0x000ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x002468A, 0x5678, 5 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x0ECA800, 0x5678, 1 ],
[ 0x00268A0, 0x1234, 1 ],
[ 0x002468A, 0xABCD, 5 ],
[ 0x1C2468A, 0xA55A, 1 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1E2468A, 0x9413, 1 ]
],

View File

@ -14,15 +14,7 @@
"commands":{
"unlock":[
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x1234, 5 ],
[ 0x000ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x002468A, 0x5678, 5 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x0ECA800, 0x5678, 1 ],
[ 0x00268A0, 0x1234, 1 ],
[ 0x002468A, 0xABCD, 5 ],
[ 0x1C2468A, 0xA55A, 1 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1E2468A, 0x9413, 1 ]
],

View File

@ -0,0 +1,73 @@
{
"type":"AGB",
"names":[
"Flash2Advance Ultra 256M with 8× 3204C3B100"
],
"flash_ids":[
[ 0x89, 0x00, 0x89, 0x00, 0xC5, 0x88, 0xC5, 0x88 ]
],
"voltage":3.3,
"flash_size":0x2000000,
"sector_size":[
[0x04000, 8],
[0x20000, 63],
[0x04000, 8],
[0x20000, 63],
[0x04000, 8],
[0x20000, 63],
[0x04000, 8],
[0x20000, 63]
],
"reset_every":0x800000,
"command_set":"INTEL",
"commands":{
"unlock":[
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1C2468A, 0xA55A, 1 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1E2468A, 0x9413, 1 ]
],
"reset":[
[ 0, 0xFF ],
[ 2, 0xFF ]
],
"read_identifier":[
[ 0, 0x90 ],
[ 2, 0x90 ]
],
"sector_erase":[
[ "SA", 0x60 ],
[ "SA", 0xD0 ],
[ "SA", 0x20 ],
[ "SA", 0xD0 ],
[ "SA+2", 0x60 ],
[ "SA+2", 0xD0 ],
[ "SA+2", 0x20 ],
[ "SA+2", 0xD0 ],
[ null, null ],
[ null, null ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ "SA+2", 0x80, 0xFFFF ]
],
"single_write":[
[ 0, 0x70 ],
[ 0, 0x10 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ 0, 0x80, 0x80 ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -16,15 +16,7 @@
"commands":{
"unlock":[
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x1234, 5 ],
[ 0x000ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x002468A, 0x5678, 5 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x0ECA800, 0x5678, 1 ],
[ 0x00268A0, 0x1234, 1 ],
[ 0x002468A, 0xABCD, 5 ],
[ 0x1C2468A, 0xA55A, 1 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1E2468A, 0x9413, 1 ]
],

View File

@ -15,15 +15,7 @@
"commands":{
"unlock":[
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x1234, 5 ],
[ 0x000ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x002468A, 0x5678, 5 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x002468A, 0x5354, 1 ],
[ 0x0ECA800, 0x5678, 1 ],
[ 0x00268A0, 0x1234, 1 ],
[ 0x002468A, 0xABCD, 5 ],
[ 0x1C2468A, 0xA55A, 1 ],
[ 0x130ECA8, 0x5354, 1 ],
[ 0x1E2468A, 0x9413, 1 ]
],

View File

@ -4,13 +4,15 @@
"4455_4400_4000_4350_36L0R_V3 with M36L0R7050T",
"4050_4400_4000_4350_36L0R_V5 with M36L0R7050T",
"4050_4400_4000_4350_36L0R_6108 with M36L0R7050B",
"4000L0ZBQ0 DRV with 3000L0YBQ0"
"4000L0ZBQ0 DRV with 3000L0YBQ0",
"F864-3 with M36L0R7050B"
],
"flash_ids":[
[ 0x20, 0x00, 0xC4, 0x88 ],
[ 0x20, 0x00, 0xC4, 0x88 ],
[ 0x20, 0x00, 0xC6, 0x88 ],
[ 0x8A, 0x00, 0x0F, 0x88 ]
[ 0x8A, 0x00, 0x0F, 0x88 ],
[ 0x20, 0x00, 0xC5, 0x88 ]
],
"voltage":3.3,
"flash_size":0x1000000,

View File

@ -0,0 +1,75 @@
{
"type":"DMG",
"names":[
"BennVenn MBC3000 RTC cart"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xCB, 0xCB ]
],
"voltage":5,
"flash_size":0x800000,
"start_addr":0,
"first_bank":1,
"write_pin":"WR",
"sector_size_from_cfi":true,
"chip_erase_timeout":200,
"mbc":"manual",
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x90 ]
],
"read_cfi":[
[ 0xAAA, 0x98 ]
],
"chip_erase":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 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, 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 ],
[ 0xAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -5,7 +5,7 @@
"Catskull 32k Gameboy Flash Cart"
],
"flash_ids":[
[ 0xBF, 0xB5, 0x01, 0xFF ]
[ 0xBF, 0xB5 ]
],
"voltage":5,
"flash_size":0x8000,

View File

@ -6,7 +6,7 @@
],
"flash_ids":[
[ 0xBF, 0xFF, 0x25, 0xFF ],
[ 0xBF, 0xB7, 0xFF, 0xFF ]
[ 0xBF, 0xB7 ]
],
"voltage":5,
"flash_size":0x8000,

View File

@ -4,7 +4,7 @@
"Ferrante Crafts cart 64 KB"
],
"flash_ids":[
[ 0xBF, 0xB5, 0x01, 0xFF ]
[ 0xBF, 0xB5 ]
],
"voltage":5,
"flash_size":0x10000,

View File

@ -5,14 +5,16 @@
"SD007_TSOP_48BALL_V10 with M29W320DT",
"SD007_TSOP_48BALL_V10 with GL032M10BFIR3",
"SD007_TSOP_48BALL_V9 with 29LV320CBTC-70G",
"SD007_TSOP_48BALL_V10 with 29DL32TF-70"
"SD007_TSOP_48BALL_V10 with 29DL32TF-70",
"SD007_48BALL_SOP28 with M29W320ET"
],
"flash_ids":[
[ 0x02, 0x02, 0x7D, 0x7D ],
[ 0x20, 0x20, 0xC9, 0xC9 ],
[ 0x02, 0x02, 0x7D, 0x7D ],
[ 0x02, 0x02, 0x7D, 0x7D ],
[ 0x04, 0x04, 0x7D, 0x7D ]
[ 0x04, 0x04, 0x7D, 0x7D ],
[ 0x20, 0x20, 0x55, 0x55 ]
],
"voltage":3.3,
"voltage_variants":true,

View File

@ -1,18 +1,20 @@
{
"type":"DMG",
"names":[
"DIY cart with MBC3 and MX29LV640 @ AUDIO"
"DIY cart with MX29LV640 @ AUDIO"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xCB, 0xCB ]
],
"voltage":3.3,
"voltage_variants":true,
"flash_size":0x800000,
"start_addr":0,
"first_bank":1,
"mbc":0x13,
"write_pin":"AUDIO",
"sector_size_from_cfi":true,
"chip_erase_timeout":200,
"mbc":"manual",
"command_set":"AMD",
"commands":{
"reset":[
@ -42,6 +44,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 ],

View File

@ -0,0 +1,76 @@
{
"type":"DMG",
"names":[
"DIY cart with MX29LV640 @ WR"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xCB, 0xCB ]
],
"voltage":3.3,
"voltage_variants":true,
"flash_size":0x800000,
"start_addr":0,
"first_bank":1,
"write_pin":"WR",
"sector_size_from_cfi":true,
"chip_erase_timeout":200,
"mbc":"manual",
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x90 ]
],
"read_cfi":[
[ 0xAAA, 0x98 ]
],
"chip_erase":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 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, 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 ],
[ 0xAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -5,7 +5,7 @@
"Ferrante Crafts cart 512 KB"
],
"flash_ids":[
[ 0xBF, 0xB7, 0x01, 0xFF ]
[ 0xBF, 0xB7 ]
],
"voltage":5,
"flash_size":0x80000,

View File

@ -0,0 +1,74 @@
{
"type":"DMG",
"names":[
"Squareboi 4 MB (2× 2 MB)"
],
"voltage":5,
"flash_size":0x400000,
"start_addr":0x4000,
"first_bank":0,
"write_pin":"AUDIO",
"chip_erase_timeout":120,
"mbc":0x1B,
"command_set":"AMD",
"commands":{
"reset":[
[ 0x4000, 0xF0 ]
],
"read_identifier":[
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0x90 ]
],
"read_cfi":[
[ 0x4AAA, 0x98 ]
],
"chip_erase":[
[ 0x2100, 0x00, "WR" ],
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0x80 ],
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0x10 ],
[ 0x2100, 0x80, "WR" ],
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0x80 ],
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0x10 ],
[ 0x2100, 0x00, "WR" ]
],
"chip_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ 0x4000, 0xFF, 0xFF ],
[ 0x4000, 0xFF, 0xFF ]
],
"single_write":[
[ 0x4AAA, 0xAA ],
[ 0x4555, 0x55 ],
[ 0x4AAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -1,10 +1,14 @@
{
"type":"DMG",
"names":[
"SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85"
"SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85",
"SD007_T40_64BALL_S71_TV_TS28 with TC58FVB016FT-85",
"SD007_T40_64BALL_SOJ28 with 29LV016T"
],
"flash_ids":[
[ 0x98, 0x45, 0x00, 0x2A ]
[ 0x98, 0x45, 0x00, 0x2A ],
[ 0x98, 0x45, 0x00, 0x2A ],
[ 0x04, 0xC7, 0x00, 0x00 ]
],
"voltage":5,
"flash_size":0x200000,

View File

@ -309,7 +309,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.reject()
return True
elif ret == 2:
text = "The firmware update is has failed. Please try again."
text = "The firmware update has failed. Please try again."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.optDevicePCBVer14.setEnabled(True)

View File

@ -17,7 +17,7 @@ class GbxDevice:
DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 1
DEVICE_MAX_FW = 8
DEVICE_LATEST_FW_TS = { 4:1619427330, 5:1669112674, 6:1669112674 }
DEVICE_LATEST_FW_TS = { 4:1619427330, 5:1673788742, 6:1673788742 }
DEVICE_CMD = {
"NULL":0x30,
@ -113,7 +113,9 @@ class GbxDevice:
FAST_READ = False
SKIPPING = False
BAUDRATE = 1000000
MAX_BUFFER_LEN = 512
MAX_BUFFER_LEN = 0x2000
DEVICE_TIMEOUT = 1
WRITE_DELAY = False
def __init__(self):
pass
@ -140,18 +142,21 @@ class GbxDevice:
if not self.LoadFirmwareVersion() and max_baud >= 1700000:
dev.close()
self.BAUDRATE = 1700000
self.MAX_BUFFER_LEN = 0x2000
dev = serial.Serial(ports[i], self.BAUDRATE, timeout=0.1)
self.DEVICE = dev
if not self.LoadFirmwareVersion():
dev.close()
self.DEVICE = None
self.BAUDRATE = 1000000
self.MAX_BUFFER_LEN = 0x100
continue
elif max_baud >= 1700000 and self.FW["pcb_ver"] in (5, 6, 101) and self.BAUDRATE < 1700000:
# Switch to higher baud rate
self._write(self.DEVICE_CMD["OFW_USART_1_7M_SPEED"])
self.BAUDRATE = 1700000
dev.close()
#self._write(self.DEVICE_CMD["OFW_USART_1_7M_SPEED"])
#self.BAUDRATE = 1700000
#dev.close()
self.ChangeBaudRate(baudrate=1700000)
dev = serial.Serial(ports[i], self.BAUDRATE, timeout=0.1)
self.DEVICE = dev
@ -186,7 +191,7 @@ class GbxDevice:
conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"])
self.PORT = ports[i]
self.DEVICE.timeout = 1
self.DEVICE.timeout = self.DEVICE_TIMEOUT
# Load Flash Cartridge Handlers
self.UpdateFlashCarts(flashcarts)
@ -242,6 +247,19 @@ class GbxDevice:
def GetBaudRate(self):
return self.BAUDRATE
def ChangeBaudRate(self, baudrate):
if not self.IsConnected(): return
if baudrate == 1700000:
self._write(self.DEVICE_CMD["OFW_USART_1_7M_SPEED"])
self.Close()
self.BAUDRATE = baudrate
self.MAX_BUFFER_LEN = 0x2000
elif baudrate == 1000000:
self._write(self.DEVICE_CMD["OFW_USART_1_0M_SPEED"])
self.Close()
self.BAUDRATE = baudrate
self.MAX_BUFFER_LEN = 0x100
def CanSetVoltageManually(self):
return False
@ -381,6 +399,10 @@ class GbxDevice:
def GetFWBuildDate(self):
return self.FW["fw_dt"]
def SetWriteDelay(self, enable=True):
dprint("Setting Write Delay to", enable)
self.WRITE_DELAY = enable
def wait_for_ack(self, values=None):
if values is None: values = [0x01, 0x03]
buffer = self._read(1)
@ -396,10 +418,10 @@ class GbxDevice:
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A timeout error has occured at {:s}() in line {:d}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(stack.name, stack.lineno)})
else:
dprint("Communication error ({:s}(), line {:d})".format(stack.name, stack.lineno))
#print(self.CANCEL_ARGS)
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A communication error has occured at {:s}() in line {:d}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(stack.name, stack.lineno)})
self.ERROR = True
self.CANCEL = True
self.SetWriteDelay(enable=True)
return False
return buffer
@ -417,7 +439,7 @@ class GbxDevice:
# On MacOS its possible not all bytes are transmitted successfully,
# even though were using flush() which is the tcdrain function.
# Still looking for a better solution than delaying here.
if platform.system() == "Darwin":
if platform.system() == "Darwin" or self.WRITE_DELAY is True:
time.sleep(0.00125)
if wait: return self.wait_for_ack()
@ -541,6 +563,7 @@ class GbxDevice:
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A critical communication error occured during a write. Please avoid passive USB hubs, try different USB ports/cables and re-connect the device."})
self.CANCEL = True
self.ERROR = True
return False
def _clk_toggle(self, num):
if self.FW["pcb_ver"] not in (5, 6, 101): return False
@ -549,6 +572,13 @@ class GbxDevice:
self._write(self.DEVICE_CMD["CLK_LOW"])
return True
def _set_we_pin_wr(self):
if self.MODE == "DMG":
self._set_fw_variable("FLASH_WE_PIN", 0x01) # FLASH_WE_PIN_WR
def _set_we_pin_audio(self):
if self.MODE == "DMG":
self._set_fw_variable("FLASH_WE_PIN", 0x02) # FLASH_WE_PIN_AUDIO
def CartPowerCycle(self, delay=0.1):
if self.CanPowerCycleCart():
dprint("Power cycling cartridge with a delay of {:.1f} seconds".format(delay))
@ -1457,7 +1487,7 @@ class GbxDevice:
elif we == "WR+RESET":
self._set_fw_variable("FLASH_WE_PIN", 0x03) # FLASH_WE_PIN_WR_RESET
flashcart = Flashcart(config=flashcart_meta, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle)
flashcart = Flashcart(config=flashcart_meta, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, set_we_pin_wr=self._set_we_pin_wr, set_we_pin_audio=self._set_we_pin_audio)
flashcart.Reset(full_reset=False)
if flashcart.Unlock() is False: return False
if "flash_ids" in flashcart_meta and len(flashcart_meta["flash_ids"]) > 0:
@ -1501,7 +1531,24 @@ class GbxDevice:
flash_type_id = flash_types[i]
size_undetected = False
break
else:
if self.MODE == "AGB":
# Check where the ROM data repeats (for unlicensed carts)
header = self.ReadROM(0, 0x180)
size_check = header[0xA0:0xA0+16]
currAddr = 0x10000
while currAddr < 0x2000000:
buffer = self.ReadROM(currAddr + 0xA0, 64)[:16]
if buffer == size_check: break
currAddr *= 2
rom_size = currAddr
for i in range(0, len(flash_types)):
if "flash_size" in supp_flash_types[1][flash_types[i]] and rom_size == supp_flash_types[1][flash_types[i]]["flash_size"]:
flash_type_id = flash_types[i]
size_undetected = False
break
else:
(flash_id, cfi_s, cfi) = self.CheckFlashChip(limitVoltage=limitVoltage)
@ -1667,7 +1714,7 @@ class GbxDevice:
self._cart_write(method['reset'][i][0], method['reset'][i][1], flashcart=True)
if cart_type is not None: # reset cartridge if method is known
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=None)
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=None, set_we_pin_wr=self._set_we_pin_wr, set_we_pin_audio=self._set_we_pin_audio)
flashcart.Reset(full_reset=False)
if "method" in cfi:
@ -1737,7 +1784,7 @@ class GbxDevice:
time.sleep(0.2)
if cart_type is not None: # reset cartridge if method is known
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=None)
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=None, set_we_pin_wr=self._set_we_pin_wr, set_we_pin_audio=self._set_we_pin_audio)
flashcart.Reset(full_reset=True)
flash_id = ""
@ -1792,7 +1839,7 @@ class GbxDevice:
file = open(args["path"], "wb")
self.FAST_READ = True
flashcart = False
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
cart_type = copy.deepcopy(supported_carts[args["cart_type"]])
@ -1802,7 +1849,7 @@ class GbxDevice:
if i == args["cart_type"]:
try:
cart_type["_index"] = cart_type["names"].index(list(self.SUPPORTED_CARTS[self.MODE].keys())[i])
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=self.SetProgress)
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=self.SetProgress, set_we_pin_wr=self._set_we_pin_wr, set_we_pin_audio=self._set_we_pin_audio)
except:
pass
@ -1890,11 +1937,12 @@ class GbxDevice:
buffer = bytearray(size)
max_length = self.MAX_BUFFER_LEN
dprint("Max buffer size: 0x{:X}".format(max_length))
if self.FAST_READ is True:
if (self.MODE == "AGB" and "command_set" in cart_type and cart_type["command_set"] == "3DMEMORY"):
max_length = 0x1000
max_length = min(max_length, 0x1000)
else:
max_length = 0x2000
max_length = min(max_length, 0x2000)
self.INFO["dump_info"]["transfer_size"] = max_length
pos_total = 0
start_address = 0
@ -1968,21 +2016,31 @@ class GbxDevice:
skip_init = True
if len(temp) != buffer_len:
if "verify_write" in args:
self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total})
else:
self.SetProgress({"action":"UPDATE_POS", "pos":pos_total})
if (max_length >> 1) < 64:
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}!".format(len(temp), buffer_len, pos_total))
max_length = 64
elif lives > 18:
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}!".format(len(temp), buffer_len, pos_total))
else:
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}! Decreasing maximum transfer buffer length to 0x{:X}.".format(len(temp), buffer_len, pos_total, max_length >> 1))
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}! Decreasing maximum transfer buffer size to 0x{:X}.".format(len(temp), buffer_len, pos_total, max_length >> 1))
max_length >>= 1
self.MAX_BUFFER_LEN = max_length
self.INFO["dump_info"]["transfer_size"] = max_length
skip_init = False
self.DEVICE.reset_input_buffer()
self.DEVICE.reset_output_buffer()
lives -= 1
if lives == 0:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while reading from the cartridge. When connecting the device, avoid passive USB hubs and try different USB ports/cables.", "abortable":False})
return False
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"An error occured while reading from the cartridge. When connecting the device, avoid passive USB hubs and try different USB ports/cables."})
self.CANCEL = True
self.ERROR = True
if "verify_write" in args: return False
continue
elif lives < 20:
lives = 20
@ -1992,8 +2050,12 @@ class GbxDevice:
pos_total += len(temp)
if "verify_write" in args:
if pos_total >= len(args["verify_write"]): break
#if pos_total >= len(args["verify_write"]): break
check = args["verify_write"][pos_total-len(temp):pos_total]
if Util.DEBUG:
dprint("Writing 0x{:X} bytes to debug_verify.bin".format(len(temp)))
with open("debug_verify.bin", "ab") as f: f.write(temp)
if temp[:len(check)] != check:
for i in range(0, pos_total):
if (i < len(args["verify_write"]) - 1) and (i < pos_total - 1) and args["verify_write"][i] != buffer[i]:
@ -2001,11 +2063,9 @@ class GbxDevice:
dprint("Skipping RTC area at 0x{:X}".format(i))
else:
dprint("Mismatch during verification at 0x{:X}".format(i))
if Util.DEBUG:
with open("debug_verify.bin", "wb") as f: f.write(temp)
return i
else:
dprint("Verification successful between 0x{:X} and 0x{:X}".format(pos_total-len(temp), pos_total-1))
dprint("Verification successful between 0x{:X} and 0x{:X}".format(pos_total-len(temp), pos_total))
self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total})
else:
self.SetProgress({"action":"UPDATE_POS", "pos":pos_total})
@ -2644,7 +2704,7 @@ class GbxDevice:
data_map_import = bytearray(data_map_import)
dprint("Hidden sector data loaded")
else:
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=self.SetProgress)
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle, progress_fncptr=self.SetProgress, set_we_pin_wr=self._set_we_pin_wr, set_we_pin_audio=self._set_we_pin_audio)
rumble = "rumble" in flashcart.CONFIG and flashcart.CONFIG["rumble"] is True
@ -2882,7 +2942,7 @@ class GbxDevice:
sector_offsets = flashcart.GetSectorOffsets(rom_size=len(data_import), rom_bank_size=rom_bank_size)
if len(sector_offsets) > 0:
flash_capacity = sector_offsets[-1][0] + sector_offsets[-1][1]
if flash_capacity < len(data_import):
if flash_capacity < len(data_import) and not (flashcart.SupportsChipErase() and args["prefer_chip_erase"]):
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"There are not enough flash sectors available to write this ROM. The maximum capacity is {:s}.".format(Util.formatFileSize(flash_capacity, asInt=False)), "abortable":False})
return False
@ -3131,10 +3191,12 @@ class GbxDevice:
time.sleep(delay)
self.CartPowerOn()
if self.MODE == "DMG" and _mbc.HasFlashBanks(): _mbc.SelectBankFlash(bank)
else:
time.sleep(delay)
time.sleep(delay)
self.DEVICE.reset_input_buffer()
self.DEVICE.reset_output_buffer()
self._cart_write(pos, 0xF0)
self._cart_write(pos, 0xFF)
flashcart.Unlock()
continue
self.CANCEL = True
@ -3178,6 +3240,8 @@ class GbxDevice:
verified = False
if "verify_write" in args and args["verify_write"] is True:
self.SetProgress({"action":"INITIALIZE", "method":"ROM_WRITE_VERIFY", "size":len(data_import)})
if Util.DEBUG:
with open("debug_verify.bin", "wb") as f: pass
for sector in write_sectors:
verified = True
if sector[0] >= len(data_import): break
@ -3192,8 +3256,8 @@ class GbxDevice:
end_address = buffer_pos
verified_size = self._BackupROM(verify_args)
if verified_size is not None: dprint("args[\"verify_len\"]=0x{:X}, verified_size=0x{:X}".format(verify_args["verify_len"], verified_size))
if self.CANCEL:
if isinstance(verified_size, int): dprint("args[\"verify_len\"]=0x{:X}, verified_size=0x{:X}".format(verify_args["verify_len"], verified_size))
if self.CANCEL or self.ERROR:
cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {}

View File

@ -5,7 +5,7 @@
# This code is used for official firmware of GBxCart v1.3 only and this code is pure chaos, sorry
# Refer to hw_GBxCartRW.py for the much cleaner rewrite (used for GBxCart RW v1.4 and firmware L1 on v1.3)
import time, math, struct, traceback, zlib, copy, hashlib, os, datetime
import time, math, struct, traceback, zlib, copy, hashlib, os, datetime, platform
import serial, serial.tools.list_ports
from serial import SerialException
from .RomFileDMG import RomFileDMG
@ -139,6 +139,7 @@ class GbxDevice:
NO_PROG_UPDATE = False
FAST_READ = False
BAUDRATE = 1000000
WRITE_DELAY = False
def __init__(self):
pass
@ -281,6 +282,9 @@ class GbxDevice:
def GetBaudRate(self):
return self.BAUDRATE
def ChangeBaudRate(self, _):
return
def GetPCBVersion(self):
_, pcb = self.FW
if pcb in self.PCB_VERSIONS:
@ -407,6 +411,9 @@ class GbxDevice:
if args["action"] == "FINISHED":
self.SIGNAL = None
def SetWriteDelay(self, enable=True):
self.WRITE_DELAY = enable
def wait_for_ack(self):
buffer = self.read(1)
if buffer == False:
@ -473,8 +480,9 @@ class GbxDevice:
data = bytearray(data, 'ascii')
self.DEVICE.write(data)
self.DEVICE.flush()
if wait_for_ack:
return self.wait_for_ack()
if platform.system() == "Darwin" or self.WRITE_DELAY is True:
time.sleep(0.00125)
if wait_for_ack: return self.wait_for_ack()
def DetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=False):
self.SIGNAL = None
@ -2498,7 +2506,6 @@ class GbxDevice:
pos += 64
else: # super slow -- for testing purposes only!
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not supported with your devices firmware version. You can try a newer firmware version which is available through the firmware updater in the “Tools” menu.\n\n{:s}".format(str(flashcart_meta["commands"]["buffer_write"])), "abortable":False})
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge is currently not supported by {:s} using the current firmware version of the {:s} device. Please try a differnt firmware version or newer hardware revision.".format(APPNAME, self.GetFullName()), "abortable":False})
return False
'''

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,7 @@
for [Windows](https://github.com/lesserkuma/FlashGBX/releases), [Linux](https://github.com/lesserkuma/FlashGBX#run-using-python-linux-macos-windows), [macOS](https://github.com/lesserkuma/FlashGBX#run-using-python-linux-macos-windows)
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/01.png" alt="FlashGBX on Windows" width="400"><br><img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/02.png" alt="GB Camera Album Viewer" width="400">
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/01.png" alt="FlashGBX on Windows 11" width="500"><br><img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/02.png" alt="GB Camera Album Viewer" width="500">
## Introduction
@ -53,6 +53,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
*FlashGBX should work on pretty much any operating system that supports Qt-GUI applications built using [Python](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/) or [PySide6](https://pypi.org/project/PySide6/), [pyserial](https://pypi.org/project/pyserial/), [Pillow](https://pypi.org/project/Pillow/), [setuptools](https://pypi.org/project/setuptools/), [requests](https://pypi.org/project/requests/) and [python-dateutil](https://pypi.org/project/python-dateutil/) packages. To run FlashGBX in portable mode without installing, you can also download the source code archive and call `python3 run.py` after installing the prerequisites yourself.*
*Note: On Linux systems, the `brltty` module may render GBxCart RW devices non-accessible. See the troubleshooting section for details.*
#### Upgrading from an older version
1. Open a Terminal or Command Prompt window
@ -93,6 +95,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- Game Boy
- 29LV Series Flash BOY with 29LV160DB
- BennVenn MBC3000 RTC cart
- BLAZE Xploder GB
- BUNG Doctor GB Card 64M
- Catskull 32k Gameboy Flash Cart
@ -124,15 +127,18 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- insideGadgets 4 MB, 32 KB FRAM, MBC3+RTC
- insideGadgets 4 MB (2× 2 MB), 32 KB FRAM, MBC5
- Mr Flash 64M
- Squareboi 4 MB (2× 2 MB)
- Game Boy Advance
- Action Replay Ultimate Codes (with SST39VF800A)
- Development AGB Cartridge 64M Flash S, E201843
- Development AGB Cartridge 128M Flash S, E201850
- Development AGB Cartridge 256M Flash S, E201868
- Flash2Advance 128M (with 2× 28F640J3A120)
- Flash2Advance 256M (with 2× 28F128J3A150)
- Flash2Advance Ultra 64M (with 2× 28F320C3B)
- Flash2Advance Ultra 256M (with 8× 3204C3B100)
- Flash Advance Card 64M (with 28F640J3A120)
- insideGadgets 16 MB, 64K EEPROM with Solar Sensor and RTC options
- insideGadgets 32 MB, 1M FLASH with RTC option
@ -164,6 +170,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- SD007_48BALL_64M_V6 with 36VF3204
- SD007_48BALL_64M_V6 with 29DL163BD-90
- SD007_48BALL_64M_V8 with M29W160ET
- SD007_48BALL_SOP28 with M29W320ET
- SD007_BV5 with 29LV160TE-70PFTN
- SD007_BV5_DRV with M29W320DT
- SD007_BV5_DRV with S29GL032M90TFIR4
@ -173,6 +180,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- SD007_BV5_V3 with HY29LV160BT-70
- SD007_BV5_V3 with AM29LV160MB
- SD007_K8D3216_32M with MX29LV160CT
- SD007_T40_64BALL_S71_TV_TS28 with TC58FVB016FT-85
- SD007_T40_64BALL_SOJ28 with 29LV016T
- SD007_T40_64BALL_TSOP28 with 29LV016T
- SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85¹
- SD007_TSOP_29LV017D with L017D70VC
@ -245,6 +254,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- BX2006_TSOPBGA_0106 with K8D6316UTM-PI07
- BX2006_TSOPBGA_6108 with M29W640
- DV15 with MSP55LV100G
- F864-3 with M36L0R7050B
- GA-07 with unlabeled flash chip
- GE28F128W30 with 128W30B0
- M5M29G130AN (no PCB text)
@ -272,6 +282,8 @@ Many different reproduction cartridges share their flash chip command set, so ev
* On some Linux systems like Fedora, you may need to install the `python3-pillow-qt` package manually in order for the GUI mode to work.
* On some Linux systems you may see the message “No devices found.” even though youre using a USB cable capable of data transfers. This may be caused by a module called `brltty` (a driver for refreshable braille displays) that is erroneously interfering and taking over control of any connected USB device that uses the CH340/341 chipset. The solution would be to uninstall or blacklist the `brltty` driver and then reboot the system.
* If youre using macOS version 10.13 or older, there may be no driver for the *insideGadgets GBxCart RW* device installed on your system. You can either upgrade your macOS version to 10.14+ or manually install a driver which is available [here](https://github.com/adrianmihalko/ch340g-ch34g-ch34x-mac-os-x-driver).
## Miscellaneous
@ -285,6 +297,7 @@ The author would like to thank the following very kind people for their help and
- 2358 (bug reports)
- 90sFlav (flash chip info)
- AcoVanConis (bug reports, flash chip info)
- ALXCO-Hardware (feature suggestions)
- AdmirtheSableye (bug reports)
- AlexiG (GBxCart RW hardware, bug reports, flash chip info)
- AndehX (app icon, flash chip info)
@ -297,11 +310,12 @@ The author would like to thank the following very kind people for their help and
- Davidish (flash chip info)
- DevDavisNunez (bug reports)
- Diddy_Kong (sample cartridge contribution)
- djedditt (testing)
- djedditt (testing, sample cartridge contribution)
- Dr-InSide (bug reports)
- dyf2007 (flash chip info)
- easthighNerd (feature suggestions)
- EchelonPrime (flash chip info)
- edo999 (flash chip info)
- EmperorOfTigers (bug reports, flash chip info)
- endrift (research, mGBA emulator)
- ethanstrax (flash chip info)
@ -310,6 +324,7 @@ The author would like to thank the following very kind people for their help and
- FerrantePescara (flash chip info)
- frarees (bug reports)
- Frost Clock (flash chip info)
- gboh (bug reports)
- gekkio (bug reports, technical information)
- Godan (flash chip info)
- Grender (testing)
@ -335,7 +350,7 @@ The author would like to thank the following very kind people for their help and
- manuelcm1 (flash chip info)
- marv17 (flash chip info, testing, bug reports, feature suggestions)
- Mr_V (flash chip info, testing)
- orangeglo (GB Memory Cartridge samples)
- orangeglo (GB Memory Cartridge samples, bug reports)
- paarongiroux (bug reports)
- Paradoxical (flash chip info)
- Rairch (bug reports)
@ -343,12 +358,14 @@ The author would like to thank the following very kind people for their help and
- redalchemy (bug reports, flash chip info)
- RetroGorek (flash chip info)
- RevZ (Linux help, testing, bug reports, flash chip info)
- s1cp (flash chip info)
- Satumox (bug reports)
- Sgt.DoudouMiel (flash chip info)
- Shinichi999 (bug reports)
- Sithdown (flash chip info)
- skite2001 (flash chip info)
- Smelly-Ghost (testing)
- Stitch (flash chip info)
- Super Maker (flash chip info, testing)
- Tauwasser (research)
- t5b6_de (flash chip info)

View File

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