mirror of
https://github.com/lesserkuma/FlashGBX.git
synced 2026-03-21 17:44:30 -05:00
3.28
This commit is contained in:
parent
e694b8927c
commit
c94e76d8cb
13
CHANGES.md
13
CHANGES.md
|
|
@ -1,4 +1,13 @@
|
|||
# Release notes
|
||||
### v3.28 (released 2023-05-05)
|
||||
- Improved support for the BennVenn MBC3000 RTC cart; can now write to Real Time Clock registers
|
||||
- Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums
|
||||
- Improved support for 8M FLASH DACS cartridges; can now backup and restore the boot sector
|
||||
- Nintendo Power GB-Memory Cartridge (DMG-MMSA-JPN): Added more information to the dump reports
|
||||
- Nintendo Power GB-Memory Cartridge (DMG-MMSA-JPN): When making a ROM backup, individual games will now also be extracted
|
||||
- Improved support for e-Reader, Card e-Reader and Card e-Reader+ cartridges; will now prevent overwriting calibration data accidentally
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
### v3.27 (released 2023-04-26)
|
||||
- Bundles GBxCart RW v1.4/v1.4a firmware version R42+L10 (improves flash cart compatibility) *(thanks wickawack)*
|
||||
- Added support for cartridges with MX29GL128EHT2I and ALTERA CPLD *(thanks Merkin)*
|
||||
|
|
@ -45,7 +54,7 @@
|
|||
### v3.22 (released 2023-02-09)
|
||||
- Added support for DRV with AM29LV160DB and ALTERA CPLD *(thanks ccs21)*
|
||||
- Added support for DIY cart with HY29F800 *(thanks Kaede)*
|
||||
- Added more information to the dump report with Nintendo Power GB-Memory Cartridges (DMG-MMSA-JPN)
|
||||
- Added more information to the dump report of Nintendo Power GB-Memory Cartridges (DMG-MMSA-JPN)
|
||||
- Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
|
|
@ -331,7 +340,7 @@
|
|||
- Previously preliminarily added mapper support including for MBC7 and GBA Video cartridges is now working (requires GBxCart RW firmware version L1+)
|
||||
- Added support for writing compilation ROMs to Nintendo Power GB-Memory Cartridges (DMG-MMSA-JPN); requires a .map file in the same directory as the ROM file; all this can be generated using orangeglo’s [GBNP ROM builder website](https://orangeglo.github.io/gbnp/index.html)
|
||||
- Confirmed support for GB-M968 with 29LV160DB *(thanks bbsan)*
|
||||
- Added support for ROM backup as well as save data backup and restore for 8M FLASH DACS cartridges; tested with “Hikaru no Go 3 – Joy Carry Cartridge” (AGB-GHTJ-JPN)
|
||||
- Added support for ROM backup as well as save data backup and restore for 8M FLASH DACS cartridges
|
||||
- Confirmed support for SD007_TSOP_29LV017D with L017D70VC *(thanks marv17)*
|
||||
- Added support for 100BS6600_48BALL_V4 with 6600M0U0BE (the “369IN1” cartridge) *(thanks to BennVenn’s research on Discord)*
|
||||
- Removed broken support for saving and restoring RTC registers of official TAMA5 cartridges inside the save file as it became clear that the year value was not correctly written; more research needed
|
||||
|
|
|
|||
|
|
@ -1057,7 +1057,7 @@ class FlashGBX_CLI():
|
|||
print("The cartridge save data will now be read{:s} and saved to the following file:\n{:s}".format(s_mbc, os.path.abspath(path)))
|
||||
elif args.action == "restore-save":
|
||||
if not args.overwrite:
|
||||
answer = input("Restoring save data to the cartridge will erase the previous save.\nDo you want to overwrite it? [y/N]: ").strip().lower()
|
||||
answer = input("Restoring save data to the cartridge will erase the existing save.\nDo you want to overwrite it? [y/N]: ").strip().lower()
|
||||
if answer != "y":
|
||||
print("Canceled.")
|
||||
return
|
||||
|
|
@ -1073,9 +1073,27 @@ class FlashGBX_CLI():
|
|||
print("The cartridge save data size will now be examined{:s}.\nNote: This is for debug use only.\n".format(s_mbc))
|
||||
|
||||
if self.CONN.GetMode() == "AGB":
|
||||
if args.action == "restore-save" or args.action == "erase-save":
|
||||
buffer = None
|
||||
if self.CONN.GetMode() == "AGB" and "ereader" in self.CONN.INFO and self.CONN.INFO["ereader"] is True:
|
||||
if self.CONN.GetFWBuildDate() == "": # Legacy Mode
|
||||
print("This cartridge is not supported in Legacy Mode.")
|
||||
return
|
||||
self.CONN.ReadInfo()
|
||||
if "ereader_calibration" in self.CONN.INFO:
|
||||
with open(path, "rb") as f: buffer = bytearray(f.read())
|
||||
if buffer[0xD000:0xF000] != self.CONN.INFO["ereader_calibration"]:
|
||||
if not args.overwrite:
|
||||
if args.action == "erase-save": args.action = "restore-save"
|
||||
print("Note: Keeping existing e-Reader calibration data.")
|
||||
buffer[0xD000:0xF000] = self.CONN.INFO["ereader_calibration"]
|
||||
else:
|
||||
print("Note: Overwriting existing e-Reader calibration data.")
|
||||
else:
|
||||
print("Note: No existing e-Reader calibration data found.")
|
||||
print("Using Save Type “{:s}”.".format(Util.AGB_Header_Save_Types[save_type]))
|
||||
elif self.CONN.GetMode() == "DMG":
|
||||
if rtc and header["mapper_raw"] in (0x10, 0x110, 0xFE): # RTC of MBC3, HuC-3
|
||||
if rtc and header["mapper_raw"] in (0x10, 0x110, 0xFE): # RTC of MBC3, MBC30, HuC-3
|
||||
print("Real Time Clock register values will also be written if applicable/possible.")
|
||||
|
||||
try:
|
||||
|
|
@ -1097,7 +1115,11 @@ class FlashGBX_CLI():
|
|||
self.CONN.TransferData(args={ 'mode':2, 'path':path, 'mbc':mbc, 'save_type':save_type, 'rtc':rtc }, signal=self.PROGRESS.SetProgress)
|
||||
elif args.action == "restore-save":
|
||||
verify_write = args.no_verify_write is False
|
||||
self.CONN.TransferData(args={ 'mode':3, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':False, 'rtc':rtc, 'verify_write':verify_write }, signal=self.PROGRESS.SetProgress)
|
||||
targs = { 'mode':3, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':False, 'rtc':rtc, 'verify_write':verify_write }
|
||||
if buffer is not None:
|
||||
targs["buffer"] = buffer
|
||||
targs["path"] = None
|
||||
self.CONN.TransferData(args=targs, signal=self.PROGRESS.SetProgress)
|
||||
elif args.action == "erase-save":
|
||||
self.CONN.TransferData(args={ 'mode':3, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':True, 'rtc':rtc }, signal=self.PROGRESS.SetProgress)
|
||||
elif args.action == "debug-test-save": # debug
|
||||
|
|
|
|||
|
|
@ -536,8 +536,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
msg_text = "A new version of {:s} has been released!\nVersion {:s} is now available.".format(APPNAME, latest_version)
|
||||
print(msg_text)
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} Update Check".format(APPNAME), text=msg_text)
|
||||
button_open = msgbox.addButton(" Open &release notes ", QtWidgets.QMessageBox.ActionRole)
|
||||
button_cancel = msgbox.addButton("&OK", QtWidgets.QMessageBox.RejectRole)
|
||||
button_open = msgbox.addButton(" &Open release notes ", QtWidgets.QMessageBox.ActionRole)
|
||||
button_cancel = msgbox.addButton("&Close", QtWidgets.QMessageBox.RejectRole)
|
||||
msgbox.setDefaultButton(button_open)
|
||||
msgbox.setEscapeButton(button_cancel)
|
||||
answer = msgbox.exec()
|
||||
|
|
@ -922,7 +922,8 @@ 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 ("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)
|
||||
msgbox.exec()
|
||||
|
|
@ -1183,7 +1184,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.btnConfig.setEnabled(False)
|
||||
self.lblStatus4a.setText("Preparing...")
|
||||
qt_app.processEvents()
|
||||
args = { "path":path, "mbc":mbc, "rom_size":rom_size, "agb_rom_size":rom_size, "fast_read_mode":True, "cart_type":cart_type }
|
||||
args = { "path":path, "mbc":mbc, "rom_size":rom_size, "agb_rom_size":rom_size, "fast_read_mode":True, "cart_type":cart_type, "settings":self.SETTINGS }
|
||||
self.CONN.BackupROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
|
||||
self.grpStatus.setTitle("Transfer Status")
|
||||
self.STATUS["time_start"] = time.time()
|
||||
|
|
@ -1339,7 +1340,7 @@ 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 logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
|
||||
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "Warning: The ROM file you selected will not boot on actual hardware due to invalid boot logo data.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel)
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
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"])
|
||||
|
|
@ -1374,6 +1375,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
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 }
|
||||
self.CONN.FlashROM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
|
||||
#self.CONN._FlashROM(args=args)
|
||||
self.grpStatus.setTitle("Transfer Status")
|
||||
buffer = None
|
||||
self.STATUS["time_start"] = time.time()
|
||||
|
|
@ -1501,7 +1503,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if save_type == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "No save type was selected.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
|
||||
#save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]
|
||||
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
setting_name = "LastDirSaveDataAGB"
|
||||
|
|
@ -1537,10 +1539,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This feature only works with newer GBxCart RW hardware revisions.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox.exec()
|
||||
return
|
||||
|
||||
if self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and "Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]:
|
||||
|
||||
if self.CONN.GetMode() == "AGB" and self.cmbAGBSaveTypeResult.currentIndex() < len(Util.AGB_Header_Save_Types) and \
|
||||
("Batteryless SRAM" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]) or \
|
||||
("8M DACS" in Util.AGB_Header_Save_Types[self.cmbAGBSaveTypeResult.currentIndex()]):
|
||||
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), "Stress test is not supported for this save type.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
if self.CONN.GetMode() == "AGB" and "ereader" in self.CONN.INFO and self.CONN.INFO["ereader"] is True:
|
||||
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), "Stress test is not supported for this cartridge.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge’s save chip will be tested for potential problems as follows:\n- Read the same data multiple times\n- Writing and reading different test patterns\n\nPlease ensure the cartridge pins are freshly cleaned and the save data is backed up before proceeding.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
else:
|
||||
|
|
@ -1554,6 +1561,46 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The size of this file is not supported.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
buffer = None
|
||||
if self.CONN.GetMode() == "AGB" and "ereader" in self.CONN.INFO and self.CONN.INFO["ereader"] is True:
|
||||
if self.CONN.GetFWBuildDate() == "": # Legacy Mode
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="This cartridge is not supported in Legacy Mode.", standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox.exec()
|
||||
return
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="")
|
||||
button_keep = msgbox.addButton(" &Keep existing calibration data ", QtWidgets.QMessageBox.ActionRole)
|
||||
self.CONN.ReadInfo()
|
||||
cart_name = "e-Reader"
|
||||
if self.CONN.INFO["db"] is not None:
|
||||
cart_name = self.CONN.INFO["db"]["gn"]
|
||||
if "ereader_calibration" in self.CONN.INFO:
|
||||
if erase:
|
||||
buffer = bytearray([0xFF] * 0x20000)
|
||||
msg_text = "This {:s} cartridge currently has calibration data in place. It is strongly recommended to keep the existing calibration data.\n\nErase everything but the calibration data? Or erase everything?".format(cart_name)
|
||||
button_overwrite = msgbox.addButton(" &Erase everything ", QtWidgets.QMessageBox.ActionRole)
|
||||
erase = False # Don’t just erase everything
|
||||
else:
|
||||
with open(path, "rb") as f: buffer = bytearray(f.read())
|
||||
msg_text = "This {:s} cartridge currently has calibration data in place that is different from this save file’s data. It is strongly recommended to keep the existing calibration data unless you actually need to restore it from a previous backup.\n\nWould you like to keep the existing calibration data, or overwrite it with data from the file you selected?".format(cart_name)
|
||||
button_overwrite = msgbox.addButton(" &Restore from save data ", QtWidgets.QMessageBox.ActionRole)
|
||||
button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole)
|
||||
msgbox.setText(msg_text)
|
||||
msgbox.setDefaultButton(button_keep)
|
||||
msgbox.setEscapeButton(button_cancel)
|
||||
|
||||
if buffer[0xD000:0xF000] != self.CONN.INFO["ereader_calibration"]:
|
||||
answer = msgbox.exec()
|
||||
if msgbox.clickedButton() == button_cancel:
|
||||
return
|
||||
elif msgbox.clickedButton() == button_keep:
|
||||
buffer[0xD000:0xF000] = self.CONN.INFO["ereader_calibration"]
|
||||
elif msgbox.clickedButton() == button_overwrite:
|
||||
pass
|
||||
else:
|
||||
msg_text = "Warning: This {:s} cartridge may currently have calibration data in place. Erasing or overwriting this data may render the “Scan Card” feature unusable. It is strongly recommended to create a backup of the original save data first and store it in a safe place. That way the calibration data can be restored later.\n\nDo you still want to continue?".format(cart_name)
|
||||
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), msg_text, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
|
||||
if answer == QtWidgets.QMessageBox.No: return
|
||||
|
||||
verify_write = self.SETTINGS.value("VerifyData", default="enabled")
|
||||
if verify_write and verify_write.lower() == "enabled":
|
||||
verify_write = True
|
||||
|
|
@ -1565,8 +1612,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if not test and self.CONN.INFO["has_rtc"]:
|
||||
if self.CONN.GetMode() == "DMG" and mbc in (0x10, 0x110) and not self.CONN.IsClkConnected():
|
||||
rtc = False
|
||||
elif (self.CONN.GetMode() == "DMG" and ((mbc == 0xFD and (filesize == save_size + 0x28 or erase)) or (mbc == 0xFE and (filesize == save_size + 0xC or erase)) or (self.CONN.IsClkConnected() and mbc in (0x10, 0x110) and filesize == save_size + 0x30 or erase))) or \
|
||||
(self.CONN.GetMode() == "AGB" and (filesize == save_size + 0x10 or erase)):
|
||||
elif erase or Util.save_size_includes_rtc(mode=self.CONN.GetMode(), mbc=mbc, save_size=filesize, save_type=save_type):
|
||||
msg = "A Real Time Clock cartridge was detected. Do you want the Real Time Clock register values to be also written?"
|
||||
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)
|
||||
|
|
@ -1791,8 +1837,13 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
#self.CONN._FlashROM(args=args)
|
||||
else:
|
||||
args = { "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase, "verify_write":verify_write }
|
||||
if buffer is not None:
|
||||
args["buffer"] = buffer
|
||||
args["path"] = None
|
||||
self.STATUS["args"] = args
|
||||
self.CONN.RestoreRAM(fncSetProgress=self.PROGRESS.SetProgress, args=args)
|
||||
#args = { "mode":3, "path":path, "mbc":mbc, "save_type":save_type, "rtc":rtc, "rtc_advance":rtc_advance, "erase":erase, "verify_write":verify_write }
|
||||
#self.CONN._BackupRestoreRAM(args=args)
|
||||
self.STATUS["time_start"] = time.time()
|
||||
self.STATUS["last_path"] = path
|
||||
self.STATUS["args"] = args
|
||||
|
|
@ -2183,8 +2234,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if data["db"] != None:
|
||||
if data["db"]['st'] < len(Util.AGB_Header_Save_Types):
|
||||
self.cmbAGBSaveTypeResult.setCurrentIndex(data["db"]['st'])
|
||||
if data["dacs_8m"] is True:
|
||||
self.cmbAGBSaveTypeResult.setCurrentIndex(6)
|
||||
|
||||
if data['empty'] == True: # defaults
|
||||
if data['empty_nocart'] == True:
|
||||
|
|
@ -2201,6 +2250,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if "3d_memory" in cart_types[1][i]:
|
||||
self.cmbAGBCartridgeTypeResult.setCurrentIndex(i)
|
||||
|
||||
if data["dacs_8m"] is True:
|
||||
self.cmbAGBSaveTypeResult.setCurrentIndex(6)
|
||||
|
||||
self.grpDMGCartridgeInfo.setVisible(False)
|
||||
self.grpAGBCartridgeInfo.setVisible(True)
|
||||
|
||||
|
|
@ -2397,11 +2449,50 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if msg_cart_type_s_detail == "": msg_cart_type_s_detail = msg_cart_type_s
|
||||
self.SetProgressBars(min=0, max=100, value=100)
|
||||
show_details = False
|
||||
|
||||
msg_gbmem = ""
|
||||
if "gbmem_parsed" in header:
|
||||
msg_gbmem = "<br><b>NP GB-Memory Cartridge Data:</b><br>"
|
||||
if isinstance(header["gbmem_parsed"], list):
|
||||
msg_gbmem += "" \
|
||||
"- Write Timestamp: {timestamp:s}<br>" \
|
||||
"- Write Kiosk ID: {kiosk_id:s}<br>" \
|
||||
"- Number of Games: {num_games:d}<br>" \
|
||||
"- Write Counter: {write_count:d}<br>" \
|
||||
"- Cartridge ID: {cart_id:s}<br>" \
|
||||
.format(
|
||||
timestamp=header["gbmem_parsed"][0]["timestamp"].replace("\0", ""),
|
||||
kiosk_id=header["gbmem_parsed"][0]["kiosk_id"].replace("\0", ""),
|
||||
cart_id=header["gbmem_parsed"][0]["cart_id"].replace("\0", ""),
|
||||
write_count=header["gbmem_parsed"][0]["write_count"],
|
||||
num_games=header["gbmem_parsed"][0]["num_games"],
|
||||
)
|
||||
for i in range(1, len(header["gbmem_parsed"])):
|
||||
if header["gbmem_parsed"][i]["menu_index"] == 0xFF: continue
|
||||
if i == 1:
|
||||
msg_gbmem += "- Menu ROM: {:s}<br>".format(header["gbmem_parsed"][i]["title"].replace("\0", ""))
|
||||
else:
|
||||
msg_gbmem += "- Game {:d}: {:s}<br>".format(i - 1, header["gbmem_parsed"][i]["title"].replace("\0", ""))
|
||||
else:
|
||||
msg_gbmem += "" \
|
||||
"- Write Timestamp: {timestamp:s}<br>" \
|
||||
"- Write Kiosk ID: {kiosk_id:s}<br>" \
|
||||
"- Write Counter: {write_count:d}<br>" \
|
||||
"- Cartridge ID: {cart_id:s}<br>" \
|
||||
"- Game Title: {game_title:s}<br>" \
|
||||
.format(
|
||||
timestamp=header["gbmem_parsed"]["timestamp"].replace("\0", ""),
|
||||
kiosk_id=header["gbmem_parsed"]["kiosk_id"].replace("\0", ""),
|
||||
cart_id=header["gbmem_parsed"]["cart_id"].replace("\0", ""),
|
||||
write_count=header["gbmem_parsed"]["write_count"],
|
||||
game_title=header["gbmem_parsed"]["title"],
|
||||
)
|
||||
|
||||
msg = "The following cartridge configuration was detected:<br><br>"
|
||||
if found_supported:
|
||||
dontShowAgain = str(self.SETTINGS.value("SkipAutodetectMessage", default="disabled")).lower() == "enabled"
|
||||
if not dontShowAgain or not canSkipMessage:
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}".format(msg, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_cart_type_s)
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_cart_type_s, msg_gbmem)
|
||||
temp = temp[:-4]
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=temp)
|
||||
msgbox.setTextFormat(QtCore.Qt.RichText)
|
||||
|
|
@ -2462,7 +2553,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.mnuConfig.actions()[4].setChecked(False)
|
||||
return self.DetectCartridge()
|
||||
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_fw)
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_gbmem, msg_fw)
|
||||
temp = temp[:-4]
|
||||
msgbox.setText(temp)
|
||||
msgbox.setTextFormat(QtCore.Qt.RichText)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
import datetime, struct, copy
|
||||
import datetime, struct, copy, zlib, hashlib
|
||||
from . import Util
|
||||
from .RomFileDMG import RomFileDMG
|
||||
|
||||
|
|
@ -25,30 +25,76 @@ class GBMemoryMap:
|
|||
|
||||
def ParseMapData(self, buffer_map, buffer_rom=None):
|
||||
data = {}
|
||||
num_games = 0
|
||||
try:
|
||||
keys = ["mapper_params", "f_size", "b_size", "game_code", "title", "timestamp", "kiosk_id", "write_count", "cart_id", "padding", "unknown"]
|
||||
values = struct.unpack("=24sHH12s44s18s8sH8s6sH", buffer_map)
|
||||
data = dict(zip(keys, values))
|
||||
for i in range(1, 8):
|
||||
if data["mapper_params"][i*3:i*3+3].hex() != "ffffff":
|
||||
num_games += 1
|
||||
|
||||
data["mapper_params"] = data["mapper_params"].hex().upper()
|
||||
data["cart_id"] = data["cart_id"].hex().upper()
|
||||
if buffer_rom is None: return data
|
||||
|
||||
rom_header = RomFileDMG(buffer_rom[:0x180]).GetHeader()
|
||||
if (rom_header["game_title"] in ("NP M-MENU MENU", "DMG MULTI MENU ")):
|
||||
keys = ["menu_index", "f_offset", "b_offset", "f_size", "b_size", "game_code", "title", "title_gfx", "timestamp", "kiosk_id", "padding", "comment"]
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s23s16s", buffer_rom[0x1C000:0x1C200])
|
||||
data_menu = dict(zip(keys, values))
|
||||
data["game_code"] = data_menu["game_code"].decode("ASCII", "ignore")
|
||||
data["title"] = data_menu["title"].decode("SHIFT-JIS", "ignore")
|
||||
data["timestamp"] = data_menu["timestamp"].decode("ASCII", "ignore")
|
||||
data["kiosk_id"] = data_menu["kiosk_id"].decode("ASCII", "ignore")
|
||||
data_list = []
|
||||
data["game_code"] = data["game_code"].decode("ASCII", "ignore")
|
||||
data["title"] = data["title"].decode("SHIFT-JIS", "ignore")
|
||||
data["timestamp"] = data["timestamp"].decode("ASCII", "ignore")
|
||||
data["kiosk_id"] = data["kiosk_id"].decode("ASCII", "ignore")
|
||||
data_list.append(data)
|
||||
data_list[0]["num_games"] = 0
|
||||
|
||||
if len(buffer_rom) < 0x100000: return data_list
|
||||
for i in range(0, 8):
|
||||
keys = ["menu_index", "f_offset", "b_offset", "f_size", "b_size", "game_code", "title", "title_gfx", "timestamp", "kiosk_id", "padding", "comment"]
|
||||
if rom_header["game_title"] == "NP M-MENU MENU":
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s23s16s", buffer_rom[0x1C000+(i*0x200):0x1C200+(i*0x200)])
|
||||
elif rom_header["game_title"] == "DMG MULTI MENU ":
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s1559s16s", buffer_rom[0x1C000+(i*0x800):0x1C800+(i*0x800)])
|
||||
data_menu = dict(zip(keys, values))
|
||||
temp = data_menu
|
||||
del(temp["title_gfx"])
|
||||
del(temp["padding"])
|
||||
temp["game_code"] = data_menu["game_code"].decode("ASCII", "ignore")
|
||||
temp["title"] = data_menu["title"].decode("SHIFT-JIS", "ignore")
|
||||
temp["timestamp"] = data_menu["timestamp"].decode("ASCII", "ignore")
|
||||
temp["kiosk_id"] = data_menu["kiosk_id"].decode("ASCII", "ignore")
|
||||
temp["rom_offset"] = data_menu["f_offset"] * (128 * 1024)
|
||||
temp["rom_size"] = data_menu["f_size"] * (128 * 1024)
|
||||
rom_header_game = RomFileDMG(buffer_rom[temp["rom_offset"]:temp["rom_offset"]+temp["rom_size"]]).GetHeader()
|
||||
temp["header"] = rom_header_game
|
||||
#if not ("logo_correct" in rom_header_game and rom_header_game["logo_correct"] is True):
|
||||
# continue
|
||||
if rom_header_game != {} and len(buffer_rom) >= (temp["rom_offset"] + temp["rom_size"]):
|
||||
temp["rom_size"] = min(temp["rom_size"], Util.DMG_Header_ROM_Sizes_Flasher_Map[rom_header_game["rom_size_raw"]])
|
||||
temp["crc32"] = zlib.crc32(buffer_rom[temp["rom_offset"]:temp["rom_offset"]+temp["rom_size"]]) & 0xFFFFFFFF
|
||||
temp["sha1"] = hashlib.sha1(buffer_rom[temp["rom_offset"]:temp["rom_offset"]+temp["rom_size"]]).hexdigest()
|
||||
temp["sha256"] = hashlib.sha256(buffer_rom[temp["rom_offset"]:temp["rom_offset"]+temp["rom_size"]]).hexdigest()
|
||||
temp["md5"] = hashlib.md5(buffer_rom[temp["rom_offset"]:temp["rom_offset"]+temp["rom_size"]]).hexdigest()
|
||||
if "db" in rom_header_game and rom_header_game["db"] is not None and rom_header_game["db"]["rc"] == temp["crc32"]:
|
||||
temp["db_entry"] = rom_header_game["db"]
|
||||
else:
|
||||
temp["header"]["db"] = None
|
||||
Util.dprint("GB-Memory Game {:d}: {:s}".format(i, str(temp)))
|
||||
data_list.append(temp)
|
||||
data_list[0]["num_games"] = num_games
|
||||
if len(data_list[0]["timestamp"]) == 0:
|
||||
data_list[0]["timestamp"] = data_list[1]["timestamp"]
|
||||
data_list[0]["kiosk_id"] = data_list[1]["kiosk_id"]
|
||||
return data_list
|
||||
else:
|
||||
data["game_code"] = data["game_code"].decode("ASCII", "ignore")
|
||||
data["title"] = data["title"].decode("SHIFT-JIS", "ignore")
|
||||
data["timestamp"] = data["timestamp"].decode("ASCII", "ignore")
|
||||
data["kiosk_id"] = data["kiosk_id"].decode("ASCII", "ignore")
|
||||
return data
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
print("ERROR: Couldn’t parse the GB-Memory Cartridge data.")
|
||||
return None
|
||||
|
||||
def ImportROM(self, data):
|
||||
info = {"map":{}, "menu":{}}
|
||||
|
|
@ -116,10 +162,13 @@ class GBMemoryMap:
|
|||
info["menu"]["metadata"]["b_size"] = 1024
|
||||
|
||||
game_code = info["rom_header"]["game_code"]
|
||||
info["menu"]["metadata"]["game_code"] = "{:s} -{:4s}- ".format("CGB" if info["rom_header"]["cgb"] == 0xC0 else "DMG", game_code).encode("ascii")
|
||||
info["menu"]["metadata"]["title"] = info["rom_header"]["game_title"].encode("ascii").ljust(0x2C)
|
||||
info["menu"]["metadata"]["timestamp"] = datetime.datetime.now().strftime('%d/%m/%Y%H:%M:%S').encode("ascii")
|
||||
info["menu"]["metadata"]["kiosk_id"] = "{:s}".format(Util.APPNAME).encode("ascii").ljust(8, b'\xFF')
|
||||
info["menu"]["metadata"]["game_code"] = "{:s} -{:4s}- ".format("CGB" if info["rom_header"]["cgb"] == 0xC0 else "DMG", game_code).encode("ASCII")
|
||||
info["menu"]["metadata"]["title"] = info["rom_header"]["game_title"].encode("SHIFT-JIS", "ignore").ljust(0x2C)
|
||||
if "db" in info["rom_header"] and info["rom_header"]["db"] is not None:
|
||||
info["menu"]["metadata"]["title"] = info["rom_header"]["db"]["gn"].encode("SHIFT-JIS", "ignore").ljust(0x2C)[:0x2C]
|
||||
|
||||
info["menu"]["metadata"]["timestamp"] = datetime.datetime.now().strftime('%d/%m/%Y%H:%M:%S').encode("ASCII")
|
||||
info["menu"]["metadata"]["kiosk_id"] = "{:s}".format(Util.APPNAME).encode("ASCII").ljust(8, b'\xFF')
|
||||
info["menu"]["raw"] = bytearray(0x56)
|
||||
data = info
|
||||
|
||||
|
|
@ -144,15 +193,25 @@ class GBMemoryMap:
|
|||
self.MAP_DATA[0:3] = struct.pack(">I", data["map"]["raw"])[:3]
|
||||
self.MAP_DATA[0x18:0x18+len(data["menu"]["raw"])] = data["menu"]["raw"]
|
||||
|
||||
elif info["rom_header"]["game_title"] == "NP M-MENU MENU":
|
||||
elif info["rom_header"]["game_title"] in ("NP M-MENU MENU", "DMG MULTI MENU "):
|
||||
if info["rom_header"]["game_title"] == "NP M-MENU MENU":
|
||||
menu_ver = 0
|
||||
elif info["rom_header"]["game_title"] == "DMG MULTI MENU ":
|
||||
menu_ver = 1
|
||||
|
||||
menu_items = []
|
||||
rom_offset = 0
|
||||
ram_offset = 0
|
||||
for i in range(0, 8):
|
||||
pos = 0x1C000 + (i * 0x200)
|
||||
menu_item = data[pos:pos+0x200]
|
||||
keys = ["menu_index", "f_offset", "b_offset", "f_size", "b_size", "game_code", "title", "title_gfx", "timestamp", "kiosk_id", "padding", "comment"]
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s23s16s", menu_item)
|
||||
if menu_ver == 0: # final
|
||||
pos = 0x1C000 + (i * 0x200)
|
||||
menu_item = data[pos:pos+0x200]
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s23s16s", menu_item)
|
||||
elif menu_ver == 1: # prototype
|
||||
pos = 0x1C000 + (i * 0x800)
|
||||
menu_item = data[pos:pos+0x800]
|
||||
values = struct.unpack("=BBBHH12s44s384s18s8s1559s16s", menu_item)
|
||||
info = dict(zip(keys, values))
|
||||
if info["menu_index"] == 0xFF: continue
|
||||
info["rom_data_offset"] = info["f_offset"] * (128 * 1024)
|
||||
|
|
@ -219,8 +278,8 @@ class GBMemoryMap:
|
|||
for i in range(0, len(menu_items)):
|
||||
pos = i * 3
|
||||
self.MAP_DATA[pos:pos+3] = struct.pack(">I", menu_items[i]["map"]["raw"])[:3]
|
||||
self.MAP_DATA[0x54:0x66] = struct.pack("=18s", datetime.datetime.now().strftime('%d/%m/%Y%H:%M:%S').encode("ascii"))
|
||||
self.MAP_DATA[0x66:0x6E] = struct.pack("=8s", "{:s}".format(Util.APPNAME).encode("ascii").ljust(8, b'\xFF'))
|
||||
self.MAP_DATA[0x54:0x66] = struct.pack("=18s", datetime.datetime.now().strftime('%d/%m/%Y%H:%M:%S').encode("ASCII"))
|
||||
self.MAP_DATA[0x66:0x6E] = struct.pack("=8s", "{:s}".format(Util.APPNAME).encode("ASCII").ljust(8, b'\xFF'))
|
||||
|
||||
def MapperToMBCType(self, mbc):
|
||||
if mbc == 0x00: # ROM only
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class DMG_MBC:
|
|||
return DMG_MBC1(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 in (0x05, 0x06): # 0x06:'MBC2+SRAM+BATTERY',
|
||||
return DMG_MBC2(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 in (0x10, 0x11, 0x12, 0x13, 0x110): # 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY',
|
||||
elif mbc_id in (0x0F, 0x10, 0x11, 0x12, 0x13, 0x110): # 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY',
|
||||
return DMG_MBC3(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 in (0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E): # 0x19:'MBC5', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY',
|
||||
return DMG_MBC5(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)
|
||||
|
|
@ -389,7 +389,7 @@ class DMG_MBC3(DMG_MBC):
|
|||
self.CLK_TOGGLE_FNCPTR(50)
|
||||
self.CartWrite([ [ 0x4000, 0x0C ] ])
|
||||
self.CLK_TOGGLE_FNCPTR(50)
|
||||
self.CartWrite([ [ 0xA000, 0x40 ] ])
|
||||
self.CartWrite([ [ 0xA000, 0x40 ] ], sram=True)
|
||||
time.sleep(0.1)
|
||||
|
||||
# Write to registers
|
||||
|
|
@ -398,7 +398,7 @@ class DMG_MBC3(DMG_MBC):
|
|||
self.CartWrite([ [ 0x4000, i ] ])
|
||||
self.CLK_TOGGLE_FNCPTR(50)
|
||||
data = struct.unpack("<I", buffer[(i-8)*4:(i-8)*4+4])[0] & 0xFF
|
||||
self.CartWrite([ [ 0xA000, data ] ])
|
||||
self.CartWrite([ [ 0xA000, data ] ], sram=True)
|
||||
time.sleep(0.1)
|
||||
|
||||
# Latch RTC
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
import hashlib, re, zlib, string, os, json
|
||||
import hashlib, re, zlib, string, os, json, copy
|
||||
from . import Util
|
||||
|
||||
class RomFileAGB:
|
||||
|
|
@ -102,7 +102,14 @@ class RomFileAGB:
|
|||
if (data["game_title"] == "NGC-HIKARU3" and data["game_code"] == "GHTJ" and data["header_checksum"] == 0xB3):
|
||||
data["dacs_8m"] = True
|
||||
|
||||
data["unchanged"] = data
|
||||
# e-Reader
|
||||
data["ereader"] = False
|
||||
if (data["game_title"] == "CARDE READER" and data["game_code"] == "PEAJ" and data["header_checksum"] == 0x9E) or \
|
||||
(data["game_title"] == "CARDEREADER+" and data["game_code"] == "PSAJ" and data["header_checksum"] == 0x85) or \
|
||||
(data["game_title"] == "CARDE READER" and data["game_code"] == "PSAE" and data["header_checksum"] == 0x95):
|
||||
data["ereader"] = True
|
||||
|
||||
data["unchanged"] = copy.copy(data)
|
||||
self.DATA = data
|
||||
data["db"] = self.GetDatabaseEntry()
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ class RomFileDMG:
|
|||
data["mapper_raw"] == 0x01 and data["game_title"] == "MORTALKOMBAT DUO" and data["header_checksum"] == 0xA7:
|
||||
data["mapper_raw"] += 0x100
|
||||
|
||||
# GB Memory
|
||||
# GB-Memory (DMG-MMSA-JPN)
|
||||
if data["mapper_raw"] == 0x19 and data["game_title"] == "NP M-MENU MENU" and data["header_checksum"] == 0xD3:
|
||||
data["rom_size_raw"] = 0x05
|
||||
data["ram_size_raw"] = 0x04
|
||||
|
|
|
|||
131
FlashGBX/Util.py
131
FlashGBX/Util.py
|
|
@ -7,9 +7,9 @@ from enum import Enum
|
|||
|
||||
# Common constants
|
||||
APPNAME = "FlashGBX"
|
||||
VERSION_PEP440 = "3.27"
|
||||
VERSION_PEP440 = "3.28"
|
||||
VERSION = "v{:s}".format(VERSION_PEP440)
|
||||
VERSION_TIMESTAMP = 1682502626
|
||||
VERSION_TIMESTAMP = 1683283512
|
||||
DEBUG = False
|
||||
DEBUG_LOG = []
|
||||
APP_PATH = ""
|
||||
|
|
@ -17,14 +17,14 @@ CONFIG_PATH = ""
|
|||
|
||||
AGB_Header_ROM_Sizes = [ "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB", "64 MiB", "128 MiB", "256 MiB" ]
|
||||
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 (1008 KiB)", "Unlicensed 512K SRAM (64 KiB)", "Unlicensed 1M SRAM (128 KiB)", "Unlicensed Batteryless SRAM" ]
|
||||
AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1032192, 65536, 131072, 0 ]
|
||||
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_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', 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":[ 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 ] }
|
||||
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 ] }
|
||||
DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB" ]
|
||||
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 ]
|
||||
|
|
@ -621,25 +621,72 @@ def GetDumpReport(di, device):
|
|||
raw_data += ''.join(format(x, '02X') for x in di["gbmem"][i*0x20:i*0x20+0x20]) + "\n "
|
||||
raw_data = raw_data[:-20]
|
||||
|
||||
if "gbmem_parsed" in di and len(di["gbmem_parsed"]) > 0:
|
||||
s += "" \
|
||||
"\n== GB-Memory Data ==\n" \
|
||||
"* Game Code: {game_code:s}\n" \
|
||||
"* Game Title: {title:s}\n" \
|
||||
"* Write Timestamp: {timestamp:s}\n" \
|
||||
"* Write Kiosk ID: {kiosk_id:s}\n" \
|
||||
"* Write Counter: {write_count:d}\n" \
|
||||
"* Cartridge Value: {cart_id:s}\n" \
|
||||
"* Raw Map Data: {raw_data:s}\n" \
|
||||
.format(
|
||||
game_code=di["gbmem_parsed"]["game_code"],
|
||||
title=di["gbmem_parsed"]["title"],
|
||||
timestamp=di["gbmem_parsed"]["timestamp"],
|
||||
kiosk_id=di["gbmem_parsed"]["kiosk_id"],
|
||||
cart_id=di["gbmem_parsed"]["cart_id"],
|
||||
write_count=di["gbmem_parsed"]["write_count"],
|
||||
raw_data=raw_data
|
||||
)
|
||||
if "gbmem_parsed" in di and di["gbmem_parsed"] is not None and len(di["gbmem_parsed"]) > 0:
|
||||
if (isinstance(di["gbmem_parsed"], list)):
|
||||
s += "" \
|
||||
"\n== GB-Memory Data (Multi Menu) ==\n" \
|
||||
"* Write Timestamp: {timestamp:s}\n" \
|
||||
"* Write Kiosk ID: {kiosk_id:s}\n" \
|
||||
"* Number of Games: {num_games:d}\n" \
|
||||
"* Write Counter: {write_count:d}\n" \
|
||||
"* Cartridge ID: {cart_id:s}\n" \
|
||||
"* Raw Map Data: {raw_data:s}\n" \
|
||||
.format(
|
||||
timestamp=di["gbmem_parsed"][0]["timestamp"],
|
||||
kiosk_id=di["gbmem_parsed"][0]["kiosk_id"],
|
||||
cart_id=di["gbmem_parsed"][0]["cart_id"],
|
||||
write_count=di["gbmem_parsed"][0]["write_count"],
|
||||
num_games=di["gbmem_parsed"][0]["num_games"],
|
||||
raw_data=raw_data
|
||||
)
|
||||
for i in range(1, len(di["gbmem_parsed"])):
|
||||
if di["gbmem_parsed"][i]["menu_index"] == 0xFF: continue
|
||||
if di["gbmem_parsed"][i]["header"]["logo_correct"] is False: continue
|
||||
if i == 1:
|
||||
s += "\n=== Menu ROM ===\n"
|
||||
else:
|
||||
s += "\n=== Game {:d} ===\n".format(i-1)
|
||||
s += "" \
|
||||
"* Game Code: {game_code:s}\n" \
|
||||
"* Game Title: {title:s}\n" \
|
||||
"* Write Timestamp: {timestamp:s}\n" \
|
||||
"* Write Kiosk ID: {kiosk_id:s}\n" \
|
||||
"* Location: {location:s}\n" \
|
||||
"* ROM Size: {size:s}\n" \
|
||||
.format(
|
||||
game_code=di["gbmem_parsed"][i]["game_code"],
|
||||
title=di["gbmem_parsed"][i]["title"],
|
||||
timestamp=di["gbmem_parsed"][i]["timestamp"],
|
||||
kiosk_id=di["gbmem_parsed"][i]["kiosk_id"],
|
||||
location="0x{:06X}–0x{:06X}".format(di["gbmem_parsed"][i]["rom_offset"], di["gbmem_parsed"][i]["rom_offset"]+di["gbmem_parsed"][i]["rom_size"]-1),
|
||||
size="{:s} ({:d} bytes)".format(formatFileSize(di["gbmem_parsed"][i]["rom_size"]), di["gbmem_parsed"][i]["rom_size"]),
|
||||
)
|
||||
if "crc32" in di["gbmem_parsed"][i]: s += "* CRC32: {:08x}\n".format(di["gbmem_parsed"][i]["crc32"])
|
||||
if "md5" in di["gbmem_parsed"][i]: s += "* MD5: {:s}\n".format(di["gbmem_parsed"][i]["md5"])
|
||||
if "sha1" in di["gbmem_parsed"][i]: s += "* SHA-1: {:s}\n".format(di["gbmem_parsed"][i]["sha1"])
|
||||
if "sha256" in di["gbmem_parsed"][i]: s += "* SHA-256: {:s}\n".format(di["gbmem_parsed"][i]["sha256"])
|
||||
|
||||
if "db_entry" in di["gbmem_parsed"][i] and "crc32" in di["gbmem_parsed"][i] and di["gbmem_parsed"][i]["db_entry"]["rc"] == di["gbmem_parsed"][i]["crc32"]:
|
||||
s += "* Database Match: {:s} {:s}\n".format(di["gbmem_parsed"][i]["db_entry"]["gn"], di["gbmem_parsed"][i]["db_entry"]["ne"])
|
||||
elif isinstance(di["gbmem_parsed"]["game_code"], str):
|
||||
s += "" \
|
||||
"\n== GB-Memory Data (Single Game) ==\n" \
|
||||
"* Game Code: {game_code:s}\n" \
|
||||
"* Game Title: {title:s}\n" \
|
||||
"* Write Timestamp: {timestamp:s}\n" \
|
||||
"* Write Kiosk ID: {kiosk_id:s}\n" \
|
||||
"* Write Counter: {write_count:d}\n" \
|
||||
"* Cartridge ID: {cart_id:s}\n" \
|
||||
"* Raw Map Data: {raw_data:s}\n" \
|
||||
.format(
|
||||
game_code=di["gbmem_parsed"]["game_code"],
|
||||
title=di["gbmem_parsed"]["title"],
|
||||
timestamp=di["gbmem_parsed"]["timestamp"],
|
||||
kiosk_id=di["gbmem_parsed"]["kiosk_id"],
|
||||
cart_id=di["gbmem_parsed"]["cart_id"],
|
||||
write_count=di["gbmem_parsed"]["write_count"],
|
||||
raw_data=raw_data
|
||||
)
|
||||
else:
|
||||
s += "" \
|
||||
"* GB-Memory Data: {:s}\n" \
|
||||
|
|
@ -708,10 +755,10 @@ def GetDumpReport(di, device):
|
|||
|
||||
return s
|
||||
|
||||
def GenerateFileName(mode, header, settings):
|
||||
fe_ni = False
|
||||
def GenerateFileName(mode, header, settings=None):
|
||||
fe_ni = True
|
||||
if settings is not None:
|
||||
fe_ni = settings.value(key="UseNoIntroFilenames", default="disabled").lower() == "enabled"
|
||||
fe_ni = settings.value(key="UseNoIntroFilenames", default="enabled").lower() == "enabled"
|
||||
|
||||
path = "ROM"
|
||||
if mode == "DMG":
|
||||
|
|
@ -724,9 +771,8 @@ def GenerateFileName(mode, header, settings):
|
|||
if settings is not None:
|
||||
path = settings.value(key="FileNameFormatDMG", default=path)
|
||||
fe_sgb = settings.value(key="AutoFileExtensionSGB", default="enabled")
|
||||
|
||||
|
||||
if len(header["game_code"]) > 0:
|
||||
path_title = header["game_title"]
|
||||
path_code = header["game_code"]
|
||||
path = "%TITLE%_%CODE%-%REVISION%"
|
||||
if settings is not None:
|
||||
|
|
@ -747,6 +793,12 @@ def GenerateFileName(mode, header, settings):
|
|||
path = path.replace("%CODE%", path_code.strip())
|
||||
path = path.replace("%REVISION%", path_revision)
|
||||
path = re.sub(r"[<>:\"/\\|\?\*]", "_", path)
|
||||
if get_mbc_name(header["mapper_raw"]) == "G-MMC1":
|
||||
if "gbmem_parsed" in header:
|
||||
if (isinstance(header["gbmem_parsed"], list)):
|
||||
path += "_{:s}".format(header["gbmem_parsed"][0]["cart_id"])
|
||||
else:
|
||||
path += "_{:s}".format(header["gbmem_parsed"]["cart_id"])
|
||||
path += ".{:s}".format(path_extension)
|
||||
elif mode == "AGB":
|
||||
path = "%TITLE%_%CODE%-%REVISION%"
|
||||
|
|
@ -766,8 +818,11 @@ def GenerateFileName(mode, header, settings):
|
|||
path += "." + path_extension
|
||||
|
||||
if fe_ni and header["db"] is not None:
|
||||
if mode == "DMG" and get_mbc_name(header["mapper_raw"]) == "G-MMC1":
|
||||
path = "{:s}.{:s}".format(header["db"]["gn"], path_extension)
|
||||
if mode == "DMG" and get_mbc_name(header["mapper_raw"]) == "G-MMC1" and "gbmem_parsed" in header:
|
||||
if (isinstance(header["gbmem_parsed"], list)):
|
||||
path = "NP GB-Memory Cartridge ({:s}).{:s}".format(header["gbmem_parsed"][0]["cart_id"], path_extension)
|
||||
else:
|
||||
path = "NP GB-Memory Cartridge ({:s}).{:s}".format(header["gbmem_parsed"]["cart_id"], path_extension)
|
||||
else:
|
||||
path = "{:s} {:s}.{:s}".format(header["db"]["gn"], header["db"]["ne"], path_extension)
|
||||
|
||||
|
|
@ -783,6 +838,18 @@ def get_mbc_name(id):
|
|||
if id in v: return k
|
||||
return "Unknown mapper type 0x{:02X}".format(id)
|
||||
|
||||
def save_size_includes_rtc(mode, mbc, save_size, save_type):
|
||||
rtc_size = 0x10
|
||||
if mode == "DMG":
|
||||
if get_mbc_name(mbc) in ("MBC3", "MBC30"): rtc_size = 0x30
|
||||
elif get_mbc_name(mbc) == "HuC-3": rtc_size = 0x0C
|
||||
elif get_mbc_name(mbc) == "TAMA5": rtc_size = 0x28
|
||||
return (((DMG_Header_RAM_Sizes_Flasher_Map[save_type] + rtc_size) % save_size) != rtc_size)
|
||||
elif mode == "AGB":
|
||||
rtc_size = 0x10
|
||||
return (((AGB_Header_Save_Sizes[save_type] + rtc_size) % save_size) != rtc_size)
|
||||
return False
|
||||
|
||||
def validate_datetime_format(string, format):
|
||||
try:
|
||||
if string != datetime.datetime.strptime(string, format).strftime(format):
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
from . import FlashGBX
|
||||
FlashGBX.main()
|
||||
|
|
|
|||
|
|
@ -31365,6 +31365,17 @@
|
|||
"lg": "Ja",
|
||||
"rg": "Japan"
|
||||
},
|
||||
"ddd5006e3ff17a60b9fd58e2a2d4fa13ba9f919d": {
|
||||
"gn": "Hikaru no Go 3 - Senyou Joy Carry Cartridge",
|
||||
"ne": "(Japan) (Rewritable Cartridge)",
|
||||
"gc": "GHTJ",
|
||||
"rc": 1543225974,
|
||||
"rs": 33554432,
|
||||
"st": 6,
|
||||
"ss": 1048576,
|
||||
"lg": "Ja",
|
||||
"rg": "Japan"
|
||||
},
|
||||
"ba92dc252389b96900bf850593e6175965ce7b7c": {
|
||||
"gn": "Pokemon - Aurora Ticket Distribution",
|
||||
"ne": "(USA) (Kiosk)",
|
||||
|
|
|
|||
|
|
@ -19082,7 +19082,7 @@
|
|||
"c26c112cdaf53efb9064e6cee3c474f2cc934782": {
|
||||
"gn": "F-15 Strike Eagle",
|
||||
"ne": "(USA, Europe)",
|
||||
"gc": "DMG-EGE",
|
||||
"gc": "DMG-EGX",
|
||||
"rc": 73264780,
|
||||
"rs": 131072,
|
||||
"lg": "En",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class GbxDevice:
|
|||
DEVICE_NAME = "GBxCart RW"
|
||||
DEVICE_MIN_FW = 1
|
||||
DEVICE_MAX_FW = 10
|
||||
DEVICE_LATEST_FW_TS = { 4:1682502626, 5:1681900614, 6:1681900614 }
|
||||
DEVICE_LATEST_FW_TS = { 4:1683283512, 5:1681900614, 6:1681900614 }
|
||||
|
||||
DEVICE_CMD = {
|
||||
"NULL":0x30,
|
||||
|
|
@ -146,7 +146,7 @@ class GbxDevice:
|
|||
self.BAUDRATE = 1700000
|
||||
dev = serial.Serial(ports[i], self.BAUDRATE, timeout=0.1)
|
||||
self.DEVICE = dev
|
||||
if not self.LoadFirmwareVersion() or self.FW["pcb_ver"] not in (5, 6, 101):
|
||||
if not self.LoadFirmwareVersion():
|
||||
dev.close()
|
||||
self.DEVICE = None
|
||||
self.BAUDRATE = 1000000
|
||||
|
|
@ -179,7 +179,7 @@ class GbxDevice:
|
|||
elif self.FW["pcb_ver"] in (5, 6, 101) and self.BAUDRATE > 1000000:
|
||||
self.MAX_BUFFER_LEN = 0x2000
|
||||
else:
|
||||
self.MAX_BUFFER_LEN = 0x800
|
||||
self.MAX_BUFFER_LEN = 0x1000
|
||||
|
||||
conn_msg.append([0, "For help please visit the insideGadgets Discord: https://gbxcart.com/discord"])
|
||||
|
||||
|
|
@ -273,9 +273,9 @@ class GbxDevice:
|
|||
|
||||
def IsSupportedMbc(self, mbc):
|
||||
if self.CanPowerCycleCart():
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205 )
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205 )
|
||||
else:
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x202, 0x205 )
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x202, 0x205 )
|
||||
|
||||
def IsSupported3dMemory(self):
|
||||
return True
|
||||
|
|
@ -514,7 +514,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} (flashcart={:s}, sram={:s})".format(address, value & 0xFF, str(flashcart), str(sram)))
|
||||
dprint("Writing to cartridge: 0x{:X} = 0x{:X} (args: {:s}, {:s})".format(address, value & 0xFF, str(flashcart), str(sram)))
|
||||
if self.MODE == "DMG":
|
||||
if flashcart:
|
||||
buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]])
|
||||
|
|
@ -714,10 +714,26 @@ class GbxDevice:
|
|||
_mbc.LatchRTC()
|
||||
data["rtc_buffer"] = _mbc.ReadRTC()
|
||||
if _mbc.GetName() == "TAMA5": self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
||||
try:
|
||||
data["rtc_string"] = _mbc.GetRTCString()
|
||||
except:
|
||||
data["rtc_string"] = "Invalid data"
|
||||
try:
|
||||
data["rtc_string"] = _mbc.GetRTCString()
|
||||
except:
|
||||
data["rtc_string"] = "Invalid data"
|
||||
if _mbc.GetName() == "G-MMC1":
|
||||
try:
|
||||
temp = bytearray([0] * 0x100000)
|
||||
temp[0:0x180] = header
|
||||
_mbc.SelectBankROM(7)
|
||||
if data["game_title"] == "NP M-MENU MENU":
|
||||
gbmem_menudata = self.ReadROM(0x4000, 0x1000)
|
||||
temp[0x1C000:0x1C000+0x1000] = gbmem_menudata
|
||||
elif data["game_title"] == "DMG MULTI MENU ":
|
||||
gbmem_menudata = self.ReadROM(0x4000, 0x4000)
|
||||
temp[0x1C000:0x1C000+0x4000] = gbmem_menudata
|
||||
_mbc.SelectBankROM(0)
|
||||
data["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=_mbc.ReadHiddenSector(), buffer_rom=temp)
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print("{:s}An error occured while trying to read the hidden sector data of the NP GB-Memory cartridge.{:s}".format(ANSI.RED, ANSI.RESET))
|
||||
|
||||
elif self.MODE == "AGB":
|
||||
# Unlock DACS carts on older firmware
|
||||
|
|
@ -747,8 +763,8 @@ class GbxDevice:
|
|||
currAddr *= 2
|
||||
data["rom_size"] = currAddr
|
||||
|
||||
if (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"):
|
||||
data["dacs_8m"] = True
|
||||
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)
|
||||
|
|
@ -767,6 +783,23 @@ class GbxDevice:
|
|||
data["has_rtc"] = False
|
||||
data["no_rtc_reason"] = None
|
||||
data["rtc_string"] = "Not available"
|
||||
|
||||
if data["ereader"] is True:
|
||||
bank = 0
|
||||
dprint("Switching to FLASH bank {:d}".format(bank))
|
||||
cmds = [
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0xB0 ],
|
||||
[ 0, bank ]
|
||||
]
|
||||
self._cart_write_flash(cmds)
|
||||
temp = self.ReadRAM(address=0xD000, length=0x2000, command=self.DEVICE_CMD["AGB_CART_READ_SRAM"])
|
||||
if temp[0:0x14] == b'Card-E Reader 2001\0\0':
|
||||
data["ereader_calibration"] = temp
|
||||
else:
|
||||
data["ereader_calibration"] = None
|
||||
del(data["ereader_calibration"])
|
||||
|
||||
dprint("Header data:", data)
|
||||
data["raw"] = header
|
||||
|
|
@ -808,7 +841,23 @@ class GbxDevice:
|
|||
checkSaveType = False
|
||||
elif self.MODE == "AGB" and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
||||
checkSaveType = False
|
||||
|
||||
elif self.MODE == "DMG" and "mbc" in cart_type and cart_type["mbc"] == 0x105: # G-MMC1
|
||||
header = self.ReadROM(0, 0x180)
|
||||
data = RomFileDMG(header).GetHeader()
|
||||
_mbc = DMG_MBC().GetInstance(args={"mbc":cart_type["mbc"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycle, clk_toggle_fncptr=self._clk_toggle)
|
||||
temp = bytearray([0] * 0x100000)
|
||||
temp[0:0x180] = header
|
||||
_mbc.SelectBankROM(7)
|
||||
if data["game_title"] == "NP M-MENU MENU":
|
||||
gbmem_menudata = self.ReadROM(0x4000, 0x1000)
|
||||
temp[0x1C000:0x1C000+0x1000] = gbmem_menudata
|
||||
elif data["game_title"] == "DMG MULTI MENU ":
|
||||
gbmem_menudata = self.ReadROM(0x4000, 0x4000)
|
||||
temp[0x1C000:0x1C000+0x4000] = gbmem_menudata
|
||||
_mbc.SelectBankROM(0)
|
||||
info["gbmem"] = _mbc.ReadHiddenSector()
|
||||
info["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=info["gbmem"], buffer_rom=temp)
|
||||
|
||||
# Save Type and Size
|
||||
if checkSaveType:
|
||||
if self.MODE == "DMG":
|
||||
|
|
@ -871,7 +920,7 @@ class GbxDevice:
|
|||
if save_type is None:
|
||||
checkBatterylessSRAM = True
|
||||
if info["dacs_8m"] is True:
|
||||
save_size = 1032192
|
||||
save_size = 1048576
|
||||
save_type = 6
|
||||
elif save_size > 256: # SRAM
|
||||
if save_size == 131072:
|
||||
|
|
@ -1145,17 +1194,17 @@ class GbxDevice:
|
|||
|
||||
# Read save state
|
||||
for i in range(0, 0x20):
|
||||
self._cart_write(0xA001, 0x06) # register select and address (high)
|
||||
self._cart_write(0xA000, i >> 4 | 0x01 << 1) # bit 0 = higher ram address, rest = command
|
||||
self._cart_write(0xA001, 0x07) # address (low)
|
||||
self._cart_write(0xA000, i & 0x0F) # bits 0-3 = lower ram address
|
||||
self._cart_write(0xA001, 0x0D) # data out (high)
|
||||
self._cart_write(0xA001, 0x06, sram=True) # register select and address (high)
|
||||
self._cart_write(0xA000, i >> 4 | 0x01 << 1, sram=True) # bit 0 = higher ram address, rest = command
|
||||
self._cart_write(0xA001, 0x07, sram=True) # address (low)
|
||||
self._cart_write(0xA000, i & 0x0F, sram=True) # bits 0-3 = lower ram address
|
||||
self._cart_write(0xA001, 0x0D, sram=True) # data out (high)
|
||||
value1, value2 = None, None
|
||||
while value1 is None or value1 != value2:
|
||||
value2 = value1
|
||||
value1 = self._cart_read(0xA000)
|
||||
data_h = value1
|
||||
self._cart_write(0xA001, 0x0C) # data out (low)
|
||||
self._cart_write(0xA001, 0x0C, sram=True) # data out (low)
|
||||
|
||||
value1, value2 = None, None
|
||||
while value1 is None or value1 != value2:
|
||||
|
|
@ -1172,11 +1221,10 @@ class GbxDevice:
|
|||
self.NO_PROG_UPDATE = npu
|
||||
return buffer
|
||||
|
||||
def WriteRAM(self, address, buffer, command=None):
|
||||
def WriteRAM(self, address, buffer, command=None, max_length=256):
|
||||
length = len(buffer)
|
||||
max_length = 256
|
||||
num = math.ceil(length / max_length)
|
||||
dprint("Write 0x{:X} bytes to cartridge RAM in {:d} iteration(s)".format(length, num))
|
||||
dprint("Writing 0x{:X} bytes to cartridge RAM in {:d} iteration(s)".format(length, num))
|
||||
if length > max_length: length = max_length
|
||||
|
||||
self._set_fw_variable("TRANSFER_SIZE", length)
|
||||
|
|
@ -1272,14 +1320,14 @@ class GbxDevice:
|
|||
self.NO_PROG_UPDATE = True
|
||||
|
||||
for i in range(0, 0x20):
|
||||
self._cart_write(0xA001, 0x05) # data in (high)
|
||||
self._cart_write(0xA000, buffer[i] >> 4)
|
||||
self._cart_write(0xA001, 0x04) # data in (low)
|
||||
self._cart_write(0xA000, buffer[i] & 0xF)
|
||||
self._cart_write(0xA001, 0x06) # register select and address (high)
|
||||
self._cart_write(0xA000, i >> 4 | 0x00 << 1) # bit 0 = higher ram address, rest = command
|
||||
self._cart_write(0xA001, 0x07) # address (low)
|
||||
self._cart_write(0xA000, i & 0x0F) # bits 0-3 = lower ram address
|
||||
self._cart_write(0xA001, 0x05, sram=True) # data in (high)
|
||||
self._cart_write(0xA000, buffer[i] >> 4, sram=True)
|
||||
self._cart_write(0xA001, 0x04, sram=True) # data in (low)
|
||||
self._cart_write(0xA000, buffer[i] & 0xF, sram=True)
|
||||
self._cart_write(0xA001, 0x06, sram=True) # register select and address (high)
|
||||
self._cart_write(0xA000, i >> 4 | 0x00 << 1, sram=True) # bit 0 = higher ram address, rest = command
|
||||
self._cart_write(0xA001, 0x07, sram=True) # address (low)
|
||||
self._cart_write(0xA000, i & 0x0F, sram=True) # bits 0-3 = lower ram address
|
||||
value1, value2 = None, None
|
||||
while value1 is None or value1 != value2:
|
||||
value2 = value1
|
||||
|
|
@ -1329,12 +1377,11 @@ class GbxDevice:
|
|||
self._set_fw_variable("ADDRESS", address >> 1)
|
||||
skip_init = True
|
||||
|
||||
if ret != 0x03:
|
||||
if rumble_stop and i == 1:
|
||||
dprint("Sending rumble stop command")
|
||||
self._cart_write(address=0xC6, value=0x00, flashcart=True)
|
||||
self._write(self.DEVICE_CMD["FLASH_PROGRAM"])
|
||||
if ret != 0x03: self._write(self.DEVICE_CMD["FLASH_PROGRAM"])
|
||||
ret = self._write(data, wait=True)
|
||||
if rumble_stop and i == 0:
|
||||
dprint("Sending rumble stop command")
|
||||
self._cart_write(address=0xC6, value=0x00, flashcart=True)
|
||||
|
||||
if ret not in (0x01, 0x03):
|
||||
dprint("Flash error at 0x{:X} in iteration {:d} of {:d} while trying to write a total of 0x{:X} bytes (response = {:s})".format(address, i, num, len(buffer), str(ret)))
|
||||
|
|
@ -1715,11 +1762,10 @@ class GbxDevice:
|
|||
{ 'read_cfi':[[0x4AAA, 0x98]], 'read_identifier':[[ 0x4AAA, 0xAA ], [ 0x4555, 0x55 ], [ 0x4AAA, 0x90 ]], 'reset':[[ 0x4000, 0xF0 ]] },
|
||||
{ 'read_cfi':[[0x7AAA, 0x98]], 'read_identifier':[[ 0x7AAA, 0xAA ], [ 0x7555, 0x55 ], [ 0x7AAA, 0x90 ]], 'reset':[[ 0x7000, 0xF0 ]] },
|
||||
{ 'read_cfi':[[0, 0x98]], 'read_identifier':[[ 0, 0x90 ]], 'reset':[[ 0, 0xFF ]] },
|
||||
#{ 'read_cfi':[[0xAA, 0x9898]], 'read_identifier':[[ 0xAAA, 0xAAAA ], [ 0x555, 0x5555 ], [ 0xAAA, 0x9090 ]], 'reset':[[ 0x0, 0xF0F0 ]] },
|
||||
#{ 'read_cfi':[[0x4000, 0x98]], 'read_identifier':[[ 0x4000, 0x90 ]], 'reset':[[ 0x4000, 0xFF ]] },
|
||||
]
|
||||
|
||||
if self.MODE == "DMG":
|
||||
del(flash_commands[4]) # 0xAAAA is in SRAM space on DMG
|
||||
if limitVoltage:
|
||||
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"])
|
||||
else:
|
||||
|
|
@ -1768,7 +1814,7 @@ class GbxDevice:
|
|||
d_swap = ( 0, 0 )
|
||||
elif magic == "RQZ": # D0D1 swapped
|
||||
d_swap = ( 0, 1 )
|
||||
if d_swap is not None:
|
||||
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)
|
||||
|
||||
|
|
@ -1794,7 +1840,7 @@ class GbxDevice:
|
|||
if self.MODE == "DMG": cfi["we"] = we
|
||||
cfi["method_id"] = flash_commands.index(method)
|
||||
|
||||
if d_swap is not None:
|
||||
if d_swap is not None and d_swap != ( 0, 0 ):
|
||||
for k in method.keys():
|
||||
for c in range(0, len(method[k])):
|
||||
if isinstance(method[k][c][1], int):
|
||||
|
|
@ -1820,11 +1866,11 @@ class GbxDevice:
|
|||
else:
|
||||
for j in range(0, 2):
|
||||
if j == 1:
|
||||
d_swap = ( 0, 1 )
|
||||
#d_swap = ( 0, 1 )
|
||||
for k in method.keys():
|
||||
for c in range(0, len(method[k])):
|
||||
if isinstance(method[k][c][1], int):
|
||||
method[k][c][1] = bitswap(method[k][c][1], d_swap)
|
||||
method[k][c][1] = bitswap(method[k][c][1], ( 0, 1 ))
|
||||
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)
|
||||
|
|
@ -2284,6 +2330,16 @@ class GbxDevice:
|
|||
self.INFO["dump_info"]["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=temp, buffer_rom=buffer)
|
||||
file.write(temp)
|
||||
file.close()
|
||||
gbmp = self.INFO["dump_info"]["gbmem_parsed"]
|
||||
if (isinstance(gbmp, list)) and len(args["path"]) > 2:
|
||||
for i in range(1, len(gbmp)):
|
||||
if gbmp[i]["header"] == {} or gbmp[i]["header"]["logo_correct"] is False: continue
|
||||
settings = None
|
||||
if "settings" in args: settings = args["settings"]
|
||||
gbmp_n = Util.GenerateFileName(mode="DMG", header=gbmp[i]["header"], settings=settings)
|
||||
gbmp_p = "{:s} - {:s}".format(os.path.splitext(args["path"])[0], gbmp_n)
|
||||
with open(gbmp_p, "wb") as f:
|
||||
f.write(buffer[gbmp[i]["rom_offset"]:gbmp[i]["rom_offset"]+gbmp[i]["rom_size"]])
|
||||
else:
|
||||
if "hidden_sector" in self.INFO: del(self.INFO["hidden_sector"])
|
||||
if "gbmem" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["gbmem"])
|
||||
|
|
@ -2474,8 +2530,8 @@ class GbxDevice:
|
|||
self._cart_write(0, 0x50)
|
||||
self._cart_write(0, 0xFF)
|
||||
if flash_id != bytearray([ 0xB0, 0x00, 0x9F, 0x00 ]):
|
||||
print("Warning: Unknown DACS flash chip ID ({:s})".format(' '.join(format(x, '02X') for x in flash_id)))
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Couldn’t detect the DACS flash chip.", "abortable":False})
|
||||
dprint("Warning: Unknown DACS flash chip ID ({:s})".format(' '.join(format(x, '02X') for x in flash_id)))
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Couldn’t detect the DACS flash chip.\nUnknown Flash ID: {:s}".format(' '.join(format(x, '02X') for x in flash_id)), "abortable":False})
|
||||
return False
|
||||
buffer_len = 0x2000
|
||||
|
||||
|
|
@ -2515,6 +2571,7 @@ class GbxDevice:
|
|||
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], self.DEVICE_CMD["AGB_CART_WRITE_SRAM"] ], # 1M SRAM
|
||||
]
|
||||
command = commands[args["save_type"]][args["mode"] - 2]
|
||||
|
||||
if args["rtc"] is True:
|
||||
extra_size = 0x10
|
||||
|
||||
|
|
@ -2539,9 +2596,14 @@ class GbxDevice:
|
|||
buffer = bytearray(f.read())
|
||||
|
||||
# Fill too small file
|
||||
if args["mode"] == 3:
|
||||
while len(buffer) < save_size:
|
||||
buffer += bytearray(buffer)
|
||||
if not (self.MODE == "AGB" and args["save_type"] == 6): # Not DACS
|
||||
if args["mode"] == 3:
|
||||
while len(buffer) < save_size:
|
||||
buffer += bytearray(buffer)
|
||||
|
||||
if self.MODE == "AGB" and "ereader" in self.INFO and self.INFO["ereader"] is True: # e-Reader
|
||||
buffer[0xFF80:0x10000] = bytearray([0] * 0x80)
|
||||
buffer[0x1FF80:0x20000] = bytearray([0] * 0x80)
|
||||
|
||||
# Main loop
|
||||
if not (args["mode"] == 2 and "verify_write" in args and args["verify_write"]):
|
||||
|
|
@ -2572,10 +2634,10 @@ class GbxDevice:
|
|||
start_address = 0
|
||||
bank_size = 0x10000
|
||||
if args["save_type"] == 6: # DACS
|
||||
bank_size = 0xFC000
|
||||
end_address = 0xFC000
|
||||
else:
|
||||
end_address = min(save_size, bank_size)
|
||||
bank_size = min(save_size, 0x100000)
|
||||
buffer_len = 0x2000
|
||||
|
||||
end_address = min(save_size, bank_size)
|
||||
|
||||
if save_size > bank_size:
|
||||
if args["save_type"] == 5: # FLASH 1M
|
||||
|
|
@ -2677,7 +2739,7 @@ class GbxDevice:
|
|||
if agb_flash_chip == 0x1F3D: # Atmel AT29LV512
|
||||
self.WriteRAM(address=int(pos/128), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
||||
else:
|
||||
dprint("sector_address:", sector_address)
|
||||
dprint("pos=0x{:X}, sector_address={:d}".format(pos, sector_address))
|
||||
cmds = [
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
|
|
@ -2699,32 +2761,66 @@ 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):
|
||||
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
||||
if ("ereader" in self.INFO and self.INFO["ereader"] is True and sector_address == 15):
|
||||
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
|
||||
if (pos+0x1F00000) in (0x1F00000, 0x1F10000, 0x1F20000, 0x1F30000, 0x1F40000, 0x1F50000, 0x1F60000, 0x1F70000, 0x1F80000, 0x1F90000, 0x1FA0000, 0x1FB0000, 0x1FC0000, 0x1FD0000, 0x1FE0000, 0x1FF0000, 0x1FF2000, 0x1FF4000, 0x1FF6000, 0x1FF8000, 0x1FFA000):
|
||||
sector_address = pos+0x1F00000
|
||||
dprint("sector_address:", hex(sector_address))
|
||||
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 = [
|
||||
[ sector_address, 0x60 ],
|
||||
[ sector_address, 0xD0 ],
|
||||
[ sector_address, 0x20 ],
|
||||
[ sector_address, 0xD0 ],
|
||||
[ sector_address, 0x70 ]
|
||||
[ # Erase Sector
|
||||
[ 0, 0x50 ],
|
||||
[ sector_address, 0x20 ],
|
||||
[ sector_address, 0xD0 ],
|
||||
]
|
||||
]
|
||||
for c in cmds:
|
||||
self._cart_write(c[0], c[1])
|
||||
sr = 0
|
||||
lives = 20
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
sr = struct.unpack("<H", self._cart_read(sector_address, 2))[0]
|
||||
dprint("Status Register Check: 0x{:X} == 0x80? {:s}".format(sr, str(sr & 0xE0 == 0x80)))
|
||||
if sr & 0xE0 == 0x80: break
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while writing to the cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
|
||||
return False
|
||||
self.WriteROM(address=0x1F00000+pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
||||
if sector_address == 0x1F00000: # First write
|
||||
temp = [
|
||||
[ # Unlock
|
||||
[ 0, 0x50 ],
|
||||
[ 0, 0x60 ],
|
||||
[ 0, 0xD0 ],
|
||||
]
|
||||
]
|
||||
temp.extend(cmds)
|
||||
cmds = temp
|
||||
elif sector_address == 0x1FFC000: # Boot sector
|
||||
temp = [
|
||||
[ # Unlock 1
|
||||
[ 0, 0x50 ],
|
||||
[ 0, 0x60 ],
|
||||
[ 0, 0xD0 ],
|
||||
],
|
||||
[ # Unlock 2
|
||||
[ 0, 0x50 ],
|
||||
[ sector_address, 0x60 ],
|
||||
[ sector_address, 0xDC ],
|
||||
]
|
||||
]
|
||||
temp.extend(cmds)
|
||||
cmds = temp
|
||||
|
||||
for cmd in cmds:
|
||||
dprint("Executing DACS commands:", cmd)
|
||||
self._cart_write_flash(commands=cmd, flashcart=True)
|
||||
sr = 0
|
||||
lives = 20
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
sr = struct.unpack("<H", self._cart_read(sector_address, 2))[0]
|
||||
dprint("DACS: Status Register Check: 0x{:X} == 0x80? {:s}".format(sr, str(sr & 0xE0 == 0x80)))
|
||||
if sr & 0xE0 == 0x80: break
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while writing to the DACS cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
|
||||
return False
|
||||
if sector_address < 0x1FFE000:
|
||||
dprint("DACS: Writing to area 0x{:X}–0x{:X}".format(0x1F00000+pos, 0x1F00000+pos+buffer_len-1))
|
||||
self.WriteROM(address=0x1F00000+pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
||||
else:
|
||||
dprint("DACS: Skipping read-only area 0x{:X}–0x{:X}".format(0x1F00000+pos, 0x1F00000+pos+buffer_len-1))
|
||||
else:
|
||||
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
||||
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_offset+buffer_len})
|
||||
|
|
@ -2786,6 +2882,8 @@ class GbxDevice:
|
|||
verify_args = copy.copy(args)
|
||||
start_address = 0
|
||||
end_address = buffer_offset
|
||||
if self.MODE == "AGB" and args["save_type"] == 6: # DACS
|
||||
end_address -= 0x2000
|
||||
|
||||
path = args["path"] # backup path
|
||||
verify_args.update({"mode":2, "verify_write":buffer, "path":None})
|
||||
|
|
@ -2801,18 +2899,20 @@ class GbxDevice:
|
|||
args["path"] = path # restore path
|
||||
if self.CANCEL is True:
|
||||
pass
|
||||
elif (self.INFO["data"] != buffer[:buffer_offset]):
|
||||
elif (self.INFO["data"][:end_address] != buffer[:end_address]):
|
||||
msg = ""
|
||||
count = 0
|
||||
for i in range(0, len(self.INFO["data"])):
|
||||
if i >= len(buffer): break
|
||||
data1 = self.INFO["data"][i]
|
||||
data2 = buffer[:buffer_offset][i]
|
||||
data2 = buffer[:end_address][i]
|
||||
if data1 != data2:
|
||||
count += 1
|
||||
if len(msg.split("\n")) <= 10:
|
||||
msg += "- 0x{:06X}: {:02X}≠{:02X}\n".format(i, data1, data2)
|
||||
elif len(msg.split("\n")) == 11:
|
||||
msg += "(more than 10 differences found)\n"
|
||||
break
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The save data was written completely, but {:d} byte(s) ({:.2f}%) didn’t pass the verification check.\n\n{:s}".format(count, (count / len(self.INFO["data"]) * 100), msg[:-1]), "abortable":False})
|
||||
return False
|
||||
else:
|
||||
|
|
@ -3024,8 +3124,9 @@ class GbxDevice:
|
|||
try:
|
||||
gbmem = GBMemoryMap(rom=temp, oldmap=_mbc.ReadHiddenSector())
|
||||
args["buffer_map"] = gbmem.GetMapData()
|
||||
except Exception as e:
|
||||
print("{:s}An error occured while trying to generate the hidden sector data for the NP GB-Memory cartridge.{:s}\n{:s}".format(ANSI.RED, ANSI.RESET, str(e)))
|
||||
except Exception:
|
||||
print(traceback.format_exc())
|
||||
print("{:s}An error occured while trying to generate the hidden sector data for the NP GB-Memory cartridge.{:s}".format(ANSI.RED, ANSI.RESET))
|
||||
args["buffer_map"] = False
|
||||
|
||||
if args["buffer_map"] is False:
|
||||
|
|
@ -3175,7 +3276,7 @@ class GbxDevice:
|
|||
# ↓↓↓ Read Flash ID
|
||||
if "flash_ids" in cart_type:
|
||||
(verified, flash_id) = flashcart.VerifyFlashID()
|
||||
if not verified:
|
||||
if not verified and not command_set_type == "BLAZE_XPLODER":
|
||||
print("Note: This cartridge’s Flash ID ({:s}) doesn’t match the cartridge type selection.".format(' '.join(format(x, '02X') for x in flash_id)))
|
||||
else:
|
||||
if flashcart.Unlock() is False: return False
|
||||
|
|
@ -3233,6 +3334,10 @@ class GbxDevice:
|
|||
if x[:-1] not in write_sectors2:
|
||||
write_sectors.append(x)
|
||||
dprint("Forcing sector:", x)
|
||||
|
||||
if len(write_sectors) == 0:
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"No flash sectors were found that would need to be updated for delta flashing.", "abortable":False})
|
||||
return False
|
||||
|
||||
elif "flash_sectors" in args and len(args["flash_sectors"]) > 0:
|
||||
write_sectors = args["flash_sectors"]
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1,2 @@
|
|||
include LICENSE
|
||||
recursive-include FlashGBX/res *
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ These work for installing fresh and upgrading from an older version.
|
|||
|
||||
1. Download and install [Python](https://www.python.org/downloads/) (version 3.7 or newer)
|
||||
2. Open a Terminal or Command Prompt window
|
||||
3. Install FlashGBX with this command:<br>`pip3 install "FlashGBX[qt5]"`
|
||||
* If installation fails, use this command instead:<br>`pip3 install "FlashGBX[qt6]"`
|
||||
3. Install FlashGBX with this command:<br>`pip3 install "FlashGBX[qt6]"`
|
||||
* If installation fails, use this command instead:<br>`pip3 install "FlashGBX[qt5]"`
|
||||
* If installation still fails, you can install the minimal version (command line interface) with this command:<br>`pip3 install FlashGBX`
|
||||
|
||||
* Pre-made Linux packages and instructions for select distributions are available [here](https://github.com/JJ-Fox/FlashGBX-Linux-builds/releases/latest).
|
||||
|
|
|
|||
4
run.py
4
run.py
|
|
@ -1,3 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
# Note: This file runs FlashGBX in portable mode.
|
||||
|
||||
from FlashGBX import FlashGBX
|
||||
|
|
|
|||
6
setup.py
6
setup.py
|
|
@ -1,10 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="FlashGBX",
|
||||
version="3.27",
|
||||
version="3.28",
|
||||
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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user