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
### 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)
- 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

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-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("--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("--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("--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("--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")

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))
self.DisconnectDevice()
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("Cartridge Information:")
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")
elif args["action"] == "SECTOR_ERASE":
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":
print("\nStopping...")
elif args["action"] == "FINISHED":
@ -244,7 +246,7 @@ class FlashGBX_CLI():
return
elif args["action"] == "PROGRESS":
# 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)
progress = min(1, max(0, pos/size))
whole_width = math.floor(progress * prog_width)
@ -272,23 +274,36 @@ class FlashGBX_CLI():
elif self.CONN.INFO["last_action"] == 1: # Backup ROM
self.CONN.INFO["last_action"] = 0
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"]))
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))
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!")
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":
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"]))
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))
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:
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
self.CONN.INFO["last_action"] = 0
@ -376,6 +391,10 @@ class FlashGBX_CLI():
self.CONN = None
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
return True
@ -409,7 +428,7 @@ class FlashGBX_CLI():
if data['has_rtc']:
if 'rtc_buffer' in data:
try:
if data['features_raw'] == 0x10: # MBC3
if data['mapper_raw'] == 0x10: # MBC3
rtc_s = data["rtc_buffer"][0x00]
rtc_m = data["rtc_buffer"][0x04]
rtc_h = data["rtc_buffer"][0x08]
@ -420,7 +439,7 @@ class FlashGBX_CLI():
s += "Invalid state"
else:
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_h = math.floor((rtc_buffer & 0xFFF) / 60)
rtc_m = (rtc_buffer & 0xFFF) % 60
@ -435,7 +454,7 @@ class FlashGBX_CLI():
if 'no_rtc_reason' in data:
if data["no_rtc_reason"] == -1:
temp = "Unknown"
if data['features_raw'] == 0xFD: # TAMA5
if data['mapper_raw'] == 0xFD: # TAMA5
temp = "OK"
s += temp
s += "\n"
@ -459,15 +478,15 @@ class FlashGBX_CLI():
bad_read = True
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])
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)])
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)])
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)])
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)])
else:
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"
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:
s += "Mapper Type: {:s}Not detected{:s}\n".format(ANSI.RED, ANSI.RESET)
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))
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()
@ -662,16 +681,27 @@ class FlashGBX_CLI():
else:
(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)):
msg_cart_type_s = "Cartridge Type: 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."
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."
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 ("[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."
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 ("[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."
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 ("[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."
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."
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):
try_this = "Generic Flash Cartridge (0/90)"
elif ("[ AAA/AA]" in flash_id):
try_this = "Generic Flash Cartridge (AAA/AA)"
elif ("[ AAA/A9]" in flash_id):
try_this = "Generic Flash Cartridge (AAA/A9)"
elif ("[WR / AAA/AA]" in flash_id):
try_this = "Generic Flash Cartridge (WR/AAA/AA)"
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"
else:
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 args.dmg_mbc == "auto":
try:
mbc = header["features_raw"]
if mbc == 0: mbc = 5
mbc = header["mapper_raw"]
if mbc == 0: mbc = 0x19
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))
mbc = 5
mbc = 0x19
else:
mbc = int(args.dmg_mbc)
if mbc == 2: mbc = 0x06
@ -733,7 +763,7 @@ class FlashGBX_CLI():
if args.agb_romsize == "auto":
rom_size = header["rom_size"]
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]
path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii')
@ -806,6 +836,7 @@ class FlashGBX_CLI():
return
cart_type = 0
mbc = 0
for i in range(0, len(carts)):
if not "names" in carts[i]: continue
if carts[i]["type"] != mode: continue
@ -840,23 +871,31 @@ class FlashGBX_CLI():
elif os.path.getsize(path) < 0x400:
print("{:s}ROM files smaller than 1 KB are not supported.{:s}".format(ANSI.RED, ANSI.RESET))
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):
print("{:s}Couldnt access file path “{:s}”.{:s}".format(ANSI.RED, args.path, ANSI.RESET))
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
if args.force_5v is True:
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']:
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
fix_header = False
try:
if self.CONN.GetMode() == "DMG":
hdr = RomFileDMG(path).GetHeader()
elif self.CONN.GetMode() == "AGB":
hdr = RomFileAGB(path).GetHeader()
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))
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))
answer = input("Fix the header checksum before continuing? [Y/n]: ").strip().lower()
print("")
if answer != "n":
fix_header = True
except:
print("{:s}The selected file could not be read.{:s}".format(ANSI.RED, ANSI.RESET))
return
if self.CONN.GetMode() == "DMG":
hdr = RomFileDMG(buffer).GetHeader()
elif self.CONN.GetMode() == "AGB":
hdr = RomFileAGB(buffer).GetHeader()
if not hdr["logo_correct"] and mbc != 0x203:
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:
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()
print("")
if answer != "n":
fix_header = True
print("")
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("")
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
def BackupRestoreRAM(self, args, header):
@ -911,11 +954,11 @@ class FlashGBX_CLI():
if self.CONN.GetMode() == "DMG":
if args.dmg_mbc == "auto":
try:
mbc = header["features_raw"]
if mbc == 0: mbc = 5
mbc = header["mapper_raw"]
if mbc == 0: mbc = 0x19
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))
mbc = 5
mbc = 0x19
else:
mbc = int(args.dmg_mbc)
if mbc == 2: mbc = 0x06
@ -926,15 +969,15 @@ class FlashGBX_CLI():
if args.dmg_savesize == "auto":
try:
if header['features_raw'] == 0x06: # MBC2
if header['mapper_raw'] == 0x06: # MBC2
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)]
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)]
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)]
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)]
else:
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":
print("Using Save Type “{:s}”.".format(Util.AGB_Header_Save_Types[save_type]))
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.")
try:
@ -1102,9 +1145,9 @@ class FlashGBX_CLI():
found_length = len(test2) - found_offset
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":
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:
(_, _, cfi) = self.CONN.CheckFlashChip(limitVoltage=False)

View File

@ -89,6 +89,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
rowActionsGeneral2.addWidget(self.btnBackupRAM)
self.cmbDMGCartridgeTypeResult.currentIndexChanged.connect(self.CartridgeTypeChanged)
self.cmbHeaderFeaturesResult.currentIndexChanged.connect(self.DMGMapperTypeChanged)
rowActionsGeneral3 = QtWidgets.QHBoxLayout()
self.btnFlashROM = QtWidgets.QPushButton("&Write ROM")
@ -665,9 +666,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
if answer == QtWidgets.QMessageBox.Yes:
self.ShowFirmwareUpdateWindow()
else:
#self.mnuTools.actions()[2].setEnabled(False)
pass
elif dev.FW_UPDATE_REQ:
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())
if not Util.DEBUG:
self.DisconnectDevice()
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
return True
return False
@ -755,7 +758,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
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)
cb = QtWidgets.QCheckBox("Dont show this message again", checked=False)
msgbox.setCheckBox(cb)
time_elapsed = None
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.lblStatus4a.setText("Done!")
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))
msgbox.setText(msg)
msgbox.setCheckBox(cb)
if not dontShowAgain:
msgbox.exec()
dontShowAgain = cb.isChecked()
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.")
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 += "\n\nCRC32: {:04X}\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)
msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
msgbox.setText(msg)
msgbox.exec()
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."
msg += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
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; }")
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":
if Util.AGB_Global_CRC32 == self.CONN.INFO["rom_checksum_calc"]:
self.lblAGBHeaderROMChecksumResult.setText("Valid (0x{:06X})".format(Util.AGB_Global_CRC32))
self.lblAGBHeaderROMChecksumResult.setStyleSheet("QLabel { color: green; }")
self.lblStatus4a.setText("Done!")
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.setCheckBox(cb)
if not dontShowAgain:
msgbox.exec()
dontShowAgain = cb.isChecked()
@ -825,17 +836,27 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblAGBHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
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 += "\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))
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
msgbox.setText(msg)
msgbox.exec()
else:
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.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 += "\n\nCRC32: {:04X}\nSHA-1: {:s}".format(self.CONN.INFO["rom_checksum_calc"], self.CONN.INFO["file_sha1"])
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 += "\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))
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
self.lblStatus4a.setText("Done!")
@ -853,6 +874,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
return
msgbox.setText("The save data backup is complete!")
msgbox.setCheckBox(cb)
if not dontShowAgain:
msgbox.exec()
dontShowAgain = cb.isChecked()
@ -868,6 +890,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
msg_text = "Save data writing complete!"
msgbox.setText(msg_text)
msgbox.setCheckBox(cb)
if not dontShowAgain:
msgbox.exec()
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))
msgbox.setText(msg)
msgbox.setCheckBox(cb)
if not dontShowAgain:
msgbox.exec()
dontShowAgain = cb.isChecked()
@ -908,8 +932,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if dontShowAgain: self.SETTINGS.setValue("SkipFinishMessage", "enabled")
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):
if index in (-1, 0): return
if self.CONN.GetMode() == "DMG":
cart_types = self.CONN.GetSupportedCartridgesDMG()
if cart_types[1][index] == "RETAIL": # special keyword
@ -996,7 +1026,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
just_erase = False
path = ""
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)
if answer == QtWidgets.QMessageBox.Cancel: return
path = dpath
@ -1029,9 +1063,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
elif self.CONN.GetMode() == "AGB":
self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderFeaturesResult.currentIndex()])[0]
if (path == ""):
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":
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)
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
if "flash_size" in carts[cart_type]:
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."
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 "mbc" in carts[cart_type]:
mbc = carts[cart_type]["mbc"]
override_voltage = False
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()
elif self.CONN.GetMode() == "AGB":
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)
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"])
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)
@ -1128,9 +1172,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.btnConfig.setEnabled(False)
self.lblStatus4a.setText("Preparing...")
qt_app.processEvents()
if just_erase:
prefer_chip_erase = True
verify_write = False
if len(buffer) > 0x1000 or just_erase:
if just_erase:
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 }
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 }
@ -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)
if answer == QtWidgets.QMessageBox.Cancel: return
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]
if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path))
if (path == ""): return
@ -1282,7 +1327,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
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.setDefaultButton(QtWidgets.QMessageBox.Yes)
msgbox.setCheckBox(cb)
if erase:
cb.setChecked(True)
else:
msgbox.setCheckBox(cb)
answer = msgbox.exec()
if answer == QtWidgets.QMessageBox.Cancel: return
rtc_advance = cb.isChecked()
@ -1442,7 +1490,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data['has_rtc']:
if 'rtc_buffer' in data:
try:
if data['features_raw'] == 0x10: # MBC3
if data['mapper_raw'] == 0x10: # MBC3
rtc_s = data["rtc_buffer"][0x00]
rtc_m = data["rtc_buffer"][0x04]
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))
else:
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_h = math.floor((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 data["no_rtc_reason"] == -1:
self.lblHeaderRtcResult.setText("Unknown")
if data['features_raw'] == 0xFD: # TAMA5
if data['mapper_raw'] == 0xFD: # TAMA5
self.lblHeaderRtcResult.setText("OK")
if data['logo_correct']:
@ -1491,13 +1539,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblHeaderChecksumResult.setStyleSheet("QLabel { color: red; }")
self.lblHeaderROMChecksumResult.setText("0x{:04X}".format(data['rom_checksum']))
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
self.cmbHeaderROMSizeResult.setCurrentIndex(data["rom_size_raw"])
for i in range(0, len(Util.DMG_Header_RAM_Sizes_Map)):
if data["ram_size_raw"] == Util.DMG_Header_RAM_Sizes_Map[i]:
self.cmbHeaderRAMSizeResult.setCurrentIndex(i)
i = 0
for k in Util.DMG_Header_Mapper.keys():
if data["features_raw"] == k:
if data["mapper_raw"] == k:
self.cmbHeaderFeaturesResult.setCurrentIndex(i)
if k == 0x06: # MBC2
self.cmbHeaderRAMSizeResult.setCurrentIndex(1)
@ -1518,12 +1567,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
self.lblHeaderTitleResult.setText("(No ROM data detected)")
self.lblHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.cmbHeaderROMSizeResult.setCurrentIndex(11)
self.cmbHeaderROMSizeResult.setCurrentIndex(0)
self.cmbHeaderRAMSizeResult.setCurrentIndex(0)
self.cmbHeaderFeaturesResult.setCurrentIndex(0)
else:
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)
if data['logo_correct'] and data['game_title'] in ("NP M-MENU MENU", "DMG MULTI MENU "):
cart_types = self.CONN.GetSupportedCartridgesDMG()
@ -1531,6 +1580,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if "dmg-mmsa-jpn" in cart_types[1][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.grpDMGCartridgeInfo.setVisible(True)
@ -1627,7 +1690,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
self.lblAGBHeaderTitleResult.setText("(No ROM data detected)")
self.lblAGBHeaderTitleResult.setStyleSheet("QLabel { color: red; }")
self.cmbAGBHeaderROMSizeResult.setCurrentIndex(3)
self.cmbAGBSaveTypeResult.setCurrentIndex(0)
else:
self.lblAGBHeaderTitleResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
@ -1646,7 +1708,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.grpStatus.setTitle("Transfer Status")
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)
if data['game_title'][:11] == "YJencrypted" and resetStatus:
@ -1852,6 +1914,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msg_fw = ""
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 = temp[:-4]
msgbox.setText(temp)
@ -1945,6 +2015,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblStatus4aResult.setText("")
self.btnCancel.setEnabled(args["abortable"])
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":
if elapsed >= 1:
self.lblStatus3aResult.setText(Util.formatProgressTime(elapsed))
@ -2065,7 +2143,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.SetMode()
if self.CONN.GetMode() == "DMG":
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 }
self.CONN.BackupRAM(fncSetProgress=False, args=args)
data = self.CONN.INFO["data"]
@ -2100,7 +2178,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
fn_split = os.path.splitext(os.path.abspath(fn))
if fn_split[1].lower() == ".sav":
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
elif self.CONN.GetMode() == "AGB" and fn_split[1].lower() in (".gba", ".srl"):
return True
@ -2120,7 +2198,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
fn = str(url.toLocalFile())
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)
elif fn_split[1].lower() == ".sav":
self.WriteRAM(fn)

View File

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

View File

@ -11,7 +11,9 @@ class GBMemoryMap:
IS_MENU = False
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)
def ImportROM(self, data):
@ -26,7 +28,7 @@ class GBMemoryMap:
data = bytearray(data) + bytearray([0xFF] * (0x20000 - len(data)))
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 len(data) <= 0x20000:
rom_size = 0b010
@ -127,7 +129,7 @@ class GBMemoryMap:
info["ram_start_block"] = int(ram_offset / 0x800)
ram_offset += info["ram_data_size"]
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 info["rom_data_size"] <= 0x20000:
@ -192,7 +194,7 @@ class GBMemoryMap:
mbc_type = 2
elif mbc in (0x10, 0x13): # MBC3
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
else:
mbc_type = False
@ -217,5 +219,6 @@ class GBMemoryMap:
return self.IS_MENU
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

View File

@ -18,6 +18,7 @@ class DMG_MBC:
RAM_BANK_SIZE = 0x2000
ROM_BANK_NUM = 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):
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)
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)
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:
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
@ -83,6 +88,9 @@ class DMG_MBC:
self.CART_WRITE_FNCPTR(address, value)
if delay is not False: time.sleep(delay)
def GetID(self):
return self.MBC_ID
def GetName(self):
return "Unknown MBC {:d}".format(self.MBC_ID)
@ -145,6 +153,9 @@ class DMG_MBC:
start_address = 0
self.CartWrite(commands)
return (start_address, self.RAM_BANK_SIZE)
def SetStartBank(self, index):
self.START_BANK = index
def HasHiddenSector(self):
return False
@ -210,19 +221,19 @@ class DMG_MBC3(DMG_MBC):
def EnableRAM(self, enable=True):
dprint(self.GetName(), "|", enable)
commands = [
#[ 0x6000, 0x01 if enable else 0x00 ],
[ 0x0000, 0x0A if enable else 0x00 ],
]
self.CartWrite(commands)
def HasRTC(self):
dprint(self.GetName())
if self.MBC_ID != 16: return False
dprint("Checking for RTC")
if self.MBC_ID != 16:
dprint("No RTC because wrong MBC ID", self.MBC_ID)
return False
self.EnableRAM(enable=False)
self.EnableRAM(enable=True)
#self.CartWrite([ [0x4000, 0x08] ])
self.LatchRTC()
skipped = True
for i in range(0x08, 0x0D):
self.CLK_TOGGLE_FNCPTR(60)
@ -230,26 +241,20 @@ class DMG_MBC3(DMG_MBC):
data = self.CartRead(0xA000, 0x800)
if data[0] in (0, 0xFF): continue
skipped = False
if data != bytearray([data[0]] * 0x800): return False
return skipped is False
if data != bytearray([data[0]] * 0x800):
dprint("No RTC because whole bank is not the same value", data[0])
skipped = True
break
#ram1 = self.CartRead(0xA000, 0x10)
#ram2 = ram1
#t1 = time.time()
#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)
self.EnableRAM(enable=False)
self.CartWrite([ [0x4000, 0] ])
return skipped is False
def GetRTCBufferSize(self):
return 0x30
def LatchRTC(self):
dprint("Latching RTC")
self.CLK_TOGGLE_FNCPTR(60)
self.CartWrite([ [ 0x0000, 0x0A ] ])
time.sleep(0.01)
@ -260,7 +265,9 @@ class DMG_MBC3(DMG_MBC):
time.sleep(0.01)
def ReadRTC(self):
#self.EnableRAM(enable=True)
dprint("Reading RTC")
self.EnableRAM(enable=True)
buffer = bytearray()
for i in range(0x08, 0x0D):
self.CLK_TOGGLE_FNCPTR(60)
@ -272,15 +279,17 @@ class DMG_MBC3(DMG_MBC):
ts = int(time.time())
buffer.extend(struct.pack("<Q", ts))
self.EnableRAM(enable=False)
self.CartWrite([ [0x4000, 0] ])
return buffer
def WriteRTC(self, buffer, advance=False):
dprint(buffer)
#self.LatchRTC()
dprint("Writing RTC:", buffer)
self.EnableRAM(enable=True)
if advance:
try:
dt_now = datetime.datetime.fromtimestamp(time.time())
if buffer == bytearray([0xFF] * len(buffer)): # Reset
if buffer == bytearray([0x00] * len(buffer)): # Reset
seconds = 0
minutes = 0
hours = 0
@ -295,10 +304,6 @@ class DMG_MBC3(DMG_MBC):
days = days & 0x1FF
timestamp_then = struct.unpack("<Q", buffer[-8:])[0]
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)
if timestamp_then < timestamp_now:
dt_then = datetime.datetime.fromtimestamp(timestamp_then)
@ -364,6 +369,9 @@ class DMG_MBC3(DMG_MBC):
self.CartWrite([ [ 0x6000, 0x01 ] ])
time.sleep(0.1)
self.CartWrite([ [0x4000, 0] ])
self.EnableRAM(enable=False)
class DMG_MBC5(DMG_MBC):
def GetName(self):
return "MBC5"
@ -817,7 +825,8 @@ class DMG_HuC3(DMG_MBC):
if advance:
try:
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
minutes = 0
days = 0
@ -987,7 +996,7 @@ class DMG_TAMA5(DMG_MBC):
if advance:
try:
dt_now = datetime.datetime.fromtimestamp(time.time())
if buffer == bytearray([0xFF] * len(buffer)): # Reset
if buffer == bytearray([0x00] * len(buffer)): # Reset
seconds = 0
minutes = 0
hours = 0
@ -1067,8 +1076,7 @@ class DMG_Unlicensed_256M(DMG_MBC5):
dprint(self.GetName(), "|", index)
flash_bank = math.floor(index / 512)
if index >= 512:
index -= (512 * flash_bank)
index = index % 512
if index == 0:
self.CART_POWERCYCLE_FNCPTR()
@ -1122,9 +1130,6 @@ class DMG_Unlicensed_WisdomTree(DMG_MBC):
if args is None: args = {}
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)
#def GetROMSize(self):
# return self.ROM_BANK_SIZE * math.floor(self.ROM_BANK_NUM / 2)
def SelectBankROM(self, index):
dprint(self.GetName(), "|", index)
@ -1134,6 +1139,44 @@ class DMG_Unlicensed_WisdomTree(DMG_MBC):
self.CartWrite(commands)
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:
CART_WRITE_FNCPTR = None
@ -1328,16 +1371,11 @@ class AGB_GPIO:
seconds = Util.DecodeBCD(buffer[0x06])
timestamp_then = struct.unpack("<Q", buffer[-8:])[0]
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:
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")
rd = relativedelta(dt_now, dt_then)
dt_new = dt_buffer + rd
#print(dt_then, dt_now, dt_buffer, dt_new, sep="\n")
years = dt_new.year - 2000
months = dt_new.month
days = dt_new.day
@ -1380,8 +1418,3 @@ class AGB_GPIO:
[ self.GPIO_REG_DAT, 1 ],
[ 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)
ext = os.path.splitext(path)[1]
if ext.lower() == ".png":
if ext == "" or ext.lower() == ".png":
outpic = pic
outpic.save(path, pnginfo=pnginfo)
elif ext.lower() == ".gif":

View File

@ -3,6 +3,7 @@
# Author: Lesserkuma (github.com/lesserkuma)
import hashlib, re, zlib, string
from .Util import dprint
class RomFileAGB:
ROMFILE_PATH = None
@ -40,10 +41,17 @@ class RomFileAGB:
return self.ROMFILE[0:0x200]
def GetHeader(self):
buffer = self.ROMFILE
buffer = bytearray(self.ROMFILE)
data = {}
data["empty"] = (buffer[0x04:0xA0] == bytearray([buffer[0x04]] * 0x9C))
data["empty_nocart"] = (buffer == bytearray([0x00] * len(buffer)))
hash = hashlib.sha1(buffer[0:0x180]).digest()
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 ])
game_title = bytearray(buffer[0xA0:0xAC]).decode("ascii", "replace")
game_title = re.sub(r"(\x00+)$", "", game_title)

View File

@ -2,7 +2,7 @@
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
import hashlib, re, string
import hashlib, re, string, struct
from . import Util
class RomFileDMG:
@ -55,7 +55,6 @@ class RomFileDMG:
buffer = self.ROMFILE
data = {}
if len(buffer) < 0x180: return {}
data["empty"] = (buffer[0x104:0x134] == bytearray([buffer[0x104]] * 0x30))
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 ])
@ -76,13 +75,13 @@ class RomFileDMG:
maker_code = bytearray(buffer[0x144:0x146]).decode("ascii", "replace")
maker_code = ''.join(filter(lambda x: x in set(string.printable), maker_code))
data["maker_code_new"] = maker_code
data["features_raw"] = int(buffer[0x147])
data["features"] = "?"
data["mapper_raw"] = int(buffer[0x147])
data["mapper"] = "?"
data["rom_size_raw"] = int(buffer[0x148])
data["rom_size"] = "?"
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])
if data["features_raw"] == 0x05 or data["features_raw"] == 0x06:
if data["mapper_raw"] == 0x05 or data["mapper_raw"] == 0x06:
data["ram_size"] = 0x200
else:
data["ram_size"] = "?"
@ -97,61 +96,191 @@ class RomFileDMG:
data["rom_checksum_correct"] = data["rom_checksum"] == data["rom_checksum_calc"]
# MBC1M
if data["features_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["features_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["features_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["features_raw"] += 0x100
if data["mapper_raw"] == 0x03 and data["game_title"] == "MOMOCOL" and data["header_checksum"] == 0x28 or \
data["mapper_raw"] == 0x01 and data["game_title"] == "BOMCOL" and data["header_checksum"] == 0x86 or \
data["mapper_raw"] == 0x01 and data["game_title"] == "GENCOL" and data["header_checksum"] == 0x8A or \
data["mapper_raw"] == 0x01 and data["game_title"] == "SUPERCHINESE 123" and data["header_checksum"] == 0xE4 or \
data["mapper_raw"] == 0x01 and data["game_title"] == "MORTALKOMBATI&II" and data["header_checksum"] == 0xB9 or \
data["mapper_raw"] == 0x01 and data["game_title"] == "MORTALKOMBAT DUO" and data["header_checksum"] == 0xA7:
data["mapper_raw"] += 0x100
# GB Memory
if data["features_raw"] == 0x19 and data["game_title"] == "NP M-MENU MENU" and data["header_checksum"] == 0xD3:
data["features_raw"] = 0x105
if data["mapper_raw"] == 0x19 and data["game_title"] == "NP M-MENU MENU" and data["header_checksum"] == 0xD3:
data["ram_size_raw"] = 0x04
elif data["features_raw"] == 0x01 and data["game_title"] == "DMG MULTI MENU " and data["header_checksum"] == 0x36:
data["features_raw"] = 0x105
data["mapper_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["mapper_raw"] = 0x105
# M161 (Mani 4 in 1)
elif data["features_raw"] == 0x10 and data["game_title"] == "TETRIS SET" and data["header_checksum"] == 0x3F:
data["features_raw"] = 0x104
elif data["mapper_raw"] == 0x10 and data["game_title"] == "TETRIS SET" and data["header_checksum"] == 0x3F:
data["mapper_raw"] = 0x104
# MMM01 (Mani 4 in 1)
elif data["features_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["features_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["features_raw"] = 0x0B
elif data["mapper_raw"] == 0x11 and data["game_title"] == "BOUKENJIMA2 SET" and data["header_checksum"] == 0 or \
data["mapper_raw"] == 0x11 and data["game_title"] == "BUBBLEBOBBLE SET" and data["header_checksum"] == 0xC6 or \
data["mapper_raw"] == 0x11 and data["game_title"] == "GANBARUGA SET" and data["header_checksum"] == 0x90 or \
data["mapper_raw"] == 0x11 and data["game_title"] == "RTYPE 2 SET" and data["header_checksum"] == 0x32:
data["mapper_raw"] = 0x0B
# Unlicensed 256M Mapper
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["features_raw"] = 0x201
data["rom_size_raw"] = 0x0A
data["ram_size_raw"] = 0x201
data["mapper_raw"] = 0x201
elif buffer[0x150:0x160].decode("ascii", "replace") == "256M ROM Builder":
data["features_raw"] = 0x201
data["ram_size_raw"] = 0x201
data["mapper_raw"] = 0x201
# 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
data["features_raw"] = 0x202
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
data["features_raw"] = 0x202
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
data["features_raw"] = 0x202
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
data["features_raw"] = 0x202
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"]:
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

View File

@ -2,12 +2,12 @@
# FlashGBX
# 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
# Common constants
APPNAME = "FlashGBX"
VERSION_PEP440 = "3.7"
VERSION_PEP440 = "3.8"
VERSION = "v{:s}".format(VERSION_PEP440)
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_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_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_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_Map = [ 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201 ]
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 = [ "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, 0x203 ]
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_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' }
@ -147,7 +147,7 @@ class Progress():
self.UPDATER(args)
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:
args["time_elapsed"] = now - self.PROGRESS["time_start"]
elif "time_start" in args:
@ -247,6 +247,32 @@ class TAMA5_REG(Enum):
MEM_READ_L = 0xC
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):
# https://stackoverflow.com/questions/50405017/
d = 10 ** 2
@ -257,23 +283,23 @@ def roundup(x):
def formatFileSize(size, asInt=False, roundUp=False):
if size == 1:
return "{:d} Byte".format(size)
return "{:d} Byte".format(size)
elif size < 1024:
return "{:d} Bytes".format(size)
return "{:d} Bytes".format(size)
elif size < 1024 * 1024:
val = size/1024
if roundUp: val = roundup(val)
if asInt:
return "{:d} KB".format(int(val))
return "{:d} KB".format(int(val))
else:
return "{:.1f} KB".format(val)
return "{:.1f} KB".format(val)
else:
val = size/1024/1024
if roundUp: val = roundup(val)
if asInt:
return "{:d} MB".format(int(val))
return "{:d} MB".format(int(val))
else:
return "{:.2f} MB".format(val)
return "{:.2f} MB".format(val)
def formatProgressTimeShort(sec):
sec = sec % (24 * 3600)
@ -285,20 +311,20 @@ def formatProgressTimeShort(sec):
def formatProgressTime(sec):
if int(sec) == 1:
return "{:d} second".format(int(sec))
return "{:d} second".format(int(sec))
elif sec < 60:
return "{:d} seconds".format(int(sec))
return "{:d} seconds".format(int(sec))
elif int(sec) == 60:
return "1 minute"
return "1 minute"
else:
min = int(sec / 60)
sec = int(sec % 60)
s = str(min) + " "
s = str(min) + " "
if min == 1:
s = s + "minute"
else:
s = s + "minutes"
s = s + ", " + str(sec) + " "
s = s + ", " + str(sec) + " "
if sec == 1:
s = s + "second"
else:

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,8 @@
"AGB-E05-02 with M29W128GH",
"AGB-E05-02 with M29W128FH",
"2006_TSOP_64BALL_6106 with W29GL128SH9B",
"AGB-E05-02 with JS28F128"
"AGB-E05-02 with JS28F128",
"AGB-E05-03H with M29W128GH"
],
"flash_ids":[
[ 0x02, 0x00, 0x7D, 0x22 ],
@ -20,7 +21,8 @@
[ 0x20, 0x00, 0x7D, 0x22 ],
[ 0x20, 0x00, 0x7D, 0x22 ],
[ 0xEF, 0x00, 0x7D, 0x22 ],
[ 0x8A, 0x00, 0x7D, 0x22 ]
[ 0x8A, 0x00, 0x7D, 0x22 ],
[ 0x20, 0x00, 0x7D, 0x22 ]
],
"voltage":3.3,
"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 ],
[ 0x5555, 0x90 ]
],
"read_cfi":[
[ 0x5555, 0x98 ]
],
"chip_erase":[
[ 0x5555, 0xAA ],
[ 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",
"names":[
"insideGadgets 512 KB"
"insideGadgets 128/256/512 KB"
],
"flash_ids":[
[ 0xBF, 0xB5, 0x01, 0xFF ],
[ 0xBF, 0xB6, 0x01, 0xFF ],
[ 0xBF, 0xB7, 0x01, 0xFF ]
],
"voltage":5,
"power_cycle":true,
"flash_size":0x80000,
"start_addr":0,
"first_bank":1,

View File

@ -10,6 +10,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
APP = None
DEVICE = None
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):
QtWidgets.QDialog.__init__(self)
@ -18,12 +19,13 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.APP = app
self.APP_PATH = app_path
self.DEVICE = device
self.PCB_VER = device.GetPCBVersion()
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)
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()
ini_file = ini_file.decode(encoding="utf-8")
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
@ -75,8 +77,8 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.grpAvailableFwUpdates.setMinimumWidth(400)
self.grpAvailableFwUpdatesLayout = QtWidgets.QVBoxLayout()
self.grpAvailableFwUpdatesLayout.setContentsMargins(-1, 3, -1, -1)
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.setWordWrap(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.mousePressEvent = lambda x: [ self.optOFW.setChecked(True) ]
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.btnUpdate = QtWidgets.QPushButton("Install Firmware Update")
@ -97,20 +96,22 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.rowUpdate.addStretch()
self.rowUpdate.addWidget(self.btnUpdate)
self.rowUpdate.addStretch()
self.grpAvailableFwUpdatesLayout.addWidget(self.optCFW)
self.grpAvailableFwUpdatesLayout.addWidget(self.lblCFW_Blerb)
if self.PCB_VER == "v1.3":
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.lblOFW_Blerb)
self.grpAvailableFwUpdatesLayout.addWidget(self.optExternal)
#self.grpAvailableFwUpdatesLayout.addWidget(self.lblExternal_Blerb)
self.grpAvailableFwUpdatesLayout.addSpacing(3)
self.grpAvailableFwUpdatesLayout.addItem(self.rowUpdate)
#self.grpAvailableFwUpdatesLayout.addWidget(self.btnUpdate)
self.grpAvailableFwUpdates.setLayout(self.grpAvailableFwUpdatesLayout)
self.layout_device.addWidget(self.grpAvailableFwUpdates)
# ↑↑↑ Available Firmware Updates
self.grpStatus = QtWidgets.QGroupBox("")
self.grpStatusLayout = QtWidgets.QGridLayout()
self.prgStatus = QtWidgets.QProgressBar()
@ -119,7 +120,6 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.prgStatus.setValue(0)
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.lblStatus, 2, 0)
@ -200,18 +200,18 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
fn = "ofw.hex"
else:
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
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:
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()
return
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
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?"
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)
@ -223,7 +223,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
self.grpAvailableFwUpdates.setEnabled(False)
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")
else:
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:
fncSetStatus(text="Status: Bootloader error.", enableUI=True)
self.prgStatus.setValue(0)
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW 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()
return 2
@ -326,13 +326,13 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog):
fncSetStatus("Status: Waiting for bootloader... (+{:d}ms)".format(math.ceil(delay * 1000)))
if self.ResetAVR(delay) is False:
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()
return 2
lives -= 1
if lives < 0:
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()
return 2
continue

View File

@ -16,7 +16,7 @@ from . import Util
class GbxDevice:
DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 1
DEVICE_MAX_FW = 5
DEVICE_MAX_FW = 6
DEVICE_CMD = {
"NULL":0x30,
@ -49,6 +49,8 @@ class GbxDevice:
"DMG_MBC7_READ_EEPROM":0xB5,
"DMG_MBC7_WRITE_EEPROM":0xB6,
"DMG_MBC6_MMSA_WRITE_FLASH":0xB7,
"DMG_SET_BANK_CHANGE_CMD":0xB8,
"DMG_EEPROM_WRITE":0xB9,
"AGB_CART_READ":0xC1,
"AGB_CART_WRITE":0xC2,
"AGB_CART_READ_SRAM":0xC3,
@ -59,7 +61,7 @@ class GbxDevice:
"AGB_CART_READ_3D_MEMORY":0xC8,
"AGB_BOOTUP_SEQUENCE":0xC9,
"DMG_FLASH_WRITE_BYTE":0xD1,
"AGB_FLASH_WRITE_BYTE":0xD2,
"AGB_FLASH_WRITE_SHORT":0xD2,
"FLASH_PROGRAM":0xD3,
"CART_WRITE_FLASH_CMD":0xD4,
}
@ -80,6 +82,8 @@ class GbxDevice:
"DMG_READ_CS_PULSE":[8, 0x08],
"DMG_WRITE_CS_PULSE":[8, 0x09],
"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'}
@ -88,6 +92,7 @@ class GbxDevice:
FW = []
FW_UPDATE_REQ = False
FW_VAR = {}
MODE = None
PORT = ''
DEVICE = None
@ -187,7 +192,6 @@ class GbxDevice:
except SerialException as 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."])
print(str(e))
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)])
continue
@ -247,7 +251,7 @@ class GbxDevice:
def IsSupportedMbc(self, mbc):
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:
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()
return self.LoadFirmwareVersion()
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))
return False
@ -321,6 +332,9 @@ class GbxDevice:
else:
return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), self.PORT)
def GetOfficialWebsite(self):
return "https://www.gbxcart.com/"
def SupportsFirmwareUpdates(self):
return self.FW["pcb_ver"] in (4, 5, 6)
@ -373,8 +387,8 @@ class GbxDevice:
if not isinstance(data, bytearray):
data = bytearray([data])
dstr = ' '.join(format(x, '02X') for x in data)
dprint("[{:02X}] {:s}".format(int(len(dstr)/3) + 1, dstr[:96]))
#dstr = ' '.join(format(x, '02X') for x in data)
#dprint("[{:02X}] {:s}".format(int(len(dstr)/3) + 1, dstr[:96]))
self.DEVICE.write(data)
self.DEVICE.flush()
@ -405,6 +419,7 @@ class GbxDevice:
def _set_fw_variable(self, key, value):
dprint("Setting firmware variable {:s} to 0x{:X}".format(key, value))
self.FW_VAR[key] = value
size = 0
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", value))
self._write(buffer)
def _cart_read(self, address, length=0, agb_save_flash=False):
if self.MODE == "DMG":
if length == 0:
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:
return self.ReadROM(address, length)
if address < 0xA000:
return self.ReadROM(address, length)
else:
return self.ReadRAM(address - 0xA000, length)
elif self.MODE == "AGB":
if length == 0:
length = 2
@ -440,7 +461,7 @@ class GbxDevice:
return self.ReadROM(address, length)
def _cart_write(self, address, value, flashcart=False, sram=False):
dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(address, value & 0xFF), 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 flashcart:
buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]])
@ -457,7 +478,7 @@ class GbxDevice:
self._read(1)
return
elif flashcart:
buffer = bytearray([self.DEVICE_CMD["AGB_FLASH_WRITE_BYTE"]])
buffer = bytearray([self.DEVICE_CMD["AGB_FLASH_WRITE_SHORT"]])
else:
buffer = bytearray([self.DEVICE_CMD["AGB_CART_WRITE"]])
@ -465,19 +486,33 @@ class GbxDevice:
buffer.extend(struct.pack(">H", value & 0xFFFF))
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)
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))
for i in range(0, num):
#dprint("Writing to cartridge: 0x{:X} = 0x{:X}".format(commands[i][0], commands[i][1] & 0xFF))
buffer.extend(struct.pack(">I", commands[i][0]))
buffer.extend(struct.pack("B", commands[i][1]))
dprint("Writing to cartridge: 0x{:X} = 0x{:X} ({:d} of {:d})".format(commands[i][0], commands[i][1], i+1, num))
if self.MODE == "AGB" and flashcart:
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)
if self._read(1) != 0x01:
print("Error!")
ret = self._read(1)
if ret != 0x01:
print("Error in _cart_write_flash():", ret)
def _clk_toggle(self, num):
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"])
elif self.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):
if self.FW["pcb_ver"] in (5, 6):
@ -519,10 +552,14 @@ class GbxDevice:
if mode == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_DMG"])
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"
elif mode == "AGB":
self._write(self.DEVICE_CMD["SET_MODE_AGB"])
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._set_fw_variable(key="ADDRESS", value=0)
self.CartPowerOn()
@ -534,7 +571,7 @@ class GbxDevice:
return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values()))
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":
self.POS = args["pos"]
self.INFO["transferred"] = args["pos"]
@ -543,7 +580,9 @@ class GbxDevice:
except AttributeError:
if self.SIGNAL is not None:
self.SIGNAL(args)
if args["action"] == "FINISHED": self.SIGNAL = None
if args["action"] == "FINISHED":
self.SIGNAL = None
def ReadInfo(self, setPinsAsInputs=False, checkRtc=True):
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._read(1)
self.CartPowerOn()
if self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"])
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)
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:
print("{:s}\n{:s}Couldnt read the cartridge information. Please try again.{:s}".format(str(header), ANSI.RED, ANSI.RESET))
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
if self.MODE == "DMG":
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)
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()
_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:
data["has_rtc"] = _mbc.HasRTC() is True
if data["has_rtc"] is True:
@ -597,26 +638,35 @@ class GbxDevice:
data["has_rtc"] = False
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()
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)
time.sleep(0.1)
header = self.ReadROM(0, 0x180)
data = RomFileAGB(header).GetHeader()
# Check where the ROM data repeats
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 (data["3d_memory"] == True):
if data["empty"] or data["empty_nocart"]:
data["rom_size"] = 0x2000000
elif (data["3d_memory"] == True):
data["rom_size"] = 0x4000000
elif (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"):
data["dacs_8m"] = True
else:
# 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:
_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
if self.MODE == "DMG" and mbc is None:
mbc = info["features_raw"]
mbc = info["mapper_raw"]
if mbc > 0x200: checkSaveType = False
(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):
self._set_fw_variable("TRANSFER_SIZE", length)
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):
skip_write = True
@ -1248,14 +1298,22 @@ class GbxDevice:
[ address, length - 1 ],
[ address, 0x00 ],
])
while True:
lives = 100
while lives > 0:
self._cart_write(0x4000, 0x70)
sr = self._cart_read(address + length - 1)
dprint("sr=0x{:X}".format(sr))
if sr & 0x80 == 0x80: break
time.sleep(0.001)
lives -= 1
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
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
self.SetProgress({"action":"WRITE", "bytes_added":length})
@ -1263,11 +1321,48 @@ class GbxDevice:
self._cart_write(address - 1, 0xFF)
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):
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)
buffer2 = self.ReadROM(0x100, 0x40)
buffer2 = self.ReadROM(0x80, 0x40)
return buffer1 == buffer2
def AutoDetectFlash(self, limitVoltage=False):
@ -1277,12 +1372,13 @@ class GbxDevice:
flash_id_found = False
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
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)
time.sleep(0.1)
self._write(self.DEVICE_CMD["SET_MODE_DMG"])
elif self.MODE == "DMG":
self._write(self.DEVICE_CMD["SET_MODE_AGB"])
@ -1295,21 +1391,21 @@ class GbxDevice:
dprint("*** Now checking: {:s}\n".format(flashcart_meta["names"][0]))
if self.MODE == "DMG":
if flashcart_meta["write_pin"] == "WR":
we = 0x01 # FLASH_WE_PIN_WR
elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"):
we = 0x02 # FLASH_WE_PIN_AUDIO
elif flashcart_meta["write_pin"] == "WR+RESET":
we = 0x03 # FLASH_WE_PIN_WR_RESET
self._set_fw_variable("FLASH_WE_PIN", we)
we = flashcart_meta["write_pin"]
if we == "WR":
self._set_fw_variable("FLASH_WE_PIN", 0x01) # FLASH_WE_PIN_WR
elif we in ("AUDIO", "VIN"):
self._set_fw_variable("FLASH_WE_PIN", 0x02) # FLASH_WE_PIN_AUDIO
elif we == "WR+RESET":
self._set_fw_variable("FLASH_WE_PIN", 0x03) # FLASH_WE_PIN_WR_RESET
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.Unlock()
if "flash_ids" in flashcart_meta and len(flashcart_meta["flash_ids"]) > 0:
vfid = flashcart.VerifyFlashID()
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"]:
flash_id = cart_flash_id
flash_id_found = True
@ -1349,10 +1445,20 @@ class GbxDevice:
if self.MODE == "DMG" and not flash_id_found:
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)
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):
self._write(self.DEVICE_CMD["OFW_CART_MODE"])
self._read(1)
@ -1375,6 +1481,13 @@ class GbxDevice:
#{ '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)
d_swap = None
cfi_info = ""
@ -1385,11 +1498,6 @@ class GbxDevice:
cfi = {'raw':b''}
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
we_pins = [ "WR", "AUDIO" ]
else:
@ -1414,7 +1522,8 @@ class GbxDevice:
buffer = self.ReadROM(0, 0x400)
for i in range(0, len(method['reset'])):
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]))
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)
flash_id = self.ReadROM(0, 8)
if flash_id == check_buffer[:len(flash_id)]: continue
if flash_id == bytearray([0x00] * len(flash_id)): continue
if self.MODE == "DMG":
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)
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)
if "method" in cfi:
@ -1543,7 +1653,8 @@ class GbxDevice:
flash_id = self.ReadROM(method["start_addr"], 8)
else:
flash_id = self.ReadROM(0, 8)
if flash_id == bytearray([0x00] * len(flash_id)): continue
for i in range(0, len(method['reset'])):
self._cart_write(method['reset'][i][0], method["reset"][i][1], flashcart=True)
@ -1561,10 +1672,10 @@ class GbxDevice:
if self.MODE == "DMG":
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
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)
flash_id = ""
@ -1626,13 +1737,18 @@ class GbxDevice:
if i == args["cart_type"]:
try:
cart_type["_index"] = cart_type["names"].index(list(self.SUPPORTED_CARTS[self.MODE].keys())[i])
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_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:
pass
buffer_len = 0x4000
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)
self._write(self.DEVICE_CMD["SET_MODE_DMG"])
@ -1643,6 +1759,9 @@ class GbxDevice:
self._set_fw_variable("DMG_READ_CS_PULSE", 1)
_mbc.EnableMapper()
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:
_mbc.EnableMapper()
@ -1701,6 +1820,7 @@ class GbxDevice:
cancel_args = {"action":"ABORT", "abortable":False}
cancel_args.update(self.CANCEL_ARGS)
self.CANCEL_ARGS = {}
self.ERROR_ARGS = {}
self.SetProgress(cancel_args)
try:
if file is not None: file.close()
@ -1770,14 +1890,26 @@ class GbxDevice:
file.close()
# 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":
chk = _mbc.CalcChecksum(buffer)
elif self.MODE == "AGB":
chk = zlib.crc32(buffer) & 0xFFFFFFFF
chk = self.INFO["file_crc32"]
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
if self.MODE == "DMG":
@ -1807,6 +1939,10 @@ class GbxDevice:
extra_size = 0
audio_low = False
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"]
ram_banks = _mbc.GetRAMBanks(save_size)
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_READ_CS_PULSE", 0)
# Enable mappers
if _mbc.GetName() == "TAMA5":
self._set_fw_variable("DMG_WRITE_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_WE_PIN", 0x01) # WR
_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:
_mbc.EnableMapper()
@ -1918,7 +2081,7 @@ class GbxDevice:
command = commands[args["save_type"]][args["mode"] - 2]
if args["rtc"] is True:
extra_size = 0x10
if args["mode"] == 2: # Backup
action = "SAVE_READ"
buffer = bytearray()
@ -1927,6 +2090,8 @@ class GbxDevice:
self.INFO["save_erase"] = args["erase"]
if args["erase"] == True:
buffer = bytearray([ empty_data_byte ] * save_size)
if self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
buffer[0] = 0x00
else:
if args["path"] is None:
buffer = self.INFO["data"]
@ -1956,6 +2121,10 @@ class GbxDevice:
if ((buffer_offset - 0x8000) % 0x20000) == 0:
dprint("Erasing flash sector at position 0x{:X}".format(buffer_offset))
_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:
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1 if _mbc.WriteWithCSPulse() else 0)
(start_address, bank_size) = _mbc.SelectBankRAM(bank)
@ -1994,6 +2163,7 @@ class GbxDevice:
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
@ -2005,6 +2175,8 @@ class GbxDevice:
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=False, max_length=max_length)
elif self.MODE == "DMG" and _mbc.GetName() == "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
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
@ -2037,6 +2209,8 @@ class GbxDevice:
self.WriteFlash_MBC6(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], mapper=_mbc)
elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5":
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
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
@ -2121,17 +2295,18 @@ class GbxDevice:
file.close()
else:
self.INFO["data"] = buffer
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest()
if "verify_write" in args and args["verify_write"] not in (None, False):
return True
elif args["mode"] == 3: # Restore
self.INFO["transferred"] = len(buffer)
if args["rtc"] is True:
advance = args["rtc_advance"]
dprint("rtc_advance:", advance)
self.SetProgress({"action":"UPDATE_RTC", "method":"write"})
if self.MODE == "DMG" and args["rtc"] is True:
_mbc.WriteRTC(buffer[-_mbc.GetRTCBufferSize():], advance=advance)
elif self.MODE == "AGB":
@ -2174,7 +2349,7 @@ class GbxDevice:
_mbc.EnableRAM(enable=False)
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
if audio_low: self._set_fw_variable("FLASH_WE_PIN", 0x02)
# Clean up
self.INFO["last_action"] = self.INFO["action"]
self.INFO["action"] = None
@ -2239,7 +2414,7 @@ class GbxDevice:
# Firmware check L2
# 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):
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
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):
@ -2256,7 +2431,7 @@ class GbxDevice:
pass
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 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()
@ -2280,7 +2455,7 @@ class GbxDevice:
data_map_import = bytearray(data_map_import)
dprint("Hidden sector data loaded")
else:
flashcart = Flashcart(config=cart_type, cart_write_fncptr=self._cart_write, cart_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
@ -2317,6 +2492,12 @@ class GbxDevice:
args["mbc"] = mbc
else:
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)
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"):
temp = 0x00
dprint("Using GB Memory command set")
elif command_set_type == "BLAZE_XPLODER":
temp = 0x00
else:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM flashing.", "abortable":False})
return False
@ -2427,10 +2610,36 @@ class GbxDevice:
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)))
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)
if self.FW["fw_ver"] >= 6:
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:
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
# ↓↓↓ Unlock cartridge
@ -2531,6 +2740,7 @@ class GbxDevice:
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
@ -2560,6 +2770,8 @@ class GbxDevice:
self._cart_write(pos + buffer_len - 1, 0xF0)
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)
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:
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:
@ -2606,8 +2818,14 @@ class GbxDevice:
verify_args.update({"verify_write":data_import, "rom_size":len(data_import), "path":"", "rtc_area":flashcart.HasRTC()})
self.ReadROM(0, 4) # dummy read
verified_size = self._BackupROM(verify_args)
if self.CANCEL is True:
pass
if self.CANCEL:
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):
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

View File

@ -17,7 +17,7 @@ class GbxDevice:
DEVICE_NAME = "GBxCart RW"
DEVICE_MIN_FW = 26
DEVICE_MAX_FW = 30
DEVICE_CMD = {
"CART_MODE":'C',
"GB_MODE":1,
@ -119,7 +119,7 @@ class GbxDevice:
"AUDIO_HIGH":'8',
"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 = {}
FW = []
@ -185,15 +185,14 @@ class GbxDevice:
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."])
continue
if self.FW[0] == 0:
dev.close()
self.DEVICE = None
return False
elif (self.FW[1] == 4 and self.FW[0] < self.DEVICE_MAX_FW) or (self.FW[0] < self.DEVICE_MIN_FW):
#dev.close()
#self.DEVICE = None
#conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " requires a firmware update to work with this software. Please try again after updating it to version 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
elif (self.FW[1] == 100 and self.FW[0] < 26):
self.FW_UPDATE_REQ = True
elif (self.FW[1] in (2, 4, 90) and self.FW[0] < self.DEVICE_MAX_FW) or (self.FW[0] < self.DEVICE_MIN_FW):
self.FW_UPDATE_REQ = True
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>."])
@ -214,7 +213,7 @@ class GbxDevice:
conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"])
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:
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:
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."])
print(str(e))
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)])
continue
continue
return conn_msg
@ -290,18 +288,24 @@ class GbxDevice:
def GetFullNameExtended(self, more=False):
return "{:s} Firmware {:s} ({:s})".format(self.GetFullName(), self.GetFirmwareVersion(), str(self.PORT))
def GetOfficialWebsite(self):
return "https://www.gbxcart.com/"
def SupportsFirmwareUpdates(self):
_, pcb = self.FW
if pcb != 4: return False
return True
fw, pcb = self.FW
#return pcb == 4
return (pcb in (2, 4, 90, 100) and fw > 3)
def FirmwareUpdateAvailable(self):
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
def GetFirmwareUpdaterClass(self):
if self.FW[1] == 4: # v1.3
_, pcb = self.FW
if pcb in (2, 4, 90, 100):
try:
from . import fw_GBxCartRW_v1_3
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_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"})
#########################################

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.*
## Cartridge Compatibility
### Supported official cartridge memory mappers
### Supported cartridge memory mappers
- Game Boy
- All cartridges without memory mapping
- MBC1
@ -71,17 +71,23 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- HuC-1
- HuC-3
- TAMA5
- Unlicensed 256M Mapper
- Unlicesned Wisdom Tree Mapper
- Unlicensed Xploder GB Mapper
- Unlicensed Sachen Mapper
- Game Boy Advance
- All cartridges without memory mapping
- 8M FLASH DACS
- 3D Memory (GBA Video)
- Unlicensed 2048M Mapper
### Currently supported flash cartridges
- Game Boy
- 29LV Series Flash BOY with 29LV160DB
- BLAZE Xploder GB
- BUNG Doctor GB Card 64M
- DIY cart with AM29F010
- 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
- HDR Game Boy Camera Flashcart
- insideGadgets 32 KB
- insideGadgets 128 KB
- insideGadgets 256 KB
- insideGadgets 512 KB
- insideGadgets 1 MB, 128 KB SRAM
- 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 AM29LV160MB
- SD007_K8D3216_32M with MX29LV160CT
- SD007_T40_64BALL_TSOP28 with 29LV016T
- SD007_T40_64BALL_TSOP28 with TC58FVB016FT-85¹
- SD007_TSOP_29LV017D with L017D70VC
- 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
- AA1030_TSOP88BALL with M36W0R603
- AGB-E05-01 with GL128S
- AGB-E05-01 with MSP55LV100G
- AGB-E05-01 with MSP55LV128M
- AGB-E05-01 with MX29GL128FHT2I-90G
- 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 M29W128GH
- AGB-E05-02 with S29GL032
- AGB-E05-03H with M29W128GH
- AGB-E05-06L with 29LV128DBT2C-90Q
- AGB-E08-09 with 29LV128DTMC-90Q
- 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_TSOPBGA_0106 with M29W640GB6AZA6
- BX2006_TSOPBGA_0106 with K8D6316UTM-PI07
- DV15 with MSP55LV100G
- GA-07 with unlabeled flash chip
- GE28F128W30 with 128W30B0
- 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)
- Icesythe7 (feature suggestions, testing, bug reports)
- 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)
- JS7457 (flash chip info)
- 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)
- Smelly-Ghost (testing)
- Super Maker (flash chip info, testing)
- Tauwasser (research)
- t5b6_de (flash chip info)
- Timville (flash chip info)
- twitnic (flash chip info)
- Veund (flash chip info)
- Voultar (bug reports, feature suggestions)
- Wkr (flash chip info)
- x7l7j8cc (flash chip info)
- Zeii (flash chip info)
- Zelante (flash chip info)
- zvxr (flash chip info)
## DISCLAIMER

View File

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