This commit is contained in:
Lesserkuma 2023-07-25 16:40:01 +02:00
parent 2951fdc7ce
commit 64c4429f14
28 changed files with 663 additions and 123 deletions

View File

@ -1,4 +1,21 @@
# Release notes
### v3.32 (released 2023-07-25)
- Added support for M5M29-39VF512 with M5M29HD528 *(thanks marv17)*
- Added support for SD007_BV5_V3 with 26LV160BTC
- Added support for DL9SEC GBA flashcart with TE28F128 *(thanks olDirdey)*
- Added support for DL9SEC GBA flashcart with TE28F256 *(thanks olDirdey)*
- Added support for M36XXX_T32_32D_16D with M36L0R8060T *(thanks Merkin)*
- Added support for Gamebank-web DMG-29W-04 with M29W320ET *(thanks zipplet)*
- Added support for a new bootleg save type based on 1M FLASH
- Added support for another flash chip used on the BennVenn MBC3000 RTC cart
- Added support for 3680x2 with TH50VSF3680 (only up to 8 MiB) *(thanks kane159)*
- Fixed support for BGA64B-71-TV-DEEP with 256M29EML
- Confirmed support for FunnyPlaying MidnightTrace 4 MiB Game Boy Flash Cart *(thanks AlexiG)*
- Confirmed support for FunnyPlaying MidnightTrace 32 MiB Game Boy Advance Flash Cart *(thanks AlexiG)*
- Confirmed support for 2006-36-71_V2 with M36L0R8060B *(thanks kane159)*
- Updated the Game Boy Advance lookup databases for save types, ROM sizes and checksums
- Minor bug fixes and improvements *(thanks Falknör, Crystal)*
### v3.31 (released 2023-06-18)
- Improved support for 32 MiB cartridges that have one of the EEPROM save types (fixes both backups and writes of B3CJ, B53E, B53P, BBAE, BBAP, BC2J, BFRP, BH3E, BH3P, BH8E, BJPP, BU7E, BU7P, BX3E, BX3P, BYUE, BYUJ, BYUP)
- Confirmed support for BGA64B-71-TV-DEEP with 256M29EML *(thanks Leitplanke)*

View File

@ -149,7 +149,7 @@ class FlashGBX_CLI():
print("\n{:s}Couldnt read cartridge header. Please try again.{:s}\n".format(ANSI.RED, ANSI.RESET))
self.DisconnectDevice()
return
elif bad_read and not args.ignore_bad_header and ("mapper_raw" in header and header["mapper_raw"] != 0x203):
if bad_read and not args.ignore_bad_header and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and "mapper_raw" in header and header["mapper_raw"] != 0x203)):
print("\n{:s}Invalid data was detected which usually means that the cartridge couldnt be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean. This check can be disabled with the command line switch “--ignore-bad-header”.{:s}\n".format(ANSI.RED, ANSI.RESET))
print("Cartridge Information:")
print(s_header)
@ -462,6 +462,9 @@ class FlashGBX_CLI():
if data["logo_correct"]:
s += "Nintendo Logo: OK\n"
if not os.path.exists(Util.CONFIG_PATH + "/bootlogo_dmg.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_dmg.bin", "wb") as f:
f.write(data['raw'][0x104:0x134])
else:
s += "Nintendo Logo: {:s}Invalid{:s}\n".format(ANSI.RED, ANSI.RESET)
bad_read = True
@ -517,6 +520,9 @@ class FlashGBX_CLI():
if data["logo_correct"]:
s += "Nintendo Logo: OK\n"
if not os.path.exists(Util.CONFIG_PATH + "/bootlogo_agb.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_agb.bin", "wb") as f:
f.write(data['raw'][0x04:0xA0])
else:
s += "Nintendo Logo: {:s}Invalid{:s}\n".format(ANSI.RED, ANSI.RESET)
bad_read = True
@ -895,6 +901,7 @@ class FlashGBX_CLI():
verify_write = args.no_verify_write is False
fix_bootlogo = False
fix_header = False
if self.CONN.GetMode() == "DMG":
hdr = RomFileDMG(buffer).GetHeader()
@ -938,7 +945,24 @@ class FlashGBX_CLI():
elif self.CONN.GetMode() == "AGB":
hdr = RomFileAGB(buffer).GetHeader()
if not hdr["logo_correct"] and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and mbc not in (0x203, 0x205))):
print("{:s}WARNING: The ROM file you selected will not boot on actual hardware due to invalid logo data.{:s}".format(ANSI.YELLOW, ANSI.RESET))
print("{:s}Warning: The ROM file you selected will not boot on actual hardware due to invalid boot logo data.{:s}".format(ANSI.YELLOW, ANSI.RESET))
bootlogo = None
if self.CONN.GetMode() == "DMG":
if os.path.exists(Util.CONFIG_PATH + "/bootlogo_dmg.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_dmg.bin", "rb") as f:
bootlogo = bytearray(f.read(0x30))
elif self.CONN.GetMode() == "AGB":
if os.path.exists(Util.CONFIG_PATH + "/bootlogo_agb.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_agb.bin", "rb") as f:
bootlogo = bytearray(f.read(0x9C))
if bootlogo is not None:
answer = input("Fix the boot logo before continuing? [Y/n]: ").strip().lower()
print("")
if answer != "n":
fix_bootlogo = bootlogo
else:
Util.dprint("Couldnt find boot logo file in configuration directory.")
if not hdr["header_checksum_correct"] and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and mbc not in (0x203, 0x205))):
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()
@ -953,9 +977,9 @@ class FlashGBX_CLI():
print("")
if len(buffer) > 0x1000:
args = { "mode":4, "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "mbc":mbc }
args = { "mode":4, "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc }
else:
args = { "mode":4, "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "mbc":mbc }
args = { "mode":4, "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc }
self.CONN.TransferData(signal=self.PROGRESS.SetProgress, args=args)
buffer = None

View File

@ -922,7 +922,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
msgbox.exec()
else:
self.lblStatus4a.setText("Done.")
#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, 0x205)):
if "mapper_raw" in self.CONN.INFO and self.CONN.INFO["mapper_raw"] in (0x202, 0x203, 0x205):
msg = "The ROM backup is complete."
msgbox.setText(msg + msg_te)
@ -1306,6 +1305,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
else:
verify_write = False
fix_bootlogo = False
fix_header = False
if not just_erase and len(buffer) >= 0x1000:
if self.CONN.GetMode() == "DMG":
@ -1341,8 +1341,42 @@ class FlashGBX_GUI(QtWidgets.QWidget):
hdr = RomFileAGB(buffer).GetHeader()
if not hdr["logo_correct"] and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and mbc not in (0x203, 0x205))):
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 boot logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
if answer == QtWidgets.QMessageBox.Cancel: return
msg_text = "Warning: The ROM file you selected will not boot on actual hardware due to invalid boot logo data."
bootlogo = None
if self.CONN.GetMode() == "DMG":
if os.path.exists(Util.CONFIG_PATH + "/bootlogo_dmg.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_dmg.bin", "rb") as f:
bootlogo = bytearray(f.read(0x30))
elif self.CONN.GetMode() == "AGB":
if os.path.exists(Util.CONFIG_PATH + "/bootlogo_agb.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_agb.bin", "rb") as f:
bootlogo = bytearray(f.read(0x9C))
if bootlogo is not None:
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg_text)
button_1 = msgbox.addButton(" &Fix and Continue ", QtWidgets.QMessageBox.ActionRole)
button_2 = msgbox.addButton(" Continue &without fixing ", QtWidgets.QMessageBox.ActionRole)
button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole)
msgbox.setDefaultButton(button_1)
msgbox.setEscapeButton(button_cancel)
msgbox.exec()
if msgbox.clickedButton() == button_1:
fix_bootlogo = bootlogo
elif msgbox.clickedButton() == button_cancel:
return
elif msgbox.clickedButton() == button_2:
pass
else:
Util.dprint("Couldnt find boot logo file in configuration directory.")
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg_text)
msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
msgbox.setDefaultButton(QtWidgets.QMessageBox.Cancel)
msgbox.setEscapeButton(QtWidgets.QMessageBox.Cancel)
retval = msgbox.exec()
if retval == QtWidgets.QMessageBox.Cancel:
return
else:
pass
if not hdr["header_checksum_correct"] and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and mbc not in (0x203, 0x205))):
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)
@ -1372,9 +1406,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
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, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "mbc":mbc }
args = { "path":"", "buffer":buffer, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc }
else:
args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "mbc":mbc, "flash_offset":flash_offset }
args = { "path":path, "cart_type":cart_type, "override_voltage":override_voltage, "prefer_chip_erase":prefer_chip_erase, "fast_read_mode":True, "verify_write":verify_write, "fix_header":fix_header, "fix_bootlogo":fix_bootlogo, "mbc":mbc, "flash_offset":flash_offset }
self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
#self.CONN._FlashROM(args=args)
self.grpStatus.setTitle("Transfer Status")
@ -1828,7 +1862,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
bl_args = self.GetBLArgs(rom_size=Util.AGB_Header_ROM_Sizes_Map[self.cmbAGBHeaderROMSizeResult.currentIndex()], detected=detected)
if bl_args is False: return
args = { "path":path, "cart_type":cart_type, "override_voltage":False, "prefer_chip_erase":False, "fast_read_mode":True, "verify_write":verify_write, "fix_header":False, "mbc":mbc }
args = { "path":path, "cart_type":cart_type, "override_voltage":False, "prefer_chip_erase":False, "fast_read_mode":True, "verify_write":verify_write, "fix_header":False, "fix_bootlogo":False, "mbc":mbc }
args.update({"bl_save":True, "flash_offset":bl_args["bl_offset"], "flash_size":bl_args["bl_size"]})
if erase:
args["path"] = ""
@ -2096,6 +2130,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data['logo_correct'] and data['header_checksum_correct']:
self.lblDMGHeaderBootlogoResult.setText("OK")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
if not os.path.exists(Util.CONFIG_PATH + "/bootlogo_dmg.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_dmg.bin", "wb") as f:
f.write(data['raw'][0x104:0x134])
else:
self.lblDMGHeaderBootlogoResult.setText("Invalid")
self.lblDMGHeaderBootlogoResult.setStyleSheet("QLabel { color: red; }")
@ -2136,7 +2173,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
self.lblDMGHeaderROMChecksumResult.setText("")
self.lblDMGHeaderROMChecksumResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
elif data["mapper_raw"] == 0x205: # Datel Orbit V2
elif data["mapper_raw"] == 0x205: # Datel
self.lblDMGHeaderRtcResult.setText("")
self.lblDMGHeaderBootlogoResult.setText("")
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
@ -2153,14 +2190,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
self.lblDMGHeaderBootlogoResult.setStyleSheet(self.lblDMGRomTitleResult.styleSheet())
if "logo_sachen" in data:
data["logo_sachen"].putpalette([ 255, 255, 255, 128, 128, 128 ])
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
try:
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo_sachen"].convert("RGBA"))))
except:
pass
else:
if "logo" in data:
if data['logo_correct']:
data["logo"].putpalette([ 255, 255, 255, self.TEXT_COLOR[0], self.TEXT_COLOR[1], self.TEXT_COLOR[2] ])
else:
data["logo"].putpalette([ 255, 255, 255, 251, 0, 24 ])
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
try:
self.lblDMGHeaderBootlogoResult.setPixmap(QtGui.QPixmap.fromImage(ImageQt(data["logo"].convert("RGBA"))))
except:
pass
self.grpAGBCartridgeInfo.setVisible(False)
self.grpDMGCartridgeInfo.setVisible(True)
@ -2194,6 +2237,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
if data['logo_correct']:
self.lblAGBHeaderLogoValidResult.setText("OK")
self.lblAGBHeaderLogoValidResult.setStyleSheet(self.lblAGBRomTitleResult.styleSheet())
if not os.path.exists(Util.CONFIG_PATH + "/bootlogo_agb.bin"):
with open(Util.CONFIG_PATH + "/bootlogo_agb.bin", "wb") as f:
f.write(data['raw'][0x04:0xA0])
else:
self.lblAGBHeaderLogoValidResult.setText("Invalid")
self.lblAGBHeaderLogoValidResult.setStyleSheet("QLabel { color: red; }")
@ -2619,18 +2665,23 @@ class FlashGBX_GUI(QtWidgets.QWidget):
speed = 0
elapsed = 0
left = 0
estimated = 0
if "pos" in args: pos = args["pos"]
if "size" in args: size = args["size"]
if "speed" in args: speed = args["speed"]
if "time_elapsed" in args: elapsed = args["time_elapsed"]
if "time_left" in args: left = args["time_left"]
if "time_estimated" in args: estimated = args["time_estimated"]
if "action" in args:
if args["action"] == "ERASE":
self.lblStatus1aResult.setText("Pending...")
self.lblStatus2aResult.setText("Pending...")
self.lblStatus3aResult.setText(Util.formatProgressTime(elapsed))
self.lblStatus4a.setText("Erasing flash... This may take some time.")
if estimated != 0:
self.lblStatus4a.setText("Erasing... This may take up to {:d} seconds.".format(estimated))
else:
self.lblStatus4a.setText("Erasing... This may take some time.")
self.lblStatus4aResult.setText("")
self.btnCancel.setEnabled(args["abortable"])
self.SetProgressBars(min=0, max=size, value=pos)

View File

@ -304,7 +304,7 @@ class Flashcart:
def ChipErase(self):
self.Reset(full_reset=True)
time_start = time.time()
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"ERASE", "time_start":time_start, "abortable":False})
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"ERASE", "time_start":time_start, "time_estimated": self.CONFIG["chip_erase_timeout"], "abortable":False})
for i in range(0, len(self.CONFIG["commands"]["chip_erase"])):
addr = self.CONFIG["commands"]["chip_erase"][i][0]
data = self.CONFIG["commands"]["chip_erase"][i][1]
@ -331,10 +331,9 @@ class Flashcart:
data = self.CONFIG["commands"]["chip_erase_wait_for"][i][1]
timeout = self.CONFIG["chip_erase_timeout"]
while True:
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"ERASE", "time_start":time_start, "abortable":False})
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"ERASE", "time_start":time_start, "time_estimated": self.CONFIG["chip_erase_timeout"], "abortable":False})
if "wait_read_status_register" in self.CONFIG and self.CONFIG["wait_read_status_register"]:
for j in range(0, len(self.CONFIG["commands"]["read_status_register"])):
#sr_addr = self.CONFIG["commands"]["read_status_register"][j][0]
sr_data = self.CONFIG["commands"]["read_status_register"][j][1]
if we == "WR":

View File

@ -224,6 +224,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()
if len(info["rom_header"]) == 0: return
mbc_type = self.MapperToMBCType(info["rom_header"]["mapper_raw"])
if mbc_type is False: return

View File

@ -75,6 +75,8 @@ class DMG_MBC:
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)
elif mbc_id == 0x205: # 0x205:'Datel Orbit V2',
return DMG_Unlicensed_DatelOrbitV2(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 == 0x206: # 0x206:'Datel MegaMem',
# return DMG_Unlicensed_DatelMegaMem(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
@ -667,6 +669,17 @@ class DMG_GBD(DMG_MBC5):
def GetName(self):
return "MAC-GBD"
def SelectBankROM(self, index):
dprint(self.GetName(), "|", index)
commands = [
[ 0x2100, index & 0xFF ],
]
start_address = 0 if index == 0 else 0x4000
self.CartWrite(commands)
return (start_address, self.ROM_BANK_SIZE)
def GetMaxROMSize(self):
return 1*1024*1024
@ -830,6 +843,17 @@ class DMG_HuC1(DMG_MBC5):
def GetName(self):
return "HuC-1"
def SelectBankROM(self, index):
dprint(self.GetName(), "|", index)
commands = [
[ 0x2100, index & 0xFF ],
]
start_address = 0 if index == 0 else 0x4000
self.CartWrite(commands)
return (start_address, self.ROM_BANK_SIZE)
def EnableRAM(self, enable=True):
dprint(self.GetName(), "|", enable)
commands = [
@ -1412,6 +1436,31 @@ class DMG_Unlicensed_DatelOrbitV2(DMG_MBC):
def GetMaxROMSize(self):
return 128*1024
# class DMG_Unlicensed_DatelMegaMem(DMG_MBC):
# def GetName(self):
# return "Datel MegaMem"
# 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.ROM_BANK_SIZE = 0x4000
# self.RAM_BANK_SIZE = 0x4000
# def SelectBankROM(self, index):
# dprint(self.GetName(), "|", index)
# return (0, self.ROM_BANK_SIZE)
# def SelectBankRAM(self, index):
# dprint(self.GetName(), "|", index)
# self.CartWrite([[ 0x2000, index & 0x20 ]])
# return (0x4000, self.RAM_BANK_SIZE)
# def GetROMBanks(self, rom_size):
# return 1
# def GetMaxROMSize(self):
# return 16*1024
class AGB_GPIO:
CART_WRITE_FNCPTR = None
@ -1590,8 +1639,7 @@ class AGB_GPIO:
buffer.append(self.RTCReadStatus()) # 24h mode = 0x40, reset flag = 0x80
buffer.extend(struct.pack("<Q", ts))
dstr = ' '.join(format(x, '02X') for x in buffer)
dprint("[{:02X}] {:s}".format(int(len(dstr)/3) + 1, dstr))
dprint(' '.join(format(x, '02X') for x in buffer))
# Digits are BCD (Binary Coded Decimal)
#[07] 00 01 27 05 06 30 20
@ -1703,6 +1751,6 @@ class AGB_GPIO:
rtc_s = (rtc_buffer[6] & 0x0F) + ((rtc_buffer[6] >> 4) * 10)
if rtc_y == 0 and rtc_m == 0 and rtc_d == 0 and rtc_h == 0 and rtc_i == 0 and rtc_s == 0:
return "Not available"
raise Exception("Invalid RTC data")
else:
return "20{:02d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(rtc_y, rtc_m, rtc_d, rtc_h, rtc_i, rtc_s)

View File

@ -251,11 +251,13 @@ class RomFileDMG:
pass
# Unlicensed Datel Orbit V2 Mapper (older firmware)
elif hashlib.sha1(buffer[0x101:0x140]).digest() == bytearray([ 0xC1, 0xF4, 0x15, 0x4A, 0xEF, 0xCC, 0x5B, 0xE7, 0xEC, 0x83, 0xA8, 0xBB, 0x7B, 0xC0, 0x95, 0x83, 0x35, 0xEC, 0x9A, 0xF2 ]):
elif (
hashlib.sha1(buffer[0x101:0x140]).digest() == bytearray([ 0xC1, 0xF4, 0x15, 0x4A, 0xEF, 0xCC, 0x5B, 0xE7, 0xEC, 0x83, 0xA8, 0xBB, 0x7B, 0xC0, 0x95, 0x83, 0x35, 0xEC, 0x9A, 0xF2 ]) or \
hashlib.sha1(buffer[0x101:0x140]).digest() == bytearray([ 0xC9, 0x50, 0x65, 0xCB, 0x31, 0x96, 0x26, 0x6C, 0x32, 0x58, 0xAB, 0x07, 0xA1, 0x9E, 0x0C, 0x10, 0xA6, 0xED, 0xCC, 0x67 ])
):
data["rom_size_raw"] = 0x02
data["ram_size_raw"] = 0
data["mapper_raw"] = 0x205
data["cgb"] = 0x80
try:
game_title = bytearray(buffer[0x134:0x140]).decode("ascii", "replace").replace("\xFF", "")
game_title = re.sub(r"(\x00+)$", "", game_title)
@ -264,7 +266,21 @@ class RomFileDMG:
data["game_title"] = game_title
except:
pass
# Unlicensed Datel Mega Memory Card
# elif hashlib.sha1(buffer[0x104:0x150]).digest() == bytearray([ 0x05, 0xBE, 0x44, 0xBA, 0xE8, 0x0A, 0x59, 0x76, 0x34, 0x1B, 0x01, 0xDF, 0x92, 0xDD, 0xAB, 0x8C, 0x7A, 0x2A, 0x8F, 0x4E ]):
# data["rom_size_raw"] = 0x00
# data["ram_size_raw"] = 0x206
# data["mapper_raw"] = 0x206
# 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
# Unlicensed Sachen MMC1/MMC2
elif len(buffer) >= 0x280:
sachen_version = 0

View File

@ -7,9 +7,9 @@ from enum import Enum
# Common constants
APPNAME = "FlashGBX"
VERSION_PEP440 = "3.31"
VERSION_PEP440 = "3.32"
VERSION = "v{:s}".format(VERSION_PEP440)
VERSION_TIMESTAMP = 1687091551
VERSION_TIMESTAMP = 1690298775
DEBUG = False
DEBUG_LOG = []
APP_PATH = ""
@ -19,8 +19,8 @@ AGB_Header_ROM_Sizes = [ "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2
AGB_Header_ROM_Sizes_Map = [ 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000 ]
AGB_Header_Save_Types = [ "None", "4K EEPROM (512 Bytes)", "64K EEPROM (8 KiB)", "256K SRAM/FRAM (32 KiB)", "512K FLASH (64 KiB)", "1M FLASH (128 KiB)", "8M DACS (1 MiB)", "Unlicensed 512K SRAM (64 KiB)", "Unlicensed 1M SRAM (128 KiB)", "Unlicensed Batteryless SRAM" ]
AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1048576, 65536, 131072, 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 ]
AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS", 0xBF5B:"Unlicensed SST SST49LF080A" }
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000, 0x20000 ]
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x0F:'MBC3+RTC+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 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:'MAC-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', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x0F, 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ] }
@ -29,7 +29,7 @@ DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0
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 KiB)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)" ]
DMG_Header_RAM_Sizes_Map = [ 0x00, 0x100, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204 ]
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_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000, 0x80000 ] # RAM size in bytes
DMG_Header_SGB = { 0x00:'No support', 0x03:'Supported' }
DMG_Header_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' }

View File

@ -3080,7 +3080,7 @@
"rg": "Japan"
},
"c5273c7680baf28bf8f67cd02656c21c698dec58": {
"gn": "Aero the Acro-Bat - Rascal Rival Revenge",
"gn": "Aero the Acro-Bat",
"ne": "(USA)",
"gc": "AAOE",
"rc": 3027943456,
@ -3102,7 +3102,7 @@
"rg": "Japan"
},
"b071b79502414d994dfdc504668bbc3aecfb6cb7": {
"gn": "Aero the Acro-Bat - Rascal Rival Revenge",
"gn": "Aero the Acro-Bat",
"ne": "(Europe)",
"gc": "AAOP",
"rc": 2817101502,
@ -21771,8 +21771,8 @@
"gc": "BGYJ",
"rc": 4094564777,
"rs": 16777216,
"st": 0,
"ss": 0,
"st": 1,
"ss": 512,
"rv": "Rev 1",
"lg": "Ja",
"rg": "Japan"
@ -23172,8 +23172,8 @@
"gc": "BJGE",
"rc": 1848475391,
"rs": 16777216,
"st": 0,
"ss": 0,
"st": 2,
"ss": 8192,
"lg": "En",
"rg": "Australia"
},
@ -31489,7 +31489,7 @@
"ss": 0,
"lg": "En",
"rg": "USA, Europe",
"3d": true
"3d": false
},
"dd992df47247919cbb252015b4ff093e3156d320": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Limited Edition",
@ -31501,7 +31501,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"54a6e77c02eae428b8b438035ba8215d621dc54f": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition",
@ -31513,7 +31513,7 @@
"ss": 0,
"lg": "En",
"rg": "USA, Europe",
"3d": true
"3d": false
},
"73f8adcdf1fb2766c13ab65c0470af2b23801a7a": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum",
@ -31525,7 +31525,7 @@
"ss": 0,
"lg": "Fr",
"rg": "France",
"3d": true
"3d": false
},
"e6976665b9e9979e270c95230fbbd08350f09f99": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Premium Edition",
@ -31537,7 +31537,7 @@
"ss": 0,
"lg": "En",
"rg": "USA, Europe",
"3d": true
"3d": false
},
"772d68fe78dc92febbb12b6c1172d26c68031706": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Edition Premium",
@ -31549,7 +31549,7 @@
"ss": 0,
"lg": "Fr",
"rg": "France",
"3d": true
"3d": false
},
"790103a28c22bfcfa52de31f9a1061b17708dc3c": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Special Edition",
@ -31574,7 +31574,7 @@
"ss": 0,
"lg": "En",
"rg": "USA, Europe",
"3d": true
"3d": false
},
"a9861107e3c0eadf7a2c6fbf970155cb059f869e": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale",
@ -31586,7 +31586,7 @@
"ss": 0,
"lg": "Fr",
"rg": "France",
"3d": true
"3d": false
},
"590318cf40cad4d866dd385b6b208a99c6eeec62": {
"gn": "Game Boy Advance Video - Cartoon Network Collection - Volume 1",
@ -31610,7 +31610,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"28c8afa6b5d0d24d2c540413e948d4c9c7c44e63": {
"gn": "Game Boy Advance Video - Disney Channel Collection - Volume 1",
@ -31622,7 +31622,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"bfe44e65a6ca22ed091c82b475999a4818f60b83": {
"gn": "Game Boy Advance Video - Dora the Explorer - Volume 1",
@ -31684,7 +31684,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"0c411aef0ac6be4e8e9ae9b9e8d259cae942b8b1": {
"gn": "Game Boy Advance Video - All Grown Up! - Volume 1",
@ -31732,7 +31732,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"1fc3d70d3f225dfdad2dfb70d8336368d4aa8982": {
"gn": "Game Boy Advance Video - Nicktoons - Volume 3",
@ -31744,7 +31744,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"49e8ad4da336076f9b0dc8c885e36f7192701fc9": {
"gn": "Game Boy Advance Video - Nicktoons Collection - Volume 1",
@ -31829,7 +31829,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"41b14883cc3e05653a7f039944b11a1223f4cf66": {
"gn": "Game Boy Advance Video - Shark Tale",
@ -31917,7 +31917,7 @@
"ss": 0,
"lg": "En",
"rg": "USA",
"3d": true
"3d": false
},
"99bf48542a03e1bceb2a9a82da7257874ec30670": {
"gn": "Game Boy Advance Video - SpongeBob SquarePants - Volume 1",
@ -31967,7 +31967,7 @@
"ss": 0,
"lg": "Fr",
"rg": "France",
"3d": true
"3d": false
},
"dc0c4e5a4c2cdcbbf86e2a16ca9e2060fea6e943": {
"gn": "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey",
@ -31991,7 +31991,7 @@
"ss": 0,
"lg": "Fr",
"rg": "France",
"3d": true
"3d": false
},
"10b3242f409611c32749f9b42f7c7ea951c5fc6d": {
"gn": "Card e-Reader",

View File

@ -1,31 +1,4 @@
{
"523920f7199a60b49357ba21c4abec6b9a3c9734": {
"gn": "2097 ROM Pack II",
"ne": "(USA) (Test Program)",
"gc": "",
"rc": 177911527,
"rs": 32768,
"lg": "En",
"rg": "USA"
},
"539cbc25e424e40a31a01160fcb47fa3487b1654": {
"gn": "2420 ROM Pack V",
"ne": "(USA) (Test Program)",
"gc": "",
"rc": 2539003264,
"rs": 32768,
"lg": "En",
"rg": "USA"
},
"8f5d0235df4ae4b8840b669c0dd979a177054eeb": {
"gn": "2440 ROM Pack",
"ne": "(USA) (Test Program)",
"gc": "",
"rc": 2056477162,
"rs": 32768,
"lg": "En",
"rg": "USA"
},
"b4abb29a67c3351eaf60be9d89320fc927937618": {
"gn": "Aladdin",
"ne": "(Europe) (SGB Enhanced)",
@ -10981,7 +10954,7 @@
},
"0129d7a3c58b6271e6d4cdab034c397e0e355aae": {
"gn": "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou",
"ne": "(Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b]",
"ne": "(Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)",
"gc": "DMG-B62J",
"rc": 665526663,
"rs": 262144,

View File

@ -1,15 +1,19 @@
{
"type":"AGB",
"names":[
"BX2006_TSOP_64BALL with GL256S"
"BX2006_TSOP_64BALL with GL256S",
"BGA64B-71-TV-DEEP with 256M29EML",
"FunnyPlaying MidnightTrace 32 MiB Flash Cart"
],
"flash_ids":[
[ 0x02, 0x00, 0x7D, 0x22 ],
[ 0x8A, 0x00, 0x7D, 0x22 ],
[ 0x02, 0x00, 0x7D, 0x22 ]
],
"voltage":3.3,
"flash_size":0x2000000,
"sector_size":0x20000,
"chip_erase_timeout":120,
"chip_erase_timeout":200,
"command_set":"AMD",
"commands":{
"reset":[

View File

@ -4,7 +4,6 @@
"Generic Flash Cartridge (0/90)"
],
"voltage":3.3,
"chip_erase_timeout":300,
"sector_size_from_cfi":true,
"command_set":"INTEL",
"commands":{
@ -23,7 +22,7 @@
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ]
],

View File

@ -6,7 +6,9 @@
"4050_4400_4000_4350_36L0R_V5 with M36L0R8060T",
"36L0R8-39VF512 with M36L0R8060T",
"4050_4400_4000_4350_36L0R_V5 with 4050L0YTQ2",
"4350Q2 with 4050V0YBQ1"
"4350Q2 with 4050V0YBQ1",
"M36XXX_T32_32D_16D with M36L0R8060T",
"2006-36-71_V2 with M36L0R8060B"
],
"flash_ids":[
[ 0x20, 0x00, 0x0D, 0x88 ],
@ -14,7 +16,9 @@
[ 0x20, 0x00, 0x0E, 0x88 ],
[ 0x20, 0x00, 0x0E, 0x88 ],
[ 0x8A, 0x00, 0x0E, 0x88 ],
[ 0x8A, 0x00, 0x1C, 0x88 ]
[ 0x8A, 0x00, 0x1C, 0x88 ],
[ 0x20, 0x00, 0x0E, 0x88 ],
[ 0x20, 0x00, 0x0D, 0x88 ]
],
"voltage":3.3,
"flash_size":0x2000000,

View File

@ -0,0 +1,47 @@
{
"type":"AGB",
"names":[
"M5M29-39VF512 with M5M29HD528"
],
"flash_ids":[
[ 0x1C, 0x00, 0xFA, 0x00 ]
],
"voltage":3.3,
"flash_size":0x2000000,
"sector_size":[
[0x4000, 8],
[0x20000, 255]
],
"command_set":"INTEL",
"commands":{
"reset":[
[ 0, 0x50 ],
[ 0, 0xFF ]
],
"read_identifier":[
[ 0, 0x90 ]
],
"sector_erase":[
[ "SA", 0x60 ],
[ "SA", 0xD0 ],
[ "SA", 0x20 ],
[ "SA", 0xD0 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ]
],
"single_write":[
[ 0, 0x70 ],
[ 0, 0x40 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ 0, 0x80, 0x80 ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

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

View File

@ -0,0 +1,58 @@
{
"type":"AGB",
"names":[
"DL9SEC GBA flashcart with TE28F128"
],
"flash_ids":[
[ 0x89, 0x00, 0x18, 0x00 ]
],
"voltage":3.3,
"flash_size":0x1000000,
"sector_size_from_cfi":true,
"command_set":"INTEL",
"commands":{
"reset":[
[ 0, 0x50 ],
[ 0, 0xFF ]
],
"read_identifier":[
[ 0, 0x90 ]
],
"sector_erase":[
[ "SA", 0x60 ],
[ "SA", 0xD0 ],
[ "SA", 0x20 ],
[ "SA", 0xD0 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ]
],
"buffer_write":[
[ "SA", 0xE8 ],
[ "SA", "BS" ],
[ "PA", "PD" ],
[ "SA", 0xD0 ],
[ "SA", 0xFF ]
],
"buffer_write_wait_for":[
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ]
],
"single_write":[
[ 0, 0x70 ],
[ 0, 0x10 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ 0, 0x80, 0x80 ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -0,0 +1,58 @@
{
"type":"AGB",
"names":[
"DL9SEC GBA flashcart with TE28F256"
],
"flash_ids":[
[ 0x89, 0x00, 0x1D, 0x00 ]
],
"voltage":3.3,
"flash_size":0x2000000,
"sector_size_from_cfi":true,
"command_set":"INTEL",
"commands":{
"reset":[
[ 0, 0x50 ],
[ 0, 0xFF ]
],
"read_identifier":[
[ 0, 0x90 ]
],
"sector_erase":[
[ "SA", 0x60 ],
[ "SA", 0xD0 ],
[ "SA", 0x20 ],
[ "SA", 0xD0 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ]
],
"buffer_write":[
[ "SA", 0xE8 ],
[ "SA", "BS" ],
[ "PA", "PD" ],
[ "SA", 0xD0 ],
[ "SA", 0xFF ]
],
"buffer_write_wait_for":[
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0x80, 0xFFFF ],
[ null, null, null ]
],
"single_write":[
[ 0, 0x70 ],
[ 0, 0x10 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ 0, 0x80, 0x80 ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -0,0 +1,59 @@
{
"type":"AGB",
"names":[
"3680x2 with TH50VSF3680"
],
"flash_ids":[
[ 0x98, 0x00, 0x93, 0x00 ]
],
"voltage":3.3,
"flash_size":0x800000,
"sector_size":[
[0x10000, 127],
[0x2000, 8],
[0x10000, 127],
[0x2000, 8]
],
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x90 ]
],
"read_cfi":[
[ 0xAA, 0x98 ]
],
"sector_erase":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ "SA", 0x30 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0xFFFF, 0xFFFF ]
],
"single_write":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -0,0 +1,88 @@
{
"type":"AGB",
"names":[
"insideGadgets 32 MiB + EEPROM"
],
"flash_ids":[],
"voltage":3.3,
"flash_size":0x1FFFF00,
"sector_size":0x20000,
"chip_erase_timeout":300,
"single_write_first_256_bytes":true,
"rtc":true,
"rumble":true,
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x90 ]
],
"read_cfi":[
[ 0xAA, 0x98 ]
],
"sector_erase":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ "SA", 0x30 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0xFFFF, 0xFFFF ]
],
"chip_erase":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0x10 ]
],
"chip_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ 0, 0xFFFF, 0xFFFF ]
],
"buffer_write":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ "SA", 0x25 ],
[ "SA", "BS" ],
[ "PA", "PD" ],
[ "SA", 0x29 ]
],
"buffer_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", "PD", 0xFFFF ]
],
"single_write":[
[ 0xAAA, 0xAA ],
[ 0x555, 0x55 ],
[ 0xAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -0,0 +1,59 @@
{
"type":"DMG",
"names":[
"SD007_BV5_V3 with 26LV160BTC"
],
"flash_ids":[
[ 0xC1, 0x00, 0x4A, 0x21 ]
],
"voltage":5,
"flash_size":0x200000,
"start_addr":0,
"first_bank":1,
"write_pin":"WR",
"sector_size":[
[0x4000, 1],
[0x2000, 2],
[0x8000, 1],
[0x10000, 31]
],
"command_set":"AMD",
"commands":{
"reset":[
[ 0, 0xF0 ]
],
"read_identifier":[
[ 0xAAA, 0xA9 ],
[ 0x555, 0x56 ],
[ 0xAAA, 0x90 ]
],
"sector_erase":[
[ 0xAAA, 0xA9 ],
[ 0x555, 0x56 ],
[ 0xAAA, 0x80 ],
[ 0xAAA, 0xA9 ],
[ 0x555, 0x56 ],
[ "SA", 0x30 ]
],
"sector_erase_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ "SA", 0xFF, 0xFF ]
],
"single_write":[
[ 0xAAA, 0xA9 ],
[ 0x555, 0x56 ],
[ 0xAAA, 0xA0 ],
[ "PA", "PD" ]
],
"single_write_wait_for":[
[ null, null, null ],
[ null, null, null ],
[ null, null, null ],
[ null, null, null ]
]
}
}

View File

@ -4,7 +4,8 @@
"BennVenn MBC3000 RTC cart"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xCB, 0xCB ]
[ 0xC2, 0xC2, 0xCB, 0xCB ],
[ 0xC2, 0xC2, 0x7E, 0x7E ]
],
"voltage":5,
"flash_size":0x800000,

View File

@ -4,7 +4,8 @@
"BennVenn MBC3000 RTC cart (ROM2)"
],
"flash_ids":[
[ 0xC2, 0xC2, 0xCB, 0xCB ]
[ 0xC2, 0xC2, 0xCB, 0xCB ],
[ 0xC2, 0xC2, 0x7E, 0x7E ]
],
"voltage":5,
"flash_size":0x400000,

View File

@ -1,10 +1,12 @@
{
"type":"DMG",
"names":[
"insideGadgets 4 MiB (S29JL032J)"
"insideGadgets 4 MiB (S29JL032J)",
"Gamebank-web DMG-29W-04 with M29W320ET"
],
"flash_ids":[
[ 0x01, 0x01, 0x56, 0x56 ]
[ 0x01, 0x01, 0x56, 0x56 ],
[ 0x20, 0x20, 0x56, 0x56 ]
],
"voltage":5,
"flash_size":0x400000,

View File

@ -1,12 +1,15 @@
{
"type":"DMG",
"names":[
"insideGadgets 8 MiB"
"insideGadgets 8 MiB",
"FunnyPlaying MidnightTrace 4 MiB Flash Cart"
],
"flash_ids":[
[ 0x01, 0x01, 0x7E, 0x7E ],
[ 0x01, 0x01, 0x7E, 0x7E ]
],
"voltage":3.3,
"voltage_variants":true,
"flash_size":0x800000,
"start_addr":0x4000,
"first_bank":0,

View File

@ -514,7 +514,10 @@ class GbxDevice:
else:
return struct.unpack(">H", self.ReadROM(address >> 1, length))[0]
else:
return self.ReadROM(address, length)
if agb_save_flash:
return self.ReadRAM(address, length, command=self.DEVICE_CMD["AGB_CART_READ_SRAM"])
else:
return self.ReadROM(address, length)
def _cart_write(self, address, value, flashcart=False, sram=False):
dprint("Writing to cartridge: 0x{:X} = 0x{:X} (args: {:s}, {:s})".format(address, value & 0xFF, str(flashcart), str(sram)))
@ -558,7 +561,7 @@ class GbxDevice:
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} ({:d} of {:d})".format(commands[i][0], commands[i][1], i+1, num))
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:
@ -767,23 +770,24 @@ class GbxDevice:
if (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"):
data["dacs_8m"] = True
data["rtc_string"] = "Not available"
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)
has_rtc = _agb_gpio.HasRTC()
data["has_rtc"] = has_rtc is True
if has_rtc is not True:
data["no_rtc_reason"] = has_rtc
data["has_rtc"] = False
else:
data["rtc_buffer"] = _agb_gpio.ReadRTC()
try:
data["rtc_string"] = _agb_gpio.GetRTCString()
except:
data["rtc_string"] = "Invalid data"
try:
data["rtc_string"] = _agb_gpio.GetRTCString()
except Exception as e:
dprint("RTC exception:", str(e.args[0]))
data["has_rtc"] = False
else:
data["has_rtc"] = False
data["no_rtc_reason"] = None
data["rtc_string"] = "Not available"
if data["ereader"] is True:
bank = 0
@ -812,7 +816,7 @@ class GbxDevice:
if self.MODE == "DMG": #and setPinsAsInputs:
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"])
return data
def DetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=True):
@ -1820,7 +1824,7 @@ class GbxDevice:
if d_swap is not None and d_swap != ( 0, 0 ):
for i in range(0, len(buffer)):
buffer[i] = bitswap(buffer[i], d_swap)
cfi_parsed = ParseCFI(buffer)
try:
if d_swap is not None:
@ -1853,7 +1857,7 @@ class GbxDevice:
for i in range(0, len(method['read_identifier'])):
self._cart_write(method['read_identifier'][i][0], method["read_identifier"][i][1], flashcart=True)
flash_id = self.ReadROM(0, 8)
if self.MODE == "DMG":
method_string = "[" + we.ljust(5) + "/{:4X}/{:2X}]".format(method['read_identifier'][0][0], method['read_identifier'][0][1])
else:
@ -2072,7 +2076,7 @@ class GbxDevice:
buffer_len = 0x4000
#self.INFO["dump_info"]["timestamp"] = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat()
self.INFO["dump_info"] = {}
self.INFO["dump_info"]["timestamp"] = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
self.INFO["dump_info"]["file_name"] = args["path"]
self.INFO["dump_info"]["file_size"] = args["rom_size"]
@ -2101,9 +2105,6 @@ 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()
@ -2111,6 +2112,9 @@ class GbxDevice:
rom_banks = _mbc.GetROMBanks(rom_size)
rom_bank_size = _mbc.GetROMBankSize()
size = _mbc.GetROMSize()
#if _mbc.GetName() == "Datel MegaMem":
# args["rom_size"] = self.INFO["dump_info"]["file_size"] = rom_size = size = _mbc.GetMaxROMSize()
elif self.MODE == "AGB":
self.INFO["dump_info"]["mapper_type"] = None
@ -2381,6 +2385,7 @@ class GbxDevice:
self.DEVICE.reset_input_buffer()
self.DEVICE.reset_output_buffer()
if "eeprom_data" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["eeprom_data"])
if "EEPROM" in temp_ver and len(buffer) == 0x2000000:
padding_byte = buffer[0x1FFFEFF]
dprint("Replacing unmapped ROM data of cartridge (32 MiB ROM + EEPROM save type) with the original padding byte of 0x{:02X}.".format(padding_byte))
@ -2534,6 +2539,8 @@ class GbxDevice:
return False
buffer_len = 0x1000
(agb_flash_chip, _) = ret
if agb_flash_chip == 0xBF5B: # Bootlegs
buffer_len = 0x800
elif args["save_type"] == 6: # DACS
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=True)
empty_data_byte = 0xFF
@ -2654,7 +2661,10 @@ class GbxDevice:
end_address = min(save_size, bank_size)
if save_size > bank_size:
if args["save_type"] == 5: # FLASH 1M
if args["save_type"] == 8 or agb_flash_chip == 0xBF5B: # Bootleg 1M
dprint("Switching to bootleg save bank {:d}".format(bank))
self._cart_write(0x1000000, bank)
elif args["save_type"] == 5: # FLASH 1M
dprint("Switching to FLASH bank {:d}".format(bank))
cmds = [
[ 0x5555, 0xAA ],
@ -2663,9 +2673,6 @@ class GbxDevice:
[ 0, bank ]
]
self._cart_write_flash(cmds)
elif args["save_type"] == 8: # SRAM 1M
dprint("Switching to SRAM bank {:d}".format(bank))
self._cart_write(0x1000000, bank)
else:
dprint("Unknown bank switching method")
time.sleep(0.05)
@ -2749,7 +2756,7 @@ class GbxDevice:
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
sector_address = int(pos / buffer_len)
sector_address = pos % 0x10000
if agb_flash_chip == 0x1F3D: # Atmel AT29LV512
self.WriteRAM(address=int(pos/128), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
else:
@ -2760,14 +2767,14 @@ class GbxDevice:
[ 0x5555, 0x80 ],
[ 0x5555, 0xAA ],
[ 0x2AAA, 0x55 ],
[ sector_address << 12, 0x30 ]
[ sector_address, 0x30 ]
]
self._cart_write_flash(cmds)
sr = 0
lives = 50
while True:
time.sleep(0.01)
sr = self._cart_read(sector_address << 12, agb_save_flash=True)
sr = self._cart_read(sector_address, agb_save_flash=True)
dprint("Data Check: 0x{:X} == 0xFFFF? {:s}".format(sr, str(sr == 0xFFFF)))
if sr == 0xFFFF: break
lives -= 1
@ -2775,12 +2782,12 @@ class GbxDevice:
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Accessing the save data flash chip failed. Please make sure you selected the correct save type. If you are using a reproduction cartridge, check if it really is equipped with a flash chip for save data, or if it uses SRAM for save data instead.", "abortable":False})
return False
if buffer[buffer_offset:buffer_offset+buffer_len] != bytearray([0xFF] * buffer_len):
if ("ereader" in self.INFO and self.INFO["ereader"] is True and sector_address == 15):
if ("ereader" in self.INFO and self.INFO["ereader"] is True and sector_address == 0xF000):
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+0xF80], command=command, max_length=0x80)
else:
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
elif self.MODE == "AGB" and args["save_type"] == 6: # DACS
sector_address = pos+0x1F00000
sector_address = pos + 0x1F00000
if sector_address in (0x1F00000, 0x1F10000, 0x1F20000, 0x1F30000, 0x1F40000, 0x1F50000, 0x1F60000, 0x1F70000, 0x1F80000, 0x1F90000, 0x1FA0000, 0x1FB0000, 0x1FC0000, 0x1FD0000, 0x1FE0000, 0x1FF0000, 0x1FF2000, 0x1FF4000, 0x1FF6000, 0x1FF8000, 0x1FFA000, 0x1FFC000):
dprint("DACS: Now at sector 0x{:X}".format(sector_address))
cmds = [
@ -2926,7 +2933,8 @@ class GbxDevice:
msg += "- 0x{:06X}: {:02X}{:02X}\n".format(i, data1, data2)
elif len(msg.split("\n")) == 11:
msg += "(more than 10 differences found)\n"
break
else:
pass
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The save data was written completely, but {:d} byte(s) ({:.2f}%) didnt pass the verification check.\n\n{:s}".format(count, (count / len(self.INFO["data"]) * 100), msg[:-1]), "abortable":False})
return False
else:
@ -2981,8 +2989,16 @@ class GbxDevice:
print("Note: The last 256 bytes of this 32 MiB ROM will not be written as this area is reserved by the EEPROM save type.")
data_import = data_import[:0x1FFFF00]
# Fix header
# Fix bootlogo and header
if "fix_bootlogo" in args and isinstance(args["fix_bootlogo"], bytearray):
dstr = ''.join(format(x, '02X') for x in args["fix_bootlogo"])
dprint("Replacing bootlogo data with", dstr)
if self.MODE == "DMG":
data_import[0x104:0x134] = args["fix_bootlogo"]
elif self.MODE == "AGB":
data_import[0x04:0xA0] = args["fix_bootlogo"]
if "fix_header" in args and args["fix_header"]:
dprint("Fixing header checksums")
if self.MODE == "DMG":
temp = RomFileDMG(data_import[0:0x200]).FixHeader()
elif self.MODE == "AGB":
@ -3386,7 +3402,10 @@ class GbxDevice:
if "bl_save" in args:
data_import = bytearray([0] * bl_sectors[0][0]) + data_import
else:
write_sectors = sector_offsets
write_sectors = []
for item in sector_offsets:
if item[0] < len(data_import):
write_sectors.append(item)
dprint("Sectors to update:", write_sectors)
# ↑↑↑ Read Sector Map
@ -3398,6 +3417,7 @@ class GbxDevice:
chip_erase = False
else:
chip_erase = True
dprint("Erasing the entire flash chip")
if flashcart.ChipErase() is False:
return False
elif flashcart.SupportsSectorErase() is False:

Binary file not shown.

View File

@ -114,6 +114,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- Ferrante Crafts cart 32 KB
- Ferrante Crafts cart 64 KB
- Ferrante Crafts cart 512 KB
- FunnyPlaying MidnightTrace 4 MiB Flash Cart
- Gamebank-web DMG-29W-04 with M29W320ET
- GB-CART32K-A with SST39SF020A
- GB Smart 32M
- HDR Game Boy Camera Flashcart
@ -137,6 +139,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- Development AGB Cartridge 64M Flash S, E201843
- Development AGB Cartridge 128M Flash S, E201850
- Development AGB Cartridge 256M Flash S, E201868
- DL9SEC GBA flashcart with TE28F128
- DL9SEC GBA flashcart with TE28F256
- Flash Advance Pro 256M
- Flash2Advance 128M (with 2× 28F640J3A120)
- Flash2Advance 256M (with 2× 28F128J3A150)
@ -144,6 +148,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- Flash2Advance Ultra 64M (with 2× 28F320C3B)
- Flash2Advance Ultra 256M (with 8× 3204C3B100)
- Flash Advance Card 64M (with 28F640J3A120)
- FunnyPlaying MidnightTrace 32 MiB Flash Cart
- insideGadgets 16 MiB, 64K EEPROM with Solar Sensor and RTC options
- insideGadgets 32 MiB, 1M FLASH with RTC option
- insideGadgets 32 MiB, 512K FLASH
@ -183,6 +188,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- SD007_BV5_DRV with S29GL032M90TFIR4
- SD007_BV5_V2 with HY29LV160TT
- SD007_BV5_V2 with MX29LV320BTC
- SD007_BV5_V3 with 26LV160BTC
- SD007_BV5_V3 with 29LV160BE-90PFTN
- SD007_BV5_V3 with HY29LV160BT-70
- SD007_BV5_V3 with AM29LV160MB
@ -220,9 +226,11 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- 0121 with 0121M0Y0BE
- 100BS6600_48BALL_V4 with 6600M0U0BE
- 100SOP with MSP55LV100S
- 2006-36-71_V2 with M36L0R8060B
- 2006_TSOP_64BALL_6106 with W29GL128SH9B
- 28F256L03B-DRV with 256L30B
- 29LV128DBT2C-90Q and ALTERA CPLD
- 3680x2 with TH50VSF3680
- 36L0R8-39VF512 with M36L0R8060B
- 36L0R8-39VF512 with M36L0R8060T
- 4000L0ZBQ0 DRV with 3000L0YBQ0
@ -267,6 +275,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
- F864-3 with M36L0R7050B
- GA-07 with unlabeled flash chip
- GE28F128W30 with 128W30B0
- M36XXX_T32_32D_16D with M36L0R806
- M5M29-39VF512 with M5M29HD528
- M5M29G130AN (no PCB text)
- M6MGJ927 (no PCB text)
- MSP54LV512 (no PCB text)
@ -308,7 +318,7 @@ Many different reproduction cartridges share their flash chip command set, so ev
The author would like to thank the following very kind people for their help, contributions or documentation (in alphabetical order):
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, Cristóbal, crizzlycruz, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, wickawack, Wkr, x7l7j8cc, xactoes, yosoo, Zeii, Zelante, Zoo, zvxr
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, bbsan, BennVenn, ccs21, ClassicOldSong, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, Jayro, Jenetrix, JFox, joyrider3774, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, olDirdey, orangeglo, paarongiroux, Paradoxical, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, Sithdown, skite2001, Smelly-Ghost, Stitch, Super Maker, t5b6_de, Tauwasser, Timville, twitnic, velipso, Veund, voltagex, Voultar, wickawack, Wkr, x7l7j8cc, xactoes, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr
## DISCLAIMER

View File

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