This commit is contained in:
Lesserkuma 2022-04-21 11:39:01 +02:00
parent 4a91df2f71
commit 50b4601d72
31 changed files with 1077 additions and 368 deletions

View File

@ -1,4 +1,18 @@
# Release notes # Release notes
### v3.8 (released 2022-04-21)
- Added support for AGB-E05-01 with MSP55LV100G *(thanks EmperorOfTigers)*
- Added support for DV15 with MSP55LV100G *(thanks zvxr)*
- Added support for SD007_T40_64BALL_TSOP28 with 29LV016T *(thanks Wkr)*
- Confirmed support for AGB-E05-03H with M29W128GH *(thanks Wkr)*
- The integrated firmware updater can now also update the firmware of insideGadgets GBxCart RW v1.1, v1.2, XMAS v1.0 and Mini v1.0 device revisions
- Added experimental support for writing ISX files to Game Boy flash cartridges
- Bundles GBxCart RW v1.4 firmware version R34+L6 (changes the way Game Boy cartridges are read and enables support for some more flash cartridges)
- Added support for the BLAZE Xploder GB unlicensed cheat cartridge (requires firmware version L6+)
- Added support for the unlicensed Sachen mapper (requires firmware version L6+)
- Added support for insideGadgets 128 KB flash cartridges *(thanks AlexiG)*
- Added support for insideGadgets 256 KB flash cartridges *(thanks AlexiG)*
- Minor bug fixes and improvements
### v3.7 (released 2022-03-31) ### v3.7 (released 2022-03-31)
- Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums (improves support for Classic NES Series, NES Classics and Famicom Mini cartridges) - Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums (improves support for Classic NES Series, NES Classics and Famicom Mini cartridges)
- When writing new ROMs to Nintendo Power GB Memory Cartridges (DMG-MMSA-JPN), hidden sector data will now be auto-generated if its not provided by the user in form of a .map file - When writing new ROMs to Nintendo Power GB Memory Cartridges (DMG-MMSA-JPN), hidden sector data will now be auto-generated if its not provided by the user in form of a .map file

View File

@ -127,11 +127,10 @@ def main(portableMode=False):
ap_cli2.add_argument("--dmg-romsize", choices=["auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb"], type=str.lower, default="auto", help="set size of Game Boy cartridge ROM data") ap_cli2.add_argument("--dmg-romsize", choices=["auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb"], type=str.lower, default="auto", help="set size of Game Boy cartridge ROM data")
ap_cli2.add_argument("--dmg-mbc", choices=["auto", "1", "2", "3", "5", "6", "7"], type=str.lower, default="auto", help="set memory bank controller type of Game Boy cartridge") ap_cli2.add_argument("--dmg-mbc", choices=["auto", "1", "2", "3", "5", "6", "7"], type=str.lower, default="auto", help="set memory bank controller type of Game Boy cartridge")
ap_cli2.add_argument("--dmg-savesize", choices=["auto", "4k", "16k", "64k", "256k", "512k", "1m", "eeprom2k", "eeprom4k", "tama5", "4m"], type=str.lower, default="auto", help="set size of Game Boy cartridge save data") ap_cli2.add_argument("--dmg-savesize", choices=["auto", "4k", "16k", "64k", "256k", "512k", "1m", "eeprom2k", "eeprom4k", "tama5", "4m"], type=str.lower, default="auto", help="set size of Game Boy cartridge save data")
ap_cli2.add_argument("--agb-romsize", choices=["auto", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb"], type=str.lower, default="auto", help="set size of Game Boy Advance cartridge ROM data") ap_cli2.add_argument("--agb-romsize", choices=["auto", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb"], type=str.lower, default="auto", help="set size of Game Boy Advance cartridge ROM data")
ap_cli2.add_argument("--agb-savetype", choices=["auto", "eeprom4k", "eeprom64k", "sram256k", "flash512k", "flash1m", "dacs8m", "sram512k", "sram1m"], type=str.lower, default="auto", help="set type of Game Boy Advance cartridge save data") ap_cli2.add_argument("--agb-savetype", choices=["auto", "eeprom4k", "eeprom64k", "sram256k", "flash512k", "flash1m", "dacs8m", "sram512k", "sram1m"], type=str.lower, default="auto", help="set type of Game Boy Advance cartridge save data")
ap_cli2.add_argument("--store-rtc", action="store_true", help="store RTC register values if supported") ap_cli2.add_argument("--store-rtc", action="store_true", help="store RTC register values if supported")
ap_cli2.add_argument("--ignore-bad-header", action="store_true", help="dont stop if invalid data found in cartridge header data") ap_cli2.add_argument("--ignore-bad-header", action="store_true", help="dont stop if invalid data found in cartridge header data")
#ap_cli2.add_argument("--fast-read-mode", action="store_true", help="enable experimental fast read mode for GBxCart RW v1.3")
ap_cli2.add_argument("--flashcart-type", type=str, default="autodetect", help="name of flash cart; see txt files in config directory") ap_cli2.add_argument("--flashcart-type", type=str, default="autodetect", help="name of flash cart; see txt files in config directory")
ap_cli2.add_argument("--prefer-chip-erase", action="store_true", help="prefer full chip erase over sector erase when both available") ap_cli2.add_argument("--prefer-chip-erase", action="store_true", help="prefer full chip erase over sector erase when both available")
ap_cli2.add_argument("--reversed-sectors", action="store_true", help="use reversed flash sectors if possible") ap_cli2.add_argument("--reversed-sectors", action="store_true", help="use reversed flash sectors if possible")

View File

@ -149,7 +149,7 @@ class FlashGBX_CLI():
print("\n{:s}Couldnt read cartridge header. Please try again.{:s}\n".format(ANSI.RED, ANSI.RESET)) print("\n{:s}Couldnt read cartridge header. Please try again.{:s}\n".format(ANSI.RED, ANSI.RESET))
self.DisconnectDevice() self.DisconnectDevice()
return return
elif bad_read and not args.ignore_bad_header: elif bad_read and not args.ignore_bad_header and ("mapper_raw" in header and header["mapper_raw"] != 0x203):
print("\n{:s}Invalid data was detected which usually means that the cartridge couldnt be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean. This check can be disabled with the command line switch “--ignore-bad-header”.{:s}\n".format(ANSI.RED, ANSI.RESET)) print("\n{:s}Invalid data was detected which usually means that the cartridge couldnt be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean. This check can be disabled with the command line switch “--ignore-bad-header”.{:s}\n".format(ANSI.RED, ANSI.RESET))
print("Cartridge Information:") print("Cartridge Information:")
print(s_header) print(s_header)
@ -227,6 +227,8 @@ class FlashGBX_CLI():
print("\033[KPlease wait while the flash chip is being unlocked... (Elapsed time: {:s})".format(Util.formatProgressTime(elapsed)), end="\r") print("\033[KPlease wait while the flash chip is being unlocked... (Elapsed time: {:s})".format(Util.formatProgressTime(elapsed)), end="\r")
elif args["action"] == "SECTOR_ERASE": elif args["action"] == "SECTOR_ERASE":
print("\033[KErasing flash sector at address 0x{:X}...".format(args["sector_pos"]), end="\r") print("\033[KErasing flash sector at address 0x{:X}...".format(args["sector_pos"]), end="\r")
elif args["action"] == "UPDATE_RTC":
print("\nUpdating Real Time Clock...")
elif args["action"] == "ABORTING": elif args["action"] == "ABORTING":
print("\nStopping...") print("\nStopping...")
elif args["action"] == "FINISHED": elif args["action"] == "FINISHED":
@ -244,7 +246,7 @@ class FlashGBX_CLI():
return return
elif args["action"] == "PROGRESS": elif args["action"] == "PROGRESS":
# pv style progress status # pv style progress status
prog_str = "{:s}/{:s} {:s} [{:s}KB/s] [{:s}] {:s}% ETA {:s} ".format(Util.formatFileSize(size=pos).replace(" ", "").replace("Bytes", "B").replace("Byte", "B").rjust(8), Util.formatFileSize(size=size).replace(" ", "").replace("Bytes", "B"), Util.formatProgressTimeShort(elapsed), "{:.2f}".format(speed).rjust(6), "%PROG_BAR%", "{:d}".format(int(pos/size*100)).rjust(3), Util.formatProgressTimeShort(left)) prog_str = "{:s}/{:s} {:s} [{:s}KB/s] [{:s}] {:s}% ETA {:s} ".format(Util.formatFileSize(size=pos).replace(" ", "").replace("Bytes", "B").replace("Byte", "B").rjust(8), Util.formatFileSize(size=size).replace(" ", "").replace("Bytes", "B"), Util.formatProgressTimeShort(elapsed), "{:.2f}".format(speed).rjust(6), "%PROG_BAR%", "{:d}".format(int(pos/size*100)).rjust(3), Util.formatProgressTimeShort(left))
prog_width = shutil.get_terminal_size((80, 20))[0] - (len(prog_str) - 10) prog_width = shutil.get_terminal_size((80, 20))[0] - (len(prog_str) - 10)
progress = min(1, max(0, pos/size)) progress = min(1, max(0, pos/size))
whole_width = math.floor(progress * prog_width) whole_width = math.floor(progress * prog_width)
@ -272,23 +274,36 @@ class FlashGBX_CLI():
elif self.CONN.INFO["last_action"] == 1: # Backup ROM elif self.CONN.INFO["last_action"] == 1: # Backup ROM
self.CONN.INFO["last_action"] = 0 self.CONN.INFO["last_action"] = 0
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
print("CRC32: {:04X}".format(self.CONN.INFO["file_crc32"])) print("CRC32: {:08x}".format(self.CONN.INFO["file_crc32"]))
print("SHA-1: {:s}\n".format(self.CONN.INFO["file_sha1"])) print("SHA-1: {:s}\n".format(self.CONN.INFO["file_sha1"]))
if self.CONN.INFO["rom_checksum"] == self.CONN.INFO["rom_checksum_calc"]: if self.CONN.INFO["rom_checksum"] == self.CONN.INFO["rom_checksum_calc"]:
print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET)) print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET))
elif ("DMG-MMSA-JPN" in self.ARGS["argparsed"].flashcart_type) or ("features_raw" in self.CONN.INFO and self.CONN.INFO["features_raw"] in (0x105, 0x202)): elif ("DMG-MMSA-JPN" in self.ARGS["argparsed"].flashcart_type) or ("mapper_raw" in self.CONN.INFO and self.CONN.INFO["mapper_raw"] in (0x105, 0x202)):
print("The ROM backup is complete!") print("The ROM backup is complete!")
else: else:
print("{:s}The ROM was dumped, but the checksum is not correct. This may indicate a bad dump, however this can be normal for some reproduction prototypes, unlicensed games, patched games and intentional overdumps.{:s}".format(ANSI.YELLOW, ANSI.RESET)) msg = "The ROM was dumped, but the checksum is not correct."
if self.CONN.INFO["loop_detected"] is not False:
msg += "\nA data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
else:
msg += "\nThis may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps."
print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET))
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
print("CRC32: {:04X}".format(self.CONN.INFO["file_crc32"])) print("CRC32: {:08x}".format(self.CONN.INFO["file_crc32"]))
print("SHA-1: {:s}\n".format(self.CONN.INFO["file_sha1"])) print("SHA-1: {:s}\n".format(self.CONN.INFO["file_sha1"]))
if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]: if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]:
print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET)) print("{:s}The ROM backup is complete and the checksum was verified successfully!{:s}".format(ANSI.GREEN, ANSI.RESET))
elif Util.AGB_Global_CRC32 == 0: elif Util.AGB_Global_CRC32 == 0:
print("The ROM backup is complete! As there is no known checksum for this ROM in the database, verification was skipped.") msg = "The ROM backup is complete! As there is no known checksum for this ROM in the database, verification was skipped."
if self.CONN.INFO["loop_detected"] is not False:
msg += "\nNOTE: A data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET))
else: else:
print("{:s}The ROM backup is complete, but the checksum doesnt match the known database entry. This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps.{:s}".format(ANSI.YELLOW, ANSI.RESET)) msg = "The ROM backup is complete, but the checksum doesnt match the known database entry."
if self.CONN.INFO["loop_detected"] is not False:
msg += "\nA data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
else:
msg += "\nThis may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps."
print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET))
elif self.CONN.INFO["last_action"] == 2: # Backup RAM elif self.CONN.INFO["last_action"] == 2: # Backup RAM
self.CONN.INFO["last_action"] = 0 self.CONN.INFO["last_action"] = 0
@ -376,6 +391,10 @@ class FlashGBX_CLI():
self.CONN = None self.CONN = None
return False return False
if dev.FW_UPDATE_REQ:
print("{:s}A firmware update for your {:s} device is required to fully use this software.\nPlease visit the official website at {:s} for updates.\n{:s}Current firmware version: {:s}{:s}".format(ANSI.RED, dev.GetFullName(), dev.GetOfficialWebsite(), ANSI.YELLOW, dev.GetFirmwareVersion(), ANSI.RESET))
time.sleep(5)
self.CONN = dev self.CONN = dev
return True return True
@ -409,7 +428,7 @@ class FlashGBX_CLI():
if data['has_rtc']: if data['has_rtc']:
if 'rtc_buffer' in data: if 'rtc_buffer' in data:
try: try:
if data['features_raw'] == 0x10: # MBC3 if data['mapper_raw'] == 0x10: # MBC3
rtc_s = data["rtc_buffer"][0x00] rtc_s = data["rtc_buffer"][0x00]
rtc_m = data["rtc_buffer"][0x04] rtc_m = data["rtc_buffer"][0x04]
rtc_h = data["rtc_buffer"][0x08] rtc_h = data["rtc_buffer"][0x08]
@ -420,7 +439,7 @@ class FlashGBX_CLI():
s += "Invalid state" s += "Invalid state"
else: else:
s += "{:d} days, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s) s += "{:d} days, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s)
elif data['features_raw'] == 0xFE: # HuC-3 elif data['mapper_raw'] == 0xFE: # HuC-3
rtc_buffer = struct.unpack("<I", data["rtc_buffer"][0:4])[0] rtc_buffer = struct.unpack("<I", data["rtc_buffer"][0:4])[0]
rtc_h = math.floor((rtc_buffer & 0xFFF) / 60) rtc_h = math.floor((rtc_buffer & 0xFFF) / 60)
rtc_m = (rtc_buffer & 0xFFF) % 60 rtc_m = (rtc_buffer & 0xFFF) % 60
@ -435,7 +454,7 @@ class FlashGBX_CLI():
if 'no_rtc_reason' in data: if 'no_rtc_reason' in data:
if data["no_rtc_reason"] == -1: if data["no_rtc_reason"] == -1:
temp = "Unknown" temp = "Unknown"
if data['features_raw'] == 0xFD: # TAMA5 if data['mapper_raw'] == 0xFD: # TAMA5
temp = "OK" temp = "OK"
s += temp s += temp
s += "\n" s += "\n"
@ -459,15 +478,15 @@ class FlashGBX_CLI():
bad_read = True bad_read = True
try: try:
if data['features_raw'] == 0x06: # MBC2 if data['mapper_raw'] == 0x06: # MBC2
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[1]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[1])
elif data['features_raw'] == 0x22 and data["game_title"] in ("KORO2 KIRBYKKKJ", "KIRBY TNT__KTNE"): # MBC7 Kirby elif data['mapper_raw'] == 0x22 and data["game_title"] in ("KORO2 KIRBYKKKJ", "KIRBY TNT__KTNE"): # MBC7 Kirby
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x101)]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x101)])
elif data['features_raw'] == 0x22 and data["game_title"] in ("CMASTER____KCEJ"): # MBC7 Command Master elif data['mapper_raw'] == 0x22 and data["game_title"] in ("CMASTER____KCEJ"): # MBC7 Command Master
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x102)]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x102)])
elif data['features_raw'] == 0xFD: # TAMA5 elif data['mapper_raw'] == 0xFD: # TAMA5
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x103)]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x103)])
elif data['features_raw'] == 0x20: # MBC6 elif data['mapper_raw'] == 0x20: # MBC6
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x104)]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(0x104)])
else: else:
s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(data['ram_size_raw'])]) s += "Save Type: {:s}\n".format(Util.DMG_Header_RAM_Sizes[Util.DMG_Header_RAM_Sizes_Map.index(data['ram_size_raw'])])
@ -475,12 +494,12 @@ class FlashGBX_CLI():
s += "Save Type: Not detected\n" s += "Save Type: Not detected\n"
try: try:
s += "Mapper Type: {:s}\n".format(Util.DMG_Header_Mapper[data['features_raw']]) s += "Mapper Type: {:s}\n".format(Util.DMG_Header_Mapper[data['mapper_raw']])
except: except:
s += "Mapper Type: {:s}Not detected{:s}\n".format(ANSI.RED, ANSI.RESET) s += "Mapper Type: {:s}Not detected{:s}\n".format(ANSI.RED, ANSI.RESET)
bad_read = True bad_read = True
if data['logo_correct'] and not self.CONN.IsSupportedMbc(data["features_raw"]): if data['logo_correct'] and not self.CONN.IsSupportedMbc(data["mapper_raw"]):
print("{:s}\nWARNING: This cartridge uses a mapper that may not be completely supported by {:s} using the current firmware version of the {:s} device. Please check for firmware updates.{:s}".format(ANSI.YELLOW, APPNAME, self.CONN.GetFullName(), ANSI.RESET)) print("{:s}\nWARNING: This cartridge uses a mapper that may not be completely supported by {:s} using the current firmware version of the {:s} device. Please check for firmware updates.{:s}".format(ANSI.YELLOW, APPNAME, self.CONN.GetFullName(), ANSI.RESET))
if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU ") and self.ARGS["argparsed"].flashcart_type == "autodetect": if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU ") and self.ARGS["argparsed"].flashcart_type == "autodetect":
cart_types = self.CONN.GetSupportedCartridgesDMG() cart_types = self.CONN.GetSupportedCartridgesDMG()
@ -662,16 +681,27 @@ class FlashGBX_CLI():
else: else:
(flash_id, cfi_s, _) = self.CONN.CheckFlashChip(limitVoltage=limitVoltage) (flash_id, cfi_s, _) = self.CONN.CheckFlashChip(limitVoltage=limitVoltage)
if (len(flash_id.split("\n")) > 2) and ((self.CONN.GetMode() == "DMG") or ("dacs_8m" in header and header["dacs_8m"] is not True)): if (len(flash_id.split("\n")) > 2) and ((self.CONN.GetMode() == "DMG") or ("dacs_8m" in header and header["dacs_8m"] is not True)):
msg_cart_type_s = "Cartridge Type: Unknown flash cartridge Please submit the displayed information along with a picture of the cartridges circuit board." msg_cart_type_s = "<b>Cartridge Type:</b> Unknown flash cartridge Please submit the displayed information along with a picture of the cartridges circuit board."
if ("[ 0/90]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (0/90)” a try." if ("[ 0/90]" in flash_id):
elif ("[ AAA/AA]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (AAA/AA)” a try." try_this = "Generic Flash Cartridge (0/90)"
elif ("[ AAA/A9]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (AAA/A9)” a try." elif ("[ AAA/AA]" in flash_id):
elif ("[WR / AAA/AA]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (WR/AAA/AA)” a try." try_this = "Generic Flash Cartridge (AAA/AA)"
elif ("[WR / AAA/A9]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (WR/AAA/A9)” a try." elif ("[ AAA/A9]" in flash_id):
elif ("[WR / 555/AA]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (WR/555/AA)” a try." try_this = "Generic Flash Cartridge (AAA/A9)"
elif ("[WR / 555/A9]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (WR/555/A9)” a try." elif ("[WR / AAA/AA]" in flash_id):
elif ("[AUDIO/ AAA/AA]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (AUDIO/AAA/AA)” a try." try_this = "Generic Flash Cartridge (WR/AAA/AA)"
elif ("[AUDIO/ 555/AA]" in flash_id): msg_cart_type_s += " For ROM writing, you can give the option called “Generic Flash Cartridge (AUDIO/555/AA)” a try." elif ("[WR / AAA/A9]" in flash_id):
try_this = "Generic Flash Cartridge (WR/AAA/A9)"
elif ("[WR / 555/AA]" in flash_id):
try_this = "Generic Flash Cartridge (WR/555/AA)"
elif ("[WR / 555/A9]" in flash_id):
try_this = "Generic Flash Cartridge (WR/555/A9)"
elif ("[AUDIO/ AAA/AA]" in flash_id):
try_this = "Generic Flash Cartridge (AUDIO/AAA/AA)"
elif ("[AUDIO/ 555/AA]" in flash_id):
try_this = "Generic Flash Cartridge (AUDIO/555/AA)"
if try_this is not None:
msg_cart_type_s += " For ROM writing, you can give the option called “{:s}” a try at your own risk.".format(try_this)
msg_cart_type_s += "\n" msg_cart_type_s += "\n"
else: else:
msg_cart_type_s = "Cartridge Type: Generic ROM Cartridge (not rewritable or not auto-detectable)\n" msg_cart_type_s = "Cartridge Type: Generic ROM Cartridge (not rewritable or not auto-detectable)\n"
@ -696,11 +726,11 @@ class FlashGBX_CLI():
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
if args.dmg_mbc == "auto": if args.dmg_mbc == "auto":
try: try:
mbc = header["features_raw"] mbc = header["mapper_raw"]
if mbc == 0: mbc = 5 if mbc == 0: mbc = 0x19
except: except:
print("{:s}Couldnt determine MBC type, will try to use MBC5. It can also be manually set with the “--dmg-mbc” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET)) print("{:s}Couldnt determine MBC type, will try to use MBC5. It can also be manually set with the “--dmg-mbc” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET))
mbc = 5 mbc = 0x19
else: else:
mbc = int(args.dmg_mbc) mbc = int(args.dmg_mbc)
if mbc == 2: mbc = 0x06 if mbc == 2: mbc = 0x06
@ -733,7 +763,7 @@ class FlashGBX_CLI():
if args.agb_romsize == "auto": if args.agb_romsize == "auto":
rom_size = header["rom_size"] rom_size = header["rom_size"]
else: else:
sizes = [ "auto", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb" ] sizes = [ "auto", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb" ]
rom_size = Util.AGB_Header_ROM_Sizes_Map[sizes.index(args.agb_romsize) - 1] rom_size = Util.AGB_Header_ROM_Sizes_Map[sizes.index(args.agb_romsize) - 1]
path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii') path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii')
@ -806,6 +836,7 @@ class FlashGBX_CLI():
return return
cart_type = 0 cart_type = 0
mbc = 0
for i in range(0, len(carts)): for i in range(0, len(carts)):
if not "names" in carts[i]: continue if not "names" in carts[i]: continue
if carts[i]["type"] != mode: continue if carts[i]["type"] != mode: continue
@ -840,23 +871,31 @@ class FlashGBX_CLI():
elif os.path.getsize(path) < 0x400: elif os.path.getsize(path) < 0x400:
print("{:s}ROM files smaller than 1 KB are not supported.{:s}".format(ANSI.RED, ANSI.RESET)) print("{:s}ROM files smaller than 1 KB are not supported.{:s}".format(ANSI.RED, ANSI.RESET))
return return
with open(path, "rb") as file: buffer = bytearray(file.read()) #with open(path, "rb") as file: buffer = bytearray(file.read())
with open(path, "rb") as file:
ext = os.path.splitext(path)[1]
if ext.lower() == ".isx":
buffer = bytearray(file.read())
buffer = Util.isx2bin(buffer)
else:
buffer = bytearray(file.read(0x1000))
rom_size = os.stat(path).st_size
if "flash_size" in carts[cart_type]:
if rom_size > carts[cart_type]['flash_size']:
msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(carts[cart_type]['flash_size']), Util.formatFileSize(os.path.getsize(path), roundUp=True))
msg += " You can still give it a try, but its possible that its too large which may cause the ROM writing to fail."
print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET))
answer = input("Do you want to continue? [y/N]: ").strip().lower()
print("")
if answer != "y":
print("Canceled.")
return
except (PermissionError, FileNotFoundError): except (PermissionError, FileNotFoundError):
print("{:s}Couldnt access file path “{:s}”.{:s}".format(ANSI.RED, args.path, ANSI.RESET)) print("{:s}Couldnt access file path “{:s}”.{:s}".format(ANSI.RED, args.path, ANSI.RESET))
return return
rom_size = len(buffer)
if "flash_size" in carts[cart_type]:
if rom_size > carts[cart_type]['flash_size']:
msg = "The selected flash cartridge type seems to support ROMs that are up to {:s} in size, but the file you selected is {:s}.".format(Util.formatFileSize(carts[cart_type]['flash_size']), Util.formatFileSize(os.path.getsize(path)))
msg += " Its possible that its too large which may cause the ROM writing to fail."
print("{:s}{:s}{:s}".format(ANSI.YELLOW, msg, ANSI.RESET))
answer = input("Do you want to continue? [y/N]: ").strip().lower()
print("")
if answer != "y":
print("Canceled.")
return
override_voltage = False override_voltage = False
if args.force_5v is True: if args.force_5v is True:
override_voltage = 5 override_voltage = 5
@ -874,26 +913,24 @@ class FlashGBX_CLI():
if not prefer_chip_erase and 'chip_erase' in carts[cart_type]['commands'] and 'sector_erase' in carts[cart_type]['commands']: if not prefer_chip_erase and 'chip_erase' in carts[cart_type]['commands'] and 'sector_erase' in carts[cart_type]['commands']:
print("This flash cartridge supports both Sector Erase and Full Chip Erase methods. You can use the “--prefer-chip-erase” command line switch if necessary.") print("This flash cartridge supports both Sector Erase and Full Chip Erase methods. You can use the “--prefer-chip-erase” command line switch if necessary.")
if "mbc" in carts[cart_type]:
mbc = carts[cart_type]["mbc"]
verify_write = args.no_verify_write is False verify_write = args.no_verify_write is False
fix_header = False fix_header = False
try: if self.CONN.GetMode() == "DMG":
if self.CONN.GetMode() == "DMG": hdr = RomFileDMG(buffer).GetHeader()
hdr = RomFileDMG(path).GetHeader() elif self.CONN.GetMode() == "AGB":
elif self.CONN.GetMode() == "AGB": hdr = RomFileAGB(buffer).GetHeader()
hdr = RomFileAGB(path).GetHeader() if not hdr["logo_correct"] and mbc != 0x203:
if not hdr["logo_correct"]: print("{:s}WARNING: The ROM file you selected will not boot on actual hardware due to invalid logo data.{:s}".format(ANSI.YELLOW, ANSI.RESET))
print("{:s}WARNING: The ROM file you selected will not boot on actual hardware due to invalid logo data.{:s}".format(ANSI.YELLOW, ANSI.RESET)) if not hdr["header_checksum_correct"] and mbc != 0x203:
if not hdr["header_checksum_correct"]: print("{:s}WARNING: The ROM file you selected will not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).{:s}".format(ANSI.YELLOW, hdr["header_checksum_calc"], hdr["header_checksum"], ANSI.RESET))
print("{:s}WARNING: The ROM file you selected will not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).{:s}".format(ANSI.YELLOW, hdr["header_checksum_calc"], hdr["header_checksum"], ANSI.RESET)) answer = input("Fix the header checksum before continuing? [Y/n]: ").strip().lower()
answer = input("Fix the header checksum before continuing? [Y/n]: ").strip().lower() print("")
print("") if answer != "n":
if answer != "n": fix_header = True
fix_header = True
except:
print("{:s}The selected file could not be read.{:s}".format(ANSI.RED, ANSI.RESET))
return
print("") print("")
v = carts[cart_type]["voltage"] v = carts[cart_type]["voltage"]
@ -901,7 +938,13 @@ class FlashGBX_CLI():
print("The following ROM file will now be written to the flash cartridge at {:s}V:\n{:s}".format(str(v), os.path.abspath(path))) print("The following ROM file will now be written to the flash cartridge at {:s}V:\n{:s}".format(str(v), os.path.abspath(path)))
print("") print("")
self.CONN.TransferData(args={ 'mode':4, 'path':path, 'cart_type':cart_type, 'override_voltage':override_voltage, 'start_addr':0, 'buffer':buffer, 'prefer_chip_erase':prefer_chip_erase, 'reverse_sectors':reverse_sectors, 'fast_read_mode':True, 'verify_write':verify_write, 'fix_header':fix_header }, signal=self.PROGRESS.SetProgress) if len(buffer) > 0x1000:
args = { "mode":4, "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header }
else:
args = { "mode":4, "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header }
self.CONN.TransferData(signal=self.PROGRESS.SetProgress, args=args)
#self.CONN.TransferData(args={ 'mode':4, 'path':path, 'cart_type':cart_type, 'override_voltage':override_voltage, 'start_addr':0, 'buffer':buffer, 'prefer_chip_erase':prefer_chip_erase, 'reverse_sectors':reverse_sectors, 'fast_read_mode':True, 'verify_write':verify_write, 'fix_header':fix_header }, signal=self.PROGRESS.SetProgress)
buffer = None buffer = None
def BackupRestoreRAM(self, args, header): def BackupRestoreRAM(self, args, header):
@ -911,11 +954,11 @@ class FlashGBX_CLI():
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
if args.dmg_mbc == "auto": if args.dmg_mbc == "auto":
try: try:
mbc = header["features_raw"] mbc = header["mapper_raw"]
if mbc == 0: mbc = 5 if mbc == 0: mbc = 0x19
except: except:
print("{:s}Couldnt determine MBC type, will try to use MBC5. It can also be manually set with the “--dmg-mbc” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET)) print("{:s}Couldnt determine MBC type, will try to use MBC5. It can also be manually set with the “--dmg-mbc” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET))
mbc = 5 mbc = 0x19
else: else:
mbc = int(args.dmg_mbc) mbc = int(args.dmg_mbc)
if mbc == 2: mbc = 0x06 if mbc == 2: mbc = 0x06
@ -926,15 +969,15 @@ class FlashGBX_CLI():
if args.dmg_savesize == "auto": if args.dmg_savesize == "auto":
try: try:
if header['features_raw'] == 0x06: # MBC2 if header['mapper_raw'] == 0x06: # MBC2
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[1] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[1]
elif header['features_raw'] == 0x22 and header["game_title"] in ("KORO2 KIRBYKKKJ", "KIRBY TNT_KTNE"): # MBC7 Kirby elif header['mapper_raw'] == 0x22 and header["game_title"] in ("KORO2 KIRBYKKKJ", "KIRBY TNT_KTNE"): # MBC7 Kirby
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x101)] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x101)]
elif header['features_raw'] == 0x22 and header["game_title"] in ("CMASTER_KCEJ"): # MBC7 Command Master elif header['mapper_raw'] == 0x22 and header["game_title"] in ("CMASTER_KCEJ"): # MBC7 Command Master
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x102)] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x102)]
elif header['features_raw'] == 0xFD: # TAMA5 elif header['mapper_raw'] == 0xFD: # TAMA5
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x103)] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x103)]
elif header['features_raw'] == 0x20: # TAMA5 elif header['mapper_raw'] == 0x20: # TAMA5
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x104)] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(0x104)]
else: else:
save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(header['ram_size_raw'])] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(header['ram_size_raw'])]
@ -1017,7 +1060,7 @@ class FlashGBX_CLI():
if self.CONN.GetMode() == "AGB": if self.CONN.GetMode() == "AGB":
print("Using Save Type “{:s}”.".format(Util.AGB_Header_Save_Types[save_type])) print("Using Save Type “{:s}”.".format(Util.AGB_Header_Save_Types[save_type]))
elif self.CONN.GetMode() == "DMG": elif self.CONN.GetMode() == "DMG":
if rtc and header["features_raw"] in (0x10, 0xFE): # RTC of MBC3, HuC-3 if rtc and header["mapper_raw"] in (0x10, 0xFE): # RTC of MBC3, HuC-3
print("Real Time Clock register values will also be written if applicable/possible.") print("Real Time Clock register values will also be written if applicable/possible.")
try: try:
@ -1102,9 +1145,9 @@ class FlashGBX_CLI():
found_length = len(test2) - found_offset found_length = len(test2) - found_offset
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
print("\nDone! The writable save data size is {:s} out of {:s} checked.".format(Util.formatFileSize(found_length), Util.formatFileSize(save_type))) print("\n{:s}Done! The writable save data size is {:s} out of {:s} checked.{:s}".format(ANSI.GREEN, Util.formatFileSize(found_length), Util.formatFileSize(save_type), ANSI.RESET))
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
print("\nDone! The writable save data size using save type “{:s}” is {:s}.".format(Util.AGB_Header_Save_Types[save_type], Util.formatFileSize(found_length))) print("\n{:s}Done! The writable save data size using save type “{:s}” is {:s}.{:s}".format(ANSI.GREEN, Util.AGB_Header_Save_Types[save_type], Util.formatFileSize(found_length), ANSI.RESET))
try: try:
(_, _, cfi) = self.CONN.CheckFlashChip(limitVoltage=False) (_, _, cfi) = self.CONN.CheckFlashChip(limitVoltage=False)

View File

@ -89,6 +89,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rowActionsGeneral2.addWidget(self.btnBackupRAM) rowActionsGeneral2.addWidget(self.btnBackupRAM)
self.cmbDMGCartridgeTypeResult.currentIndexChanged.connect(self.CartridgeTypeChanged) self.cmbDMGCartridgeTypeResult.currentIndexChanged.connect(self.CartridgeTypeChanged)
self.cmbHeaderFeaturesResult.currentIndexChanged.connect(self.DMGMapperTypeChanged)
rowActionsGeneral3 = QtWidgets.QHBoxLayout() rowActionsGeneral3 = QtWidgets.QHBoxLayout()
self.btnFlashROM = QtWidgets.QPushButton("&Write ROM") self.btnFlashROM = QtWidgets.QPushButton("&Write ROM")
@ -665,9 +666,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled") if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
if answer == QtWidgets.QMessageBox.Yes: if answer == QtWidgets.QMessageBox.Yes:
self.ShowFirmwareUpdateWindow() self.ShowFirmwareUpdateWindow()
else: elif dev.FW_UPDATE_REQ:
#self.mnuTools.actions()[2].setEnabled(False) text = "A firmware update for your {:s} device is required to use this software. Please visit the official website (<a href=\"{:s}\">{:s}</a>) for updates.<br><br>Current firmware version: {:s}".format(dev.GetFullName(), dev.GetOfficialWebsite(), dev.GetOfficialWebsite(), dev.GetFirmwareVersion())
pass if not Util.DEBUG:
self.DisconnectDevice()
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
return True return True
return False return False
@ -755,7 +758,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
dontShowAgain = str(self.SETTINGS.value("SkipFinishMessage", default="disabled")).lower() == "enabled" dontShowAgain = str(self.SETTINGS.value("SkipFinishMessage", default="disabled")).lower() == "enabled"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Operation complete!", standardButtons=QtWidgets.QMessageBox.Ok) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Operation complete!", standardButtons=QtWidgets.QMessageBox.Ok)
cb = QtWidgets.QCheckBox("Dont show this message again", checked=False) cb = QtWidgets.QCheckBox("Dont show this message again", checked=False)
msgbox.setCheckBox(cb)
time_elapsed = None time_elapsed = None
if "time_start" in self.STATUS and self.STATUS["time_start"] > 0: if "time_start" in self.STATUS and self.STATUS["time_start"] > 0:
@ -791,32 +793,41 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }") self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }")
self.lblStatus4a.setText("Done!") self.lblStatus4a.setText("Done!")
msg = "The ROM backup is complete and the checksum was verified successfully!" msg = "The ROM backup is complete and the checksum was verified successfully!"
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"]) msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed)) if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed))
msgbox.setText(msg) msgbox.setText(msg)
msgbox.setCheckBox(cb)
if not dontShowAgain: if not dontShowAgain:
msgbox.exec() msgbox.exec()
dontShowAgain = cb.isChecked() dontShowAgain = cb.isChecked()
else: else:
self.lblHeaderROMChecksumResult.setText("Invalid (0x{:04X}≠0x{:04X})".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["rom_checksum"]))
self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblStatus4a.setText("Done.") self.lblStatus4a.setText("Done.")
if ("cart_type" in self.STATUS and "dmg-mmsa-jpn" in self.STATUS["cart_type"]) or ("features_raw" in self.CONN.INFO and self.CONN.INFO["features_raw"] in (0x105, 0x202)): if ("cart_type" in self.STATUS and "dmg-mmsa-jpn" in self.STATUS["cart_type"]) or ("mapper_raw" in self.CONN.INFO and self.CONN.INFO["mapper_raw"] in (0x105, 0x202, 0x203)):
msg = "The ROM backup is complete." msg = "The ROM backup is complete."
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"]) msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok) msgbox.setText(msg)
msgbox.exec()
else: else:
msg = "The ROM was dumped, but the checksum is not correct. This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps." self.lblHeaderROMChecksumResult.setText("Invalid (0x{:04X}≠0x{:04X})".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["rom_checksum"]))
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"]) self.lblHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }")
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok) msg = "The ROM was dumped, but the checksum is not correct."
if self.CONN.INFO["loop_detected"] is not False:
msg += "\n\nA data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
else:
msg += " This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps."
msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
msgbox.setText(msg)
msgbox.setIcon(QtWidgets.QMessageBox.Warning)
msgbox.exec()
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]: if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]:
self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(Util.AGB_Global_CRC32)) self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(Util.AGB_Global_CRC32))
self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }") self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }")
self.lblStatus4a.setText("Done!") self.lblStatus4a.setText("Done!")
msg = "The ROM backup is complete and the checksum was verified successfully!" msg = "The ROM backup is complete and the checksum was verified successfully!"
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"]) msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
msgbox.setText(msg) msgbox.setText(msg)
msgbox.setCheckBox(cb)
if not dontShowAgain: if not dontShowAgain:
msgbox.exec() msgbox.exec()
dontShowAgain = cb.isChecked() dontShowAgain = cb.isChecked()
@ -825,17 +836,27 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet()) self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblStatus4a.setText("Done!") self.lblStatus4a.setText("Done!")
msg = "The ROM backup is complete! As there is no known checksum for this ROM in the database, verification was skipped." msg = "The ROM backup is complete! As there is no known checksum for this ROM in the database, verification was skipped."
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["file_sha1"]) if self.CONN.INFO["loop_detected"] is not False:
msg += "\n\nNOTE: A data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
msgbox.setIcon(QtWidgets.QMessageBox.Warning)
msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["file_sha1"])
if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed)) if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed))
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok) msgbox.setText(msg)
msgbox.exec()
else: else:
self.lblAGBHeaderROMChecksumResult.setText("Invalid (0x{:06X}≠0x{:06X})".format(self.CONN.INFO["rom_checksum_calc"], Util.AGB_Global_CRC32)) self.lblAGBHeaderROMChecksumResult.setText("Invalid (0x{:06X}≠0x{:06X})".format(self.CONN.INFO["rom_checksum_calc"], Util.AGB_Global_CRC32))
self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }") self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblStatus4a.setText("Done.") self.lblStatus4a.setText("Done.")
msg = "The ROM backup is complete, but the checksum doesnt match the known database entry. This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps." msg = "The ROM backup is complete, but the checksum doesnt match the known database entry."
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["file_sha1"]) if self.CONN.INFO["loop_detected"] is not False:
msg += "\n\nA data loop was detected in the ROM backup at position 0x{:X} ({:s}). This may indicate a bad dump or overdump.".format(self.CONN.INFO["loop_detected"], Util.formatFileSize(self.CONN.INFO["loop_detected"], asInt=True))
else:
msg += " This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps."
msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["file_sha1"])
if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed)) if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed))
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok) msgbox.setText(msg)
msgbox.setIcon(QtWidgets.QMessageBox.Warning)
msgbox.exec()
elif self.CONN.INFO["last_action"] == 2: # Backup RAM elif self.CONN.INFO["last_action"] == 2: # Backup RAM
self.lblStatus4a.setText("Done!") self.lblStatus4a.setText("Done!")
@ -853,6 +874,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
return return
msgbox.setText("The save data backup is complete!") msgbox.setText("The save data backup is complete!")
msgbox.setCheckBox(cb)
if not dontShowAgain: if not dontShowAgain:
msgbox.exec() msgbox.exec()
dontShowAgain = cb.isChecked() dontShowAgain = cb.isChecked()
@ -868,6 +890,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else: else:
msg_text = "Save data writing complete!" msg_text = "Save data writing complete!"
msgbox.setText(msg_text) msgbox.setText(msg_text)
msgbox.setCheckBox(cb)
if not dontShowAgain: if not dontShowAgain:
msgbox.exec() msgbox.exec()
dontShowAgain = cb.isChecked() dontShowAgain = cb.isChecked()
@ -895,6 +918,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed)) if time_elapsed is not None: msg += "\n\nTotal time elapsed: {:s}".format(Util.formatProgressTime(time_elapsed))
msgbox.setText(msg) msgbox.setText(msg)
msgbox.setCheckBox(cb)
if not dontShowAgain: if not dontShowAgain:
msgbox.exec() msgbox.exec()
dontShowAgain = cb.isChecked() dontShowAgain = cb.isChecked()
@ -908,8 +932,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if dontShowAgain: self.SETTINGS.setValue("SkipFinishMessage", "enabled") if dontShowAgain: self.SETTINGS.setValue("SkipFinishMessage", "enabled")
self.SetProgressBars(min=0, max=1, value=1) self.SetProgressBars(min=0, max=1, value=1)
def DMGMapperTypeChanged(self, index):
if index in (-1, 0): return
#if ((list(Util.DMG_Header_Mapper.items())[index])[0]) == 0x203: # Xploder GB
# self.cmbHeaderROMSizeResult.setCurrentIndex(Util.DMG_Header_ROM_Sizes_Flasher_Map.index(0x40000))
def CartridgeTypeChanged(self, index): def CartridgeTypeChanged(self, index):
if index in (-1, 0): return
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
cart_types = self.CONN.GetSupportedCartridgesDMG() cart_types = self.CONN.GetSupportedCartridgesDMG()
if cart_types[1][index] == "RETAIL": # special keyword if cart_types[1][index] == "RETAIL": # special keyword
@ -996,7 +1026,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
just_erase = False just_erase = False
path = "" path = ""
if dpath != "": if dpath != "":
text = "The following ROM file will now be written to the flash cartridge:\n" + dpath ext = os.path.splitext(dpath)[1]
if ext.lower() == ".isx":
text = "The following ISX file will now be converted to a regular ROM file and then written to the flash cartridge:\n" + dpath
else:
text = "The following ROM file will now be written to the flash cartridge:\n" + dpath
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok) answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
if answer == QtWidgets.QMessageBox.Cancel: return if answer == QtWidgets.QMessageBox.Cancel: return
path = dpath path = dpath
@ -1029,9 +1063,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type) self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderFeaturesResult.currentIndex()])[0]
if (path == ""): if (path == ""):
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
path = QtWidgets.QFileDialog.getOpenFileName(self, "Write ROM", last_dir, "Game Boy ROM File (*.gb *.gbc *.sgb *.bin);;All Files (*.*)")[0] path = QtWidgets.QFileDialog.getOpenFileName(self, "Write ROM", last_dir, "Game Boy ROM File (*.gb *.gbc *.sgb *.bin *.isx);;All Files (*.*)")[0]
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
path = QtWidgets.QFileDialog.getOpenFileName(self, "Write ROM", last_dir, "Game Boy Advance ROM File (*.gba *.srl *.bin);;All Files (*.*)")[0] path = QtWidgets.QFileDialog.getOpenFileName(self, "Write ROM", last_dir, "Game Boy Advance ROM File (*.gba *.srl *.bin);;All Files (*.*)")[0]
@ -1049,7 +1085,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "ROM files bigger than 256 MB are not supported.", QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "ROM files bigger than 256 MB are not supported.", QtWidgets.QMessageBox.Ok)
return return
with open(path, "rb") as file: buffer = bytearray(file.read(0x1000)) with open(path, "rb") as file:
ext = os.path.splitext(path)[1]
if ext.lower() == ".isx":
buffer = bytearray(file.read())
buffer = Util.isx2bin(buffer)
else:
buffer = bytearray(file.read(0x1000))
rom_size = os.stat(path).st_size rom_size = os.stat(path).st_size
if "flash_size" in carts[cart_type]: if "flash_size" in carts[cart_type]:
if rom_size > carts[cart_type]['flash_size']: if rom_size > carts[cart_type]['flash_size']:
@ -1057,6 +1099,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msg += " You can still give it a try, but its possible that its too large which may cause the ROM writing to fail." msg += " You can still give it a try, but its possible that its too large which may cause the ROM writing to fail."
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
if answer == QtWidgets.QMessageBox.Cancel: return if answer == QtWidgets.QMessageBox.Cancel: return
if "mbc" in carts[cart_type]:
mbc = carts[cart_type]["mbc"]
override_voltage = False override_voltage = False
if 'voltage_variants' in carts[cart_type] and carts[cart_type]['voltage'] == 3.3: if 'voltage_variants' in carts[cart_type] and carts[cart_type]['voltage'] == 3.3:
@ -1102,10 +1146,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
hdr = RomFileDMG(buffer).GetHeader() hdr = RomFileDMG(buffer).GetHeader()
elif self.CONN.GetMode() == "AGB": elif self.CONN.GetMode() == "AGB":
hdr = RomFileAGB(buffer).GetHeader() hdr = RomFileAGB(buffer).GetHeader()
if not hdr["logo_correct"]: if not hdr["logo_correct"] and mbc != 0x203:
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "Warning: The ROM file you selected will not boot on actual hardware due to invalid logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "Warning: The ROM file you selected will not boot on actual hardware due to invalid logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
if answer == QtWidgets.QMessageBox.Cancel: return if answer == QtWidgets.QMessageBox.Cancel: return
if not hdr["header_checksum_correct"]: if not hdr["header_checksum_correct"] and mbc != 0x203:
msg_text = "Warning: The ROM file you selected will not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).".format(hdr["header_checksum_calc"], hdr["header_checksum"]) msg_text = "Warning: The ROM file you selected will not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).".format(hdr["header_checksum_calc"], hdr["header_checksum"])
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg_text) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg_text)
button_fix = msgbox.addButton(" &Fix and Continue ", QtWidgets.QMessageBox.ActionRole) button_fix = msgbox.addButton(" &Fix and Continue ", QtWidgets.QMessageBox.ActionRole)
@ -1128,9 +1172,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.btnConfig.setEnabled(False) self.btnConfig.setEnabled(False)
self.lblStatus4a.setText("Preparing...") self.lblStatus4a.setText("Preparing...")
qt_app.processEvents() qt_app.processEvents()
if just_erase: if len(buffer) > 0x1000 or just_erase:
prefer_chip_erase = True if just_erase:
verify_write = False prefer_chip_erase = True
verify_write = False
args = { "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header } args = { "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header }
else: else:
args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header } args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "reverse_sectors":reverse_sectors, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header }
@ -1254,7 +1299,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The save data on your cartridge will now be erased.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The save data on your cartridge will now be erased.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
if answer == QtWidgets.QMessageBox.Cancel: return if answer == QtWidgets.QMessageBox.Cancel: return
else: else:
path = path + ".sav" path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav"
path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0] path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0]
if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path)) if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path))
if (path == ""): return if (path == ""): return
@ -1282,7 +1327,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
cb = QtWidgets.QCheckBox("&Adjust RTC", checked=True) cb = QtWidgets.QCheckBox("&Adjust RTC", checked=True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes) msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes)
msgbox.setCheckBox(cb) if erase:
cb.setChecked(True)
else:
msgbox.setCheckBox(cb)
answer = msgbox.exec() answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return if answer == QtWidgets.QMessageBox.Cancel: return
rtc_advance = cb.isChecked() rtc_advance = cb.isChecked()
@ -1442,7 +1490,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data['has_rtc']: if data['has_rtc']:
if 'rtc_buffer' in data: if 'rtc_buffer' in data:
try: try:
if data['features_raw'] == 0x10: # MBC3 if data['mapper_raw'] == 0x10: # MBC3
rtc_s = data["rtc_buffer"][0x00] rtc_s = data["rtc_buffer"][0x00]
rtc_m = data["rtc_buffer"][0x04] rtc_m = data["rtc_buffer"][0x04]
rtc_h = data["rtc_buffer"][0x08] rtc_h = data["rtc_buffer"][0x08]
@ -1456,7 +1504,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblHeaderRtcResult.setText("{:d} day, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s)) self.lblHeaderRtcResult.setText("{:d} day, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s))
else: else:
self.lblHeaderRtcResult.setText("{:d} days, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s)) self.lblHeaderRtcResult.setText("{:d} days, {:02d}:{:02d}:{:02d}".format(rtc_d, rtc_h, rtc_m, rtc_s))
elif data['features_raw'] == 0xFE: # HuC-3 elif data['mapper_raw'] == 0xFE: # HuC-3
rtc_buffer = struct.unpack("<I", data["rtc_buffer"][0:4])[0] rtc_buffer = struct.unpack("<I", data["rtc_buffer"][0:4])[0]
rtc_h = math.floor((rtc_buffer & 0xFFF) / 60) rtc_h = math.floor((rtc_buffer & 0xFFF) / 60)
rtc_m = (rtc_buffer & 0xFFF) % 60 rtc_m = (rtc_buffer & 0xFFF) % 60
@ -1474,7 +1522,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if 'no_rtc_reason' in data: if 'no_rtc_reason' in data:
if data["no_rtc_reason"] == -1: if data["no_rtc_reason"] == -1:
self.lblHeaderRtcResult.setText("Unknown") self.lblHeaderRtcResult.setText("Unknown")
if data['features_raw'] == 0xFD: # TAMA5 if data['mapper_raw'] == 0xFD: # TAMA5
self.lblHeaderRtcResult.setText("OK") self.lblHeaderRtcResult.setText("OK")
if data['logo_correct']: if data['logo_correct']:
@ -1491,13 +1539,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblHeaderChecksumResult.setStyleSheet("QLabel { color: red; }") self.lblHeaderChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblHeaderROMChecksumResult.setText("0x{:04X}".format(data['rom_checksum'])) self.lblHeaderROMChecksumResult.setText("0x{:04X}".format(data['rom_checksum']))
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet()) self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.cmbHeaderROMSizeResult.setCurrentIndex(data["rom_size_raw"]) self.cmbHeaderROMSizeResult.setCurrentIndex(data["rom_size_raw"])
for i in range(0, len(Util.DMG_Header_RAM_Sizes_Map)): for i in range(0, len(Util.DMG_Header_RAM_Sizes_Map)):
if data["ram_size_raw"] == Util.DMG_Header_RAM_Sizes_Map[i]: if data["ram_size_raw"] == Util.DMG_Header_RAM_Sizes_Map[i]:
self.cmbHeaderRAMSizeResult.setCurrentIndex(i) self.cmbHeaderRAMSizeResult.setCurrentIndex(i)
i = 0 i = 0
for k in Util.DMG_Header_Mapper.keys(): for k in Util.DMG_Header_Mapper.keys():
if data["features_raw"] == k: if data["mapper_raw"] == k:
self.cmbHeaderFeaturesResult.setCurrentIndex(i) self.cmbHeaderFeaturesResult.setCurrentIndex(i)
if k == 0x06: # MBC2 if k == 0x06: # MBC2
self.cmbHeaderRAMSizeResult.setCurrentIndex(1) self.cmbHeaderRAMSizeResult.setCurrentIndex(1)
@ -1518,12 +1567,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else: else:
self.lblHeaderTitleResult.setText("(No ROM data detected)") self.lblHeaderTitleResult.setText("(No ROM data detected)")
self.lblHeaderTitleResult.setStyleSheet("QLabel { color: red; }") self.lblHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.cmbHeaderROMSizeResult.setCurrentIndex(11) self.cmbHeaderROMSizeResult.setCurrentIndex(0)
self.cmbHeaderRAMSizeResult.setCurrentIndex(0) self.cmbHeaderRAMSizeResult.setCurrentIndex(0)
self.cmbHeaderFeaturesResult.setCurrentIndex(0) self.cmbHeaderFeaturesResult.setCurrentIndex(0)
else: else:
self.lblHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet()) self.lblHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
if data['logo_correct'] and not self.CONN.IsSupportedMbc(data["features_raw"]) and resetStatus: if data['logo_correct'] and not self.CONN.IsSupportedMbc(data["mapper_raw"]) and resetStatus:
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge uses a mapper that may not be completely supported by {:s} using the current firmware version of the {:s} device. Please check for firmware updates in the Tools menu or the makers website.".format(APPNAME, self.CONN.GetFullName()), QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge uses a mapper that may not be completely supported by {:s} using the current firmware version of the {:s} device. Please check for firmware updates in the Tools menu or the makers website.".format(APPNAME, self.CONN.GetFullName()), QtWidgets.QMessageBox.Ok)
if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU "): if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU "):
cart_types = self.CONN.GetSupportedCartridgesDMG() cart_types = self.CONN.GetSupportedCartridgesDMG()
@ -1531,6 +1580,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if "dmg-mmsa-jpn" in cart_types[1][i]: if "dmg-mmsa-jpn" in cart_types[1][i]:
self.cmbDMGCartridgeTypeResult.setCurrentIndex(i) self.cmbDMGCartridgeTypeResult.setCurrentIndex(i)
if data["mapper_raw"] == 0x203: # Xploder GB
self.lblHeaderRtcResult.setText("")
self.lblHeaderLogoValidResult.setText("")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderChecksumResult.setText("")
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.lblHeaderROMChecksumResult.setText("")
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
elif data["mapper_raw"] == 0x204: # Sachen
self.lblHeaderRtcResult.setText("")
self.lblHeaderRevisionResult.setText("")
self.lblHeaderLogoValidResult.setText("")
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.grpAGBCartridgeInfo.setVisible(False) self.grpAGBCartridgeInfo.setVisible(False)
self.grpDMGCartridgeInfo.setVisible(True) self.grpDMGCartridgeInfo.setVisible(True)
@ -1627,7 +1690,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else: else:
self.lblAGBHeaderTitleResult.setText("(No ROM data detected)") self.lblAGBHeaderTitleResult.setText("(No ROM data detected)")
self.lblAGBHeaderTitleResult.setStyleSheet("QLabel { color: red; }") self.lblAGBHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.cmbAGBHeaderROMSizeResult.setCurrentIndex(3)
self.cmbAGBSaveTypeResult.setCurrentIndex(0) self.cmbAGBSaveTypeResult.setCurrentIndex(0)
else: else:
self.lblAGBHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet()) self.lblAGBHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
@ -1646,7 +1708,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.grpStatus.setTitle("Transfer Status") self.grpStatus.setTitle("Transfer Status")
self.FinishOperation() self.FinishOperation()
if not data['logo_correct'] and data['empty'] == False and resetStatus: if not data['logo_correct'] and data['empty'] == False and resetStatus and not (self.CONN.GetMode() == "DMG" and data["mapper_raw"] in (0x203, 0x204)):
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The Nintendo Logo check failed which usually means that the cartridge cant be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean.", QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The Nintendo Logo check failed which usually means that the cartridge cant be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean.", QtWidgets.QMessageBox.Ok)
if data['game_title'][:11] == "YJencrypted" and resetStatus: if data['game_title'][:11] == "YJencrypted" and resetStatus:
@ -1852,6 +1914,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msg_fw = "" msg_fw = ""
button_clipboard = None button_clipboard = None
if self.CONN.GetMode() == "DMG" and limitVoltage and (is_generic or not found_supported):
text = "No known flash cartridge type could be detected. The option “Limit voltage to 3.3V when detecting Game Boy flash cartridges” has been enabled which can cause auto-detection to fail. As it is usually not recommended to enable this option, do you now want to disable it and try again?"
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes)
if answer == QtWidgets.QMessageBox.Yes:
self.SETTINGS.setValue("AutoDetectLimitVoltage", "disabled")
self.mnuConfig.actions()[4].setChecked(False)
return self.DetectCartridge()
temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_fw) temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_fw)
temp = temp[:-4] temp = temp[:-4]
msgbox.setText(temp) msgbox.setText(temp)
@ -1945,6 +2015,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblStatus4aResult.setText("") self.lblStatus4aResult.setText("")
self.btnCancel.setEnabled(args["abortable"]) self.btnCancel.setEnabled(args["abortable"])
self.SetProgressBars(min=0, max=size, value=pos) self.SetProgressBars(min=0, max=size, value=pos)
elif args["action"] == "UPDATE_RTC":
self.lblStatus1aResult.setText("Pending...")
self.lblStatus2aResult.setText("Pending...")
self.lblStatus3aResult.setText("Pending...")
self.lblStatus4a.setText("Updating Real Time Clock...")
self.lblStatus4aResult.setText("")
self.btnCancel.setEnabled(False)
self.SetProgressBars(min=0, max=size, value=pos)
elif args["action"] == "SECTOR_ERASE": elif args["action"] == "SECTOR_ERASE":
if elapsed >= 1: if elapsed >= 1:
self.lblStatus3aResult.setText(Util.formatProgressTime(elapsed)) self.lblStatus3aResult.setText(Util.formatProgressTime(elapsed))
@ -2065,7 +2143,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.SetMode() self.SetMode()
if self.CONN.GetMode() == "DMG": if self.CONN.GetMode() == "DMG":
header = self.CONN.ReadInfo(setPinsAsInputs=True) header = self.CONN.ReadInfo(setPinsAsInputs=True)
if header["features_raw"] == 252: # GBD if header["mapper_raw"] == 252: # GBD
args = { "path":None, "mbc":252, "save_type":128*1024, "rtc":False } args = { "path":None, "mbc":252, "save_type":128*1024, "rtc":False }
self.CONN.BackupRAM(fncSetProgress=False, args=args) self.CONN.BackupRAM(fncSetProgress=False, args=args)
data = self.CONN.INFO["data"] data = self.CONN.INFO["data"]
@ -2100,7 +2178,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
fn_split = os.path.splitext(os.path.abspath(fn)) fn_split = os.path.splitext(os.path.abspath(fn))
if fn_split[1].lower() == ".sav": if fn_split[1].lower() == ".sav":
return True return True
elif self.CONN.GetMode() == "DMG" and fn_split[1].lower() in (".gb", ".sgb", ".gbc", ".bin"): elif self.CONN.GetMode() == "DMG" and fn_split[1].lower() in (".gb", ".sgb", ".gbc", ".bin", ".isx"):
return True return True
elif self.CONN.GetMode() == "AGB" and fn_split[1].lower() in (".gba", ".srl"): elif self.CONN.GetMode() == "AGB" and fn_split[1].lower() in (".gba", ".srl"):
return True return True
@ -2120,7 +2198,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
fn = str(url.toLocalFile()) fn = str(url.toLocalFile())
fn_split = os.path.splitext(os.path.abspath(fn)) fn_split = os.path.splitext(os.path.abspath(fn))
if fn_split[1].lower() in (".gb", ".sgb", ".gbc", ".bin", ".gba", ".srl"): if fn_split[1].lower() in (".gb", ".sgb", ".gbc", ".bin", ".isx", ".gba", ".srl"):
self.FlashROM(fn) self.FlashROM(fn)
elif fn_split[1].lower() == ".sav": elif fn_split[1].lower() == ".sav":
self.WriteRAM(fn) self.WriteRAM(fn)

View File

@ -9,6 +9,7 @@ class Flashcart:
CONFIG = {} CONFIG = {}
COMMAND_SET = None COMMAND_SET = None
CART_WRITE_FNCPTR = None CART_WRITE_FNCPTR = None
CART_WRITE_FAST_FNCPTR = None
CART_READ_FNCPTR = None CART_READ_FNCPTR = None
CART_POWERCYCLE_FNCPTR = None CART_POWERCYCLE_FNCPTR = None
PROGRESS_FNCPTR = None PROGRESS_FNCPTR = None
@ -17,9 +18,10 @@ class Flashcart:
SECTOR_MAP = None SECTOR_MAP = None
CFI = None CFI = None
def __init__(self, config=None, cart_write_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):
if config is None: config = {} if config is None: config = {}
self.CART_WRITE_FNCPTR = cart_write_fncptr 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_READ_FNCPTR = cart_read_fncptr
self.CART_POWERCYCLE_FNCPTR = cart_powercycle_fncptr self.CART_POWERCYCLE_FNCPTR = cart_powercycle_fncptr
self.PROGRESS_FNCPTR = progress_fncptr self.PROGRESS_FNCPTR = progress_fncptr
@ -40,12 +42,16 @@ class Flashcart:
return self.CART_READ_FNCPTR(address, length) return self.CART_READ_FNCPTR(address, length)
def CartWrite(self, commands, flashcart=True, sram=False): def CartWrite(self, commands, flashcart=True, sram=False):
if "command_set" in self.CONFIG and self.CONFIG["command_set"] == "DMG-MBC5-32M-FLASH": flashcart = False if "command_set" in self.CONFIG and self.CONFIG["command_set"] in ("GBMEMORY", "DMG-MBC5-32M-FLASH"): flashcart = False
for command in commands: dprint(commands, flashcart, sram)
address = command[0] if flashcart and not sram:
value = command[1] self.CART_WRITE_FAST_FNCPTR(commands, flashcart=True)
self.CART_WRITE_FNCPTR(address, value, flashcart=flashcart, sram=sram) else:
for command in commands:
address = command[0]
value = command[1]
self.CART_WRITE_FNCPTR(address, value, flashcart=flashcart, sram=sram)
def GetCommandSetType(self): def GetCommandSetType(self):
return self.CONFIG["_command_set"].upper() return self.CONFIG["_command_set"].upper()
@ -144,6 +150,12 @@ class Flashcart:
def Unlock(self): def Unlock(self):
self.CartRead(0) # dummy read self.CartRead(0) # dummy read
if "unlock_read" in self.CONFIG["commands"]:
for command in self.CONFIG["commands"]["unlock_read"]:
for _ in range(0, command[2]):
temp = self.CartRead(command[0], command[1])
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"]: if "unlock" in self.CONFIG["commands"]:
self.CartWrite(self.CONFIG["commands"]["unlock"]) self.CartWrite(self.CONFIG["commands"]["unlock"])
time.sleep(0.001) time.sleep(0.001)
@ -167,13 +179,18 @@ class Flashcart:
def VerifyFlashID(self): def VerifyFlashID(self):
if "read_identifier" not in self.CONFIG["commands"]: return False if "read_identifier" not in self.CONFIG["commands"]: return False
if len(self.CONFIG["flash_ids"]) == 0: return False if len(self.CONFIG["flash_ids"]) == 0: return False
if "power_cycle" in self.CONFIG and self.CONFIG["power_cycle"] is True:
self.CART_POWERCYCLE_FNCPTR()
self.Reset() self.Reset()
rom = list(self.CartRead(0, len(self.CONFIG["flash_ids"][0]))) rom = list(self.CartRead(0, len(self.CONFIG["flash_ids"][0])))
self.Unlock() self.Unlock()
self.CartWrite(self.CONFIG["commands"]["read_identifier"]) self.CartWrite(self.CONFIG["commands"]["read_identifier"])
time.sleep(0.001) time.sleep(0.001)
cart_flash_id = list(self.CartRead(0, len(self.CONFIG["flash_ids"][0]))) read_identifier_at = 0
if "read_identifier_at" in self.CONFIG: read_identifier_at = self.CONFIG["read_identifier_at"]
cart_flash_id = list(self.CartRead(read_identifier_at, len(self.CONFIG["flash_ids"][0])))
self.Reset() self.Reset()
dprint(self.CONFIG["names"], self.CONFIG["commands"]["read_identifier"])
dprint("Flash ID: {:s}".format(' '.join(format(x, '02X') for x in cart_flash_id))) dprint("Flash ID: {:s}".format(' '.join(format(x, '02X') for x in cart_flash_id)))
verified = True verified = True
if (rom == cart_flash_id): if (rom == cart_flash_id):

View File

@ -11,7 +11,9 @@ class GBMemoryMap:
IS_MENU = False IS_MENU = False
def __init__(self, rom=None): def __init__(self, rom=None):
if rom is not None: if rom == bytearray([0xFF] * len(rom)):
self.MAP_DATA = bytearray([0x00] * 0x80)
elif rom is not None:
self.ImportROM(rom) self.ImportROM(rom)
def ImportROM(self, data): def ImportROM(self, data):
@ -26,7 +28,7 @@ class GBMemoryMap:
data = bytearray(data) + bytearray([0xFF] * (0x20000 - len(data))) data = bytearray(data) + bytearray([0xFF] * (0x20000 - len(data)))
if not self.IS_MENU: if not self.IS_MENU:
mbc_type = self.MapperToMBCType(info["rom_header"]["features_raw"]) mbc_type = self.MapperToMBCType(info["rom_header"]["mapper_raw"])
if mbc_type is False: return if mbc_type is False: return
if len(data) <= 0x20000: if len(data) <= 0x20000:
rom_size = 0b010 rom_size = 0b010
@ -127,7 +129,7 @@ class GBMemoryMap:
info["ram_start_block"] = int(ram_offset / 0x800) info["ram_start_block"] = int(ram_offset / 0x800)
ram_offset += info["ram_data_size"] ram_offset += info["ram_data_size"]
info["rom_header"] = RomFileDMG(data[info["rom_data_offset"]:info["rom_data_offset"]+0x180]).GetHeader() info["rom_header"] = RomFileDMG(data[info["rom_data_offset"]:info["rom_data_offset"]+0x180]).GetHeader()
mbc_type = self.MapperToMBCType(info["rom_header"]["features_raw"]) mbc_type = self.MapperToMBCType(info["rom_header"]["mapper_raw"])
if mbc_type is False: return if mbc_type is False: return
if info["rom_data_size"] <= 0x20000: if info["rom_data_size"] <= 0x20000:
@ -192,7 +194,7 @@ class GBMemoryMap:
mbc_type = 2 mbc_type = 2
elif mbc in (0x10, 0x13): # MBC3 elif mbc in (0x10, 0x13): # MBC3
mbc_type = 3 mbc_type = 3
elif mbc in (0x19, 0x1A, 0x1B, 0x1C, 0x1E): # MBC5 elif mbc in (0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x105): # MBC5
mbc_type = 5 mbc_type = 5
else: else:
mbc_type = False mbc_type = False
@ -217,5 +219,6 @@ class GBMemoryMap:
return self.IS_MENU return self.IS_MENU
def GetMapData(self): def GetMapData(self):
if self.MAP_DATA == bytearray([0xFF] * 0x80): return False if self.MAP_DATA == bytearray([0xFF] * 0x80):
return False
return self.MAP_DATA return self.MAP_DATA

View File

@ -18,6 +18,7 @@ class DMG_MBC:
RAM_BANK_SIZE = 0x2000 RAM_BANK_SIZE = 0x2000
ROM_BANK_NUM = 0 ROM_BANK_NUM = 0
CURRENT_ROM_BANK = 0 CURRENT_ROM_BANK = 0
START_BANK = 0
def __init__(self, args=None, cart_write_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, clk_toggle_fncptr=None): def __init__(self, args=None, cart_write_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, clk_toggle_fncptr=None):
if args is None: args = {} if args is None: args = {}
@ -66,6 +67,10 @@ class DMG_MBC:
return DMG_Unlicensed_256M(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr) return DMG_Unlicensed_256M(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x202: # 0x202:'Wisdom Tree Mapper', elif mbc_id == 0x202: # 0x202:'Wisdom Tree Mapper',
return DMG_Unlicensed_WisdomTree(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr) return DMG_Unlicensed_WisdomTree(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x203: # 0x203:'Xploder GB',
return DMG_Unlicensed_XploderGB(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
elif mbc_id == 0x204: # 0x204:'Sachen',
return DMG_Unlicensed_Sachen(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
else: else:
self.__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr) self.__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
return self return self
@ -83,6 +88,9 @@ class DMG_MBC:
self.CART_WRITE_FNCPTR(address, value) self.CART_WRITE_FNCPTR(address, value)
if delay is not False: time.sleep(delay) if delay is not False: time.sleep(delay)
def GetID(self):
return self.MBC_ID
def GetName(self): def GetName(self):
return "Unknown MBC {:d}".format(self.MBC_ID) return "Unknown MBC {:d}".format(self.MBC_ID)
@ -145,6 +153,9 @@ class DMG_MBC:
start_address = 0 start_address = 0
self.CartWrite(commands) self.CartWrite(commands)
return (start_address, self.RAM_BANK_SIZE) return (start_address, self.RAM_BANK_SIZE)
def SetStartBank(self, index):
self.START_BANK = index
def HasHiddenSector(self): def HasHiddenSector(self):
return False return False
@ -210,19 +221,19 @@ class DMG_MBC3(DMG_MBC):
def EnableRAM(self, enable=True): def EnableRAM(self, enable=True):
dprint(self.GetName(), "|", enable) dprint(self.GetName(), "|", enable)
commands = [ commands = [
#[ 0x6000, 0x01 if enable else 0x00 ],
[ 0x0000, 0x0A if enable else 0x00 ], [ 0x0000, 0x0A if enable else 0x00 ],
] ]
self.CartWrite(commands) self.CartWrite(commands)
def HasRTC(self): def HasRTC(self):
dprint(self.GetName()) dprint("Checking for RTC")
if self.MBC_ID != 16: return False if self.MBC_ID != 16:
dprint("No RTC because wrong MBC ID", self.MBC_ID)
return False
self.EnableRAM(enable=False) self.EnableRAM(enable=False)
self.EnableRAM(enable=True) self.EnableRAM(enable=True)
#self.CartWrite([ [0x4000, 0x08] ])
self.LatchRTC() self.LatchRTC()
skipped = True skipped = True
for i in range(0x08, 0x0D): for i in range(0x08, 0x0D):
self.CLK_TOGGLE_FNCPTR(60) self.CLK_TOGGLE_FNCPTR(60)
@ -230,26 +241,20 @@ class DMG_MBC3(DMG_MBC):
data = self.CartRead(0xA000, 0x800) data = self.CartRead(0xA000, 0x800)
if data[0] in (0, 0xFF): continue if data[0] in (0, 0xFF): continue
skipped = False skipped = False
if data != bytearray([data[0]] * 0x800): return False if data != bytearray([data[0]] * 0x800):
return skipped is False dprint("No RTC because whole bank is not the same value", data[0])
skipped = True
break
#ram1 = self.CartRead(0xA000, 0x10) self.EnableRAM(enable=False)
#ram2 = ram1 self.CartWrite([ [0x4000, 0] ])
#t1 = time.time() return skipped is False
#t2 = 0
#while t2 < (t1 + 1):
# self.LatchRTC()
# ram2 = self.CartRead(0xA000, 0x10)
# if ram1 != ram2: break
# t2 = time.time()
#dprint("RTC_S {:02X} != {:02X}?".format(ram1[0], ram2[0]), ram1 != ram2)
#time.sleep(0.1)
#return (ram1 != ram2)
def GetRTCBufferSize(self): def GetRTCBufferSize(self):
return 0x30 return 0x30
def LatchRTC(self): def LatchRTC(self):
dprint("Latching RTC")
self.CLK_TOGGLE_FNCPTR(60) self.CLK_TOGGLE_FNCPTR(60)
self.CartWrite([ [ 0x0000, 0x0A ] ]) self.CartWrite([ [ 0x0000, 0x0A ] ])
time.sleep(0.01) time.sleep(0.01)
@ -260,7 +265,9 @@ class DMG_MBC3(DMG_MBC):
time.sleep(0.01) time.sleep(0.01)
def ReadRTC(self): def ReadRTC(self):
#self.EnableRAM(enable=True) dprint("Reading RTC")
self.EnableRAM(enable=True)
buffer = bytearray() buffer = bytearray()
for i in range(0x08, 0x0D): for i in range(0x08, 0x0D):
self.CLK_TOGGLE_FNCPTR(60) self.CLK_TOGGLE_FNCPTR(60)
@ -272,15 +279,17 @@ class DMG_MBC3(DMG_MBC):
ts = int(time.time()) ts = int(time.time())
buffer.extend(struct.pack("<Q", ts)) buffer.extend(struct.pack("<Q", ts))
self.EnableRAM(enable=False)
self.CartWrite([ [0x4000, 0] ])
return buffer return buffer
def WriteRTC(self, buffer, advance=False): def WriteRTC(self, buffer, advance=False):
dprint(buffer) dprint("Writing RTC:", buffer)
#self.LatchRTC() self.EnableRAM(enable=True)
if advance: if advance:
try: try:
dt_now = datetime.datetime.fromtimestamp(time.time()) dt_now = datetime.datetime.fromtimestamp(time.time())
if buffer == bytearray([0xFF] * len(buffer)): # Reset if buffer == bytearray([0x00] * len(buffer)): # Reset
seconds = 0 seconds = 0
minutes = 0 minutes = 0
hours = 0 hours = 0
@ -295,10 +304,6 @@ class DMG_MBC3(DMG_MBC):
days = days & 0x1FF days = days & 0x1FF
timestamp_then = struct.unpack("<Q", buffer[-8:])[0] timestamp_then = struct.unpack("<Q", buffer[-8:])[0]
timestamp_now = int(time.time()) timestamp_now = int(time.time())
#debug
#timestamp_now = 1663106370 # 2022-09-13 23:59:30
#dt_now = datetime.datetime.fromtimestamp(timestamp_now)
#debug
dprint(seconds, minutes, hours, days, carry) dprint(seconds, minutes, hours, days, carry)
if timestamp_then < timestamp_now: if timestamp_then < timestamp_now:
dt_then = datetime.datetime.fromtimestamp(timestamp_then) dt_then = datetime.datetime.fromtimestamp(timestamp_then)
@ -364,6 +369,9 @@ class DMG_MBC3(DMG_MBC):
self.CartWrite([ [ 0x6000, 0x01 ] ]) self.CartWrite([ [ 0x6000, 0x01 ] ])
time.sleep(0.1) time.sleep(0.1)
self.CartWrite([ [0x4000, 0] ])
self.EnableRAM(enable=False)
class DMG_MBC5(DMG_MBC): class DMG_MBC5(DMG_MBC):
def GetName(self): def GetName(self):
return "MBC5" return "MBC5"
@ -817,7 +825,8 @@ class DMG_HuC3(DMG_MBC):
if advance: if advance:
try: try:
dt_now = datetime.datetime.fromtimestamp(time.time()) dt_now = datetime.datetime.fromtimestamp(time.time())
if buffer == bytearray([0xFF] * len(buffer)): # Reset print(buffer)
if buffer == bytearray([0x00] * len(buffer)): # Reset
hours = 0 hours = 0
minutes = 0 minutes = 0
days = 0 days = 0
@ -987,7 +996,7 @@ class DMG_TAMA5(DMG_MBC):
if advance: if advance:
try: try:
dt_now = datetime.datetime.fromtimestamp(time.time()) dt_now = datetime.datetime.fromtimestamp(time.time())
if buffer == bytearray([0xFF] * len(buffer)): # Reset if buffer == bytearray([0x00] * len(buffer)): # Reset
seconds = 0 seconds = 0
minutes = 0 minutes = 0
hours = 0 hours = 0
@ -1067,8 +1076,7 @@ class DMG_Unlicensed_256M(DMG_MBC5):
dprint(self.GetName(), "|", index) dprint(self.GetName(), "|", index)
flash_bank = math.floor(index / 512) flash_bank = math.floor(index / 512)
if index >= 512: index = index % 512
index -= (512 * flash_bank)
if index == 0: if index == 0:
self.CART_POWERCYCLE_FNCPTR() self.CART_POWERCYCLE_FNCPTR()
@ -1122,9 +1130,6 @@ class DMG_Unlicensed_WisdomTree(DMG_MBC):
if args is None: args = {} if args is None: args = {}
self.ROM_BANK_SIZE = 0x8000 self.ROM_BANK_SIZE = 0x8000
super().__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=None) super().__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=None)
#def GetROMSize(self):
# return self.ROM_BANK_SIZE * math.floor(self.ROM_BANK_NUM / 2)
def SelectBankROM(self, index): def SelectBankROM(self, index):
dprint(self.GetName(), "|", index) dprint(self.GetName(), "|", index)
@ -1134,6 +1139,44 @@ class DMG_Unlicensed_WisdomTree(DMG_MBC):
self.CartWrite(commands) self.CartWrite(commands)
return (0, 0x8000) return (0, 0x8000)
class DMG_Unlicensed_XploderGB(DMG_MBC):
def GetName(self):
return "Xploder GB"
def __init__(self, args=None, cart_write_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, clk_toggle_fncptr=None):
if args is None: args = {}
super().__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=None)
self.RAM_BANK_SIZE = 0x4000
def SelectBankROM(self, index):
dprint(self.GetName(), "|", index)
if index == 0:
self.CART_POWERCYCLE_FNCPTR()
self.CartRead(0x0102, 1)
self.CartWrite([[ 0x0006, index & 0xFF ]])
start_address = 0x4000
return (start_address, self.ROM_BANK_SIZE)
def SelectBankRAM(self, index):
dprint(self.GetName(), "|", index)
if index == 0:
self.CART_POWERCYCLE_FNCPTR()
self.CartRead(0x0102, 1)
return self.SelectBankROM(index + 8)
class DMG_Unlicensed_Sachen(DMG_MBC):
def GetName(self):
return "Sachen"
def SelectBankROM(self, index):
dprint(self.GetName(), "|", index)
commands = [
[ 0x2000, index + self.START_BANK ]
]
self.CartWrite(commands)
start_address = 0x4000
return (start_address, self.ROM_BANK_SIZE)
class AGB_GPIO: class AGB_GPIO:
CART_WRITE_FNCPTR = None CART_WRITE_FNCPTR = None
@ -1328,16 +1371,11 @@ class AGB_GPIO:
seconds = Util.DecodeBCD(buffer[0x06]) seconds = Util.DecodeBCD(buffer[0x06])
timestamp_then = struct.unpack("<Q", buffer[-8:])[0] timestamp_then = struct.unpack("<Q", buffer[-8:])[0]
timestamp_now = int(time.time()) timestamp_now = int(time.time())
#debug
#timestamp_now = 4102441140 # 2099-12-23 23:59:00
#dt_now = datetime.datetime.fromtimestamp(timestamp_now)
#debug
if timestamp_then < timestamp_now: if timestamp_then < timestamp_now:
dt_then = datetime.datetime.fromtimestamp(timestamp_then) dt_then = datetime.datetime.fromtimestamp(timestamp_then)
dt_buffer = datetime.datetime.strptime("{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(years + 2000, months % 12, days % 31, hours % 60, minutes % 60, seconds % 60), "%Y-%m-%d %H:%M:%S") dt_buffer = datetime.datetime.strptime("{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(years + 2000, months % 12, days % 31, hours % 60, minutes % 60, seconds % 60), "%Y-%m-%d %H:%M:%S")
rd = relativedelta(dt_now, dt_then) rd = relativedelta(dt_now, dt_then)
dt_new = dt_buffer + rd dt_new = dt_buffer + rd
#print(dt_then, dt_now, dt_buffer, dt_new, sep="\n")
years = dt_new.year - 2000 years = dt_new.year - 2000
months = dt_new.month months = dt_new.month
days = dt_new.day days = dt_new.day
@ -1380,8 +1418,3 @@ class AGB_GPIO:
[ self.GPIO_REG_DAT, 1 ], [ self.GPIO_REG_DAT, 1 ],
[ self.GPIO_REG_RE, 0 ], # Disable RTC Mapping [ self.GPIO_REG_RE, 0 ], # Disable RTC Mapping
]) ])
#[0F] 00 01 27 05 08 24 08 51 4B 7C 60 00 00 00 00
#[0F] 00 01 27 05 08 25 03 88 4B 7C 60 00 00 00 00
#[0F] 00 01 01 05 08 00 15 96 4B 7C 60 00 00 00 00
#[0F] 00 01 01 05 08 00 39 BF 4B 7C 60 00 00 00 00
#[0F] 00 01 01 05 08 00 46 BB 4B 7C 60 00 00 00 00

View File

@ -137,7 +137,7 @@ class PocketCamera:
pic = pic.resize((pic.width * scale, pic.height * scale), Image.NEAREST) pic = pic.resize((pic.width * scale, pic.height * scale), Image.NEAREST)
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if ext.lower() == ".png": if ext == "" or ext.lower() == ".png":
outpic = pic outpic = pic
outpic.save(path, pnginfo=pnginfo) outpic.save(path, pnginfo=pnginfo)
elif ext.lower() == ".gif": elif ext.lower() == ".gif":

View File

@ -3,6 +3,7 @@
# Author: Lesserkuma (github.com/lesserkuma) # Author: Lesserkuma (github.com/lesserkuma)
import hashlib, re, zlib, string import hashlib, re, zlib, string
from .Util import dprint
class RomFileAGB: class RomFileAGB:
ROMFILE_PATH = None ROMFILE_PATH = None
@ -40,10 +41,17 @@ class RomFileAGB:
return self.ROMFILE[0:0x200] return self.ROMFILE[0:0x200]
def GetHeader(self): def GetHeader(self):
buffer = self.ROMFILE buffer = bytearray(self.ROMFILE)
data = {} data = {}
data["empty"] = (buffer[0x04:0xA0] == bytearray([buffer[0x04]] * 0x9C)) hash = hashlib.sha1(buffer[0:0x180]).digest()
data["empty_nocart"] = (buffer == bytearray([0x00] * len(buffer))) nocart_hashes = []
nocart_hashes.append(bytearray([ 0x4F, 0xE9, 0x3E, 0xEE, 0xBC, 0x55, 0x93, 0xFE, 0x2E, 0x23, 0x1A, 0x39, 0x86, 0xCE, 0x86, 0xC9, 0x5C, 0x11, 0x00, 0xDD ])) # Method 0
nocart_hashes.append(bytearray([ 0xA5, 0x03, 0xA1, 0xB5, 0xF5, 0xDD, 0xBE, 0xFC, 0x87, 0xC7, 0x9B, 0x13, 0x59, 0xF7, 0xE1, 0xA5, 0xCF, 0xE0, 0xAC, 0x9F ])) # Method 1
nocart_hashes.append(bytearray([ 0x46, 0x86, 0xE3, 0x81, 0xB2, 0x4A, 0x2D, 0xB0, 0x7D, 0xE8, 0x3D, 0x45, 0x2F, 0xA3, 0x1E, 0x8A, 0x04, 0x4B, 0x3A, 0x50 ])) # Method 2
data["empty_nocart"] = hash in nocart_hashes
data["empty"] = (buffer[0x04:0xA0] == bytearray([buffer[0x04]] * 0x9C)) or data["empty_nocart"]
dprint("Hash: 0x{:s} -- Is Empty?".format((' '.join(format(x, '02X') for x in hash).replace(" ", ", 0x"))), data["empty_nocart"])
if data["empty_nocart"]: buffer = bytearray([0x00] * len(buffer))
data["logo_correct"] = hashlib.sha1(buffer[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ]) data["logo_correct"] = hashlib.sha1(buffer[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ])
game_title = bytearray(buffer[0xA0:0xAC]).decode("ascii", "replace") game_title = bytearray(buffer[0xA0:0xAC]).decode("ascii", "replace")
game_title = re.sub(r"(\x00+)$", "", game_title) game_title = re.sub(r"(\x00+)$", "", game_title)

View File

@ -2,7 +2,7 @@
# FlashGBX # FlashGBX
# Author: Lesserkuma (github.com/lesserkuma) # Author: Lesserkuma (github.com/lesserkuma)
import hashlib, re, string import hashlib, re, string, struct
from . import Util from . import Util
class RomFileDMG: class RomFileDMG:
@ -55,7 +55,6 @@ class RomFileDMG:
buffer = self.ROMFILE buffer = self.ROMFILE
data = {} data = {}
if len(buffer) < 0x180: return {} if len(buffer) < 0x180: return {}
data["empty"] = (buffer[0x104:0x134] == bytearray([buffer[0x104]] * 0x30)) data["empty"] = (buffer[0x104:0x134] == bytearray([buffer[0x104]] * 0x30))
data["empty_nocart"] = (buffer == bytearray([0x00] * len(buffer))) data["empty_nocart"] = (buffer == bytearray([0x00] * len(buffer)))
data["logo_correct"] = hashlib.sha1(buffer[0x104:0x134]).digest() == bytearray([ 0x07, 0x45, 0xFD, 0xEF, 0x34, 0x13, 0x2D, 0x1B, 0x3D, 0x48, 0x8C, 0xFB, 0xDF, 0x03, 0x79, 0xA3, 0x9F, 0xD5, 0x4B, 0x4C ]) data["logo_correct"] = hashlib.sha1(buffer[0x104:0x134]).digest() == bytearray([ 0x07, 0x45, 0xFD, 0xEF, 0x34, 0x13, 0x2D, 0x1B, 0x3D, 0x48, 0x8C, 0xFB, 0xDF, 0x03, 0x79, 0xA3, 0x9F, 0xD5, 0x4B, 0x4C ])
@ -76,13 +75,13 @@ class RomFileDMG:
maker_code = bytearray(buffer[0x144:0x146]).decode("ascii", "replace") maker_code = bytearray(buffer[0x144:0x146]).decode("ascii", "replace")
maker_code = ''.join(filter(lambda x: x in set(string.printable), maker_code)) maker_code = ''.join(filter(lambda x: x in set(string.printable), maker_code))
data["maker_code_new"] = maker_code data["maker_code_new"] = maker_code
data["features_raw"] = int(buffer[0x147]) data["mapper_raw"] = int(buffer[0x147])
data["features"] = "?" data["mapper"] = "?"
data["rom_size_raw"] = int(buffer[0x148]) data["rom_size_raw"] = int(buffer[0x148])
data["rom_size"] = "?" data["rom_size"] = "?"
if buffer[0x148] < len(Util.DMG_Header_ROM_Sizes): data["rom_size"] = Util.DMG_Header_ROM_Sizes[buffer[0x148]] if buffer[0x148] < len(Util.DMG_Header_ROM_Sizes): data["rom_size"] = Util.DMG_Header_ROM_Sizes[buffer[0x148]]
data["ram_size_raw"] = int(buffer[0x149]) data["ram_size_raw"] = int(buffer[0x149])
if data["features_raw"] == 0x05 or data["features_raw"] == 0x06: if data["mapper_raw"] == 0x05 or data["mapper_raw"] == 0x06:
data["ram_size"] = 0x200 data["ram_size"] = 0x200
else: else:
data["ram_size"] = "?" data["ram_size"] = "?"
@ -97,61 +96,191 @@ class RomFileDMG:
data["rom_checksum_correct"] = data["rom_checksum"] == data["rom_checksum_calc"] data["rom_checksum_correct"] = data["rom_checksum"] == data["rom_checksum_calc"]
# MBC1M # MBC1M
if data["features_raw"] == 0x03 and data["game_title"] == "MOMOCOL" and data["header_checksum"] == 0x28 or \ if data["mapper_raw"] == 0x03 and data["game_title"] == "MOMOCOL" and data["header_checksum"] == 0x28 or \
data["features_raw"] == 0x01 and data["game_title"] == "BOMCOL" and data["header_checksum"] == 0x86 or \ data["mapper_raw"] == 0x01 and data["game_title"] == "BOMCOL" and data["header_checksum"] == 0x86 or \
data["features_raw"] == 0x01 and data["game_title"] == "GENCOL" and data["header_checksum"] == 0x8A or \ data["mapper_raw"] == 0x01 and data["game_title"] == "GENCOL" and data["header_checksum"] == 0x8A or \
data["features_raw"] == 0x01 and data["game_title"] == "SUPERCHINESE 123" and data["header_checksum"] == 0xE4 or \ data["mapper_raw"] == 0x01 and data["game_title"] == "SUPERCHINESE 123" and data["header_checksum"] == 0xE4 or \
data["features_raw"] == 0x01 and data["game_title"] == "MORTALKOMBATI&II" and data["header_checksum"] == 0xB9 or \ data["mapper_raw"] == 0x01 and data["game_title"] == "MORTALKOMBATI&II" and data["header_checksum"] == 0xB9 or \
data["features_raw"] == 0x01 and data["game_title"] == "MORTALKOMBAT DUO" and data["header_checksum"] == 0xA7: data["mapper_raw"] == 0x01 and data["game_title"] == "MORTALKOMBAT DUO" and data["header_checksum"] == 0xA7:
data["features_raw"] += 0x100 data["mapper_raw"] += 0x100
# GB Memory # GB Memory
if data["features_raw"] == 0x19 and data["game_title"] == "NP M-MENU MENU" and data["header_checksum"] == 0xD3: if data["mapper_raw"] == 0x19 and data["game_title"] == "NP M-MENU MENU" and data["header_checksum"] == 0xD3:
data["features_raw"] = 0x105
data["ram_size_raw"] = 0x04 data["ram_size_raw"] = 0x04
elif data["features_raw"] == 0x01 and data["game_title"] == "DMG MULTI MENU " and data["header_checksum"] == 0x36: data["mapper_raw"] = 0x105
data["features_raw"] = 0x105 elif data["mapper_raw"] == 0x01 and data["game_title"] == "DMG MULTI MENU " and data["header_checksum"] == 0x36:
data["ram_size_raw"] = 0x04 data["ram_size_raw"] = 0x04
data["mapper_raw"] = 0x105
# M161 (Mani 4 in 1) # M161 (Mani 4 in 1)
elif data["features_raw"] == 0x10 and data["game_title"] == "TETRIS SET" and data["header_checksum"] == 0x3F: elif data["mapper_raw"] == 0x10 and data["game_title"] == "TETRIS SET" and data["header_checksum"] == 0x3F:
data["features_raw"] = 0x104 data["mapper_raw"] = 0x104
# MMM01 (Mani 4 in 1) # MMM01 (Mani 4 in 1)
elif data["features_raw"] == 0x11 and data["game_title"] == "BOUKENJIMA2 SET" and data["header_checksum"] == 0 or \ elif data["mapper_raw"] == 0x11 and data["game_title"] == "BOUKENJIMA2 SET" and data["header_checksum"] == 0 or \
data["features_raw"] == 0x11 and data["game_title"] == "BUBBLEBOBBLE SET" and data["header_checksum"] == 0xC6 or \ data["mapper_raw"] == 0x11 and data["game_title"] == "BUBBLEBOBBLE SET" and data["header_checksum"] == 0xC6 or \
data["features_raw"] == 0x11 and data["game_title"] == "GANBARUGA SET" and data["header_checksum"] == 0x90 or \ data["mapper_raw"] == 0x11 and data["game_title"] == "GANBARUGA SET" and data["header_checksum"] == 0x90 or \
data["features_raw"] == 0x11 and data["game_title"] == "RTYPE 2 SET" and data["header_checksum"] == 0x32: data["mapper_raw"] == 0x11 and data["game_title"] == "RTYPE 2 SET" and data["header_checksum"] == 0x32:
data["features_raw"] = 0x0B data["mapper_raw"] = 0x0B
# Unlicensed 256M Mapper # Unlicensed 256M Mapper
elif (data["game_title"].upper() == "GB HICOL" and data["header_checksum"] in (0x4A, 0x49, 0xE9)) or \ elif (data["game_title"].upper() == "GB HICOL" and data["header_checksum"] in (0x4A, 0x49, 0xE9)) or \
(data["game_title"] == "BennVenn" and data["header_checksum"] == 0x48): (data["game_title"] == "BennVenn" and data["header_checksum"] == 0x48):
data["features_raw"] = 0x201
data["rom_size_raw"] = 0x0A data["rom_size_raw"] = 0x0A
data["ram_size_raw"] = 0x201 data["ram_size_raw"] = 0x201
data["mapper_raw"] = 0x201
elif buffer[0x150:0x160].decode("ascii", "replace") == "256M ROM Builder": elif buffer[0x150:0x160].decode("ascii", "replace") == "256M ROM Builder":
data["features_raw"] = 0x201
data["ram_size_raw"] = 0x201 data["ram_size_raw"] = 0x201
data["mapper_raw"] = 0x201
# Unlicensed Wisdom Tree Mapper # Unlicensed Wisdom Tree Mapper
elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xF5, 0xD2, 0x91, 0x7D, 0x5E, 0x5B, 0xAB, 0xD8, 0x5F, 0x0A, 0xC7, 0xBA, 0x56, 0xEB, 0x49, 0x8A, 0xBA, 0x12, 0x49, 0x13 ]): # Exodus / Joshua elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xF5, 0xD2, 0x91, 0x7D, 0x5E, 0x5B, 0xAB, 0xD8, 0x5F, 0x0A, 0xC7, 0xBA, 0x56, 0xEB, 0x49, 0x8A, 0xBA, 0x12, 0x49, 0x13 ]): # Exodus / Joshua
data["features_raw"] = 0x202
data["rom_size_raw"] = 0x02 data["rom_size_raw"] = 0x02
data["mapper_raw"] = 0x202
elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xE9, 0xF8, 0x32, 0x78, 0x39, 0x19, 0xE3, 0xB2, 0xFC, 0x6F, 0xC2, 0x60, 0x30, 0x33, 0x20, 0xD0, 0x3B, 0x1A, 0xA9, 0xA2 ]): # Spiritual Warfare elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xE9, 0xF8, 0x32, 0x78, 0x39, 0x19, 0xE3, 0xB2, 0xFC, 0x6F, 0xC2, 0x60, 0x30, 0x33, 0x20, 0xD0, 0x3B, 0x1A, 0xA9, 0xA2 ]): # Spiritual Warfare
data["features_raw"] = 0x202
data["rom_size_raw"] = 0x03 data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x202
elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xE6, 0xC0, 0x39, 0x7F, 0xA5, 0x99, 0xD6, 0x60, 0xD7, 0x90, 0x45, 0xB9, 0xF0, 0x64, 0x3B, 0x2A, 0x41, 0xA4, 0xD6, 0x35 ]): # King James Bible elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0xE6, 0xC0, 0x39, 0x7F, 0xA5, 0x99, 0xD6, 0x60, 0xD7, 0x90, 0x45, 0xB9, 0xF0, 0x64, 0x3B, 0x2A, 0x41, 0xA4, 0xD6, 0x35 ]): # King James Bible
data["features_raw"] = 0x202
data["rom_size_raw"] = 0x05 data["rom_size_raw"] = 0x05
data["mapper_raw"] = 0x202
elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0x36, 0x89, 0x60, 0xDD, 0x1B, 0xE1, 0x73, 0x86, 0x8B, 0x24, 0xA3, 0xDC, 0x57, 0xA5, 0xCB, 0x7C, 0xCA, 0x62, 0xDD, 0x34 ]): # NIV Bible elif hashlib.sha1(buffer[0x0:0x150]).digest() == bytearray([ 0x36, 0x89, 0x60, 0xDD, 0x1B, 0xE1, 0x73, 0x86, 0x8B, 0x24, 0xA3, 0xDC, 0x57, 0xA5, 0xCB, 0x7C, 0xCA, 0x62, 0xDD, 0x34 ]): # NIV Bible
data["features_raw"] = 0x202
data["rom_size_raw"] = 0x06 data["rom_size_raw"] = 0x06
data["mapper_raw"] = 0x202
# Unlicensed Xploder GB Mapper
elif hashlib.sha1(buffer[0x104:0x150]).digest() == bytearray([ 0x06, 0xAC, 0xDC, 0xB6, 0xD1, 0x9B, 0xD9, 0xE3, 0x95, 0xA2, 0x38, 0xB8, 0x00, 0x97, 0x0D, 0x78, 0x3F, 0xC6, 0xB7, 0xBD ]):
data["rom_size_raw"] = 0x02
data["ram_size_raw"] = 0x203
data["mapper_raw"] = 0x203
data["cgb"] = 0x80
try:
game_title = bytearray(buffer[0:0x10]).decode("ascii", "replace").replace("\xFF", "")
game_title = re.sub(r"(\x00+)$", "", game_title)
game_title = re.sub(r"((_)_+|(\x00)\x00+|(\s)\s+)", "\\2\\3\\4", game_title).replace("\x00", "")
game_title = ''.join(filter(lambda x: x in set(string.printable), game_title))
data["game_title"] = game_title
except:
pass
data["version"] = "{:d}.{:d}.{:d}:{:c} ({:02d}:{:02d} {:02d}-{:02d}-{:02d} / {:04X})".format(buffer[0xD8], buffer[0xD9], buffer[0xDA], buffer[0xD7], buffer[0xD0], buffer[0xD1], buffer[0xD2], buffer[0xD3], buffer[0xD4], struct.unpack("<H", buffer[0xD5:0xD7])[0]).replace("\x00", "")
# Unlicensed Sachen MMC1/MMC2
elif len(buffer) >= 0x280:
sachen_hash = hashlib.sha1(buffer[0x200:0x280]).digest()
if sachen_hash == bytearray([ 0x73, 0x9B, 0x46, 0x86, 0x97, 0x1C, 0x0B, 0xAE, 0xAF, 0x26, 0xF1, 0x73, 0xAC, 0xAE, 0x4B, 0x2B, 0xBF, 0x00, 0x70, 0x77 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x8793
data["game_title"] = "SACHEN 4B-001"
elif sachen_hash == bytearray([ 0x22, 0x8B, 0x40, 0x4F, 0x1C, 0xCF, 0x4D, 0xDC, 0x4D, 0xF2, 0x35, 0xF3, 0x7B, 0x6D, 0x61, 0x5E, 0xBE, 0xF1, 0xEF, 0x42 ]):
data["rom_size_raw"] = 0x01
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0xB180
data["game_title"] = "SACHEN 4B-002"
elif sachen_hash == bytearray([ 0xF9, 0xB8, 0x6A, 0x8F, 0x2E, 0x8B, 0x31, 0xD4, 0xC5, 0x02, 0xC8, 0x80, 0x75, 0x35, 0x9C, 0x02, 0xB3, 0xB5, 0x68, 0x01 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x7CFD
data["game_title"] = "SACHEN 4B-004"
elif sachen_hash == bytearray([ 0xD6, 0xB5, 0x33, 0x81, 0x1A, 0x01, 0x0D, 0x4D, 0x1C, 0xCC, 0x5A, 0x2C, 0x34, 0x9D, 0x0F, 0x63, 0xD3, 0xF4, 0x9D, 0x34 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x80DF
data["game_title"] = "SACHEN 4B-005"
elif sachen_hash == bytearray([ 0x3E, 0x56, 0xCC, 0x2D, 0xDF, 0xE0, 0x00, 0xED, 0x53, 0xA7, 0x9D, 0x62, 0xC8, 0xBF, 0x7F, 0x20, 0x27, 0x47, 0xCD, 0x8E ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x55E6
data["game_title"] = "SACHEN 4B-006"
elif sachen_hash == bytearray([ 0xD8, 0x24, 0xD2, 0xB2, 0x71, 0x6B, 0x08, 0xFA, 0xEA, 0xA4, 0xFB, 0xD9, 0x7D, 0x81, 0x94, 0x57, 0x46, 0x77, 0x91, 0x60 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x8E9F
data["game_title"] = "SACHEN 4B-007"
elif sachen_hash == bytearray([ 0x19, 0x3E, 0xF8, 0xE2, 0x12, 0x8A, 0x24, 0x10, 0xFE, 0xE9, 0xEA, 0x27, 0xC9, 0x1B, 0xC4, 0xDD, 0x04, 0x74, 0x1B, 0xA8 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x99C7
data["game_title"] = "SACHEN 4B-008"
elif sachen_hash == bytearray([ 0xA5, 0x07, 0xCB, 0xB0, 0x63, 0x7A, 0xE7, 0x1A, 0xF2, 0xC8, 0x32, 0x9B, 0xA6, 0x6D, 0xC4, 0x21, 0x68, 0x78, 0xE5, 0x39 ]):
data["rom_size_raw"] = 0x03
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0xCDD3
data["game_title"] = "SACHEN 4B-009"
elif sachen_hash == bytearray([ 0x18, 0xEC, 0x2B, 0x15, 0x97, 0xD7, 0x80, 0x51, 0x58, 0xB2, 0xB8, 0x53, 0xA7, 0x00, 0xD7, 0x0B, 0xCE, 0x0A, 0xB3, 0xFF ]):
data["rom_size_raw"] = 0x01
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x3889
data["game_title"] = "SACHEN 4B-003"
elif sachen_hash == bytearray([ 0x96, 0x1C, 0xE3, 0x5D, 0x3A, 0x81, 0x44, 0x95, 0xCF, 0x42, 0x92, 0x42, 0x30, 0x83, 0x14, 0x17, 0xA9, 0xBF, 0xE0, 0x9F ]):
data["rom_size_raw"] = 0x06
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x3934
data["cgb"] = 0x80
data["game_title"] = "SACHEN 31B-001"
elif sachen_hash == bytearray([ 0xB8, 0x59, 0x61, 0x1C, 0x03, 0xAF, 0x5F, 0x7F, 0x50, 0x3E, 0x8C, 0xB0, 0x9C, 0x81, 0x4A, 0x0C, 0xE8, 0xBA, 0xB5, 0x99 ]):
data["rom_size_raw"] = 0x06
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x2E45
data["cgb"] = 0x80
data["game_title"] = "SACHEN 31B-001"
elif sachen_hash == bytearray([ 0xF5, 0xC6, 0xC2, 0xE6, 0xA6, 0xF2, 0xEE, 0x86, 0x29, 0x22, 0x3D, 0x7C, 0x72, 0xF9, 0xDD, 0x6F, 0x32, 0x0A, 0xA0, 0x9D ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x125B
data["cgb"] = 0x80
data["game_title"] = "SACHEN 8B-001"
elif sachen_hash == bytearray([ 0xF2, 0x8A, 0xDF, 0x84, 0xBA, 0x56, 0x8C, 0x54, 0xF9, 0x4B, 0x25, 0xFA, 0x12, 0x92, 0x4E, 0xD6, 0x7D, 0xD1, 0x7E, 0x9D ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x598F
data["cgb"] = 0x80
data["game_title"] = "SACHEN 8B-002"
elif sachen_hash == bytearray([ 0x1C, 0x08, 0x6F, 0x94, 0xD8, 0xFD, 0x40, 0x4D, 0xA3, 0x85, 0xCE, 0x57, 0x35, 0xF3, 0x43, 0x92, 0xEE, 0xB7, 0x26, 0xE1 ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x6485
data["cgb"] = 0x80
data["game_title"] = "SACHEN 8B-003"
elif sachen_hash == bytearray([ 0x2C, 0xFD, 0xE1, 0x8D, 0x2C, 0x57, 0xBA, 0xDB, 0xC0, 0xF8, 0xDF, 0x52, 0x79, 0x38, 0x44, 0x56, 0x3B, 0xB0, 0xA0, 0xDE ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x02A4
data["cgb"] = 0x80
data["game_title"] = "SACHEN 8B-004"
elif sachen_hash == bytearray([ 0x4E, 0xEA, 0x3C, 0x0A, 0x23, 0x5C, 0xF9, 0x2D, 0xC6, 0x22, 0xC2, 0x21, 0xD3, 0xBB, 0x73, 0x3B, 0xA7, 0x21, 0xFB, 0x78 ]):
data["rom_size_raw"] = 0x02
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0xA709
data["cgb"] = 0x80
data["game_title"] = "SACHEN"
elif sachen_hash == bytearray([ 0x3F, 0xE9, 0xB7, 0xAB, 0xBC, 0x18, 0x95, 0x60, 0x80, 0xF7, 0xDF, 0x9B, 0x5E, 0x5A, 0x0C, 0x9F, 0x18, 0x63, 0x34, 0x7B ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x929D
data["cgb"] = 0x80
data["game_title"] = "SACHEN 1B-003"
elif sachen_hash == bytearray([ 0xD8, 0x9B, 0x0D, 0x55, 0x48, 0x97, 0x7F, 0xD5, 0x0E, 0x46, 0x20, 0xD6, 0x9E, 0x0B, 0x8C, 0x6B, 0x05, 0xD4, 0x8F, 0x2C ]):
data["rom_size_raw"] = 0x04
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x2F50
data["cgb"] = 0x80
data["game_title"] = "SACHEN 4B-003"
elif sachen_hash == bytearray([ 0x8B, 0x98, 0xB1, 0xD3, 0x6B, 0x84, 0x66, 0x51, 0xC0, 0x23, 0x19, 0xF2, 0xDC, 0xD3, 0xF4, 0x97, 0xDB, 0x39, 0x47, 0xE7 ]):
data["rom_size_raw"] = 0x06
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x9769
data["cgb"] = 0x80
data["game_title"] = "SACHEN"
elif sachen_hash == bytearray([ 0xD0, 0xAE, 0xC9, 0xFB, 0xF0, 0x8D, 0x7A, 0x72, 0x34, 0x8E, 0x96, 0xB6, 0x75, 0x6B, 0x30, 0xC1, 0xCB, 0xF6, 0x2F, 0x00 ]):
data["rom_size_raw"] = 0x06
data["mapper_raw"] = 0x204
data["rom_checksum"] = 0x0346
data["game_title"] = "SACHEN 6B-001"
if data["features_raw"] in Util.DMG_Header_Mapper:
data["features"] = Util.DMG_Header_Mapper[data["features_raw"]] if data["mapper_raw"] in Util.DMG_Header_Mapper:
data["mapper"] = Util.DMG_Header_Mapper[data["mapper_raw"]]
elif data["logo_correct"]: elif data["logo_correct"]:
print("{:s}WARNING: Unknown memory bank controller type 0x{:02X}{:s}".format(Util.ANSI.YELLOW, data["features_raw"], Util.ANSI.RESET)) print("{:s}WARNING: Unknown memory bank controller type 0x{:02X}{:s}".format(Util.ANSI.YELLOW, data["mapper_raw"], Util.ANSI.RESET))
return data return data

View File

@ -2,12 +2,12 @@
# FlashGBX # FlashGBX
# Author: Lesserkuma (github.com/lesserkuma) # Author: Lesserkuma (github.com/lesserkuma)
import math, time, datetime, copy, configparser, threading, statistics, os, platform, traceback import math, time, datetime, copy, configparser, threading, statistics, os, platform, traceback, io, struct
from enum import Enum from enum import Enum
# Common constants # Common constants
APPNAME = "FlashGBX" APPNAME = "FlashGBX"
VERSION_PEP440 = "3.7" VERSION_PEP440 = "3.8"
VERSION = "v{:s}".format(VERSION_PEP440) VERSION = "v{:s}".format(VERSION_PEP440)
DEBUG = False DEBUG = False
@ -19,13 +19,13 @@ AGB_Global_CRC32 = 0
AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS" } AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS" }
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000 ] 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' } 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' }
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 = [ "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_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 ] DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]
DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KB)", "64K SRAM (8 KB)", "256K SRAM (32 KB)", "512K SRAM (64 KB)", "1M SRAM (128 KB)", "MBC6 SRAM+FLASH (1.03 MB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KB)" ] DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KB)", "64K SRAM (8 KB)", "256K SRAM (32 KB)", "512K SRAM (64 KB)", "1M SRAM (128 KB)", "MBC6 SRAM+FLASH (1.03 MB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KB)", "Unlicensed 1M EEPROM (128 KB)" ]
DMG_Header_RAM_Sizes_Map = [ 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201 ] DMG_Header_RAM_Sizes_Map = [ 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203 ]
DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000 ] # RAM size in bytes DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000 ] # RAM size in bytes
DMG_Header_SGB = { 0x00:'No support', 0x03:'Supported' } DMG_Header_SGB = { 0x00:'No support', 0x03:'Supported' }
DMG_Header_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' } DMG_Header_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' }
@ -147,7 +147,7 @@ class Progress():
self.UPDATER(args) self.UPDATER(args)
self.PROGRESS = {} self.PROGRESS = {}
elif args["action"] in ("ERASE", "SECTOR_ERASE", "UNLOCK"): elif args["action"] in ("ERASE", "SECTOR_ERASE", "UNLOCK", "UPDATE_RTC"):
if "time_start" in self.PROGRESS: if "time_start" in self.PROGRESS:
args["time_elapsed"] = now - self.PROGRESS["time_start"] args["time_elapsed"] = now - self.PROGRESS["time_start"]
elif "time_start" in args: elif "time_start" in args:
@ -247,6 +247,32 @@ class TAMA5_REG(Enum):
MEM_READ_L = 0xC MEM_READ_L = 0xC
MEM_READ_H = 0xD MEM_READ_H = 0xD
def isx2bin(buffer):
data_input = io.BytesIO(buffer)
data_output = bytearray(8 * 1024 * 1024)
rom_size = 0
temp = 32 * 1024
while 1:
try:
type = struct.unpack('B', data_input.read(1))[0]
if type == 4:
break
elif type != 1:
print("WARNING: Unhandled ISX record type 0x{:02X} found. Converted ROM may not be working correctly.".format(type))
continue
bank = struct.unpack('B', data_input.read(1))[0]
offset = struct.unpack('<H', data_input.read(2))[0] % 0x4000
realoffset = bank * 16 * 1024 + offset
size = struct.unpack('<H', data_input.read(2))[0]
data_output[realoffset:realoffset+size] = data_input.read(size)
rom_size = max(rom_size, realoffset+size)
temp = 32 * 1024
while temp < rom_size: temp *= 2
except:
print("ERROR: Couldnt convert ISX file correctly.")
break
return data_output[:temp]
def roundup(x): def roundup(x):
# https://stackoverflow.com/questions/50405017/ # https://stackoverflow.com/questions/50405017/
d = 10 ** 2 d = 10 ** 2
@ -257,23 +283,23 @@ def roundup(x):
def formatFileSize(size, asInt=False, roundUp=False): def formatFileSize(size, asInt=False, roundUp=False):
if size == 1: if size == 1:
return "{:d} Byte".format(size) return "{:d} Byte".format(size)
elif size < 1024: elif size < 1024:
return "{:d} Bytes".format(size) return "{:d} Bytes".format(size)
elif size < 1024 * 1024: elif size < 1024 * 1024:
val = size/1024 val = size/1024
if roundUp: val = roundup(val) if roundUp: val = roundup(val)
if asInt: if asInt:
return "{:d} KB".format(int(val)) return "{:d} KB".format(int(val))
else: else:
return "{:.1f} KB".format(val) return "{:.1f} KB".format(val)
else: else:
val = size/1024/1024 val = size/1024/1024
if roundUp: val = roundup(val) if roundUp: val = roundup(val)
if asInt: if asInt:
return "{:d} MB".format(int(val)) return "{:d} MB".format(int(val))
else: else:
return "{:.2f} MB".format(val) return "{:.2f} MB".format(val)
def formatProgressTimeShort(sec): def formatProgressTimeShort(sec):
sec = sec % (24 * 3600) sec = sec % (24 * 3600)
@ -285,20 +311,20 @@ def formatProgressTimeShort(sec):
def formatProgressTime(sec): def formatProgressTime(sec):
if int(sec) == 1: if int(sec) == 1:
return "{:d} second".format(int(sec)) return "{:d} second".format(int(sec))
elif sec < 60: elif sec < 60:
return "{:d} seconds".format(int(sec)) return "{:d} seconds".format(int(sec))
elif int(sec) == 60: elif int(sec) == 60:
return "1 minute" return "1 minute"
else: else:
min = int(sec / 60) min = int(sec / 60)
sec = int(sec % 60) sec = int(sec % 60)
s = str(min) + " " s = str(min) + " "
if min == 1: if min == 1:
s = s + "minute" s = s + "minute"
else: else:
s = s + "minutes" s = s + "minutes"
s = s + ", " + str(sec) + " " s = s + ", " + str(sec) + " "
if sec == 1: if sec == 1:
s = s + "second" s = s + "second"
else: else:

View File

@ -4,7 +4,7 @@
"Generic Flash Cartridge (0/90)" "Generic Flash Cartridge (0/90)"
], ],
"voltage":3.3, "voltage":3.3,
"chip_erase_timeout":120, "chip_erase_timeout":300,
"sector_size_from_cfi":true, "sector_size_from_cfi":true,
"command_set":"INTEL", "command_set":"INTEL",
"commands":{ "commands":{

View File

@ -4,7 +4,7 @@
"Generic Flash Cartridge (AAA/A9)" "Generic Flash Cartridge (AAA/A9)"
], ],
"voltage":3.3, "voltage":3.3,
"chip_erase_timeout":120, "chip_erase_timeout":300,
"command_set":"AMD", "command_set":"AMD",
"commands":{ "commands":{
"reset":[ "reset":[

View File

@ -4,7 +4,7 @@
"Generic Flash Cartridge (AAA/AA)" "Generic Flash Cartridge (AAA/AA)"
], ],
"voltage":3.3, "voltage":3.3,
"chip_erase_timeout":120, "chip_erase_timeout":300,
"command_set":"AMD", "command_set":"AMD",
"commands":{ "commands":{
"reset":[ "reset":[

View File

@ -1,9 +1,11 @@
{ {
"type":"AGB", "type":"AGB",
"names":[ "names":[
"39VF512 with MSP55LV100G" "AGB-E05-01 with MSP55LV100G",
"DV15 with MSP55LV100G"
], ],
"flash_ids":[ "flash_ids":[
[ 0x02, 0x01, 0x02, 0x01, 0x7D, 0x7E, 0x7D, 0x7E ],
[ 0x02, 0x01, 0x02, 0x01, 0x7D, 0x7E, 0x7D, 0x7E ] [ 0x02, 0x01, 0x02, 0x01, 0x7D, 0x7E, 0x7D, 0x7E ]
], ],
"voltage":3.3, "voltage":3.3,

View File

@ -9,7 +9,8 @@
"AGB-E05-02 with M29W128GH", "AGB-E05-02 with M29W128GH",
"AGB-E05-02 with M29W128FH", "AGB-E05-02 with M29W128FH",
"2006_TSOP_64BALL_6106 with W29GL128SH9B", "2006_TSOP_64BALL_6106 with W29GL128SH9B",
"AGB-E05-02 with JS28F128" "AGB-E05-02 with JS28F128",
"AGB-E05-03H with M29W128GH"
], ],
"flash_ids":[ "flash_ids":[
[ 0x02, 0x00, 0x7D, 0x22 ], [ 0x02, 0x00, 0x7D, 0x22 ],
@ -20,7 +21,8 @@
[ 0x20, 0x00, 0x7D, 0x22 ], [ 0x20, 0x00, 0x7D, 0x22 ],
[ 0x20, 0x00, 0x7D, 0x22 ], [ 0x20, 0x00, 0x7D, 0x22 ],
[ 0xEF, 0x00, 0x7D, 0x22 ], [ 0xEF, 0x00, 0x7D, 0x22 ],
[ 0x8A, 0x00, 0x7D, 0x22 ] [ 0x8A, 0x00, 0x7D, 0x22 ],
[ 0x20, 0x00, 0x7D, 0x22 ]
], ],
"voltage":3.3, "voltage":3.3,
"flash_size":0x1000000, "flash_size":0x1000000,

View File

@ -0,0 +1,49 @@
{
"type":"DMG",
"names":[
"SD007_T40_64BALL_TSOP28 with 29LV016T"
],
"flash_ids":[
[ 0x04, 0xC7, 0x00, 0x00 ]
],
"voltage":5,
"flash_size":0x200000,
"start_addr":0,
"first_bank":1,
"write_pin":"WR",
"chip_erase_timeout":60,
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"chip_erase":[
[ 0x555, 0xA9 ],
[ 0x2AA, 0x56 ],
[ 0x555, 0x80 ],
[ 0x555, 0xA9 ],
[ 0x2AA, 0x56 ],
[ 0x555, 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":[
[ 0x555, 0xA9 ],
[ 0x2AA, 0x56 ],
[ 0x555, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -24,9 +24,6 @@
[ 0x2AAA, 0x55 ], [ 0x2AAA, 0x55 ],
[ 0x5555, 0x90 ] [ 0x5555, 0x90 ]
], ],
"read_cfi":[
[ 0x5555, 0x98 ]
],
"chip_erase":[ "chip_erase":[
[ 0x5555, 0xAA ], [ 0x5555, 0xAA ],
[ 0x2AAA, 0x55 ], [ 0x2AAA, 0x55 ],

View File

@ -0,0 +1,55 @@
{
"type":"DMG",
"names":[
"BLAZE Xploder GB"
],
"flash_ids":[
[ 0xBF, 0x10, 0xFF, 0xFF ]
],
"voltage":5,
"flash_size":0x40000,
"start_addr":0x4000,
"first_bank":1,
"flash_commands_on_bank_1":true,
"mbc":0x203,
"write_pin":"WR",
"read_identifier_at":0x4000,
"power_cycle":true,
"sector_size":0x4000,
"command_set":"BLAZE_XPLODER",
"commands":{
"reset":[
[ 0x5555, 0xAA ],
[ 0x2AAA, 0x55 ],
[ 0x5555, 0xF0 ]
],
"unlock_read":[
[ 0x0102, 1, 1 ]
],
"unlock":[
[ 0x0006, 0x01 ]
],
"bank_switch":[
[ 0x0006, "ID" ]
],
"read_identifier":[
[ 0x5555, 0xAA ],
[ 0x2AAA, 0x55 ],
[ 0x5555, 0x90 ]
],
"sector_erase":[],
"sector_erase_wait_for":[],
"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 ]
]
}
}

View File

@ -1,12 +1,15 @@
{ {
"type":"DMG", "type":"DMG",
"names":[ "names":[
"insideGadgets 512 KB" "insideGadgets 128/256/512 KB"
], ],
"flash_ids":[ "flash_ids":[
[ 0xBF, 0xB5, 0x01, 0xFF ],
[ 0xBF, 0xB6, 0x01, 0xFF ],
[ 0xBF, 0xB7, 0x01, 0xFF ] [ 0xBF, 0xB7, 0x01, 0xFF ]
], ],
"voltage":5, "voltage":5,
"power_cycle":true,
"flash_size":0x80000, "flash_size":0x80000,
"start_addr":0, "start_addr":0,
"first_bank":1, "first_bank":1,

View File

@ -10,6 +10,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None APP = None
DEVICE = None DEVICE = None
PORT = "" PORT = ""
FW_FILES = {"v1.1/v1.2":"fw_GBxCart_RW_v1_1_v1_2.zip", "v1.3":"fw_GBxCart_RW_v1_3.zip", "XMAS v1.0":"fw_GBxCart_RW_XMAS_v1_0.zip", "Mini v1.0":"fw_GBxCart_RW_Mini_v1_0.zip"}
def __init__(self, app, app_path, file=None, icon=None, device=None): def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self) QtWidgets.QDialog.__init__(self)
@ -18,12 +19,13 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.APP = app self.APP = app
self.APP_PATH = app_path self.APP_PATH = app_path
self.DEVICE = device self.DEVICE = device
self.PCB_VER = device.GetPCBVersion()
self.PORT = device.GetPort() self.PORT = device.GetPort()
self.setWindowTitle("FlashGBX Firmware Updater for GBxCart RW v1.3") self.setWindowTitle("FlashGBX Firmware Updater for GBxCart RW")
self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint) self.setWindowFlags((self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint) & ~QtCore.Qt.WindowContextHelpButtonHint)
with zipfile.ZipFile(self.APP_PATH + "/res/fw_GBxCart_RW_v1_3.zip") as zip: with zipfile.ZipFile(self.APP_PATH + "/res/{:s}".format(self.FW_FILES[self.PCB_VER])) as zip:
with zip.open("fw.ini") as f: ini_file = f.read() with zip.open("fw.ini") as f: ini_file = f.read()
ini_file = ini_file.decode(encoding="utf-8") ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware") self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
@ -75,8 +77,8 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.grpAvailableFwUpdates.setMinimumWidth(400) self.grpAvailableFwUpdates.setMinimumWidth(400)
self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout() self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout()
self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1) self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1)
self.optCFW = QtWidgets.QRadioButton("{:s}".format(self.CFW_VER)) self.optCFW = QtWidgets.QRadioButton("{:s}".format(self.CFW_VER))
self.optCFW.setChecked(True)
self.lblCFW_Blerb = QtWidgets.QLabel("{:s}".format(self.CFW_TEXT)) self.lblCFW_Blerb = QtWidgets.QLabel("{:s}".format(self.CFW_TEXT))
self.lblCFW_Blerb.setWordWrap(True) self.lblCFW_Blerb.setWordWrap(True)
self.lblCFW_Blerb.mousePressEvent = lambda x: [ self.optCFW.setChecked(True) ] self.lblCFW_Blerb.mousePressEvent = lambda x: [ self.optCFW.setChecked(True) ]
@ -85,9 +87,6 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.lblOFW_Blerb.setWordWrap(True) self.lblOFW_Blerb.setWordWrap(True)
self.lblOFW_Blerb.mousePressEvent = lambda x: [ self.optOFW.setChecked(True) ] self.lblOFW_Blerb.mousePressEvent = lambda x: [ self.optOFW.setChecked(True) ]
self.optExternal = QtWidgets.QRadioButton("External firmware file") self.optExternal = QtWidgets.QRadioButton("External firmware file")
#self.lblExternal_Blerb = QtWidgets.QLabel("<ul><li>Please check <a href=\"https://www.gbxcart.com/\">gbxcart.com</a> for the latest official firmware version</li></ul>")
#self.lblExternal_Blerb.setWordWrap(True)
#self.lblExternal_Blerb.mousePressEvent = lambda x: [ self.optOFW.setChecked(True) ]
self.rowUpdate = QtWidgets.QHBoxLayout() self.rowUpdate = QtWidgets.QHBoxLayout()
self.btnUpdate = QtWidgets.QPushButton("Install Firmware Update") self.btnUpdate = QtWidgets.QPushButton("Install Firmware Update")
@ -97,20 +96,22 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.rowUpdate.addStretch() self.rowUpdate.addStretch()
self.rowUpdate.addWidget(self.btnUpdate) self.rowUpdate.addWidget(self.btnUpdate)
self.rowUpdate.addStretch() self.rowUpdate.addStretch()
self.grpAvailableFwUpdatesLayout.addWidget(self.optCFW) if self.PCB_VER == "v1.3":
self.grpAvailableFwUpdatesLayout.addWidget(self.lblCFW_Blerb) self.grpAvailableFwUpdatesLayout.addWidget(self.optCFW)
self.grpAvailableFwUpdatesLayout.addWidget(self.lblCFW_Blerb)
self.optCFW.setChecked(True)
else:
self.optOFW.setChecked(True)
self.grpAvailableFwUpdatesLayout.addWidget(self.optOFW) self.grpAvailableFwUpdatesLayout.addWidget(self.optOFW)
self.grpAvailableFwUpdatesLayout.addWidget(self.lblOFW_Blerb) self.grpAvailableFwUpdatesLayout.addWidget(self.lblOFW_Blerb)
self.grpAvailableFwUpdatesLayout.addWidget(self.optExternal) self.grpAvailableFwUpdatesLayout.addWidget(self.optExternal)
#self.grpAvailableFwUpdatesLayout.addWidget(self.lblExternal_Blerb)
self.grpAvailableFwUpdatesLayout.addSpacing(3) self.grpAvailableFwUpdatesLayout.addSpacing(3)
self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate) self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate)
#self.grpAvailableFwUpdatesLayout.addWidget(self.btnUpdate)
self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout) self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout)
self.layout_device.addWidget(self.grpAvailableFwUpdates) self.layout_device.addWidget(self.grpAvailableFwUpdates)
# ↑↑↑ Available Firmware Updates # ↑↑↑ Available Firmware Updates
self.grpStatus = QtWidgets.QGroupBox("") self.grpStatus = QtWidgets.QGroupBox("")
self.grpStatusLayout = QtWidgets.QGridLayout() self.grpStatusLayout = QtWidgets.QGridLayout()
self.prgStatus = QtWidgets.QProgressBar() self.prgStatus = QtWidgets.QProgressBar()
@ -119,7 +120,6 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.prgStatus.setValue(0) self.prgStatus.setValue(0)
self.lblStatus = QtWidgets.QLabel("Status: Ready.") self.lblStatus = QtWidgets.QLabel("Status: Ready.")
#self.grpStatusLayout.addWidget(self.btnUpdate, 0, 0, QtCore.Qt.AlignCenter)
self.grpStatusLayout.addWidget(self.prgStatus, 1, 0) self.grpStatusLayout.addWidget(self.prgStatus, 1, 0)
self.grpStatusLayout.addWidget(self.lblStatus, 2, 0) self.grpStatusLayout.addWidget(self.lblStatus, 2, 0)
@ -200,18 +200,18 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
fn = "ofw.hex" fn = "ofw.hex"
else: else:
path = self.APP.SETTINGS.value("LastDirFirmwareUpdate") path = self.APP.SETTINGS.value("LastDirFirmwareUpdate")
path = QtWidgets.QFileDialog.getOpenFileName(self, "Choose GBxCart RW v1.3 Firmware File", path, "Firmware Update (*.hex);;All Files (*.*)")[0] path = QtWidgets.QFileDialog.getOpenFileName(self, "Choose GBxCart RW Firmware File", path, "Firmware Update (*.hex);;All Files (*.*)")[0]
if path == "": return if path == "": return
temp = re.search(r"^(gbxcart_rw_v1\.3_pcb_r.+\.hex)$", os.path.basename(path)) temp = re.search(r"^(gbx(?:cart|mas)_rw_.+_pcb_r.+\.hex)$", os.path.basename(path))
if temp is None: if temp is None:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The expected filename for a valid firmware file is <b>gbxcart_rw_v1.3_pcb_r**.hex</b>. Please visit <a href=\"https://www.gbxcart.com/\">gbxcart.com</a> for the latest official firmware updates.", standardButtons=QtWidgets.QMessageBox.Ok) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The expected filename for a valid firmware file is <b>gbx*_rw_*_pcb_r*.hex</b>. Please visit <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com</a> for the latest official firmware updates.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec() answer = msgbox.exec()
return return
self.APP.SETTINGS.setValue("LastDirFirmwareUpdate", os.path.dirname(path)) self.APP.SETTINGS.setValue("LastDirFirmwareUpdate", os.path.dirname(path))
fw = "{:s}<br><br><b>Please double check that this is a valid firmware file for the GBxCart RW v1.3. If it is invalid or an update for a different device, it may render your device unusable.</b>".format(path) fw = "{:s}<br><br><b>Please double check that this is a valid firmware file for your GBxCart RW. If it is invalid or an update for a different device, it may render your device unusable.</b>".format(path)
fn = None fn = None
text = "The following firmware will now be written to your GBxCart v1.3 device:<br>- {:s}".format(fw) text = "The following firmware will now be written to your GBxCart RW device:<br>- {:s}".format(fw)
text += "<br><br>Do you want to continue?" text += "<br><br>Do you want to continue?"
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes) msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes)
@ -223,7 +223,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.grpAvailableFwUpdates.setEnabled(False) self.grpAvailableFwUpdates.setEnabled(False)
if path == "": if path == "":
with zipfile.ZipFile(self.APP_PATH + "/res/fw_GBxCart_RW_v1_3.zip") as archive: with zipfile.ZipFile(self.APP_PATH + "/res/{:s}".format(self.FW_FILES[self.PCB_VER])) as archive:
with archive.open(fn) as f: ihex = f.read().decode("ascii") with archive.open(fn) as f: ihex = f.read().decode("ascii")
else: else:
with open(path, "rb") as f: ihex = f.read().decode("ascii") with open(path, "rb") as f: ihex = f.read().decode("ascii")
@ -298,7 +298,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
if self.ResetAVR(delay) is False: if self.ResetAVR(delay) is False:
fncSetStatus(text="Status: Bootloader error.", enableUI=True) fncSetStatus(text="Status: Bootloader error.", enableUI=True)
self.prgStatus.setValue(0) 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 v1.3 bootloader is not responding. If it doesnt 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 was not successful as the GBxCart RW bootloader is not responding. If it doesnt work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec() answer = msgbox.exec()
return 2 return 2
@ -326,13 +326,13 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
fncSetStatus("Status: Waiting for bootloader... (+{:d}ms)".format(math.ceil(delay * 1000))) fncSetStatus("Status: Waiting for bootloader... (+{:d}ms)".format(math.ceil(delay * 1000)))
if self.ResetAVR(delay) is False: if self.ResetAVR(delay) is False:
fncSetStatus(text="Status: Bootloader error.", enableUI=True) fncSetStatus(text="Status: Bootloader error.", enableUI=True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW v1.3 bootloader is not responding. If it doesnt 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 was not successful as the GBxCart RW bootloader is not responding. If it doesnt work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec() answer = msgbox.exec()
return 2 return 2
lives -= 1 lives -= 1
if lives < 0: if lives < 0:
fncSetStatus(text="Status: Bootloader timeout.", enableUI=True) fncSetStatus(text="Status: Bootloader timeout.", enableUI=True)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW v1.3 bootloader is not responding. If it doesnt 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 was not successful as the GBxCart RW bootloader is not responding. If it doesnt work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
answer = msgbox.exec() answer = msgbox.exec()
return 2 return 2
continue continue

View File

@ -16,7 +16,7 @@ from . import Util
class GbxDevice: class GbxDevice:
DEVICE_NAME = "GBxCart RW" DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 1 DEVICE_MIN_FW = 1
DEVICE_MAX_FW = 5 DEVICE_MAX_FW = 6
DEVICE_CMD = { DEVICE_CMD = {
"NULL":0x30, "NULL":0x30,
@ -49,6 +49,8 @@ class GbxDevice:
"DMG_MBC7_READ_EEPROM":0xB5, "DMG_MBC7_READ_EEPROM":0xB5,
"DMG_MBC7_WRITE_EEPROM":0xB6, "DMG_MBC7_WRITE_EEPROM":0xB6,
"DMG_MBC6_MMSA_WRITE_FLASH":0xB7, "DMG_MBC6_MMSA_WRITE_FLASH":0xB7,
"DMG_SET_BANK_CHANGE_CMD":0xB8,
"DMG_EEPROM_WRITE":0xB9,
"AGB_CART_READ":0xC1, "AGB_CART_READ":0xC1,
"AGB_CART_WRITE":0xC2, "AGB_CART_WRITE":0xC2,
"AGB_CART_READ_SRAM":0xC3, "AGB_CART_READ_SRAM":0xC3,
@ -59,7 +61,7 @@ class GbxDevice:
"AGB_CART_READ_3D_MEMORY":0xC8, "AGB_CART_READ_3D_MEMORY":0xC8,
"AGB_BOOTUP_SEQUENCE":0xC9, "AGB_BOOTUP_SEQUENCE":0xC9,
"DMG_FLASH_WRITE_BYTE":0xD1, "DMG_FLASH_WRITE_BYTE":0xD1,
"AGB_FLASH_WRITE_BYTE":0xD2, "AGB_FLASH_WRITE_SHORT":0xD2,
"FLASH_PROGRAM":0xD3, "FLASH_PROGRAM":0xD3,
"CART_WRITE_FLASH_CMD":0xD4, "CART_WRITE_FLASH_CMD":0xD4,
} }
@ -80,6 +82,8 @@ class GbxDevice:
"DMG_READ_CS_PULSE":[8, 0x08], "DMG_READ_CS_PULSE":[8, 0x08],
"DMG_WRITE_CS_PULSE":[8, 0x09], "DMG_WRITE_CS_PULSE":[8, 0x09],
"FLASH_DOUBLE_DIE":[8, 0x0A], "FLASH_DOUBLE_DIE":[8, 0x0A],
"DMG_READ_METHOD":[8, 0x0B],
"AGB_READ_METHOD":[8, 0x0C],
} }
PCB_VERSIONS = {4:'v1.3', 5:'v1.4', 6:'v1.4a', 101:'Mini v1.0d'} PCB_VERSIONS = {4:'v1.3', 5:'v1.4', 6:'v1.4a', 101:'Mini v1.0d'}
@ -88,6 +92,7 @@ class GbxDevice:
FW = [] FW = []
FW_UPDATE_REQ = False FW_UPDATE_REQ = False
FW_VAR = {}
MODE = None MODE = None
PORT = '' PORT = ''
DEVICE = None DEVICE = None
@ -187,7 +192,6 @@ class GbxDevice:
except SerialException as e: except SerialException as e:
if "Permission" in str(e): if "Permission" in str(e):
conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " couldnt be accessed. Make sure your user account has permission to use it and its not already in use by another application."]) conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " couldnt be accessed. Make sure your user account has permission to use it and its not already in use by another application."])
print(str(e))
else: else:
conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)]) conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)])
continue continue
@ -247,7 +251,7 @@ class GbxDevice:
def IsSupportedMbc(self, mbc): def IsSupportedMbc(self, mbc):
if self.CanPowerCycleCart(): if self.CanPowerCycleCart():
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x201, 0x202 ) return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x201, 0x202, 0x203, 0x204 )
else: else:
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x202 ) return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x202 )
@ -277,6 +281,13 @@ class GbxDevice:
self.DEVICE.reset_output_buffer() self.DEVICE.reset_output_buffer()
return self.LoadFirmwareVersion() return self.LoadFirmwareVersion()
except SerialException as e: except SerialException as e:
print("Connection lost!")
try:
if e.args[0].startswith("ClearCommError failed"):
self.DEVICE.close()
return False
except:
pass
print(str(e)) print(str(e))
return False return False
@ -321,6 +332,9 @@ class GbxDevice:
else: else:
return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), self.PORT) return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), self.PORT)
def GetOfficialWebsite(self):
return "https://www.gbxcart.com/"
def SupportsFirmwareUpdates(self): def SupportsFirmwareUpdates(self):
return self.FW["pcb_ver"] in (4, 5, 6) return self.FW["pcb_ver"] in (4, 5, 6)
@ -373,8 +387,8 @@ class GbxDevice:
if not isinstance(data, bytearray): if not isinstance(data, bytearray):
data = bytearray([data]) data = bytearray([data])
dstr = ' '.join(format(x, '02X') for x in data) #dstr = ' '.join(format(x, '02X') for x in data)
dprint("[{:02X}] {:s}".format(int(len(dstr)/3) + 1, dstr[:96])) #dprint("[{:02X}] {:s}".format(int(len(dstr)/3) + 1, dstr[:96]))
self.DEVICE.write(data) self.DEVICE.write(data)
self.DEVICE.flush() self.DEVICE.flush()
@ -405,6 +419,7 @@ class GbxDevice:
def _set_fw_variable(self, key, value): def _set_fw_variable(self, key, value):
dprint("Setting firmware variable {:s} to 0x{:X}".format(key, value)) dprint("Setting firmware variable {:s} to 0x{:X}".format(key, value))
self.FW_VAR[key] = value
size = 0 size = 0
for (k, v) in self.DEVICE_VAR.items(): for (k, v) in self.DEVICE_VAR.items():
@ -421,14 +436,20 @@ class GbxDevice:
buffer.extend(struct.pack(">I", key)) buffer.extend(struct.pack(">I", key))
buffer.extend(struct.pack(">I", value)) buffer.extend(struct.pack(">I", value))
self._write(buffer) self._write(buffer)
def _cart_read(self, address, length=0, agb_save_flash=False): def _cart_read(self, address, length=0, agb_save_flash=False):
if self.MODE == "DMG": if self.MODE == "DMG":
if length == 0: if length == 0:
length = 1 length = 1
return struct.unpack("B", self.ReadROM(address, 1))[0] if address < 0xA000:
return struct.unpack("B", self.ReadROM(address, 1))[0]
else:
return struct.unpack("B", self.ReadRAM(address - 0xA000, 1))[0]
else: else:
return self.ReadROM(address, length) if address < 0xA000:
return self.ReadROM(address, length)
else:
return self.ReadRAM(address - 0xA000, length)
elif self.MODE == "AGB": elif self.MODE == "AGB":
if length == 0: if length == 0:
length = 2 length = 2
@ -440,7 +461,7 @@ class GbxDevice:
return self.ReadROM(address, length) return self.ReadROM(address, length)
def _cart_write(self, address, value, flashcart=False, sram=False): def _cart_write(self, address, value, flashcart=False, sram=False):
dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(address, value & 0xFF), flashcart, sram) dprint("Writing to cartridge: 0x{:X} = 0x{:X} (flashcart={:s}, sram={:s})".format(address, value & 0xFF, str(flashcart), str(sram)))
if self.MODE == "DMG": if self.MODE == "DMG":
if flashcart: if flashcart:
buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]]) buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]])
@ -457,7 +478,7 @@ class GbxDevice:
self._read(1) self._read(1)
return return
elif flashcart: elif flashcart:
buffer = bytearray([self.DEVICE_CMD["AGB_FLASH_WRITE_BYTE"]]) buffer = bytearray([self.DEVICE_CMD["AGB_FLASH_WRITE_SHORT"]])
else: else:
buffer = bytearray([self.DEVICE_CMD["AGB_CART_WRITE"]]) buffer = bytearray([self.DEVICE_CMD["AGB_CART_WRITE"]])
@ -465,19 +486,33 @@ class GbxDevice:
buffer.extend(struct.pack(">H", value & 0xFFFF)) buffer.extend(struct.pack(">H", value & 0xFFFF))
self._write(buffer) self._write(buffer)
def _cart_write_flash(self, commands): def _cart_write_flash(self, commands, flashcart=False):
if self.FW["fw_ver"] < 6 and not (self.MODE == "AGB" and not flashcart):
for command in commands:
self._cart_write(command[0], command[1], flashcart=flashcart)
return
num = len(commands) num = len(commands)
buffer = bytearray([self.DEVICE_CMD["CART_WRITE_FLASH_CMD"]]) buffer = bytearray([self.DEVICE_CMD["CART_WRITE_FLASH_CMD"]])
if self.FW["fw_ver"] >= 6:
buffer.extend(struct.pack("B", 1 if flashcart else 0))
buffer.extend(struct.pack("B", num)) buffer.extend(struct.pack("B", num))
for i in range(0, num): for i in range(0, num):
#dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(commands[i][0], commands[i][1] & 0xFF)) dprint("Writing to cartridge: 0x{:X} = 0x{:X} ({:d} of {:d})".format(commands[i][0], commands[i][1], i+1, num))
buffer.extend(struct.pack(">I", commands[i][0])) if self.MODE == "AGB" and flashcart:
buffer.extend(struct.pack("B", commands[i][1])) buffer.extend(struct.pack(">I", commands[i][0] >> 1))
else:
buffer.extend(struct.pack(">I", commands[i][0]))
if self.FW["fw_ver"] < 6:
buffer.extend(struct.pack("B", commands[i][1]))
else:
buffer.extend(struct.pack(">H", commands[i][1]))
self._write(buffer) self._write(buffer)
ret = self._read(1)
if self._read(1) != 0x01: if ret != 0x01:
print("Error!") print("Error in _cart_write_flash():", ret)
def _clk_toggle(self, num): def _clk_toggle(self, num):
if self.FW["pcb_ver"] not in (5, 6, 101): return False if self.FW["pcb_ver"] not in (5, 6, 101): return False
@ -495,8 +530,6 @@ class GbxDevice:
self._write(self.DEVICE_CMD["SET_MODE_DMG"]) self._write(self.DEVICE_CMD["SET_MODE_DMG"])
elif self.MODE == "AGB": elif self.MODE == "AGB":
self._write(self.DEVICE_CMD["SET_MODE_AGB"]) self._write(self.DEVICE_CMD["SET_MODE_AGB"])
else:
print("{:s}NOTE: Cartridge power cycling is not supported by this device.{:s}".format(ANSI.YELLOW, ANSI.RESET))
def CartPowerOff(self, delay=0.1): def CartPowerOff(self, delay=0.1):
if self.FW["pcb_ver"] in (5, 6): if self.FW["pcb_ver"] in (5, 6):
@ -519,10 +552,14 @@ class GbxDevice:
if mode == "DMG": if mode == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_DMG"]) self._write(self.DEVICE_CMD["SET_MODE_DMG"])
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
self._set_fw_variable("DMG_READ_METHOD", 1)
self._set_fw_variable("CART_MODE", 1)
self.MODE = "DMG" self.MODE = "DMG"
elif mode == "AGB": elif mode == "AGB":
self._write(self.DEVICE_CMD["SET_MODE_AGB"]) self._write(self.DEVICE_CMD["SET_MODE_AGB"])
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"])
self._set_fw_variable("AGB_READ_METHOD", 0)
self._set_fw_variable("CART_MODE", 2)
self.MODE = "AGB" self.MODE = "AGB"
self._set_fw_variable(key="ADDRESS", value=0) self._set_fw_variable(key="ADDRESS", value=0)
self.CartPowerOn() self.CartPowerOn()
@ -534,7 +571,7 @@ class GbxDevice:
return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values())) return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values()))
def SetProgress(self, args): def SetProgress(self, args):
if self.CANCEL and args["action"] != "ABORT": return if self.CANCEL and args["action"] not in ("ABORT", "FINISHED"): return
if args["action"] == "UPDATE_POS": if args["action"] == "UPDATE_POS":
self.POS = args["pos"] self.POS = args["pos"]
self.INFO["transferred"] = args["pos"] self.INFO["transferred"] = args["pos"]
@ -543,7 +580,9 @@ class GbxDevice:
except AttributeError: except AttributeError:
if self.SIGNAL is not None: if self.SIGNAL is not None:
self.SIGNAL(args) self.SIGNAL(args)
if args["action"] == "FINISHED": self.SIGNAL = None
if args["action"] == "FINISHED":
self.SIGNAL = None
def ReadInfo(self, setPinsAsInputs=False, checkRtc=True): def ReadInfo(self, setPinsAsInputs=False, checkRtc=True):
if not self.IsConnected(): raise Exception("Couldnt access the the device.") if not self.IsConnected(): raise Exception("Couldnt access the the device.")
@ -555,7 +594,7 @@ class GbxDevice:
self._write(self.DEVICE_CMD["OFW_CART_MODE"]) # Reset LEDs self._write(self.DEVICE_CMD["OFW_CART_MODE"]) # Reset LEDs
self._read(1) self._read(1)
self.CartPowerOn() self.CartPowerOn()
if self.MODE == "DMG": if self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True) self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
@ -566,28 +605,30 @@ class GbxDevice:
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=True) self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=True)
header = self.ReadROM(0, 0x180) header = self.ReadROM(0, 0x180)
if Util.DEBUG:
with open("debug_header.bin", "wb") as f: f.write(header)
if header is False or len(header) != 0x180: if header is False or len(header) != 0x180:
print("{:s}\n{:s}Couldnt read the cartridge information. Please try again.{:s}".format(str(header), ANSI.RED, ANSI.RESET)) print("{:s}\n{:s}Couldnt read the cartridge information. Please try again.{:s}".format(str(header), ANSI.RED, ANSI.RESET))
return False return False
if Util.DEBUG:
with open("debug_header.bin", "wb") as f: f.write(header)
# Unlock DACS carts on older firmware
if self.MODE == "AGB" and (self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] == 1):
if header[0x04:0x04+0x9C] == bytearray([0x00] * 0x9C):
self.ReadROM(0x1FFFFE0, 20)
header = self.ReadROM(0, 0x180)
# Parse ROM header # Parse ROM header
if self.MODE == "DMG": if self.MODE == "DMG":
data = RomFileDMG(header).GetHeader() data = RomFileDMG(header).GetHeader()
if data["logo_correct"] is False: # try to fix weird bootlegs if data["game_title"] == "TETRIS" and hashlib.sha1(header).digest() != bytearray([0x1D, 0x69, 0x2A, 0x4B, 0x31, 0x7A, 0xA5, 0xE9, 0x67, 0xEE, 0xC2, 0x2F, 0xCC, 0x32, 0x43, 0x8C, 0xCB, 0xC5, 0x78, 0x0B]): # Sachen
header = self.ReadROM(0, 0x280)
data = RomFileDMG(header).GetHeader()
if data["logo_correct"] is False and not b"Future Console Design" in header: # workaround for strange bootlegs
self._cart_write(0, 0xFF) self._cart_write(0, 0xFF)
time.sleep(0.1) time.sleep(0.1)
header = self.ReadROM(0, 0x180) header = self.ReadROM(0, 0x280)
data = RomFileDMG(header).GetHeader()
if data["mapper_raw"] == 0x203 or b"Future Console Design" in header: # Xploder GB version number
self._cart_write(0x0006, 0)
header[0:0x10] = self.ReadROM(0x4000, 0x10)
header[0xD0:0xE0] = self.ReadROM(0x40D0, 0x10)
data = RomFileDMG(header).GetHeader() data = RomFileDMG(header).GetHeader()
_mbc = DMG_MBC().GetInstance(args={"mbc":data["features_raw"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle) _mbc = DMG_MBC().GetInstance(args={"mbc":data["mapper_raw"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
if checkRtc: if checkRtc:
data["has_rtc"] = _mbc.HasRTC() is True data["has_rtc"] = _mbc.HasRTC() is True
if data["has_rtc"] is True: if data["has_rtc"] is True:
@ -597,26 +638,35 @@ class GbxDevice:
data["has_rtc"] = False data["has_rtc"] = False
elif self.MODE == "AGB": elif self.MODE == "AGB":
# Unlock DACS carts on older firmware
if self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] == 1:
if header[0x04:0x04+0x9C] == bytearray([0x00] * 0x9C):
self.ReadROM(0x1FFFFE0, 20)
header = self.ReadROM(0, 0x180)
data = RomFileAGB(header).GetHeader() data = RomFileAGB(header).GetHeader()
if data["logo_correct"] is False: # workaround for weird bootlegs if data["logo_correct"] is False: # workaround for strange bootlegs
self._cart_write(0, 0xFF) self._cart_write(0, 0xFF)
time.sleep(0.1) time.sleep(0.1)
header = self.ReadROM(0, 0x180) header = self.ReadROM(0, 0x180)
data = RomFileAGB(header).GetHeader() data = RomFileAGB(header).GetHeader()
# Check where the ROM data repeats if data["empty"] or data["empty_nocart"]:
size_check = header[0xA0:0xA0+16] data["rom_size"] = 0x2000000
currAddr = 0x400000 elif (data["3d_memory"] == True):
while currAddr < 0x2000000:
buffer = self.ReadROM(currAddr + 0xA0, 64)[:16]
if buffer == size_check: break
currAddr += 0x400000
data["rom_size"] = currAddr
if (data["3d_memory"] == True):
data["rom_size"] = 0x4000000 data["rom_size"] = 0x4000000
elif (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"): else:
data["dacs_8m"] = True # Check where the ROM data repeats (for unlicensed carts)
size_check = header[0xA0:0xA0+16]
currAddr = 0x400000
while currAddr < 0x2000000:
buffer = self.ReadROM(currAddr + 0xA0, 64)[:16]
if buffer == size_check: break
currAddr += 0x400000
data["rom_size"] = currAddr
if (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"):
data["dacs_8m"] = True
if checkRtc and data["logo_correct"] is True and header[0xC5] == 0 and header[0xC7] == 0 and header[0xC9] == 0: if checkRtc and data["logo_correct"] is True and header[0xC5] == 0 and header[0xC7] == 0 and header[0xC9] == 0:
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle) _agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
@ -653,7 +703,7 @@ class GbxDevice:
self.INFO["has_rtc"] = has_rtc self.INFO["has_rtc"] = has_rtc
if self.MODE == "DMG" and mbc is None: if self.MODE == "DMG" and mbc is None:
mbc = info["features_raw"] mbc = info["mapper_raw"]
if mbc > 0x200: checkSaveType = False if mbc > 0x200: checkSaveType = False
(cart_types, cart_type_id, flash_id, cfi_s, cfi) = self.AutoDetectFlash(limitVoltage=limitVoltage) (cart_types, cart_type_id, flash_id, cfi_s, cfi) = self.AutoDetectFlash(limitVoltage=limitVoltage)
@ -1124,7 +1174,7 @@ class GbxDevice:
for i in range(0, num): for i in range(0, num):
self._set_fw_variable("TRANSFER_SIZE", length) self._set_fw_variable("TRANSFER_SIZE", length)
self._set_fw_variable("ADDRESS", address) self._set_fw_variable("ADDRESS", address)
#dprint("Now in iteration {:d}".format(i)) dprint("Now in iteration {:d}".format(i))
if buffer[i*length:i*length+length] == bytearray([0xFF] * length): if buffer[i*length:i*length+length] == bytearray([0xFF] * length):
skip_write = True skip_write = True
@ -1248,14 +1298,22 @@ class GbxDevice:
[ address, length - 1 ], [ address, length - 1 ],
[ address, 0x00 ], [ address, 0x00 ],
]) ])
while True: lives = 100
while lives > 0:
self._cart_write(0x4000, 0x70) self._cart_write(0x4000, 0x70)
sr = self._cart_read(address + length - 1) sr = self._cart_read(address + length - 1)
dprint("sr=0x{:X}".format(sr)) dprint("sr=0x{:X}".format(sr))
if sr & 0x80 == 0x80: break if sr & 0x80 == 0x80: break
time.sleep(0.001) time.sleep(0.001)
lives -= 1
self._cart_write(0x4000, 0xFF) self._cart_write(0x4000, 0xFF)
if lives == 0:
self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(sr), i, length)}
self.CANCEL = True
self.ERROR = True
return False
address += length address += length
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE: if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
self.SetProgress({"action":"WRITE", "bytes_added":length}) self.SetProgress({"action":"WRITE", "bytes_added":length})
@ -1263,11 +1321,48 @@ class GbxDevice:
self._cart_write(address - 1, 0xFF) self._cart_write(address - 1, 0xFF)
self.SKIPPING = skip_write self.SKIPPING = skip_write
def WriteROM_DMG_EEPROM(self, address, buffer, bank, eeprom_buffer_size=0x80):
length = len(buffer)
if self.FW["pcb_ver"] not in (5, 6, 101):
max_length = 256
else:
max_length = 1024
num = math.ceil(length / max_length)
if length > max_length: length = max_length
dprint("Writing 0x{:X} bytes to EEPROM in {:d} iteration(s)".format(length, num))
for i in range(0, num):
self._set_fw_variable("BUFFER_SIZE", eeprom_buffer_size)
self._set_fw_variable("TRANSFER_SIZE", length)
self._set_fw_variable("ADDRESS", address)
dprint("Now in iteration {:d}".format(i))
self._write(self.DEVICE_CMD["DMG_EEPROM_WRITE"])
self._write(buffer[i*length:i*length+length])
ret = self._read(1)
if ret not in (0x01, 0x03):
self.CANCEL_ARGS = {"info_type":"msgbox_critical", "info_msg":"EEPROM write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)}
self.CANCEL = True
self.ERROR = True
return False
address += length
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
self.SetProgress({"action":"WRITE", "bytes_added":length})
self._cart_write_flash([
[0x0006, 0x01],
[0x5555, 0xAA],
[0x2AAA, 0x55],
[0x5555, 0xF0],
[0x0006, bank]
])
def CheckROMStable(self): def CheckROMStable(self):
if not self.IsConnected(): raise Exception("Couldnt access the the device.") if not self.IsConnected(): raise Exception("Couldnt access the the device.")
buffer1 = self.ReadROM(0x100, 0x40) buffer1 = self.ReadROM(0x80, 0x40)
time.sleep(0.05) time.sleep(0.05)
buffer2 = self.ReadROM(0x100, 0x40) buffer2 = self.ReadROM(0x80, 0x40)
return buffer1 == buffer2 return buffer1 == buffer2
def AutoDetectFlash(self, limitVoltage=False): def AutoDetectFlash(self, limitVoltage=False):
@ -1277,12 +1372,13 @@ class GbxDevice:
flash_id_found = False flash_id_found = False
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values()) supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
if self.MODE == "DMG": if self.MODE == "DMG":
if limitVoltage: if limitVoltage:
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"])
else: else:
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
time.sleep(0.25) time.sleep(0.1)
self._write(self.DEVICE_CMD["SET_MODE_DMG"]) self._write(self.DEVICE_CMD["SET_MODE_DMG"])
elif self.MODE == "DMG": elif self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_AGB"]) self._write(self.DEVICE_CMD["SET_MODE_AGB"])
@ -1295,21 +1391,21 @@ class GbxDevice:
dprint("*** Now checking: {:s}\n".format(flashcart_meta["names"][0])) dprint("*** Now checking: {:s}\n".format(flashcart_meta["names"][0]))
if self.MODE == "DMG": if self.MODE == "DMG":
if flashcart_meta["write_pin"] == "WR": we = flashcart_meta["write_pin"]
we = 0x01 # FLASH_WE_PIN_WR if we == "WR":
elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"): self._set_fw_variable("FLASH_WE_PIN", 0x01) # FLASH_WE_PIN_WR
we = 0x02 # FLASH_WE_PIN_AUDIO elif we in ("AUDIO", "VIN"):
elif flashcart_meta["write_pin"] == "WR+RESET": self._set_fw_variable("FLASH_WE_PIN", 0x02) # FLASH_WE_PIN_AUDIO
we = 0x03 # FLASH_WE_PIN_WR_RESET elif we == "WR+RESET":
self._set_fw_variable("FLASH_WE_PIN", we) 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_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)
flashcart.Reset(full_reset=False) flashcart.Reset(full_reset=False)
flashcart.Unlock() flashcart.Unlock()
if "flash_ids" in flashcart_meta and len(flashcart_meta["flash_ids"]) > 0: if "flash_ids" in flashcart_meta and len(flashcart_meta["flash_ids"]) > 0:
vfid = flashcart.VerifyFlashID() vfid = flashcart.VerifyFlashID()
if vfid is not False: if vfid is not False:
(verified, cart_flash_id) = flashcart.VerifyFlashID() (verified, cart_flash_id) = vfid
if verified and cart_flash_id in flashcart_meta["flash_ids"]: if verified and cart_flash_id in flashcart_meta["flash_ids"]:
flash_id = cart_flash_id flash_id = cart_flash_id
flash_id_found = True flash_id_found = True
@ -1349,10 +1445,20 @@ class GbxDevice:
if self.MODE == "DMG" and not flash_id_found: if self.MODE == "DMG" and not flash_id_found:
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
time.sleep(0.25) time.sleep(0.1)
return (flash_types, flash_type_id, flash_id, cfi_s, cfi) return (flash_types, flash_type_id, flash_id, cfi_s, cfi)
def CheckFlashChip(self, limitVoltage=False, cart_type=None): # aka. the most horribly written function def CheckFlashChip(self, limitVoltage=False, cart_type=None): # aka. the most horribly written function
if cart_type is not None:
if self.MODE == "DMG":
if cart_type["write_pin"] == "WR":
we = 0x01 # FLASH_WE_PIN_WR
elif cart_type["write_pin"] in ("AUDIO", "VIN"):
we = 0x02 # FLASH_WE_PIN_AUDIO
elif cart_type["write_pin"] == "WR+RESET":
we = 0x03 # FLASH_WE_PIN_WR_RESET
self._set_fw_variable("FLASH_WE_PIN", we)
if self.FW["pcb_ver"] in (5, 6, 101): if self.FW["pcb_ver"] in (5, 6, 101):
self._write(self.DEVICE_CMD["OFW_CART_MODE"]) self._write(self.DEVICE_CMD["OFW_CART_MODE"])
self._read(1) self._read(1)
@ -1375,6 +1481,13 @@ class GbxDevice:
#{ 'read_cfi':[[0x4000, 0x98]], 'read_identifier':[[ 0x4000, 0x90 ]], 'reset':[[ 0x4000, 0xFF ]] }, #{ 'read_cfi':[[0x4000, 0x98]], 'read_identifier':[[ 0x4000, 0x90 ]], 'reset':[[ 0x4000, 0xFF ]] },
] ]
if self.MODE == "DMG":
if limitVoltage:
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"])
else:
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
time.sleep(0.1)
check_buffer = self.ReadROM(0, 0x400) check_buffer = self.ReadROM(0, 0x400)
d_swap = None d_swap = None
cfi_info = "" cfi_info = ""
@ -1385,11 +1498,6 @@ class GbxDevice:
cfi = {'raw':b''} cfi = {'raw':b''}
if self.MODE == "DMG": if self.MODE == "DMG":
if limitVoltage:
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"])
else:
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
time.sleep(0.25)
rom_string = "[ ROM ] " + rom_string rom_string = "[ ROM ] " + rom_string
we_pins = [ "WR", "AUDIO" ] we_pins = [ "WR", "AUDIO" ]
else: else:
@ -1414,7 +1522,8 @@ class GbxDevice:
buffer = self.ReadROM(0, 0x400) buffer = self.ReadROM(0, 0x400)
for i in range(0, len(method['reset'])): for i in range(0, len(method['reset'])):
self._cart_write(method['reset'][i][0], method['reset'][i][1], flashcart=True) self._cart_write(method['reset'][i][0], method['reset'][i][1], flashcart=True)
if buffer == check_buffer: continue #if buffer == check_buffer: continue
if buffer == bytearray([0x00] * len(buffer)): continue
magic = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24])) magic = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24]))
if magic == "QRY": # nothing swapped if magic == "QRY": # nothing swapped
@ -1482,6 +1591,7 @@ class GbxDevice:
self._cart_write(method['read_identifier'][i][0], method["read_identifier"][i][1], flashcart=True) self._cart_write(method['read_identifier'][i][0], method["read_identifier"][i][1], flashcart=True)
flash_id = self.ReadROM(0, 8) flash_id = self.ReadROM(0, 8)
if flash_id == check_buffer[:len(flash_id)]: continue if flash_id == check_buffer[:len(flash_id)]: continue
if flash_id == bytearray([0x00] * len(flash_id)): continue
if self.MODE == "DMG": if self.MODE == "DMG":
method_string = "[" + we.ljust(5) + "/{:4X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1]) method_string = "[" + we.ljust(5) + "/{:4X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1])
@ -1495,7 +1605,7 @@ class GbxDevice:
self._cart_write(method['reset'][i][0], method['reset'][i][1], flashcart=True) 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 if cart_type is not None: # reset cartridge if method is known
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, 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)
flashcart.Reset(full_reset=False) flashcart.Reset(full_reset=False)
if "method" in cfi: if "method" in cfi:
@ -1543,7 +1653,8 @@ class GbxDevice:
flash_id = self.ReadROM(method["start_addr"], 8) flash_id = self.ReadROM(method["start_addr"], 8)
else: else:
flash_id = self.ReadROM(0, 8) flash_id = self.ReadROM(0, 8)
if flash_id == bytearray([0x00] * len(flash_id)): continue
for i in range(0, len(method['reset'])): for i in range(0, len(method['reset'])):
self._cart_write(method['reset'][i][0], method["reset"][i][1], flashcart=True) self._cart_write(method['reset'][i][0], method["reset"][i][1], flashcart=True)
@ -1561,10 +1672,10 @@ class GbxDevice:
if self.MODE == "DMG": if self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"]) self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
time.sleep(0.25) time.sleep(0.2)
if cart_type is not None: # reset cartridge if method is known if cart_type is not None: # reset cartridge if method is known
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, 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)
flashcart.Reset(full_reset=True) flashcart.Reset(full_reset=True)
flash_id = "" flash_id = ""
@ -1626,13 +1737,18 @@ class GbxDevice:
if i == args["cart_type"]: if i == args["cart_type"]:
try: try:
cart_type["_index"] = cart_type["names"].index(list(self.SUPPORTED_CARTS[self.MODE].keys())[i]) 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_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)
except: except:
pass pass
buffer_len = 0x4000 buffer_len = 0x4000
if self.MODE == "DMG": if self.MODE == "DMG":
self.INFO["features_raw"] = args["mbc"] self.INFO["mapper_raw"] = args["mbc"]
if not self.IsSupportedMbc(args["mbc"]):
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device. An updated hardware revision is required.".format(Util.APPNAME, self.GetFullName())
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
return False
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle) _mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
self._write(self.DEVICE_CMD["SET_MODE_DMG"]) self._write(self.DEVICE_CMD["SET_MODE_DMG"])
@ -1643,6 +1759,9 @@ class GbxDevice:
self._set_fw_variable("DMG_READ_CS_PULSE", 1) self._set_fw_variable("DMG_READ_CS_PULSE", 1)
_mbc.EnableMapper() _mbc.EnableMapper()
self._set_fw_variable("DMG_READ_CS_PULSE", 0) self._set_fw_variable("DMG_READ_CS_PULSE", 0)
elif _mbc.GetName() == "Sachen":
start_bank = int(args["rom_size"] / 0x4000)
_mbc.SetStartBank(start_bank)
else: else:
_mbc.EnableMapper() _mbc.EnableMapper()
@ -1701,6 +1820,7 @@ class GbxDevice:
cancel_args = {"action":"ABORT", "abortable":False} cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS) cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {} self.CANCEL_ARGS = {}
self.ERROR_ARGS = {}
self.SetProgress(cancel_args) self.SetProgress(cancel_args)
try: try:
if file is not None: file.close() if file is not None: file.close()
@ -1770,14 +1890,26 @@ class GbxDevice:
file.close() file.close()
# Calculate Global Checksum # Calculate Global Checksum
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest()
#self.INFO["file_sha256"] = hashlib.sha256(buffer).hexdigest()
#self.INFO["file_md5"] = hashlib.md5(buffer).hexdigest()
if self.MODE == "DMG": if self.MODE == "DMG":
chk = _mbc.CalcChecksum(buffer) chk = _mbc.CalcChecksum(buffer)
elif self.MODE == "AGB": elif self.MODE == "AGB":
chk = zlib.crc32(buffer) & 0xFFFFFFFF chk = self.INFO["file_crc32"]
self.INFO["rom_checksum_calc"] = chk self.INFO["rom_checksum_calc"] = chk
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest() # Check for ROM loops
self.INFO["loop_detected"] = False
temp = min(0x2000000, len(buffer))
while temp > 0x4000:
temp = temp >> 1
if (buffer[0:0x4000] == buffer[temp:temp+0x4000]):
if buffer[0:temp] == buffer[temp:temp*2]:
self.INFO["loop_detected"] = temp
else:
break
# ↓↓↓ Switch to first ROM bank # ↓↓↓ Switch to first ROM bank
if self.MODE == "DMG": if self.MODE == "DMG":
@ -1807,6 +1939,10 @@ class GbxDevice:
extra_size = 0 extra_size = 0
audio_low = False audio_low = False
if self.MODE == "DMG": if self.MODE == "DMG":
if not self.IsSupportedMbc(args["mbc"]):
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device. An updated hardware revision is required.".format(Util.APPNAME, self.GetFullName())
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
return False
save_size = args["save_type"] save_size = args["save_type"]
ram_banks = _mbc.GetRAMBanks(save_size) ram_banks = _mbc.GetRAMBanks(save_size)
buffer_len = min(0x200, _mbc.GetRAMBankSize()) buffer_len = min(0x200, _mbc.GetRAMBankSize())
@ -1814,6 +1950,7 @@ class GbxDevice:
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0) self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
self._set_fw_variable("DMG_READ_CS_PULSE", 0) self._set_fw_variable("DMG_READ_CS_PULSE", 0)
# Enable mappers
if _mbc.GetName() == "TAMA5": if _mbc.GetName() == "TAMA5":
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1) self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
self._set_fw_variable("DMG_READ_CS_PULSE", 1) self._set_fw_variable("DMG_READ_CS_PULSE", 1)
@ -1828,6 +1965,32 @@ class GbxDevice:
self._set_fw_variable("FLASH_METHOD", 0x04) # FLASH_METHOD_DMG_MBC6 self._set_fw_variable("FLASH_METHOD", 0x04) # FLASH_METHOD_DMG_MBC6
self._set_fw_variable("FLASH_WE_PIN", 0x01) # WR self._set_fw_variable("FLASH_WE_PIN", 0x01) # WR
_mbc.EnableFlash(enable=True, enable_write=True if (args["mode"] == 3) else False) _mbc.EnableFlash(enable=True, enable_write=True if (args["mode"] == 3) else False)
elif _mbc.GetName() == "Xploder GB":
empty_data_byte = 0xFF
self._set_fw_variable("FLASH_PULSE_RESET", 0)
self._set_fw_variable("FLASH_DOUBLE_DIE", 0)
self._write(self.DEVICE_CMD["SET_FLASH_CMD"])
self._write(0x00) # FLASH_COMMAND_SET_NONE
self._write(0x01) # FLASH_METHOD_UNBUFFERED
self._write(0x01) # FLASH_WE_PIN_WR
commands = [
[ 0x5555, 0xAA ],
[ 0x2AAA, 0x55 ],
[ 0x5555, 0xA0 ],
]
for i in range(0, 6):
if i >= len(commands):
self._write(bytearray(struct.pack(">I", 0)) + bytearray(struct.pack(">H", 0)))
else:
self._write(bytearray(struct.pack(">I", commands[i][0])) + bytearray(struct.pack(">H", commands[i][1])))
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
self._write(1) # number of commands
self._write(bytearray(struct.pack(">I", 0x0006))) # address/value
self._write(0) # type = address
ret = self._read(1)
if ret != 0x01:
print("Error in DMG_SET_BANK_CHANGE_CMD:", ret)
else: else:
_mbc.EnableMapper() _mbc.EnableMapper()
@ -1918,7 +2081,7 @@ class GbxDevice:
command = commands[args["save_type"]][args["mode"] - 2] command = commands[args["save_type"]][args["mode"] - 2]
if args["rtc"] is True: if args["rtc"] is True:
extra_size = 0x10 extra_size = 0x10
if args["mode"] == 2: # Backup if args["mode"] == 2: # Backup
action = "SAVE_READ" action = "SAVE_READ"
buffer = bytearray() buffer = bytearray()
@ -1927,6 +2090,8 @@ class GbxDevice:
self.INFO["save_erase"] = args["erase"] self.INFO["save_erase"] = args["erase"]
if args["erase"] == True: if args["erase"] == True:
buffer = bytearray([ empty_data_byte ] * save_size) buffer = bytearray([ empty_data_byte ] * save_size)
if self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
buffer[0] = 0x00
else: else:
if args["path"] is None: if args["path"] is None:
buffer = self.INFO["data"] buffer = self.INFO["data"]
@ -1956,6 +2121,10 @@ class GbxDevice:
if ((buffer_offset - 0x8000) % 0x20000) == 0: if ((buffer_offset - 0x8000) % 0x20000) == 0:
dprint("Erasing flash sector at position 0x{:X}".format(buffer_offset)) dprint("Erasing flash sector at position 0x{:X}".format(buffer_offset))
_mbc.EraseFlashSector() _mbc.EraseFlashSector()
elif _mbc.GetName() == "Xploder GB":
self._set_fw_variable("DMG_ROM_BANK", bank + 8)
(start_address, bank_size) = _mbc.SelectBankRAM(bank)
end_address = min(save_size, start_address + bank_size)
else: else:
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1 if _mbc.WriteWithCSPulse() else 0) self._set_fw_variable("DMG_WRITE_CS_PULSE", 1 if _mbc.WriteWithCSPulse() else 0)
(start_address, bank_size) = _mbc.SelectBankRAM(bank) (start_address, bank_size) = _mbc.SelectBankRAM(bank)
@ -1994,6 +2163,7 @@ class GbxDevice:
cancel_args = {"action":"ABORT", "abortable":False} cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS) cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {} self.CANCEL_ARGS = {}
self.ERROR_ARGS = {}
self.SetProgress(cancel_args) self.SetProgress(cancel_args)
if self.CanPowerCycleCart(): self.CartPowerCycle() if self.CanPowerCycleCart(): self.CartPowerCycle()
return return
@ -2005,6 +2175,8 @@ class GbxDevice:
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=False, max_length=max_length) temp = self.ReadROM(address=pos, length=buffer_len, skip_init=False, max_length=max_length)
elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5": elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5":
temp = self.ReadRAM_TAMA5() temp = self.ReadRAM_TAMA5()
elif self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
temp = self.ReadROM(address=0x20000+pos, length=buffer_len, skip_init=False, max_length=max_length)
elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM
temp = self.ReadRAM(address=int(pos/8), length=buffer_len, command=command, max_length=max_length) temp = self.ReadRAM(address=int(pos/8), length=buffer_len, command=command, max_length=max_length)
elif self.MODE == "AGB" and args["save_type"] == 6: # DACS elif self.MODE == "AGB" and args["save_type"] == 6: # DACS
@ -2037,6 +2209,8 @@ class GbxDevice:
self.WriteFlash_MBC6(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], mapper=_mbc) self.WriteFlash_MBC6(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], mapper=_mbc)
elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5": elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5":
self.WriteRAM_TAMA5(buffer=buffer[buffer_offset:buffer_offset+buffer_len]) self.WriteRAM_TAMA5(buffer=buffer[buffer_offset:buffer_offset+buffer_len])
elif self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
self.WriteROM_DMG_EEPROM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], bank=bank+8)
elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM
self.WriteRAM(address=int(pos/8), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command) self.WriteRAM(address=int(pos/8), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
elif self.MODE == "AGB" and args["save_type"] in (4, 5): # FLASH elif self.MODE == "AGB" and args["save_type"] in (4, 5): # FLASH
@ -2121,17 +2295,18 @@ class GbxDevice:
file.close() file.close()
else: else:
self.INFO["data"] = buffer self.INFO["data"] = buffer
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest() self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest()
if "verify_write" in args and args["verify_write"] not in (None, False): if "verify_write" in args and args["verify_write"] not in (None, False):
return True return True
elif args["mode"] == 3: # Restore elif args["mode"] == 3: # Restore
self.INFO["transferred"] = len(buffer) self.INFO["transferred"] = len(buffer)
if args["rtc"] is True: if args["rtc"] is True:
advance = args["rtc_advance"] advance = args["rtc_advance"]
dprint("rtc_advance:", advance) self.SetProgress({"action":"UPDATE_RTC", "method":"write"})
if self.MODE == "DMG" and args["rtc"] is True: if self.MODE == "DMG" and args["rtc"] is True:
_mbc.WriteRTC(buffer[-_mbc.GetRTCBufferSize():], advance=advance) _mbc.WriteRTC(buffer[-_mbc.GetRTCBufferSize():], advance=advance)
elif self.MODE == "AGB": elif self.MODE == "AGB":
@ -2174,7 +2349,7 @@ class GbxDevice:
_mbc.EnableRAM(enable=False) _mbc.EnableRAM(enable=False)
self._set_fw_variable("DMG_READ_CS_PULSE", 0) self._set_fw_variable("DMG_READ_CS_PULSE", 0)
if audio_low: self._set_fw_variable("FLASH_WE_PIN", 0x02) if audio_low: self._set_fw_variable("FLASH_WE_PIN", 0x02)
# Clean up # Clean up
self.INFO["last_action"] = self.INFO["action"] self.INFO["last_action"] = self.INFO["action"]
self.INFO["action"] = None self.INFO["action"] = None
@ -2239,7 +2414,7 @@ class GbxDevice:
# Firmware check L2 # Firmware check L2
# Firmware check L5 # Firmware check L5
if (self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] < 5) and ("flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True): if (self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] < 5) and ("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 requires firmware version L5 or higher on the GBxCart RW v1.4 hardware revision. It may also work with older hardware revisions using the official firmware and insideGadgets flasher software available from <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a>.", "abortable":False}) self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L5 on the GBxCart RW v1.4 hardware revision or newer. It may also work with older hardware revisions using the official firmware and insideGadgets flasher software available from <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a>.", "abortable":False})
return False return False
if (self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] < 5) and ("double_die" in cart_type and cart_type["double_die"] is True): if (self.FW["pcb_ver"] not in (5, 6, 101) or self.FW["fw_ver"] < 5) and ("double_die" in cart_type and cart_type["double_die"] is True):
if self.FW["pcb_ver"] in (5, 6): if self.FW["pcb_ver"] in (5, 6):
@ -2256,7 +2431,7 @@ class GbxDevice:
pass pass
if cart_type["command_set"] == "GBMEMORY": if cart_type["command_set"] == "GBMEMORY":
flashcart = Flashcart_DMG_MMSA(config=cart_type, cart_write_fncptr=self._cart_write, cart_read_fncptr=self.ReadROM, progress_fncptr=self.SetProgress) flashcart = Flashcart_DMG_MMSA(config=cart_type, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, progress_fncptr=self.SetProgress)
if "buffer_map" not in args: if "buffer_map" not in args:
if os.path.exists(os.path.splitext(args["path"])[0] + ".map"): if os.path.exists(os.path.splitext(args["path"])[0] + ".map"):
with open(os.path.splitext(args["path"])[0] + ".map", "rb") as file: args["buffer_map"] = file.read() with open(os.path.splitext(args["path"])[0] + ".map", "rb") as file: args["buffer_map"] = file.read()
@ -2280,7 +2455,7 @@ class GbxDevice:
data_map_import = bytearray(data_map_import) data_map_import = bytearray(data_map_import)
dprint("Hidden sector data loaded") dprint("Hidden sector data loaded")
else: else:
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, 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)
rumble = "rumble" in flashcart.CONFIG and flashcart.CONFIG["rumble"] is True rumble = "rumble" in flashcart.CONFIG and flashcart.CONFIG["rumble"] is True
@ -2317,6 +2492,12 @@ class GbxDevice:
args["mbc"] = mbc args["mbc"] = mbc
else: else:
args["mbc"] = 0x1B # MBC5+SRAM+BATTERY args["mbc"] = 0x1B # MBC5+SRAM+BATTERY
if not self.IsSupportedMbc(args["mbc"]):
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device. An updated hardware revision is required.".format(Util.APPNAME, self.GetFullName())
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
return False
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle) _mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
self._set_fw_variable("FLASH_PULSE_RESET", 1 if flashcart.PulseResetAfterWrite() else 0) self._set_fw_variable("FLASH_PULSE_RESET", 1 if flashcart.PulseResetAfterWrite() else 0)
@ -2356,6 +2537,8 @@ class GbxDevice:
elif command_set_type in ("GBMEMORY", "DMG-MBC5-32M-FLASH"): elif command_set_type in ("GBMEMORY", "DMG-MBC5-32M-FLASH"):
temp = 0x00 temp = 0x00
dprint("Using GB Memory command set") dprint("Using GB Memory command set")
elif command_set_type == "BLAZE_XPLODER":
temp = 0x00
else: else:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM flashing.", "abortable":False}) self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM flashing.", "abortable":False})
return False return False
@ -2427,10 +2610,36 @@ class GbxDevice:
dprint("Setting command #{:d} to 0x{:X}=0x{:X}".format(i, address, value)) dprint("Setting command #{:d} to 0x{:X}=0x{:X}".format(i, address, value))
self._write(bytearray(struct.pack(">I", address)) + bytearray(struct.pack(">H", value))) self._write(bytearray(struct.pack(">I", address)) + bytearray(struct.pack(">H", value)))
if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True: if self.FW["fw_ver"] >= 6:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1) if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
if "bank_switch" in cart_type["commands"]:
self._write(len(cart_type["commands"]["bank_switch"])) # number of commands
for command in cart_type["commands"]["bank_switch"]:
address = command[0]
value = command[1]
if value == "ID":
self._write(bytearray(struct.pack(">I", address))) # address
self._write(0) # type = address
else:
self._write(bytearray(struct.pack(">I", value))) # value
self._write(1) # type = value
ret = self._read(1)
if ret != 0x01:
print("Error in DMG_SET_BANK_CHANGE_CMD:", ret)
else:
self._write(0, wait=True)
else:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 0)
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
self._write(0, wait=True)
else: else:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 0) if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
else:
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 0)
# ↑↑↑ Load commands into firmware # ↑↑↑ Load commands into firmware
# ↓↓↓ Unlock cartridge # ↓↓↓ Unlock cartridge
@ -2531,6 +2740,7 @@ class GbxDevice:
cancel_args = {"action":"ABORT", "abortable":False} cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS) cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {} self.CANCEL_ARGS = {}
self.ERROR_ARGS = {}
self.SetProgress(cancel_args) self.SetProgress(cancel_args)
if self.CanPowerCycleCart(): self.CartPowerCycle() if self.CanPowerCycleCart(): self.CartPowerCycle()
return return
@ -2560,6 +2770,8 @@ class GbxDevice:
self._cart_write(pos + buffer_len - 1, 0xF0) self._cart_write(pos + buffer_len - 1, 0xF0)
elif command_set_type == "DMG-MBC5-32M-FLASH": elif command_set_type == "DMG-MBC5-32M-FLASH":
status = self.WriteROM_DMG_MBC5_32M_FLASH(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank) status = self.WriteROM_DMG_MBC5_32M_FLASH(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
elif command_set_type == "BLAZE_XPLODER":
status = self.WriteROM_DMG_EEPROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
else: 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), rumble_stop=rumble) 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: if status is False:
@ -2606,8 +2818,14 @@ class GbxDevice:
verify_args.update({"verify_write":data_import, "rom_size":len(data_import), "path":"", "rtc_area":flashcart.HasRTC()}) verify_args.update({"verify_write":data_import, "rom_size":len(data_import), "path":"", "rtc_area":flashcart.HasRTC()})
self.ReadROM(0, 4) # dummy read self.ReadROM(0, 4) # dummy read
verified_size = self._BackupROM(verify_args) verified_size = self._BackupROM(verify_args)
if self.CANCEL is True: if self.CANCEL:
pass cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {}
self.ERROR_ARGS = {}
self.SetProgress(cancel_args)
if self.CanPowerCycleCart(): self.CartPowerCycle()
return
elif (verified_size is not True) and (len(data_import) != verified_size): elif (verified_size is not True) and (len(data_import) != verified_size):
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}.\n\nPlease make sure that the correct cart type was selected, that your cartridge actually does support ROMs that are {:s} in size and that the cartridge contacts are clean, then re-connect the device and try again from the beginning.".format(verified_size, Util.formatFileSize(len(data_import))), "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}.\n\nPlease make sure that the correct cart type was selected, that your cartridge actually does support ROMs that are {:s} in size and that the cartridge contacts are clean, then re-connect the device and try again from the beginning.".format(verified_size, Util.formatFileSize(len(data_import))), "abortable":False})
return False return False

View File

@ -17,7 +17,7 @@ class GbxDevice:
DEVICE_NAME = "GBxCart RW" DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 26 DEVICE_MIN_FW = 26
DEVICE_MAX_FW = 30 DEVICE_MAX_FW = 30
DEVICE_CMD = { DEVICE_CMD = {
"CART_MODE":'C', "CART_MODE":'C',
"GB_MODE":1, "GB_MODE":1,
@ -119,7 +119,7 @@ class GbxDevice:
"AUDIO_HIGH":'8', "AUDIO_HIGH":'8',
"AUDIO_LOW":'9', "AUDIO_LOW":'9',
} }
PCB_VERSIONS = {1:'v1.0', 2:'v1.1/v1.2', 4:'v1.3', 90:'XMAS', 100:'Mini v1.0', 101:'Mini v1.0d'} PCB_VERSIONS = {2:'v1.1/v1.2', 4:'v1.3', 90:'XMAS v1.0', 100:'Mini v1.0', 101:'Mini v1.0d'}
SUPPORTED_CARTS = {} SUPPORTED_CARTS = {}
FW = [] FW = []
@ -185,15 +185,14 @@ class GbxDevice:
self.DEVICE = None self.DEVICE = None
conn_msg.append([0, "Couldnt communicate with the GBxCart RW device on port " + ports[i] + ". Please disconnect and reconnect the device, then try again."]) conn_msg.append([0, "Couldnt communicate with the GBxCart RW device on port " + ports[i] + ". Please disconnect and reconnect the device, then try again."])
continue continue
if self.FW[0] == 0: if self.FW[0] == 0:
dev.close() dev.close()
self.DEVICE = None self.DEVICE = None
return False return False
elif (self.FW[1] == 4 and self.FW[0] < self.DEVICE_MAX_FW) or (self.FW[0] < self.DEVICE_MIN_FW): elif (self.FW[1] == 100 and self.FW[0] < 26):
#dev.close() self.FW_UPDATE_REQ = True
#self.DEVICE = None elif (self.FW[1] in (2, 4, 90) and self.FW[0] < self.DEVICE_MAX_FW) or (self.FW[0] < self.DEVICE_MIN_FW):
#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 R" + str(self.DEVICE_MIN_FW) + " or higher.<br><br>Firmware updates are available at <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a>."])
#continue
self.FW_UPDATE_REQ = True self.FW_UPDATE_REQ = True
elif self.FW[0] < self.DEVICE_MAX_FW: elif self.FW[0] < 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 R" + str(self.DEVICE_MAX_FW) + " to make use of the latest features.<br><br>Firmware updates are available at <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a>."]) #conn_msg.append([1, "The GBxCart RW device on port " + ports[i] + " is running an older firmware version. Please consider updating to version R" + str(self.DEVICE_MAX_FW) + " to make use of the latest features.<br><br>Firmware updates are available at <a href=\"https://www.gbxcart.com/\">https://www.gbxcart.com/</a>."])
@ -214,7 +213,7 @@ class GbxDevice:
conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"]) conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"])
if (self.FW[1] not in (4, 5)): if (self.FW[1] not in (4, 5)):
conn_msg.append([0, "\nNow running in Legacy Mode.\nThis version of FlashGBX was developed to be used with GBxCart RW v1.3 and v1.4.\nOther revisions are untested and may not be fully compatible.\n"]) conn_msg.append([0, "\nNow running in Legacy Mode.\nThis version of FlashGBX has been tested with GBxCart RW v1.3 and v1.4.\nOther revisions may not be fully compatible.\n"])
elif self.FW[1] == 4: elif self.FW[1] == 4:
conn_msg.append([0, "{:s}Now running in Legacy Mode. You can install the optimized firmware version L1 in GUI mode from the “Tools” menu.{:s}".format(ANSI.YELLOW, ANSI.RESET)]) conn_msg.append([0, "{:s}Now running in Legacy Mode. You can install the optimized firmware version L1 in GUI mode from the “Tools” menu.{:s}".format(ANSI.YELLOW, ANSI.RESET)])
@ -230,10 +229,9 @@ class GbxDevice:
except SerialException as e: except SerialException as e:
if "Permission" in str(e): if "Permission" in str(e):
conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " couldnt be accessed. Make sure your user account has permission to use it and its not already in use by another application."]) conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " couldnt be accessed. Make sure your user account has permission to use it and its not already in use by another application."])
print(str(e))
else: else:
conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)]) conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)])
continue continue
return conn_msg return conn_msg
@ -290,18 +288,24 @@ class GbxDevice:
def GetFullNameExtended(self, more=False): def GetFullNameExtended(self, more=False):
return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), str(self.PORT)) return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), str(self.PORT))
def GetOfficialWebsite(self):
return "https://www.gbxcart.com/"
def SupportsFirmwareUpdates(self): def SupportsFirmwareUpdates(self):
_, pcb = self.FW fw, pcb = self.FW
if pcb != 4: return False #return pcb == 4
return True return (pcb in (2, 4, 90, 100) and fw > 3)
def FirmwareUpdateAvailable(self): def FirmwareUpdateAvailable(self):
fw, pcb = self.FW fw, pcb = self.FW
if pcb != 4: return False #if pcb != 4: return False
if pcb not in (2, 4, 90, 100) and fw <= 3: return False
if (self.FW[1] == 100 and self.FW[0] >= 26): return False
return fw < self.DEVICE_MAX_FW return fw < self.DEVICE_MAX_FW
def GetFirmwareUpdaterClass(self): def GetFirmwareUpdaterClass(self):
if self.FW[1] == 4: # v1.3 _, pcb = self.FW
if pcb in (2, 4, 90, 100):
try: try:
from . import fw_GBxCartRW_v1_3 from . import fw_GBxCartRW_v1_3
return (None, fw_GBxCartRW_v1_3.FirmwareUpdaterWindow) return (None, fw_GBxCartRW_v1_3.FirmwareUpdaterWindow)
@ -1449,6 +1453,20 @@ class GbxDevice:
self.INFO["file_crc32"] = zlib.crc32(data_dump) & 0xFFFFFFFF self.INFO["file_crc32"] = zlib.crc32(data_dump) & 0xFFFFFFFF
self.INFO["file_sha1"] = hashlib.sha1(data_dump).hexdigest() self.INFO["file_sha1"] = hashlib.sha1(data_dump).hexdigest()
#self.INFO["file_sha256"] = hashlib.sha256(buffer).hexdigest()
#self.INFO["file_md5"] = hashlib.md5(buffer).hexdigest()
# Check for ROM loops
self.INFO["loop_detected"] = False
temp = min(0x2000000, len(data_dump))
while temp > 0x4000:
temp = temp >> 1
if (data_dump[0:0x4000] == data_dump[temp:temp+0x4000]):
if data_dump[0:temp] == data_dump[temp:temp*2]:
self.INFO["loop_detected"] = temp
else:
break
self.SetProgress({"action":"FINISHED"}) self.SetProgress({"action":"FINISHED"})
######################################### #########################################

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -53,7 +53,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
*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.* *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.*
## Cartridge Compatibility ## Cartridge Compatibility
### Supported official cartridge memory mappers ### Supported cartridge memory mappers
- Game Boy - Game Boy
- All cartridges without memory mapping - All cartridges without memory mapping
- MBC1 - MBC1
@ -71,17 +71,23 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- HuC-1 - HuC-1
- HuC-3 - HuC-3
- TAMA5 - TAMA5
- Unlicensed 256M Mapper
- Unlicesned Wisdom Tree Mapper
- Unlicensed Xploder GB Mapper
- Unlicensed Sachen Mapper
- Game Boy Advance - Game Boy Advance
- All cartridges without memory mapping - All cartridges without memory mapping
- 8M FLASH DACS - 8M FLASH DACS
- 3D Memory (GBA Video) - 3D Memory (GBA Video)
- Unlicensed 2048M Mapper
### Currently supported flash cartridges ### Currently supported flash cartridges
- Game Boy - Game Boy
- 29LV Series Flash BOY with 29LV160DB - 29LV Series Flash BOY with 29LV160DB
- BLAZE Xploder GB
- BUNG Doctor GB Card 64M - BUNG Doctor GB Card 64M
- DIY cart with AM29F010 - DIY cart with AM29F010
- DIY cart with AM29F016 - DIY cart with AM29F016
@ -97,6 +103,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- GB Smart 32M - GB Smart 32M
- HDR Game Boy Camera Flashcart - HDR Game Boy Camera Flashcart
- insideGadgets 32 KB - insideGadgets 32 KB
- insideGadgets 128 KB
- insideGadgets 256 KB
- insideGadgets 512 KB - insideGadgets 512 KB
- insideGadgets 1 MB, 128 KB SRAM - insideGadgets 1 MB, 128 KB SRAM
- insideGadgets 2 MB, 128 KB SRAM/32 KB FRAM - insideGadgets 2 MB, 128 KB SRAM/32 KB FRAM
@ -153,6 +161,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- SD007_BV5_V3 with HY29LV160BT-70 - SD007_BV5_V3 with HY29LV160BT-70
- SD007_BV5_V3 with AM29LV160MB - SD007_BV5_V3 with AM29LV160MB
- SD007_K8D3216_32M with MX29LV160CT - SD007_K8D3216_32M with MX29LV160CT
- SD007_T40_64BALL_TSOP28 with 29LV016T
- SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85¹ - SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85¹
- SD007_TSOP_29LV017D with L017D70VC - SD007_TSOP_29LV017D with L017D70VC
- SD007_TSOP_29LV017D with S29GL032M90T - SD007_TSOP_29LV017D with S29GL032M90T
@ -197,6 +206,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- 4455_4400_4000_4350_36L0R_V3 with M36L0R7050T - 4455_4400_4000_4350_36L0R_V3 with M36L0R7050T
- AA1030_TSOP88BALL with M36W0R603 - AA1030_TSOP88BALL with M36W0R603
- AGB-E05-01 with GL128S - AGB-E05-01 with GL128S
- AGB-E05-01 with MSP55LV100G
- AGB-E05-01 with MSP55LV128M - AGB-E05-01 with MSP55LV128M
- AGB-E05-01 with MX29GL128FHT2I-90G - AGB-E05-01 with MX29GL128FHT2I-90G
- AGB-E05-01 with S29GL064 - AGB-E05-01 with S29GL064
@ -204,6 +214,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- AGB-E05-02 with M29W128FH - AGB-E05-02 with M29W128FH
- AGB-E05-02 with M29W128GH - AGB-E05-02 with M29W128GH
- AGB-E05-02 with S29GL032 - AGB-E05-02 with S29GL032
- AGB-E05-03H with M29W128GH
- AGB-E05-06L with 29LV128DBT2C-90Q - AGB-E05-06L with 29LV128DBT2C-90Q
- AGB-E08-09 with 29LV128DTMC-90Q - AGB-E08-09 with 29LV128DTMC-90Q
- AGB-E20-30 with M29W128GH - AGB-E20-30 with M29W128GH
@ -218,6 +229,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- BX2006_TSOP_64BALL with GL256S - BX2006_TSOP_64BALL with GL256S
- BX2006_TSOPBGA_0106 with M29W640GB6AZA6 - BX2006_TSOPBGA_0106 with M29W640GB6AZA6
- BX2006_TSOPBGA_0106 with K8D6316UTM-PI07 - BX2006_TSOPBGA_0106 with K8D6316UTM-PI07
- DV15 with MSP55LV100G
- GA-07 with unlabeled flash chip - GA-07 with unlabeled flash chip
- GE28F128W30 with 128W30B0 - GE28F128W30 with 128W30B0
- M5M29G130AN (no PCB text) - M5M29G130AN (no PCB text)
@ -282,7 +294,7 @@ The author would like to thank the following very kind people for their help and
- iamevn (flash chip info) - iamevn (flash chip info)
- Icesythe7 (feature suggestions, testing, bug reports) - Icesythe7 (feature suggestions, testing, bug reports)
- Jayro (flash chip info) - Jayro (flash chip info)
- JFox (help with properly packaging the app for pip, Linux help) - JFox (help with properly packaging the app for pip, Linux help, bug reports)
- joyrider3774 (flash chip info) - joyrider3774 (flash chip info)
- JS7457 (flash chip info) - JS7457 (flash chip info)
- julgr (macOS help, testing) - julgr (macOS help, testing)
@ -308,14 +320,17 @@ The author would like to thank the following very kind people for their help and
- skite2001 (flash chip info) - skite2001 (flash chip info)
- Smelly-Ghost (testing) - Smelly-Ghost (testing)
- Super Maker (flash chip info, testing) - Super Maker (flash chip info, testing)
- Tauwasser (research)
- t5b6_de (flash chip info) - t5b6_de (flash chip info)
- Timville (flash chip info) - Timville (flash chip info)
- twitnic (flash chip info) - twitnic (flash chip info)
- Veund (flash chip info) - Veund (flash chip info)
- Voultar (bug reports, feature suggestions) - Voultar (bug reports, feature suggestions)
- Wkr (flash chip info)
- x7l7j8cc (flash chip info) - x7l7j8cc (flash chip info)
- Zeii (flash chip info) - Zeii (flash chip info)
- Zelante (flash chip info) - Zelante (flash chip info)
- zvxr (flash chip info)
## DISCLAIMER ## DISCLAIMER

View File

@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read(
setuptools.setup( setuptools.setup(
name="FlashGBX", name="FlashGBX",
version="3.7", version="3.8",
author="Lesserkuma", 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.", 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", url="https://github.com/lesserkuma/FlashGBX",