This commit is contained in:
Lesserkuma 2023-05-05 12:51:56 +02:00
parent e694b8927c
commit c94e76d8cb
18 changed files with 545 additions and 161 deletions

View File

@ -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 orangeglos [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 BennVenns 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

View File

@ -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

View File

@ -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"
@ -1538,9 +1540,14 @@ class FlashGBX_GUI(QtWidgets.QWidget):
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 cartridges 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 # Dont 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 files 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)

View File

@ -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 ")):
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"]
values = struct.unpack("=BBBHH12s44s384s18s8s23s16s", buffer_rom[0x1C000:0x1C200])
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))
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")
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")
except:
pass
return data
except:
print("ERROR: Couldnt 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):
keys = ["menu_index", "f_offset", "b_offset", "f_size", "b_size", "game_code", "title", "title_gfx", "timestamp", "kiosk_id", "padding", "comment"]
if menu_ver == 0: # final
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)
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,15 +621,62 @@ 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:
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 ==\n" \
"\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 Value: {cart_id:s}\n" \
"* Cartridge ID: {cart_id:s}\n" \
"* Raw Map Data: {raw_data:s}\n" \
.format(
game_code=di["gbmem_parsed"]["game_code"],
@ -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":
@ -726,7 +773,6 @@ def GenerateFileName(mode, header, settings):
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):

View File

@ -1,2 +1,6 @@
# -*- coding: utf-8 -*-
# FlashGBX
# Author: Lesserkuma (github.com/lesserkuma)
from . import FlashGBX
FlashGBX.main()

View File

@ -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)",

View File

@ -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",

View File

@ -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"]])
@ -718,6 +718,22 @@ class GbxDevice:
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
@ -768,6 +784,23 @@ class GbxDevice:
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
self.INFO = {**self.INFO, **data}
@ -808,6 +841,22 @@ 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:
@ -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:
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)
self._write(self.DEVICE_CMD["FLASH_PROGRAM"])
ret = self._write(data, wait=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":"Couldnt 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":"Couldnt 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,10 +2596,15 @@ class GbxDevice:
buffer = bytearray(f.read())
# Fill too small file
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"]):
self.INFO["action"] = self.ACTIONS[action]
@ -2572,9 +2634,9 @@ class GbxDevice:
start_address = 0
bank_size = 0x10000
if args["save_type"] == 6: # DACS
bank_size = 0xFC000
end_address = 0xFC000
else:
bank_size = min(save_size, 0x100000)
buffer_len = 0x2000
end_address = min(save_size, bank_size)
if save_size > bank_size:
@ -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):
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))
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 ],
[ # Erase Sector
[ 0, 0x50 ],
[ sector_address, 0x20 ],
[ sector_address, 0xD0 ],
[ sector_address, 0x70 ]
]
for c in cmds:
self._cart_write(c[0], c[1])
]
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("Status Register Check: 0x{:X} == 0x80? {:s}".format(sr, str(sr & 0xE0 == 0x80)))
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 cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
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}%) didnt 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 cartridges Flash ID ({:s}) doesnt match the cartridge type selection.".format(' '.join(format(x, '02X') for x in flash_id)))
else:
if flashcart.Unlock() is False: return False
@ -3234,6 +3335,10 @@ class GbxDevice:
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.

View File

@ -1 +1,2 @@
include LICENSE
recursive-include FlashGBX/res *

View File

@ -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
View File

@ -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

View File

@ -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",