From 681cfcd1de783b52b3f0bea6546d4ad65dfdb875 Mon Sep 17 00:00:00 2001 From: Lesserkuma Date: Fri, 29 Apr 2022 12:25:54 +0200 Subject: [PATCH] 3.9 --- CHANGES.md | 5 ++ FlashGBX/FlashGBX.py | 25 ++++---- FlashGBX/Util.py | 2 +- FlashGBX/config/fc_DMG_AT49F040_WR.txt | 3 - .../config/fc_DMG_FerranteCrafts_32KB.txt | 54 +++++++++++++++++ .../config/fc_DMG_FerranteCrafts_64KB.txt | 55 +++++++++++++++++ FlashGBX/config/fc_DMG_MX29GL256EL.txt | 3 +- ...O.txt => fc_DMG_SST39SF020_MBC1_AUDIO.txt} | 11 ++-- .../config/fc_DMG_SST39SF040_MBC1_AUDIO.txt | 1 + .../config/fc_DMG_SST39SF040_MBC5_AUDIO.txt | 56 ++++++++++++++++++ FlashGBX/config/fc_DMG_TC58FVB016FT.txt | 5 +- FlashGBX/config/fc_DMG_iG_512KB.txt | 1 - FlashGBX/fw_GBxCartRW_v1_3.py | 6 +- FlashGBX/fw_GBxCartRW_v1_4.py | 2 +- FlashGBX/hw_GBxCartRW.py | 33 ++++++----- FlashGBX/hw_GBxCartRW_ofw.py | 27 +++++---- FlashGBX/res/config.zip | Bin 145188 -> 146685 bytes FlashGBX/res/fw_GBxCart_RW_v1_3.zip | Bin 15557 -> 15567 bytes README.md | 9 ++- setup.py | 2 +- 20 files changed, 238 insertions(+), 62 deletions(-) create mode 100644 FlashGBX/config/fc_DMG_FerranteCrafts_32KB.txt create mode 100644 FlashGBX/config/fc_DMG_FerranteCrafts_64KB.txt rename FlashGBX/config/{fc_DMG_SST39SF010_AUDIO.txt => fc_DMG_SST39SF020_MBC1_AUDIO.txt} (84%) create mode 100644 FlashGBX/config/fc_DMG_SST39SF040_MBC5_AUDIO.txt diff --git a/CHANGES.md b/CHANGES.md index fd80833..8e96838 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,9 @@ # Release notes +### v3.9 (released 2022-04-29) +- Added support for Ferrante Crafts cart 64 KB *(thanks FerrantePescara)* +- Added support for Ferrante Crafts cart 512 KB *(thanks FerrantePescara)* +- Fixed a bug with querying MBC3/MBC30 Real Time Clock values *(thanks HDR and AdmirtheSableye)* + ### v3.8 (released 2022-04-21) - Added support for AGB-E05-01 with MSP55LV100G *(thanks EmperorOfTigers)* - Added support for DV15 with MSP55LV100G *(thanks zvxr)* diff --git a/FlashGBX/FlashGBX.py b/FlashGBX/FlashGBX.py index c4f6512..3ea433b 100644 --- a/FlashGBX/FlashGBX.py +++ b/FlashGBX/FlashGBX.py @@ -33,7 +33,7 @@ def LoadConfig(args): (config_version, fc_files) = ReadConfigFiles(args=args) if config_version != Util.VERSION: # Rename old files that have since been replaced/renamed/merged - deprecated_files = [ "fc_AGB_TEST.txt", "fc_DMG_TEST.txt", "fc_AGB_Nintendo_E201850.txt", "fc_AGB_Nintendo_E201868.txt", "config.ini", "fc_DMG_MX29LV320ABTC.txt", "fc_DMG_iG_4MB_MBC3_RTC.txt", "fc_AGB_Flash2Advance.txt", "fc_AGB_MX29LV640_AUDIO.txt", "fc_AGB_M36L0R7050T.txt", "fc_AGB_M36L0R8060B.txt", "fc_AGB_M36L0R8060T.txt", "fc_AGB_iG_32MB_S29GL512N.txt" ] + deprecated_files = [ "fc_AGB_TEST.txt", "fc_DMG_TEST.txt", "fc_AGB_Nintendo_E201850.txt", "fc_AGB_Nintendo_E201868.txt", "config.ini", "fc_DMG_MX29LV320ABTC.txt", "fc_DMG_iG_4MB_MBC3_RTC.txt", "fc_AGB_Flash2Advance.txt", "fc_AGB_MX29LV640_AUDIO.txt", "fc_AGB_M36L0R7050T.txt", "fc_AGB_M36L0R8060B.txt", "fc_AGB_M36L0R8060T.txt", "fc_AGB_iG_32MB_S29GL512N.txt", "fc_DMG_SST39SF010_AUDIO.txt" ] for file in deprecated_files: if os.path.exists(config_path + "/" + file): os.rename(config_path + "/" + file, config_path + "/" + file + "_" + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + ".bak") @@ -59,17 +59,18 @@ def LoadConfig(args): # Read flash cart types for file in fc_files: - with open(file, encoding='utf-8') as f: - data = f.read() - specs_int = re.sub("(0x[0-9A-F]+)", lambda m: str(int(m.group(1), 16)), data) # hex numbers to int numbers, otherwise not valid json - try: - specs = json.loads(specs_int) - except: - ret.append([2, "The flashchip type file “{:s}” could not be parsed and needs to be fixed before it can be used.".format(os.path.basename(file))]) - continue - for name in specs["names"]: - if not specs["type"] in flashcarts: continue # only DMG and AGB are supported right now - flashcarts[specs["type"]][name] = specs + if os.path.exists(file): + with open(file, encoding='utf-8') as f: + data = f.read() + specs_int = re.sub("(0x[0-9A-F]+)", lambda m: str(int(m.group(1), 16)), data) # hex numbers to int numbers, otherwise not valid json + try: + specs = json.loads(specs_int) + except: + ret.append([2, "The flashchip type file “{:s}” could not be parsed and needs to be fixed before it can be used.".format(os.path.basename(file))]) + continue + for name in specs["names"]: + if not specs["type"] in flashcarts: continue # only DMG and AGB are supported right now + flashcarts[specs["type"]][name] = specs return { "flashcarts":flashcarts, "config_ret":ret } diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py index 684a452..265394c 100644 --- a/FlashGBX/Util.py +++ b/FlashGBX/Util.py @@ -7,7 +7,7 @@ from enum import Enum # Common constants APPNAME = "FlashGBX" -VERSION_PEP440 = "3.8" +VERSION_PEP440 = "3.9" VERSION = "v{:s}".format(VERSION_PEP440) DEBUG = False diff --git a/FlashGBX/config/fc_DMG_AT49F040_WR.txt b/FlashGBX/config/fc_DMG_AT49F040_WR.txt index 1d900db..98863c4 100644 --- a/FlashGBX/config/fc_DMG_AT49F040_WR.txt +++ b/FlashGBX/config/fc_DMG_AT49F040_WR.txt @@ -24,9 +24,6 @@ [ 0x2AAA, 0x55 ], [ 0x5555, 0x90 ] ], - "read_cfi":[ - [ 0x5555, 0x98 ] - ], "chip_erase":[ [ 0x5555, 0xAA ], [ 0x2AAA, 0x55 ], diff --git a/FlashGBX/config/fc_DMG_FerranteCrafts_32KB.txt b/FlashGBX/config/fc_DMG_FerranteCrafts_32KB.txt new file mode 100644 index 0000000..43ea70d --- /dev/null +++ b/FlashGBX/config/fc_DMG_FerranteCrafts_32KB.txt @@ -0,0 +1,54 @@ +{ + "type":"DMG", + "names":[ + "Ferrante Crafts cart 32 KB" + ], + "flash_ids":[ + [ 0xBF, 0xB5, 0x01, 0xFF ] + ], + "voltage":5, + "flash_size":0x8000, + "start_addr":0, + "first_bank":1, + "write_pin":"AUDIO", + "chip_erase_timeout":20, + "command_set":"AMD", + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x90 ] + ], + "chip_erase":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x80 ], + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 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":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_DMG_FerranteCrafts_64KB.txt b/FlashGBX/config/fc_DMG_FerranteCrafts_64KB.txt new file mode 100644 index 0000000..bf2a1db --- /dev/null +++ b/FlashGBX/config/fc_DMG_FerranteCrafts_64KB.txt @@ -0,0 +1,55 @@ +{ + "type":"DMG", + "names":[ + "Ferrante Crafts cart 64 KB" + ], + "flash_ids":[ + [ 0xBF, 0xB5, 0x01, 0xFF ] + ], + "voltage":5, + "flash_size":0x10000, + "start_addr":0, + "first_bank":1, + "flash_commands_on_bank_1":true, + "write_pin":"AUDIO", + "chip_erase_timeout":20, + "command_set":"AMD", + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x90 ] + ], + "chip_erase":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x80 ], + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 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":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_DMG_MX29GL256EL.txt b/FlashGBX/config/fc_DMG_MX29GL256EL.txt index 9fd9265..003a418 100644 --- a/FlashGBX/config/fc_DMG_MX29GL256EL.txt +++ b/FlashGBX/config/fc_DMG_MX29GL256EL.txt @@ -1,8 +1,7 @@ { "type":"DMG", "names":[ - "SD008-6810-V4 with MX29GL256EL", - "SD008-6810-V4 with unlabeled flash chip" + "SD008-6810-V4 with MX29GL256EL" ], "flash_ids":[ [ 0xF3, 0xC3, 0x00, 0x01 ], diff --git a/FlashGBX/config/fc_DMG_SST39SF010_AUDIO.txt b/FlashGBX/config/fc_DMG_SST39SF020_MBC1_AUDIO.txt similarity index 84% rename from FlashGBX/config/fc_DMG_SST39SF010_AUDIO.txt rename to FlashGBX/config/fc_DMG_SST39SF020_MBC1_AUDIO.txt index 7b57b33..82b58c0 100644 --- a/FlashGBX/config/fc_DMG_SST39SF010_AUDIO.txt +++ b/FlashGBX/config/fc_DMG_SST39SF020_MBC1_AUDIO.txt @@ -1,19 +1,19 @@ { "type":"DMG", "names":[ - "Ferrante Crafts cart with SST39SF010A", "GB-CART32K-A with SST39SF020A" ], "flash_ids":[ - [ 0xBF, 0xB5, 0x01, 0xFF ], [ 0xBF, 0xB6, 0x01, 0xFF ] ], "voltage":5, - "flash_size":0x8000, + "flash_size":0x10000, "start_addr":0, "first_bank":1, + "mbc":0x03, + "flash_commands_on_bank_1":true, "write_pin":"AUDIO", - "chip_erase_timeout":60, + "chip_erase_timeout":20, "command_set":"AMD", "commands":{ "reset":[ @@ -24,9 +24,6 @@ [ 0x2AAA, 0x55 ], [ 0x5555, 0x90 ] ], - "read_cfi":[ - [ 0x5555, 0x98 ] - ], "chip_erase":[ [ 0x5555, 0xAA ], [ 0x2AAA, 0x55 ], diff --git a/FlashGBX/config/fc_DMG_SST39SF040_MBC1_AUDIO.txt b/FlashGBX/config/fc_DMG_SST39SF040_MBC1_AUDIO.txt index 3858d6b..dc8eae1 100644 --- a/FlashGBX/config/fc_DMG_SST39SF040_MBC1_AUDIO.txt +++ b/FlashGBX/config/fc_DMG_SST39SF040_MBC1_AUDIO.txt @@ -10,6 +10,7 @@ "flash_size":0x80000, "start_addr":0, "first_bank":1, + "mbc":0x03, "flash_commands_on_bank_1":true, "write_pin":"AUDIO", "chip_erase_timeout":30, diff --git a/FlashGBX/config/fc_DMG_SST39SF040_MBC5_AUDIO.txt b/FlashGBX/config/fc_DMG_SST39SF040_MBC5_AUDIO.txt new file mode 100644 index 0000000..010be6c --- /dev/null +++ b/FlashGBX/config/fc_DMG_SST39SF040_MBC5_AUDIO.txt @@ -0,0 +1,56 @@ +{ + "type":"DMG", + "names":[ + "Ferrante Crafts cart 512 KB" + ], + "flash_ids":[ + [ 0xBF, 0xB7, 0x01, 0xFF ] + ], + "voltage":5, + "flash_size":0x80000, + "start_addr":0, + "first_bank":1, + "mbc":0x19, + "flash_commands_on_bank_1":true, + "write_pin":"AUDIO", + "chip_erase_timeout":30, + "command_set":"AMD", + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x90 ] + ], + "chip_erase":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0x80 ], + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 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":[ + [ 0x5555, 0xAA ], + [ 0x2AAA, 0x55 ], + [ 0x5555, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_DMG_TC58FVB016FT.txt b/FlashGBX/config/fc_DMG_TC58FVB016FT.txt index b74e547..42327f0 100644 --- a/FlashGBX/config/fc_DMG_TC58FVB016FT.txt +++ b/FlashGBX/config/fc_DMG_TC58FVB016FT.txt @@ -3,7 +3,10 @@ "names":[ "SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85" ], - "voltage":3.3, + "flash_ids":[ + [ 0x98, 0x45, 0x00, 0x2A ] + ], + "voltage":5, "flash_size":0x200000, "start_addr":0, "first_bank":1, diff --git a/FlashGBX/config/fc_DMG_iG_512KB.txt b/FlashGBX/config/fc_DMG_iG_512KB.txt index ec8ab7f..0122fc0 100644 --- a/FlashGBX/config/fc_DMG_iG_512KB.txt +++ b/FlashGBX/config/fc_DMG_iG_512KB.txt @@ -10,7 +10,6 @@ ], "voltage":5, "power_cycle":true, - "flash_size":0x80000, "start_addr":0, "first_bank":1, "flash_commands_on_bank_1":true, diff --git a/FlashGBX/fw_GBxCartRW_v1_3.py b/FlashGBX/fw_GBxCartRW_v1_3.py index 3cbc200..2f71627 100644 --- a/FlashGBX/fw_GBxCartRW_v1_3.py +++ b/FlashGBX/fw_GBxCartRW_v1_3.py @@ -193,10 +193,10 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog): fw = "" path = "" if self.optCFW.isChecked(): - fw = "Version {:s}".format(self.CFW_VER) + fw = self.CFW_VER fn = "cfw.hex" elif self.optOFW.isChecked(): - fw = "Version {:s}".format(self.OFW_VER) + fw = self.OFW_VER fn = "ofw.hex" else: path = self.APP.SETTINGS.value("LastDirFirmwareUpdate") @@ -298,7 +298,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog): if self.ResetAVR(delay) is False: fncSetStatus(text="Status: Bootloader error.", enableUI=True) self.prgStatus.setValue(0) - msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW bootloader is not responding. If it doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok) + msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update failed as the device is not responding correctly. Please ensure you use a genuine GBxCart RW, re-connect using a different USB cable and try again.\n\n⚠️ For safety reasons and to avoid potential fire hazards, do not use unauthorized clone hardware that have no electrical fuses, such as the “FLASH BOY” series.".replace("\n", "
"), standardButtons=QtWidgets.QMessageBox.Ok) answer = msgbox.exec() return 2 diff --git a/FlashGBX/fw_GBxCartRW_v1_4.py b/FlashGBX/fw_GBxCartRW_v1_4.py index abcc3cc..3c1a05b 100644 --- a/FlashGBX/fw_GBxCartRW_v1_4.py +++ b/FlashGBX/fw_GBxCartRW_v1_4.py @@ -109,7 +109,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog): 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\nGBxCart RW v1.3 can be updated only after connecting to it first. If you want to update another GBxCart RW hardware revision, please use the official firmware updater by insideGadgets instead." + text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nOlder GBxCart RW revisions can be updated only after connecting to them first." msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok) answer = msgbox.exec() diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index b61d5e5..8f93d29 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -895,7 +895,7 @@ class GbxDevice: return buffer - def ReadROM_3DMemory(self, address, length, max_length=512): + def ReadROM_3DMemory(self, address, length, max_length=64): buffer_size = 0x1000 num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge ROM in {:d} iteration(s)".format(length, num)) @@ -920,7 +920,7 @@ class GbxDevice: return buffer - def ReadRAM(self, address, length, command=None, max_length=512): + def ReadRAM(self, address, length, command=None, max_length=64): num = math.ceil(length / max_length) dprint("Reading 0x{:X} bytes from cartridge RAM in {:d} iteration(s)".format(length, num)) if length > max_length: length = max_length @@ -1414,6 +1414,8 @@ class GbxDevice: flashcart.Reset(full_reset=False) dprint("Found the correct cartridge type!") + if self.CanPowerCycleCart(): self.CartPowerCycle() + # Check flash size flash_type_id = 0 cfi_s = "" @@ -1426,19 +1428,20 @@ class GbxDevice: supp_flash_types = self.GetSupportedCartridgesAGB() (flash_id, cfi_s, cfi) = self.CheckFlashChip(limitVoltage=limitVoltage, cart_type=supp_flash_types[1][flash_type_id]) - size = supp_flash_types[1][flash_types[0]]["flash_size"] - size_undetected = False - for i in range(0, len(flash_types)): - if size != supp_flash_types[1][flash_types[i]]["flash_size"]: - size_undetected = True - - if size_undetected: - if isinstance(cfi, dict) and "device_size" in cfi: - for i in range(0, len(flash_types)): - if cfi['device_size'] == supp_flash_types[1][flash_types[i]]["flash_size"]: - flash_type_id = flash_types[i] - size_undetected = False - break + if "flash_size" in supp_flash_types[1][flash_types[0]]: + size = supp_flash_types[1][flash_types[0]]["flash_size"] + size_undetected = False + for i in range(0, len(flash_types)): + if size != supp_flash_types[1][flash_types[i]]["flash_size"]: + size_undetected = True + + if size_undetected: + if isinstance(cfi, dict) and "device_size" in cfi: + for i in range(0, len(flash_types)): + if cfi['device_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) diff --git a/FlashGBX/hw_GBxCartRW_ofw.py b/FlashGBX/hw_GBxCartRW_ofw.py index cda2270..3fbd4d5 100644 --- a/FlashGBX/hw_GBxCartRW_ofw.py +++ b/FlashGBX/hw_GBxCartRW_ofw.py @@ -903,19 +903,20 @@ class GbxDevice: supp_flash_types = self.GetSupportedCartridgesAGB() (flash_id, cfi_s, cfi) = self.CheckFlashChip(limitVoltage=limitVoltage, cart_type=supp_flash_types[1][flash_type_id]) - size = supp_flash_types[1][flash_types[0]]["flash_size"] - size_undetected = False - for i in range(0, len(flash_types)): - if size != supp_flash_types[1][flash_types[i]]["flash_size"]: - size_undetected = True - - if size_undetected: - if isinstance(cfi, dict) and "device_size" in cfi: - for i in range(0, len(flash_types)): - if cfi['device_size'] == supp_flash_types[1][flash_types[i]]["flash_size"]: - flash_type_id = flash_types[i] - size_undetected = False - break + if "flash_size" in supp_flash_types[1][flash_types[0]]: + size = supp_flash_types[1][flash_types[0]]["flash_size"] + size_undetected = False + for i in range(0, len(flash_types)): + if size != supp_flash_types[1][flash_types[i]]["flash_size"]: + size_undetected = True + + if size_undetected: + if isinstance(cfi, dict) and "device_size" in cfi: + for i in range(0, len(flash_types)): + if cfi['device_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) diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index 53c0b14743c601ee68280ad56cb86eead5dca14c..1bfab2eb42bc6977d2e1aa7904cdfc92baf6b8a6 100644 GIT binary patch delta 6061 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 E1LeFuE&u=k diff --git a/FlashGBX/res/fw_GBxCart_RW_v1_3.zip b/FlashGBX/res/fw_GBxCart_RW_v1_3.zip index 53f0126367226a26d42d759f4958497871843327..69e9e83e3ccbf5402e53c473122e7983101208ba 100644 GIT binary patch delta 512 zcmX?FdA_nfz?+#xgn@y9gQ1#hZb9IlEN*}>v+ZZza^c?q0QFH20sSNrky-VzY)uA)a zT^lD&$c)@Df$OM=hg;T(2_a_glWy@^MTPDuubdY7>CBWu>Cv zu$Ozf#6$P2N%AicT2y~}!pjP#-k`^2hn2R^p3tu{`_Dx&r4;*RkKei_WUYMQesOog z8DF_=-#a_sF$)?TRpEFabz#2DD({pHcWqWk-&9fP;tawr4X!0X#b$;8B xNflwyHyEMxWCeY$6oZ6&M(jff1OIS`pyQ3W`kzW*~e9q%T{8cmU9I<5~a! delta 489 zcmV@6aWAK2mp42$5PfKfk<}&005E#000I6003rpE@^IQol?P$ z+At8kueAR#N03-86qQO9AcdgPLw)I1+e5Wl8D~O9>)6)VS;*h;Eqqxgfg)~|_VUJw z=goV1<3Hbs>%C@txxX{z{`DPs@nZr5)5PR!sAyM#@paIDiMC`ZwNPFN!!se_AuZ;O zUCu2ne|vt5u%}dkwiYZIbP)^K=o(?=ThQ+2BP-suGYCd=Vk&kRM}P-|Awi7=X$V%g z$gyXM46@S}0y2dPS=%^&wAAZ9H6eITqEzc34e=|eRX!PY_^HR~8OMG3+~cgrFFj6_ z6s5*O*{s%oFz7sC>IY(