mirror of
https://github.com/lesserkuma/FlashGBX.git
synced 2026-03-21 17:44:30 -05:00
3.8
This commit is contained in:
parent
4a91df2f71
commit
50b4601d72
14
CHANGES.md
14
CHANGES.md
|
|
@ -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 it’s not provided by the user in form of a .map 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="don’t 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")
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class FlashGBX_CLI():
|
|||
print("\n{:s}Couldn’t 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 couldn’t 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 doesn’t 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 doesn’t 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 cartridge’s 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 cartridge’s 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}Couldn’t 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 it’s possible that it’s 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}Couldn’t 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 += " It’s possible that it’s 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}Couldn’t 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)
|
||||
|
|
|
|||
|
|
@ -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("Don’t 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 doesn’t 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 doesn’t 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 it’s possible that it’s 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 maker’s 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 can’t 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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: Couldn’t 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:
|
||||
|
|
|
|||
|
|
@ -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":{
|
||||
|
|
|
|||
|
|
@ -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":[
|
||||
|
|
|
|||
|
|
@ -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":[
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
49
FlashGBX/config/fc_DMG_29LV016T.txt
Normal file
49
FlashGBX/config/fc_DMG_29LV016T.txt
Normal 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 ]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,6 @@
|
|||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x90 ]
|
||||
],
|
||||
"read_cfi":[
|
||||
[ 0x5555, 0x98 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
|
|
|
|||
55
FlashGBX/config/fc_DMG_XploderGB.txt
Normal file
55
FlashGBX/config/fc_DMG_XploderGB.txt
Normal 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 ]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW bootloader is not responding. If it doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
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 doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW bootloader is not responding. If it doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
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 doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text="The firmware update was not successful as the GBxCart RW bootloader is not responding. If it doesn’t work even after multiple retries, please use the official firmware updater instead.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
answer = msgbox.exec()
|
||||
return 2
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -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] + " couldn’t be accessed. Make sure your user account has permission to use it and it’s 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("Couldn’t 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}Couldn’t 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("Couldn’t 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
|
||||
|
|
|
|||
|
|
@ -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, "Couldn’t 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] + " couldn’t be accessed. Make sure your user account has permission to use it and it’s 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.
BIN
FlashGBX/res/fw_GBxCart_RW_Mini_v1_0.zip
Normal file
BIN
FlashGBX/res/fw_GBxCart_RW_Mini_v1_0.zip
Normal file
Binary file not shown.
BIN
FlashGBX/res/fw_GBxCart_RW_XMAS_v1_0.zip
Normal file
BIN
FlashGBX/res/fw_GBxCart_RW_XMAS_v1_0.zip
Normal file
Binary file not shown.
BIN
FlashGBX/res/fw_GBxCart_RW_v1_1_v1_2.zip
Normal file
BIN
FlashGBX/res/fw_GBxCart_RW_v1_1_v1_2.zip
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
19
README.md
19
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read(
|
|||
|
||||
setuptools.setup(
|
||||
name="FlashGBX",
|
||||
version="3.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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user