This commit is contained in:
Lesserkuma 2021-08-09 14:31:30 +02:00
parent 4883088a1e
commit 5b517c9526
19 changed files with 635 additions and 78 deletions

View File

@ -1,6 +1,15 @@
# Release notes
### v2.6 (released 2021-08-09)
- Bundles GBxCart RW v1.4 firmware version R31+L2
- Added a firmware updater for GBxCart v1.4 devices
- Configuration path bug fix for systems that do not support PySide2 *(thanks JFox)*
- Added support for S29GL032N90T and ALTERA CPLD (configured for MBC1) *(thanks t5b6_de)*
- Flashing rumble enabled Game Boy Advance flash cartridges by insideGadgets is no longer noisy *(thanks AlexiG)*
- Confirmed support for DMG-GBRW-20 with 29LV320ETMI-70G
- Confirmed support for AGB-E20-30 with S29GL256N10TFI01
### v2.5 (released 2021-07-15)
- Added support for 4350Q2 with 4050V0YBQ1
- Added support for 4350Q2 with 4050V0YBQ1 *(thanks Shinichi999)*
- Fixed Real Time Clock register access for MBC3B and MBC30 cartridges on the GBxCart RW v1.4 hardware
- Added support for SD007_BV5 with 29LV160TE-70PFTN *(thanks RetroGorek)*
@ -23,7 +32,7 @@
- Added support for SD007_TSOP_29LV017D with M29W320DT *(thanks marv17)*
- Added support for SD007_TSOP_29LV017D with S29GL032M90T *(thanks marv17)*
- Fixed detection of Classic NES Series and Famicom Mini game cartridges for Game Boy Advance *(thanks LucentW)*
- Added support for 29LV128DBT2C-90Q and ALTERA CPLD *(thanks Sgt.DoudouMiel)*
- Added support for 29LV128DBT2C-90Q and ALTERA CPLD (configured for MBC5) *(thanks Sgt.DoudouMiel)*
- Added support for 0121 with 0121M0Y0BE *(thanks RetroGorek)*
- Fixed detection of Pocket Monsters Ruby v1.1 (AGB-AXVJ-JPN) and Pocket Monsters Sapphire v1.1 (AGB-AXPJ-JPN) genuine game cartridges *(thanks Icesythe7)*
- Fixed flash chip query for flash cartridges that have no CFI data but swapped pins *(thanks marv17)*

View File

@ -88,11 +88,10 @@ def main(portableMode=False):
app_path = os.path.dirname(os.path.abspath(__file__))
try:
from . import FlashGBX_GUI
from PySide2 import QtCore
cp = { "subdir":app_path + "/config", "appdata":QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppConfigLocation) }
except:
cp = { "subdir":app_path + "/config" }
cp = { "subdir":app_path + "/config", "appdata":os.path.expanduser('~') }
if portableMode:
cfgdir_default = "subdir"
@ -117,10 +116,10 @@ def main(portableMode=False):
parser.add_argument_group('')
ap_config = parser.add_argument_group('configuration arguments')
if "appdata" in cp: ap_config.add_argument("--cfgdir", choices=["appdata", "subdir"], type=str.lower, default=cfgdir_default, help="sets the config directory to either the OS-provided local app config directory (" + cp['appdata'] + "), or a subdirectory of this application (" + cp['subdir'].replace("\\", "/") + ")")
ap_cli1 = parser.add_argument_group('main command line interface arguments')
ap_cli1.add_argument("--mode", choices=["dmg", "agb"], type=str.lower, default=None, help="set cartridge mode to \"dmg\" (Game Boy) or \"agb\" (Game Boy Advance)")
ap_cli1.add_argument("--action", choices=["info", "backup-rom", "flash-rom", "backup-save", "restore-save", "erase-save", "gbcamera-extract", "debug-test-save"], type=str.lower, default=None, help="select program action")
ap_cli1.add_argument("--action", choices=["info", "backup-rom", "flash-rom", "backup-save", "restore-save", "erase-save", "gbcamera-extract", "fwupdate-gbxcartrw", "debug-test-save"], type=str.lower, default=None, help="select program action")
ap_cli1.add_argument("--overwrite", action="store_true", help="overwrite without asking if target file already exists")
ap_cli1.add_argument("path", nargs="?", default="auto", help="target or source file path (optional when reading, required when writing)")
@ -141,6 +140,7 @@ def main(portableMode=False):
ap_cli2.add_argument("--save-filename-add-datetime", action="store_true", help="adds a timestamp to the file name of save data backups")
ap_cli2.add_argument("--gbcamera-palette", choices=["grayscale", "dmg", "sgb", "cgb1", "cgb2", "cgb3"], type=str.lower, default="grayscale", help="sets the palette of pictures extracted from Game Boy Camera saves")
ap_cli2.add_argument("--gbcamera-outfile-format", choices=["png", "bmp", "gif", "jpg"], type=str.lower, default="png", help="sets the file format of saved pictures extracted from Game Boy Camera saves")
ap_cli2.add_argument("--fwupdate-port", help="override device port for the firmware updater", default=None)
args = parser.parse_args()
if "appdata" in cp:

View File

@ -2,7 +2,7 @@
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
import datetime, shutil, platform, os, json, math, traceback, re, time
import datetime, shutil, platform, os, json, math, traceback, re, time, serial, zipfile
try:
# pylint: disable=import-error
import readline
@ -26,9 +26,11 @@ class FlashGBX_CLI():
CONN = None
DEVICE = None
PROGRESS = None
FWUPD_R = False
def __init__(self, args):
self.ARGS = args
self.APP_PATH = args['app_path']
self.CONFIG_PATH = args['config_path']
self.FLASHCARTS = args["flashcarts"]
self.PROGRESS = Util.Progress(self.UpdateProgress)
@ -55,11 +57,13 @@ class FlashGBX_CLI():
# Ask interactively if no args set
if args.action is None:
actions = ["info", "backup-rom", "flash-rom", "backup-save", "restore-save", "erase-save", "gbcamera-extract", "debug-test-save"]
print("Select Operation:\n 1) Read Cartridge Information\n 2) Backup ROM\n 3) Write ROM\n 4) Backup Save Data\n 5) Restore Save Data\n 6) Erase Save Data\n 7) Extract Game Boy Camera Pictures\n")
args.action = input("Enter number 1-7 [1]: ").lower().strip()
print("")
actions = ["info", "backup-rom", "flash-rom", "backup-save", "restore-save", "erase-save", "gbcamera-extract", "fwupdate-gbxcartrw", "debug-test-save"]
print("Select Operation:\n 1) Read Cartridge Information\n 2) Backup ROM\n 3) Write ROM\n 4) Backup Save Data\n 5) Restore Save Data\n 6) Erase Save Data\n 7) Extract Game Boy Camera Pictures\n 8) Firmware Update (for GBxCart RW v1.4 only)\n")
args.action = input("Enter number 1-8 [1]: ").lower().strip()
try:
if int(args.action) == 0:
print("Canceled.")
return
args.action = actions[int(args.action) - 1]
except:
if args.action == "":
@ -68,7 +72,7 @@ class FlashGBX_CLI():
print("Canceled.")
return
if args.action is None or args.action not in ("gbcamera-extract"):
if args.action is None or args.action not in ("gbcamera-extract", "fwupdate-gbxcartrw"):
if not self.FindDevices():
print("No devices found.")
return
@ -109,7 +113,11 @@ class FlashGBX_CLI():
print("\n{:s}Couldnt parse the save data file.{:s}\n".format(ANSI.RED, ANSI.RESET))
return
if args.mode is None:
if args.action == "fwupdate-gbxcartrw":
self.UpdateFirmwareGBxCartRW(pcb=5, port=args.fwupdate_port)
return 0
elif args.mode is None:
print("Select Cartridge Mode:\n 1) Game Boy or Game Boy Color\n 2) Game Boy Advance\n")
answer = input("Enter number 1-2 [2]: ").lower().strip()
print("")
@ -385,13 +393,11 @@ class FlashGBX_CLI():
str += "{:s}\n".format(Util.DMG_Header_SGB[data['sgb']])
else:
str += "Unknown (0x{:02X})\n".format(data['sgb'])
bad_read = True
str += "Game Boy Color: "
if data['cgb'] in Util.DMG_Header_CGB:
str += "{:s}\n".format(Util.DMG_Header_CGB[data['cgb']])
else:
str += "Unknown (0x{:02X})\n".format(data['cgb'])
bad_read = True
if data["logo_correct"]:
str += "Nintendo Logo: OK\n"
else:
@ -728,7 +734,7 @@ class FlashGBX_CLI():
if cart_type == 0:
msg_5v = ""
if mode == "DMG": msg_5v = "If your flash cartridge requires 5V to work, you can use the “--force-5v” command line switch, however please note that 5V can be unsafe for some flash chips."
print("\n{:s}Auto-detection failed. Please use the “--flashcart-handler” command line switch to select the flash cartridge type manually.\n{:s}{:s}{:s}".format(ANSI.RED, ANSI.YELLOW, msg_5v, ANSI.RESET))
print("\n{:s}Auto-detection failed. Please use the “--flashcart-handler” command line switch to select the flash cartridge type manually.\n{:s}{:s}{:s}".format(ANSI.RED, ANSI.RESET, msg_5v, ANSI.RESET))
return
elif cart_type < 0: return
elif cart_type == 0 and args.flashcart_handler != "autodetect":
@ -955,10 +961,25 @@ class FlashGBX_CLI():
self.CONN._TransferData(args={ 'mode':2, 'path':self.CONFIG_PATH + "/test3.bin", 'mbc':mbc, 'save_type':save_type }, signal=self.PROGRESS.SetProgress)
time.sleep(0.1)
with open(self.CONFIG_PATH + "/test3.bin", "rb") as f: test3 = bytearray(f.read())
print("\nPower cycling.")
self.CONN.CartPowerOff()
time.sleep(1)
self.CONN.CartPowerOn()
time.sleep(0.2)
print("\nReading back and comparing data again.")
self.CONN._TransferData(args={ 'mode':2, 'path':self.CONFIG_PATH + "/test4.bin", 'mbc':mbc, 'save_type':save_type }, signal=self.PROGRESS.SetProgress)
time.sleep(0.1)
with open(self.CONFIG_PATH + "/test4.bin", "rb") as f: test4 = bytearray(f.read())
print("Restoring original save data.")
self.CONN._TransferData(args={ 'mode':3, 'path':self.CONFIG_PATH + "/test1.bin", 'mbc':mbc, 'save_type':save_type, 'erase':False }, signal=self.PROGRESS.SetProgress)
time.sleep(0.1)
if test3 != test4:
diffcount = 0
for i in range(0, len(test3)):
if test3[i] != test4[i]: diffcount += 1
print("\n{:s}Differences found after two consecutive reads: {:d}{:s}".format(ANSI.RED, diffcount, ANSI.RESET))
if mbc == 6:
for i in range(0, len(test2)):
test2[i] &= 0x0F
@ -992,7 +1013,79 @@ class FlashGBX_CLI():
except:
pass
#input("\nPress ENTER to erase the temporary files.")
os.unlink(self.CONFIG_PATH + "/test1.bin")
os.unlink(self.CONFIG_PATH + "/test2.bin")
os.unlink(self.CONFIG_PATH + "/test3.bin")
os.unlink(self.CONFIG_PATH + "/test4.bin")
def UpdateFirmwareGBxCartRW_PrintText(self, text, enableUI=False, setProgress=None):
if setProgress is not None:
self.FWUPD_R = True
print("\r{:s} ({:d}%)".format(text, int(setProgress)), flush=True, end="")
else:
if self.FWUPD_R is True:
print("")
print(text, flush=True)
def UpdateFirmwareGBxCartRW(self, pcb=5, port=False):
if pcb != 5: return False
print("\nFirmware Updater for GBxCart RW v1.4")
print("====================================\n")
with zipfile.ZipFile(self.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip") as zip:
with zip.open("fw.ini") as f: ini_file = f.read()
ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
fw_ver = self.INI.GetValue("fw_ver")
fw_buildts = self.INI.GetValue("fw_buildts")
fw_text = self.INI.GetValue("fw_text")
print("Available firmware version:\n{:s}\n".format("{:s} (dated {:s})".format(fw_ver, datetime.datetime.fromtimestamp(int(fw_buildts)).astimezone().replace(microsecond=0).isoformat())))
print("Please follow these steps to proceed with the firmware update:\n1. Disconnect the USB cable of your GBxCart RW v1.4 device.\n2. On the circuit board of your GBxCart RW v1.4, press and hold down\n the small button while connecting the USB cable again.\n3. Keep the small button held for at least 2 seconds, then let go of it.\n If done right, the green LED labeled “Done” should remain lit.\n4. Press ENTER or RETURN to continue.")
if len(input("").strip()) != 0:
print("Canceled.")
return False
try:
ports = []
if port is None or port is False:
comports = serial.tools.list_ports.comports()
for i in range(0, len(comports)):
if comports[i].vid == 0x1A86 and comports[i].pid == 0x7523:
ports.append(comports[i].device)
if len(ports) == 0:
print("No device found.")
return False
port = ports[0]
from . import fw_GBxCartRW_v1_4
while True:
try:
print("Using port {:s}.\n".format(port))
FirmwareUpdater = fw_GBxCartRW_v1_4.FirmwareUpdater
FWUPD = FirmwareUpdater(port=port)
ret = FWUPD.WriteFirmware(self.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip", self.UpdateFirmwareGBxCartRW_PrintText)
break
except serial.serialutil.SerialException:
port = input("Couldnt access port {:s}.\nEnter new port: ".format(port)).strip()
if len(port) == 0:
print("Canceled.")
return False
continue
except Exception as err:
traceback.print_exception(type(err), err, err.__traceback__)
print(err)
return False
if ret == 1:
print("The firmware update is complete!")
return True
elif ret == 3:
print("Please re-install the application.")
return False
else:
return False
except Exception as err:
traceback.print_exception(type(err), err, err.__traceback__)
print(err)
return False

View File

@ -174,8 +174,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.mnuTools.addAction("Game Boy Camera Album Viewer", lambda: self.ShowPocketCameraWindow())
#self.mnuTools.addAction("GB Memory Cartridge Manager", lambda: self.ShowGBMemoryWindow())
self.mnuTools.addSeparator()
self.mnuTools.addAction("Firmware &Updater", lambda: self.ShowFirmwareUpdateWindow()) # GBxCart RW v1.3
self.mnuTools.actions()[2].setEnabled(False)
self.mnuTools.addAction("Firmware &Updater", lambda: self.ShowFirmwareUpdateWindow())
#self.mnuTools.actions()[2].setEnabled(False)
self.btnTools.setMenu(self.mnuTools)
btnText = "C&onfig"
@ -558,7 +558,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.btnRestoreRAM.setEnabled(False)
self.btnConnect.setText("&Connect")
self.lblDevice.setText("Disconnected.")
self.mnuTools.actions()[2].setEnabled(False)
#self.mnuTools.actions()[2].setEnabled(False)
def OpenConfigDir(self):
path = 'file://{0:s}'.format(self.CONFIG_PATH)
@ -656,7 +656,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
print(msg, end="")
if dev.SupportsFirmwareUpdates():
self.mnuTools.actions()[2].setEnabled(True)
#self.mnuTools.actions()[2].setEnabled(True)
if dev.FirmwareUpdateAvailable():
dontShowAgain = str(self.SETTINGS.value("SkipFirmwareUpdate", default="disabled")).lower() == "enabled"
if not dontShowAgain or dev.FW_UPDATE_REQ:
@ -680,7 +680,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if answer == QtWidgets.QMessageBox.Yes:
self.ShowFirmwareUpdateWindow()
else:
self.mnuTools.actions()[2].setEnabled(False)
#self.mnuTools.actions()[2].setEnabled(False)
pass
return True
return False
@ -918,7 +919,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if answer == QtWidgets.QMessageBox.No:
return 0
else:
self.lblStatus4a.setText("Scanning...")
qt_app.processEvents()
detected = self.CONN.AutoDetectFlash(limitVoltage)
self.lblStatus4a.setText("Ready.")
if len(detected) == 0:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), 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 ROM writing 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)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes)
@ -1106,6 +1110,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet())
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderCGBResult.styleSheet())
self.lblStatus4a.setText("Preparing...")
qt_app.processEvents()
args = { "path":path, "mbc":mbc, "rom_banks":rom_banks, "agb_rom_size":rom_size, "fast_read_mode":fast_read_mode, "cart_type":cart_type }
self.CONN.BackupROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
self.grpStatus.setTitle("Transfer Status")
@ -1234,6 +1240,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
# QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The file you selected could not be read.", QtWidgets.QMessageBox.Ok)
# return
self.lblStatus4a.setText("Preparing...")
qt_app.processEvents()
args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":fast_read_mode, "verify_flash":verify_flash }
self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
self.grpStatus.setTitle("Transfer Status")
@ -1298,6 +1306,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.SETTINGS.setValue(setting_name, os.path.dirname(path))
self.lblStatus4a.setText("Preparing...")
qt_app.processEvents()
args = { "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc }
self.CONN.BackupRAM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
self.grpStatus.setTitle("Transfer Status")
@ -1374,6 +1384,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rtc_advance = cb.isChecked()
rtc = (answer == QtWidgets.QMessageBox.Yes)
self.lblStatus4a.setText("Preparing...")
qt_app.processEvents()
args = { "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase }
self.CONN.RestoreRAM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
self.grpStatus.setTitle("Transfer Status")
@ -1822,13 +1834,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.TBPROG.setPaused(False)
def ShowFirmwareUpdateWindow(self):
FirmwareUpdater = self.CONN.GetFirmwareUpdaterClass()
if self.CONN is None:
try:
from . import fw_GBxCartRW_v1_4
FirmwareUpdater = fw_GBxCartRW_v1_4.FirmwareUpdaterWindow
except:
return False
else:
FirmwareUpdater = self.CONN.GetFirmwareUpdaterClass()[1]
self.FWUPWIN = None
self.FWUPWIN = FirmwareUpdater(self, app_path=self.APP_PATH, icon=self.windowIcon(), device=self.CONN)
self.FWUPWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
self.FWUPWIN.setModal(True)
self.FWUPWIN.run()
def ShowPocketCameraWindow(self):
self.CAMWIN = None
self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon())

View File

@ -118,11 +118,11 @@ class Flashcart:
else:
cfi = self.ReadCFI()
if cfi is False:
print("CFI error")
print("CFI ERROR: Couldnt retrieve buffer size from the cartridge.")
return False
if not "buffer_size" in cfi: return False
buffer_size = cfi["buffer_size"]
dprint("Buffer size was read from Common Flash Interface (CFI) data:", cfi["buffer_size"])
dprint("Buffer size was read from CFI data:", cfi["buffer_size"])
self.CONFIG["buffer_size"] = buffer_size
return buffer_size
else:
@ -142,9 +142,10 @@ class Flashcart:
#dprint(full_reset, "reset_every" in self.CONFIG)
if full_reset and "reset_every" in self.CONFIG:
for j in range(0, self.CONFIG["flash_size"], self.CONFIG["reset_every"]):
dprint("reset_every @ 0x{:X}".format(j))
for command in self.CONFIG["commands"]["reset"]:
self.CartWrite([[j, command[1]]])
time.sleep(0.001)
time.sleep(0.01)
elif "reset" in self.CONFIG["commands"]:
self.CartWrite(self.CONFIG["commands"]["reset"])
time.sleep(0.001)
@ -207,11 +208,11 @@ class Flashcart:
else:
cfi = self.ReadCFI()
if cfi is False:
print("CFI error")
print("CFI ERROR: Couldnt retrieve sector size map from the cartridge.")
return False
sector_size = cfi["erase_sector_blocks"]
if cfi["tb_boot_sector_raw"] == 0x03: sector_size.reverse()
dprint("Sector map was read from Common Flash Interface (CFI) data:", cfi["erase_sector_blocks"])
dprint("Sector size map was read from CFI data:", cfi["erase_sector_blocks"])
self.CONFIG["sector_size"] = sector_size
return sector_size
else:
@ -239,7 +240,7 @@ class Flashcart:
self.CartWrite([[addr, sr_data]])
self.CartRead(addr, 2) # dummy read (fixes some bootlegs)
wait_for = struct.unpack("<H", self.CartRead(addr, 2))[0]
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(wait_for, self.CONFIG["commands"]["chip_erase_wait_for"][i][2], data, str(wait_for == data)))
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(wait_for, self.CONFIG["commands"]["chip_erase_wait_for"][i][2], data, str((wait_for & self.CONFIG["commands"]["chip_erase_wait_for"][i][2]) == data)))
wait_for = wait_for & self.CONFIG["commands"]["chip_erase_wait_for"][i][2]
if wait_for == data: break
time.sleep(0.5)

View File

@ -75,13 +75,13 @@ class DMG_MBC:
if delay is not False: time.sleep(delay)
def GetName(self):
return "Unknown MBC"
return "Unknown MBC {:d}".format(self.MBC_ID)
def GetFullName(self):
try:
return Util.DMG_Header_Mapper[self.MBC_ID]
except:
return "Unknown MBC"
return "Unknown MBC {:d}".format(self.MBC_ID)
def GetROMBank(self):
return self.CURRENT_ROM_BANK

View File

@ -7,7 +7,7 @@ from enum import Enum
# Common constants
APPNAME = "FlashGBX"
VERSION_PEP440 = "2.5"
VERSION_PEP440 = "2.6"
VERSION = "v{:s}".format(VERSION_PEP440)
DEBUG = False

View File

@ -2,10 +2,12 @@
"type":"AGB",
"names":[
"insideGadgets 32 MB (28EW256A) + RTC/Rumble",
"insideGadgets 32 MB (S29GL512N) + RTC"
"insideGadgets 32 MB (S29GL512N) + RTC",
"AGB-E20-30 with S29GL256N10TFI01"
],
"flash_ids":[
[ 0x89, 0x00, 0x7E, 0x22 ],
[ 0x01, 0x00, 0x7E, 0x22 ],
[ 0x01, 0x00, 0x7E, 0x22 ]
],
"voltage":3.3,
@ -14,6 +16,7 @@
"chip_erase_timeout":300,
"single_write_first_256_bytes":true,
"rtc":true,
"rumble":true,
"command_set":"AMD",
"commands":{
"reset":[

View File

@ -19,7 +19,7 @@
[0x2000, 8],
[0x10000, 63]
],
"chip_erase_timeout":60,
"chip_erase_timeout":120,
"mbc":5,
"command_set":"AMD",
"commands":{

View File

@ -1,9 +1,11 @@
{
"type":"DMG",
"names":[
"DMG-DHCN-20 with MX29LV320ET"
"DMG-DHCN-20 with MX29LV320ET",
"DMG-GBRW-20 with 29LV320ETMI-70G"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xA7, 0xA7 ],
[ 0xC2, 0xC2, 0xA7, 0xA7 ]
],
"voltage":3.3,

View File

@ -0,0 +1,80 @@
{
"type":"DMG",
"names":[
"S29GL032N90T and ALTERA CPLD (MBC1)"
],
"flash_ids":[
[ 0x02, 0x02, 0x7D, 0x7D ]
],
"manual_select":true,
"voltage":3.3,
"flash_size":0x80000,
"start_addr":0x4000,
"first_bank":1,
"write_pin":"WR",
"sector_size":[
[0x2000, 8],
[0x10000, 63]
],
"chip_erase_timeout":40,
"mbc":1,
"pulse_reset_after_write":true,
"command_set":"AMD",
"commands":{
"reset":[
[ 0x7000, 0xF0 ]
],
"read_identifier":[
[ 0x7AAA, 0xA9 ],
[ 0x7555, 0x56 ],
[ 0x7AAA, 0x90 ]
],
"read_cfi":[
[ 0x7AAA, 0x98 ]
],
"chip_erase":[
[ 0x7AAA, 0xA9 ],
[ 0x7555, 0x56 ],
[ 0x7AAA, 0x80 ],
[ 0x7AAA, 0xA9 ],
[ 0x7555, 0x56 ],
[ 0x7AAA, 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":[
[ 0x7AAA, 0xA9 ],
[ 0x7555, 0x56 ],
[ 0x7AAA, 0x80 ],
[ 0x7AAA, 0xA9 ],
[ 0x7555, 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":[
[ 0x7AAA, 0xA9 ],
[ 0x7555, 0x56 ],
[ 0x7AAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -6,7 +6,7 @@ import zipfile, os, serial, struct, time, re, math, platform
from PySide2 import QtCore, QtWidgets, QtGui
from . import Util
class FirmwareUpdater(QtWidgets.QDialog):
class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None
DEVICE = None
PORT = ""
@ -14,6 +14,7 @@ class FirmwareUpdater(QtWidgets.QDialog):
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.APP = app
self.APP_PATH = app_path
self.DEVICE = device
@ -250,7 +251,7 @@ class FirmwareUpdater(QtWidgets.QDialog):
chk = chk & 0xFF
chk = (~chk + 1) & 0xFF
if (chk != data["checksum"]):
fncSetStatus("Status: Firmware checksum error.")
self.SetStatus("Status: Firmware checksum error.")
self.prgStatus.setValue(0)
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
@ -261,7 +262,7 @@ class FirmwareUpdater(QtWidgets.QDialog):
buffer += bytearray(data["data"])
if len(buffer) >= 7168:
fncSetStatus("Status: Firmware file is too large.")
self.SetStatus("Status: Firmware file is too large.")
self.prgStatus.setValue(0)
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
@ -518,7 +519,7 @@ class FirmwareUpdater(QtWidgets.QDialog):
self.grpAvailableFwUpdates.setEnabled(True)
flash_ok = True
text = "The firmware update is complete!"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
self.reject()
return 1

View File

@ -0,0 +1,307 @@
# -*- coding: utf-8 -*-
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
import zipfile, serial, struct, time, random, hashlib, datetime
from PySide2 import QtCore, QtWidgets, QtGui
try:
from . import Util
except ImportError:
import Util
class FirmwareUpdater():
PORT = ""
def __init__(self, app_path=".", port=None):
self.APP_PATH = app_path
self.PORT = port
def WriteFirmware(self, zipfn, fncSetStatus):
with zipfile.ZipFile(zipfn) as archive:
with archive.open("fw.ini") as f: buffer1 = bytearray(f.read())
with archive.open("fw.bin") as f: buffer2 = bytearray(f.read())
while len(buffer1) < len(buffer2): buffer1 = buffer1 + buffer1
random.seed(struct.unpack("<I", buffer2[-0x18:-0x14])[0])
chk = buffer2[-0x14:]
buffer = bytearray()
for i in range(0, len(buffer2[0:-0x18])):
r = int(random.random()*256) % 256
buffer.append(buffer2[0:-0x18][len(buffer2[0:-0x18]) - i - 1] ^ r ^ buffer1[len(buffer1) - i - 1])
if (chk != hashlib.sha1(buffer).digest()):
fncSetStatus("The firmware update file is corrupted.")
return 3
if self.PORT is None:
ports = []
comports = serial.tools.list_ports.comports()
for i in range(0, len(comports)):
if comports[i].vid == 0x1A86 and comports[i].pid == 0x7523:
ports.append(comports[i].device)
if len(ports) == 0:
fncSetStatus("No device found.")
return 2
port = ports[0]
else:
port = self.PORT
delay = 0
lives = 10
data = buffer
buffer = bytearray()
fncSetStatus(text="Connecting...")
try:
dev = serial.Serial(port=port, baudrate=57600, timeout=1)
except:
fncSetStatus(text="Device not accessible.", enableUI=True)
return 2
dev.reset_input_buffer()
# Write firmware
fncSetStatus("Updating firmware...", setProgress=0)
size = len(data)
failed = 0
counter = 0
last_percent = 0
while counter < size:
byte = data[counter:counter+1]
dev.write(byte)
tmp_byte = dev.read(1)
if (tmp_byte != byte):
try:
tmp_byte = int.from_bytes(tmp_byte, byteorder="little")
except:
tmp_byte = 0
byte = int.from_bytes(byte, byteorder="little")
if counter == 0:
fncSetStatus(text="Update failed!".format(counter, byte, tmp_byte), enableUI=True)
else:
fncSetStatus(text="Update failed at offset 0x{:04X}!".format(counter, byte, tmp_byte), enableUI=True)
return 2
counter += 1
percent = float(counter)/size*100
#if int(percent) > int(last_percent):
fncSetStatus(text="Updating firmware... Do not unplug the device!", setProgress=percent)
last_percent = percent
dev.close()
time.sleep(0.8)
fncSetStatus("Done.")
time.sleep(0.2)
flash_ok = True
return 1
class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None
DEVICE = None
FWUPD = None
DEV_NAME = ""
FW_VER = ""
PCB_VER = ""
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle("FlashGBX Firmware Updater for GBxCart RW v1.4")
self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint)
self.APP = app
if device is not None:
self.FWUPD = FirmwareUpdater(app_path, device.GetPort())
self.DEV_NAME = device.GetName()
self.FW_VER = device.GetFirmwareVersion(more=True)
self.PCB_VER = device.GetPCBVersion()
self.DEVICE = device
else:
self.APP.QT_APP.processEvents()
text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nIf you want to update another GBxCart RW hardware revision, please use the official firmware updater by insideGadgets instead."
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
self.FWUPD = FirmwareUpdater(app_path, None)
with zipfile.ZipFile(app_path + "/res/fw_GBxCart_RW_v1_4.zip") as zip:
with zip.open("fw.ini") as f: ini_file = f.read()
ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
self.OFW_VER = self.INI.GetValue("fw_ver")
self.OFW_BUILDTS = self.INI.GetValue("fw_buildts")
self.OFW_TEXT = self.INI.GetValue("fw_text")
self.layout = QtWidgets.QGridLayout()
self.layout.setContentsMargins(-1, 8, -1, 8)
self.layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.layout_device = QtWidgets.QVBoxLayout()
# ↓↓↓ Current Device Information
self.grpDeviceInfo = QtWidgets.QGroupBox("Current Firmware")
self.grpDeviceInfo.setMinimumWidth(420)
self.grpDeviceInfoLayout = QtWidgets.QVBoxLayout()
self.grpDeviceInfoLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo1 = QtWidgets.QHBoxLayout()
self.lblDeviceName = QtWidgets.QLabel("Device:")
self.lblDeviceName.setMinimumWidth(120)
self.lblDeviceNameResult = QtWidgets.QLabel("GBxCart RW")
rowDeviceInfo1.addWidget(self.lblDeviceName)
rowDeviceInfo1.addWidget(self.lblDeviceNameResult)
rowDeviceInfo1.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo1)
rowDeviceInfo2 = QtWidgets.QHBoxLayout()
self.lblDevicePCBVer = QtWidgets.QLabel("PCB version:")
self.lblDevicePCBVer.setMinimumWidth(120)
self.lblDevicePCBVerResult = QtWidgets.QLabel("v1.4")
rowDeviceInfo2.addWidget(self.lblDevicePCBVer)
rowDeviceInfo2.addWidget(self.lblDevicePCBVerResult)
rowDeviceInfo2.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo2)
rowDeviceInfo3 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer.setMinimumWidth(120)
self.lblDeviceFWVerResult = QtWidgets.QLabel("R30+L1")
rowDeviceInfo3.addWidget(self.lblDeviceFWVer)
rowDeviceInfo3.addWidget(self.lblDeviceFWVerResult)
rowDeviceInfo3.addStretch(1)
self.grpDeviceInfoLayout.addLayout(rowDeviceInfo3)
self.grpDeviceInfo.setLayout(self.grpDeviceInfoLayout)
self.layout_device.addWidget(self.grpDeviceInfo)
# ↑↑↑ Current Device Information
# ↓↓↓ Available Firmware Updates
self.grpAvailableFwUpdates = QtWidgets.QGroupBox("Available Firmware")
self.grpAvailableFwUpdates.setMinimumWidth(400)
self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout()
self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1)
rowDeviceInfo4 = QtWidgets.QHBoxLayout()
self.lblDeviceFWVer2 = QtWidgets.QLabel("Firmware version:")
self.lblDeviceFWVer2.setMinimumWidth(120)
self.lblDeviceFWVer2Result = QtWidgets.QLabel("R31+L2")
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2)
rowDeviceInfo4.addWidget(self.lblDeviceFWVer2Result)
rowDeviceInfo4.addStretch(1)
self.grpAvailableFwUpdatesLayout.addLayout(rowDeviceInfo4)
self.rowUpdate = QtWidgets.QHBoxLayout()
self.btnUpdate = QtWidgets.QPushButton("Install Firmware Update")
self.btnUpdate.setMinimumWidth(200)
self.btnUpdate.setContentsMargins(20, 20, 20, 20)
self.connect(self.btnUpdate, QtCore.SIGNAL("clicked()"), lambda: [ self.UpdateFirmware() ])
self.rowUpdate.addStretch()
self.rowUpdate.addWidget(self.btnUpdate)
self.rowUpdate.addStretch()
self.grpAvailableFwUpdatesLayout.addSpacing(3)
self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate)
self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout)
self.layout_device.addWidget(self.grpAvailableFwUpdates)
# ↑↑↑ Available Firmware Updates
self.grpStatus = QtWidgets.QGroupBox("")
self.grpStatusLayout = QtWidgets.QGridLayout()
self.prgStatus = QtWidgets.QProgressBar()
self.prgStatus.setMinimum(0)
self.prgStatus.setMaximum(1000)
self.prgStatus.setValue(0)
self.lblStatus = QtWidgets.QLabel("Ready.")
self.grpStatusLayout.addWidget(self.prgStatus, 1, 0)
self.grpStatusLayout.addWidget(self.lblStatus, 2, 0)
self.grpStatus.setLayout(self.grpStatusLayout)
self.layout_device.addWidget(self.grpStatus)
self.grpFooterLayout = QtWidgets.QHBoxLayout()
self.btnClose = QtWidgets.QPushButton("&Close")
self.connect(self.btnClose, QtCore.SIGNAL("clicked()"), lambda: [ self.reject() ])
self.grpFooterLayout.addStretch()
self.grpFooterLayout.addWidget(self.btnClose)
self.layout_device.addItem(self.grpFooterLayout)
self.layout.addLayout(self.layout_device, 0, 0)
self.setLayout(self.layout)
self.lblDeviceNameResult.setText(self.DEV_NAME)
self.lblDeviceFWVerResult.setText(self.FW_VER)
self.lblDevicePCBVerResult.setText(self.PCB_VER)
self.lblDeviceFWVer2Result.setText("{:s} (dated {:s})".format(self.OFW_VER, datetime.datetime.fromtimestamp(int(self.OFW_BUILDTS)).astimezone().replace(microsecond=0).isoformat()))
def run(self):
try:
self.layout.update()
self.layout.activate()
screenGeometry = QtWidgets.QDesktopWidget().screenGeometry()
x = (screenGeometry.width() - self.width()) / 2
y = (screenGeometry.height() - self.height()) / 2
self.move(x, y)
self.show()
except:
return
def hideEvent(self, event):
if self.DEVICE is None:
self.APP.ConnectDevice()
self.APP.activateWindow()
def reject(self):
if self.CloseDialog():
super().reject()
def CloseDialog(self):
if self.btnClose.isEnabled() is False:
text = "<b>WARNING:</b> If you close this window while a firmware update is still running, it may leave the device in an unbootable state. You can still recover it by running the Firmware Updater again later.<br><br>Are you sure you want to close this window?"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.No)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.No: return False
return True
def UpdateFirmware(self):
self.APP.DisconnectDevice()
text = "Please follow these steps to proceed with the firmware update:<ol><li>Disconnect the USB cable of your GBxCart RW v1.4 device.</li><li>On the circuit board of your GBxCart RW v1.4, press and hold down the small button while connecting the USB cable again.</li><li>Keep the small button held for at least 2 seconds, then let go of it. If done right, the green LED labeled “Done” should remain lit.</li><li>Click OK to continue.</li></ol>"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
msgbox.setTextFormat(QtCore.Qt.TextFormat.RichText)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
self.btnUpdate.setEnabled(False)
self.btnClose.setEnabled(False)
while True:
ret = self.FWUPD.WriteFirmware(self.FWUPD.APP_PATH + "/res/fw_GBxCart_RW_v1_4.zip", self.SetStatus)
if ret == 1:
text = "The firmware update is complete!"
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
self.DEVICE = None
self.reject()
return True
elif ret == 2:
text = "The firmware update is has failed. Please try again."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
elif ret == 3:
text = "The firmware update file is corrupted. Please re-install the application."
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec()
return False
def SetStatus(self, text, enableUI=False, setProgress=None):
self.lblStatus.setText("Status: {:s}".format(text))
if setProgress is not None:
self.prgStatus.setValue(setProgress * 10)
if enableUI:
self.btnUpdate.setEnabled(True)
self.btnClose.setEnabled(True)
self.APP.QT_APP.processEvents()

View File

@ -15,7 +15,7 @@ from . import Util
class GbxDevice:
DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 1
DEVICE_MAX_FW = 1
DEVICE_MAX_FW = 2
DEVICE_CMD = {
"NULL":0x30,
@ -29,6 +29,8 @@ class GbxDevice:
"OFW_QUERY_CART_PWR":0x5D,
"OFW_DONE_LED_ON":0x3D,
"OFW_ERROR_LED_ON":0x3F,
"OFW_GB_CART_MODE":0x47,
"OFW_GB_FLASH_BANK_1_COMMAND_WRITES":0x4E,
"QUERY_FW_INFO":0xA1,
"SET_MODE_AGB":0xA2,
"SET_MODE_DMG":0xA3,
@ -54,6 +56,7 @@ class GbxDevice:
"AGB_CART_WRITE_EEPROM":0xC6,
"AGB_CART_WRITE_FLASH_DATA":0xC7,
"AGB_CART_READ_3D_MEMORY":0xC8,
"AGB_BOOTUP_SEQUENCE":0xC9,
"DMG_FLASH_WRITE_BYTE":0xD1,
"AGB_FLASH_WRITE_BYTE":0xD2,
"FLASH_PROGRAM":0xD3,
@ -309,18 +312,24 @@ class GbxDevice:
def SupportsFirmwareUpdates(self):
#if self.FW["pcb_ver"] != 4: return False
return self.FW["pcb_ver"] == 4
return self.FW["pcb_ver"] in (4, 5)
def FirmwareUpdateAvailable(self):
#if self.FW["pcb_ver"] != 4: return False
#return self.FW["fw_ver"] < self.DEVICE_MAX_FW
return (self.FW["pcb_ver"] == 4 and self.FW["fw_ver"] < self.DEVICE_MAX_FW)
return (self.FW["pcb_ver"] in (4, 5) and self.FW["fw_ver"] < self.DEVICE_MAX_FW)
def GetFirmwareUpdaterClass(self):
if self.FW["pcb_ver"] == 4: # v1.3
try:
from . import fw_GBxCartRW_v1_3
return fw_GBxCartRW_v1_3.FirmwareUpdater
return (None, fw_GBxCartRW_v1_3.FirmwareUpdaterWindow)
except:
return False
elif self.FW["pcb_ver"] == 5: # v1.4
try:
from . import fw_GBxCartRW_v1_4
return (fw_GBxCartRW_v1_4.FirmwareUpdater, fw_GBxCartRW_v1_4.FirmwareUpdaterWindow)
except:
return False
else:
@ -416,7 +425,7 @@ class GbxDevice:
return self.ReadROM(address, length)
def _cart_write(self, address, value, flashcart=False, sram=False):
dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(address, value & 0xFF))
dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(address, value & 0xFF), flashcart, sram)
if self.MODE == "DMG":
if flashcart:
buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]])
@ -463,7 +472,6 @@ class GbxDevice:
return True
def CartPowerOff(self):
#return
if self.FW["pcb_ver"] == 5:
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"])
time.sleep(0.05)
@ -525,6 +533,9 @@ class GbxDevice:
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
elif self.MODE == "AGB":
if self.FW["pcb_ver"] == 5 and self.FW["fw_ver"] > 1:
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=True)
header = self.ReadROM(0, 0x180)
if header is False or len(header) != 0x180: raise Exception("Couldnt read the cartridge information. Please try again.")
@ -533,9 +544,10 @@ class GbxDevice:
# Check for DACS
dacs_8m = False
if header[0x04:0x04+0x9C] == bytearray([0x00] * 0x9C):
self.ReadROM(0x1FFFFE0, 20) # Unlock DACS
header = self.ReadROM(0, 0x180)
if self.FW["pcb_ver"] != 5 or self.FW["fw_ver"] == 1:
if header[0x04:0x04+0x9C] == bytearray([0x00] * 0x9C):
self.ReadROM(0x1FFFFE0, 20) # Unlock DACS
header = self.ReadROM(0, 0x180)
temp = self.ReadROM(0x1FFE000, 0x0C)
if temp == b"AGBFLASHDACS":
dacs_8m = True
@ -742,6 +754,10 @@ class GbxDevice:
if self.INFO["action"] == self.ACTIONS["SAVE_WRITE"] and not self.NO_PROG_UPDATE:
self.SetProgress({"action":"WRITE", "bytes_added":length})
if self.MODE == "DMG":
self._set_fw_variable("ADDRESS", 0)
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
return True
def WriteFlash_MBC6(self, address, buffer, mapper):
@ -826,7 +842,7 @@ class GbxDevice:
self.NO_PROG_UPDATE = False
def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False):
def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False):
length = len(buffer)
if self.FW["pcb_ver"] != 5:
max_length = 256
@ -847,6 +863,10 @@ class GbxDevice:
self._set_fw_variable("BUFFER_SIZE", flash_buffer_size)
for i in range(0, num):
if rumble_stop:
dprint("Sending rumble stop command")
self._cart_write(address=0xC6, value=0x00, flashcart=True)
data = bytearray(buffer[i*length:i*length+length])
if (num_of_chunks == 1 or flash_buffer_size == 0) and (data == bytearray([0xFF] * len(data))):
skip_init = False
@ -869,7 +889,7 @@ class GbxDevice:
ret = self._write(data, wait=True)
if ret not in (0x01, 0x03):
print("{:s}Flash error in iteration {:d} of {:d} while trying to write a total of 0x{:X} bytes (response = {:s}){:s}".format(ANSI.RED, i, num, len(buffer), str(ret), ANSI.RESET))
print("{:s}Flash error at 0x{:X} in iteration {:d} of {:d} while trying to write a total of 0x{:X} bytes (response = {:s}){:s}".format(ANSI.RED, address, i, num, len(buffer), str(ret), ANSI.RESET))
self.SKIPPING = False
return False
pos += len(data)
@ -1031,7 +1051,7 @@ class GbxDevice:
flash_id_found = True
flash_type = f
flash_types.append(flash_type)
flashcart.Reset(full_reset=True)
flashcart.Reset(full_reset=False)
dprint("Found the correct cartridge type!")
if self.MODE == "DMG" and not flash_id_found:
@ -1213,6 +1233,13 @@ class GbxDevice:
if cfi['raw'] == b'':
for we in we_pins:
if we == "WR":
self._set_fw_variable("FLASH_WE_PIN", 0x01) # FLASH_WE_PIN_WR
elif we in ("AUDIO", "VIN"):
self._set_fw_variable("FLASH_WE_PIN", 0x02) # FLASH_WE_PIN_AUDIO
elif we == "WR+RESET":
self._set_fw_variable("FLASH_WE_PIN", 0x03) # FLASH_WE_PIN_WR_RESET
for method in flash_commands:
for i in range(0, len(method['reset'])):
self._cart_write(method['reset'][i][0], method["reset"][i][1], flashcart=True)
@ -1746,15 +1773,15 @@ class GbxDevice:
]
self._cart_write_flash(cmds)
sr = 0
lives = 1000
lives = 50
while True:
time.sleep(0.001)
time.sleep(0.01)
sr = self._cart_read(sector_address << 12, agb_save_flash=True)
dprint("Data Check: 0x{:X} == 0xFFFF? {:s}".format(sr, str(sr == 0xFFFF)))
if sr == 0xFFFF: break
lives -= 1
if lives == 0:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while writing to the cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Accessing the save data flash chip failed. Please make sure you selected the correct save type. If you are using a reproduction cartridge, check if it really is equipped with a flash chip for save data, or if it uses SRAM for save data instead.", "abortable":False})
return False
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
elif self.MODE == "AGB" and args["save_type"] == 8: # DACS
@ -1817,6 +1844,9 @@ class GbxDevice:
_agb_gpio.WriteRTC(buffer[-0x10:], advance=advance)
self.SetProgress({"action":"UPDATE_POS", "pos":len(buffer)})
if self.MODE == "DMG":
_mbc.EnableRAM(enable=False)
# Clean up
self.INFO["last_action"] = self.INFO["action"]
self.INFO["action"] = None
@ -1852,12 +1882,15 @@ class GbxDevice:
return False
# Special carts
# Firmware check L1
if "flash_commands_on_bank_1" in cart_type:
if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported by FlashGBX. Please try the official GBxCart RW firmware and interface software available from <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a> instead.", "abortable":False})
return False
elif cart_type["type"] == "DMG" and "write_pin" in cart_type and cart_type["write_pin"] == "WR+RESET":
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported by FlashGBX. Please try the official GBxCart RW firmware and interface software available from <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a> instead.", "abortable":False})
return False
elif (self.FW["pcb_ver"] != 5 or self.FW["fw_ver"] < 2) and ("pulse_reset_after_write" in cart_type and cart_type["pulse_reset_after_write"] is True):
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is only supported by FlashGBX using GBxCart RW v1.4 and firmware version R31+L2 or higher. You can also try the official GBxCart RW firmware and interface software available from <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a> instead.", "abortable":False})
return False
# Firmware check L1
cart_type["_index"] = 0
@ -1885,6 +1918,8 @@ class GbxDevice:
else:
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_read_fncptr=self.ReadROM, progress_fncptr=self.SetProgress)
rumble = "rumble" in flashcart.CONFIG and flashcart.CONFIG["rumble"] is True
# ↓↓↓ Set Voltage
if args["override_voltage"] is not False:
if args["override_voltage"] == 5:
@ -1909,10 +1944,8 @@ class GbxDevice:
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, clk_toggle_fncptr=self._clk_toggle)
# I need a cart for testing before this has a chance to work ↓
#temp = 1 if flashcart.FlashCommandsOnBank1() else 0
#self._set_fw_variable("FLASH_COMMANDS_BANK_1", temp)
#temp = 1 if flashcart.PulseResetAfterWrite() else 0
#self._set_fw_variable("FLASH_PULSE_RESET", temp)
#self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1 if flashcart.FlashCommandsOnBank1() else 0)
self._set_fw_variable("FLASH_PULSE_RESET", 1 if flashcart.PulseResetAfterWrite() else 0)
rom_banks = math.ceil(len(data_import) / _mbc.GetROMBankSize())
@ -2045,8 +2078,11 @@ class GbxDevice:
chip_erase = False
else:
chip_erase = True
#if flashcart.FlashCommandsOnBank1():
# _mbc.SelectBankROM(1)
if flashcart.ChipErase() is False:
return False
#_mbc.SelectBankROM(0)
elif flashcart.SupportsSectorErase() is False:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"No erase method available.", "abortable":False})
return False
@ -2076,6 +2112,12 @@ class GbxDevice:
dprint("Resetting the MBC")
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
(start_address, bank_size) = _mbc.SelectBankROM(bank)
if flashcart.PulseResetAfterWrite():
if bank == 0:
self._write(self.DEVICE_CMD["OFW_GB_CART_MODE"])
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
else:
self._write(self.DEVICE_CMD["OFW_GB_FLASH_BANK_1_COMMAND_WRITES"])
self._set_fw_variable("DMG_ROM_BANK", bank)
buffer_len = min(buffer_len, bank_size)
@ -2086,13 +2128,6 @@ class GbxDevice:
flashcart.SelectBankROM(bank)
start_address = 0
end_address = cart_type["flash_bank_size"]
'''
elif self.MODE == "AGB":
if "agb-ghtj-jpn" in flashcart.CONFIG: # DACS
start_address = 0x1F00000
end_address = 0x1FFC000
buffer_pos = start_address
'''
# ↑↑↑ Switch ROM bank
skip_init = False
@ -2113,6 +2148,7 @@ class GbxDevice:
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos})
dprint("Erasing sector of size 0x{:X} at position 0x{:X} (0x{:X})".format(sector_size, buffer_pos, pos))
current_sector_size = sector_size
if flashcart.FlashCommandsOnBank1(): _mbc.SelectBankROM(bank)
ret = flashcart.SectorErase(pos=pos, buffer_pos=buffer_pos)
if ret is False:
return False
@ -2128,9 +2164,9 @@ class GbxDevice:
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING))
self._cart_write(pos + buffer_len - 1, 0xF0)
else:
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING))
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING), rumble_stop=rumble)
if status is False:
self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"An error occured while writing to the flash cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning."}
self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"An error occured while writing 0x{:X} bytes to the flash cartridge at position 0x{:X}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(buffer_len, buffer_pos)}
self.CANCEL = True
self.ERROR = True
continue
@ -2184,7 +2220,7 @@ class GbxDevice:
if self.CANCEL is True:
pass
elif (verified_size is not True) and (buffer_pos != verified_size):
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The ROM was flashed completely, but verification of written data failed at address 0x{:X}.".format(verified_size), "abortable":False})
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The ROM was written completely, but verification of written data failed at address 0x{:X}.".format(verified_size), "abortable":False})
return False
else:
verified = True

View File

@ -303,7 +303,7 @@ class GbxDevice:
if self.FW[1] == 4: # v1.3
try:
from . import fw_GBxCartRW_v1_3
return fw_GBxCartRW_v1_3.FirmwareUpdater
return fw_GBxCartRW_v1_3.FirmwareUpdaterWindow
except:
return False
else:
@ -742,13 +742,17 @@ class GbxDevice:
else:
return buffer[0]
def CartPowerOff(self):
if self.FW["pcb_ver"] == 5:
self._write(self.DEVICE_CMD["CART_PWR_OFF"])
time.sleep(0.05)
def CartPowerOn(self):
if self.FW[1] == 5:
self.write(self.DEVICE_CMD["QUERY_CART_PWR"])
ret = self.DEVICE.read(1)
if ret == b'\x00':
self.set_mode(self.DEVICE_CMD["CART_PWR_ON"])
print("POWER ON")
time.sleep(0.2)
self.DEVICE.reset_input_buffer() # bug workaround

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
# FlashGBX (by Lesserkuma)
for Windows, Linux, macOS
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">
@ -14,13 +14,13 @@ for Windows, Linux, macOS
- Many reproduction cartridges and flash cartridges can be auto-detected
- A flash chip query (including Common Flash Interface information) can be performed for flash cartridges
- Decode and extract Game Boy Camera photos from save data
- Update firmware of insideGadgets GBxCart RW v1.3 devices
- Update firmware of insideGadgets GBxCart RW v1.3 and v1.4 devices
### Confirmed working reader/writer hardware and firmware versions
- [insideGadgets GBxCart RW Mini, v1.3 and v1.4 (Standard and Pro versions)](https://www.gbxcart.com/)
- Official firmware versions R19 to R30 (other hardware revisions and firmware versions may also work, but are untested)
- Lesserkumas high compatibility firmware version L1
- Official firmware versions R19 to R31 (other hardware revisions and firmware versions may also work, but are untested)
- Lesserkumas high compatibility firmware version L1 and L2
### Currently supported official cartridge memory mappers
@ -84,11 +84,12 @@ for Windows, Linux, macOS
- Game Boy
- DMG-DHCN-20 with MX29LV320ET
- DMG-GBRW-20 with 29LV320ETMI-70G
- ES29LV160_DRV with 29DL32TF-70
- GB-M968 with 29LV160DB
- GB-M968 with M29W160EB
- GB-M968 with MX29LV320ABTC
- S29GL032N90T and ALTERA CPLD configured for MBC5
- S29GL032N90T and ALTERA CPLD configured for MBC1 or MBC5
- SD007_48BALL_64M with GL032M11BAIR4
- SD007_48BALL_64M with M29W640
- SD007_48BALL_64M_V2 with GL032M11BAIR4
@ -144,6 +145,7 @@ for Windows, Linux, macOS
- AGB-E05-02 with M29W128GH
- AGB-E05-06L with 29LV128DBT2C-90Q
- AGB-E08-09 with 29LV128DTMC-90Q
- AGB-E20-30 with S29GL256N10TFI01
- AGB-SD-E05 with MSP55LV128
- B104 with MSP55LV128
- B11 with 26L6420MC-90

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="2.5",
version="2.6",
author="Lesserkuma",
description="Reads and writes Game Boy and Game Boy Advance cartridge data. Currently supports the GBxCart RW hardware device by insideGadgets.",
url="https://github.com/lesserkuma/FlashGBX",