diff --git a/CHANGES.md b/CHANGES.md index e31d105..22ad66d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,9 @@ # Release notes +### v3.26 (released 2023-04-18) +- Fixed a bug that made exporting Game Boy Camera pictures with a frame not work *(thanks Ell)* +- Fixed a bug with Game Boy ROM write verification on sectors smaller than 0x4000 bytes *(thanks KOOORAY)* +- Improved auto-detect for the BennVenn MBC3000 RTC cart + ### v3.25 (released 2023-04-14) - Bundles GBxCart RW v1.4/v1.4a firmware version R41+L9 (minor improvements) - Fixed an issue with the Game Boy Camera Album Viewer *(thanks CodyWick13)* diff --git a/FlashGBX/FlashGBX_GUI.py b/FlashGBX/FlashGBX_GUI.py index 6026f5b..8061828 100644 --- a/FlashGBX/FlashGBX_GUI.py +++ b/FlashGBX/FlashGBX_GUI.py @@ -1017,7 +1017,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): if dontShowAgainCameraSavePopup: self.SETTINGS.setValue("SkipCameraSavePopup", "enabled") if answer == QtWidgets.QMessageBox.Yes: self.CAMWIN = None - self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=Util.CONFIG_PATH) + self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH) self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.CAMWIN.setModal(True) self.CAMWIN.run() @@ -2690,7 +2690,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): data = self.CONN.INFO["data"] self.CAMWIN = None - self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=data, config_path=Util.CONFIG_PATH) + self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=data, config_path=Util.CONFIG_PATH, app_path=Util.APP_PATH) self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) self.CAMWIN.setModal(True) self.CAMWIN.run() diff --git a/FlashGBX/PocketCameraWindow.py b/FlashGBX/PocketCameraWindow.py index 0177534..f52aef9 100644 --- a/FlashGBX/PocketCameraWindow.py +++ b/FlashGBX/PocketCameraWindow.py @@ -17,6 +17,7 @@ class PocketCameraWindow(QtWidgets.QDialog): CUR_EXPORT_PATH = "." CUR_PC = None CUR_PALETTE = 3 + APP_PATH = "." CONFIG_PATH = "." APP = None PALETTES = [ @@ -28,12 +29,13 @@ class PocketCameraWindow(QtWidgets.QDialog): [ 240, 240, 240, 134, 200, 100, 58, 96, 132, 30, 30, 30 ], # Game Boy Color (USA/EUR) ] - def __init__(self, app, file=None, icon=None, config_path="."): + def __init__(self, app, file=None, icon=None, config_path=".", app_path="."): QtWidgets.QDialog.__init__(self) self.setAcceptDrops(True) if icon is not None: self.setWindowIcon(QtGui.QIcon(icon)) self.CUR_FILE = file self.CONFIG_PATH = config_path + self.APP_PATH = app_path self.setWindowTitle("FlashGBX – GB Camera Album Viewer") self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint) @@ -343,7 +345,7 @@ class PocketCameraWindow(QtWidgets.QDialog): frame = True own_frame = self.CONFIG_PATH + "/pc_frame.png" if not os.path.exists(own_frame): - shutil.copy(self.APP.APP_PATH + "/res/pc_frame.png", own_frame) + shutil.copy(self.APP_PATH + "/res/pc_frame.png", own_frame) with open(own_frame, "rb") as f: frame = f.read() if index == 31: frame = False # last seen image diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py index f6fdd31..52b4b7a 100644 --- a/FlashGBX/Util.py +++ b/FlashGBX/Util.py @@ -7,9 +7,9 @@ from enum import Enum # Common constants APPNAME = "FlashGBX" -VERSION_PEP440 = "3.25" +VERSION_PEP440 = "3.26" VERSION = "v{:s}".format(VERSION_PEP440) -VERSION_TIMESTAMP = 1681495829 +VERSION_TIMESTAMP = 1681817120 DEBUG = False DEBUG_LOG = [] APP_PATH = "" @@ -809,4 +809,4 @@ def dprint(*args, **kwargs): DEBUG_LOG = DEBUG_LOG[-32768:] if DEBUG: msg = "{:s}{:s}".format(ANSI.CLEAR_LINE, msg) - #print(msg) + print(msg) diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index a1c9f51..8ade987 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -17,7 +17,7 @@ class GbxDevice: DEVICE_NAME = "GBxCart RW" DEVICE_MIN_FW = 1 DEVICE_MAX_FW = 9 - DEVICE_LATEST_FW_TS = { 4:1619427330, 5:1681395695, 6:1681395696 } + DEVICE_LATEST_FW_TS = { 4:1681739002, 5:1681395695, 6:1681395696 } DEVICE_CMD = { "NULL":0x30, @@ -114,7 +114,7 @@ class GbxDevice: FAST_READ = False SKIPPING = False BAUDRATE = 1000000 - MAX_BUFFER_LEN = 0x2000 + MAX_BUFFER_LEN = 0x800 DEVICE_TIMEOUT = 0.75 WRITE_DELAY = False READ_ERRORS = 0 @@ -144,14 +144,12 @@ 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(): + if not self.LoadFirmwareVersion() or self.FW["pcb_ver"] not in (5, 6, 101): 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: self.ChangeBaudRate(baudrate=1700000) @@ -176,15 +174,17 @@ class GbxDevice: self.DEVICE = None conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " requires a firmware update to work with this software. Please try again after updating it to version L" + str(self.DEVICE_MIN_FW) + " or higher.

Firmware updates are available at https://www.gbxcart.com/."]) continue - #elif self.FW["fw_ver"] < self.DEVICE_MAX_FW: - # conn_msg.append([1, "The GBxCart RW device on port " + ports[i] + " is running an older firmware version. Please consider updating to version L" + str(self.DEVICE_MAX_FW) + " to make use of the latest features.

Firmware updates are available at https://www.gbxcart.com/."]) - elif self.FW["fw_ver"] > self.DEVICE_MAX_FW: + elif self.FW["fw_ts"] > self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]: conn_msg.append([0, "Note: The GBxCart RW device on port " + ports[i] + " is running a firmware version that is newer than what this version of FlashGBX was developed to work with, so errors may occur."]) - if (self.FW["pcb_ver"] not in (4, 5, 6, 101)): # only the v1.3, v1.4, v1.4a, Mini v1.1 PCB revisions are supported + if self.FW["pcb_ver"] not in (4, 5, 6, 101): # only the v1.3, v1.4, v1.4a, Mini v1.1 PCB revisions are supported dev.close() self.DEVICE = None continue + elif self.FW["pcb_ver"] in (5, 6, 101) and self.BAUDRATE > 1000000: + self.MAX_BUFFER_LEN = 0x2000 + else: + self.MAX_BUFFER_LEN = 0x800 conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"]) @@ -213,7 +213,6 @@ class GbxDevice: self.DEVICE.reset_output_buffer() self._write(self.DEVICE_CMD["OFW_PCB_VER"]) temp = self.DEVICE.read(1) - #if len(temp) == 0: return False pcb = temp[0] if pcb == b'': return False self._write(self.DEVICE_CMD["OFW_FW_VER"]) @@ -255,12 +254,10 @@ class GbxDevice: 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 @@ -377,8 +374,10 @@ class GbxDevice: return self.FW["pcb_ver"] in (4, 5, 6) def FirmwareUpdateAvailable(self): - if self.FW["pcb_ver"] not in (5, 6): return False - return (self.FW["pcb_ver"] in (4, 5, 6) and self.FW["fw_ts"] < self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]) + if self.FW["pcb_ver"] not in (4, 5, 6): return False + if (self.FW["pcb_ver"] in (4, 5, 6) and self.FW["fw_ts"] < self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]): + if self.FW["pcb_ver"] == 4: self.FW_UPDATE_REQ = True + return True def GetFirmwareUpdaterClass(self): if self.FW["pcb_ver"] == 4: # v1.3 @@ -1115,7 +1114,10 @@ class GbxDevice: buffer += temp if not self.NO_PROG_UPDATE: self.SetProgress({"action":"READ", "bytes_added":len(temp)}) - + + if self.MODE == "DMG": + self._set_fw_variable("DMG_READ_CS_PULSE", 0) + return buffer def ReadRAM_MBC7(self, address, length): @@ -1168,6 +1170,8 @@ class GbxDevice: buffer.append(data) self.SetProgress({"action":"UPDATE_POS", "abortable":False, "pos":i+1}) + self._set_fw_variable("DMG_READ_CS_PULSE", 0) + self.NO_PROG_UPDATE = npu return buffer @@ -2128,11 +2132,8 @@ class GbxDevice: end_bank = math.ceil((buffer_pos + args["bl_size"]) / rom_bank_size) rom_banks = end_bank - dprint("start_address=0x{:X}, end_address=0x{:X}, start_bank=0x{:X}, rom_banks=0x{:X}".format(start_address, end_address, start_bank, rom_banks)) - + dprint("start_address=0x{:X}, end_address=0x{:X}, start_bank=0x{:X}, rom_banks=0x{:X}, buffer_len=0x{:X}, max_length=0x{:X}".format(start_address, end_address, start_bank, rom_banks, buffer_len, max_length)) bank = start_bank - - #for bank in range(0, rom_banks): while bank < rom_banks: # ↓↓↓ Switch ROM bank if self.MODE == "DMG": @@ -2141,17 +2142,16 @@ class GbxDevice: self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True) (start_address, bank_size) = _mbc.SelectBankROM(bank) end_address = start_address + bank_size - buffer_len = _mbc.GetROMBankSize() + buffer_len = min(buffer_len, _mbc.GetROMBankSize()) if "verify_write" in args: - buffer_len = min(buffer_len, bank_size) - #if "start_addr" in flashcart.CONFIG and bank == 0: start_address = flashcart.CONFIG["start_addr"] + buffer_len = min(buffer_len, bank_size, len(args["verify_write"])) end_address = start_address + bank_size start_address += (buffer_pos % rom_bank_size) if end_address > start_address + args["verify_len"]: end_address = start_address + args["verify_len"] - - # dprint("{:X}/{:X}/{:X}".format(start_address, bank_size, buffer_len)) elif self.MODE == "AGB": + if "verify_write" in args: + buffer_len = min(buffer_len, len(args["verify_write"])) if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0: flashcart.SelectBankROM(bank) temp = end_address - start_address @@ -2163,8 +2163,6 @@ class GbxDevice: pos = start_address lives = 20 - # dprint("pos_total=0x{:X}, start_address=0x{:X}, end_address=0x{:X}".format(pos_total, start_address, end_address)) - while pos < end_address: temp = bytearray() if self.CANCEL: @@ -2184,26 +2182,26 @@ class GbxDevice: temp = self.ReadROM_3DMemory(address=pos, length=buffer_len, max_length=max_length) else: if self.FW["fw_ver"] >= 9 and "verify_write" in args and (self.MODE != "AGB" or args["verify_base_pos"] > 0xC9): - # Verify mode (by checksum) - dprint("Checksum verification (verify_base_pos=0x{:X}, pos=0x{:X}, pos_total=0x{:X}, buffer_len=0x{:X})".format(args["verify_base_pos"], pos, pos_total, buffer_len)) + # Verify mode (by CRC32) + dprint("CRC32 verification (verify_base_pos=0x{:X}, pos=0x{:X}, pos_total=0x{:X}, buffer_len=0x{:X})".format(args["verify_base_pos"], pos, pos_total, buffer_len)) if self.MODE == "DMG": self._set_fw_variable("ADDRESS", pos) elif self.MODE == "AGB": self._set_fw_variable("ADDRESS", pos >> 1) self._write(self.DEVICE_CMD["CALC_CRC32"]) self._write(bytearray(struct.pack(">I", buffer_len))) - checksum_expected = zlib.crc32(args["verify_write"][pos_total:pos_total+buffer_len]) - checksum_calculated = struct.unpack(">I", self._read(4))[0] - dprint("Expected Checksum: 0x{:X}".format(checksum_expected)) - dprint("Calculated Checksum: 0x{:X}".format(checksum_calculated)) - if checksum_expected == checksum_calculated: + crc32_expected = zlib.crc32(args["verify_write"][pos_total:pos_total+buffer_len]) + crc32_calculated = struct.unpack(">I", self._read(4))[0] + dprint("Expected CRC32: 0x{:X}".format(crc32_expected)) + dprint("Calculated CRC32: 0x{:X}".format(crc32_calculated)) + if crc32_expected == crc32_calculated: pos += buffer_len pos_total += buffer_len - dprint("Verification successful between 0x{:X} and 0x{:X}".format(pos_total-buffer_len, pos_total)) + dprint("CRC32 verification successful between 0x{:X} and 0x{:X}".format(pos_total-buffer_len, pos_total)) self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total}) continue else: - dprint("Mismatch during verification between 0x{:X} and 0x{:X}".format(pos_total, pos_total+buffer_len)) + dprint("Mismatch during CRC32 verification between 0x{:X} and 0x{:X}".format(pos_total, pos_total+buffer_len)) return pos_total else: # Normal read @@ -3468,7 +3466,7 @@ class GbxDevice: if self.MODE == "DMG" and _mbc.HasFlashBanks(): _mbc.SelectBankFlash(bank) time.sleep(delay) if self.DEVICE is None: - raise ConnectionAbortedError("An critical connection error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False))) + raise ConnectionAbortedError("A critical connection error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False))) self.CANCEL_ARGS = {} self.CANCEL = False diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index 0665202..43f18ab 100644 Binary files a/FlashGBX/res/config.zip and b/FlashGBX/res/config.zip differ diff --git a/FlashGBX/res/fw_GBxCart_RW_v1_3.zip b/FlashGBX/res/fw_GBxCart_RW_v1_3.zip index 44a9268..fd3d2d3 100644 Binary files a/FlashGBX/res/fw_GBxCart_RW_v1_3.zip and b/FlashGBX/res/fw_GBxCart_RW_v1_3.zip differ diff --git a/README.md b/README.md index 42c7bc1..7b987bd 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ Many different reproduction cartridges share their flash chip command set, so ev The author would like to thank the following very kind people for their help, contributions or documentation (in alphabetical order): -2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, kscheel, kyokohunter, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, Shinichi999, sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, Wkr, x7l7j8cc, xactoes, Zeii, Zelante, zvxr +2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, KOOORAY, kscheel, kyokohunter, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, Shinichi999, sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, Wkr, x7l7j8cc, xactoes, Zeii, Zelante, zvxr ## DISCLAIMER diff --git a/setup.py b/setup.py index dae4935..02a81db 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read( setuptools.setup( name="FlashGBX", - version="3.25", + version="3.26", author="Lesserkuma", description="Reads and writes Game Boy and Game Boy Advance cartridge data using the GBxCart RW by insideGadgets", url="https://github.com/lesserkuma/FlashGBX",