mirror of
https://github.com/lesserkuma/FlashGBX.git
synced 2026-08-24 09:45:18 -05:00
3.10
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
# Release notes
|
||||
### v3.10 (released 2022-05-17)
|
||||
- Added support for the Datel Orbit V2 mapper (Action Replay and GameShark) *(thanks Jenetrix)*
|
||||
- Fixed verification with AA1030_TSOP88BALL with M36W0R603 *(thanks DevDavisNunez)*
|
||||
- Added support for SD007_TSOP_48BALL_V9 with 32M29EWB *(thanks marv17)*
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
### v3.9 (released 2022-04-29)
|
||||
- Added support for Ferrante Crafts cart 64 KB *(thanks FerrantePescara)*
|
||||
- Added support for Ferrante Crafts cart 512 KB *(thanks FerrantePescara)*
|
||||
|
||||
@@ -107,6 +107,7 @@ def main(portableMode=False):
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=ArgParseCustomFormatter, epilog=examples)
|
||||
try:
|
||||
# pylint: disable=protected-access
|
||||
parser._action_groups[1].title = "general arguments"
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -543,6 +543,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
self.SETTINGS.setValue("SkipModeChangeWarning", "disabled")
|
||||
self.SETTINGS.setValue("SkipAutodetectMessage", "disabled")
|
||||
self.SETTINGS.setValue("SkipFinishMessage", "disabled")
|
||||
self.SETTINGS.setValue("SkipCameraSavePopup", "disabled")
|
||||
|
||||
def OpenConfigDir(self):
|
||||
path = 'file://{0:s}'.format(self.CONFIG_PATH)
|
||||
@@ -802,7 +803,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
dontShowAgain = cb.isChecked()
|
||||
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)):
|
||||
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)):
|
||||
msg = "The ROM backup is complete."
|
||||
msg += "\n\nCRC32: {:08x}\nSHA-1: {:s}".format(self.CONN.INFO["file_crc32"], self.CONN.INFO["file_sha1"])
|
||||
msgbox.setText(msg)
|
||||
@@ -861,17 +862,27 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
elif self.CONN.INFO["last_action"] == 2: # Backup RAM
|
||||
self.lblStatus4a.setText("Done!")
|
||||
self.CONN.INFO["last_action"] = 0
|
||||
if self.CONN.INFO["transferred"] == 131072: # 128 KB
|
||||
with open(self.CONN.INFO["last_path"], "rb") as file: temp = file.read()
|
||||
if temp[0x1FFB1:0x1FFB6] == b'Magic':
|
||||
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), "Game Boy Camera save data was detected.\nWould you like to load it with the GB Camera Viewer now?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes)
|
||||
if answer == QtWidgets.QMessageBox.Yes:
|
||||
self.CAMWIN = None
|
||||
self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=self.CONFIG_PATH)
|
||||
self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
|
||||
self.CAMWIN.setModal(True)
|
||||
self.CAMWIN.run()
|
||||
return
|
||||
|
||||
dontShowAgainCameraSavePopup = str(self.SETTINGS.value("SkipCameraSavePopup", default="disabled")).lower() == "enabled"
|
||||
if not dontShowAgainCameraSavePopup:
|
||||
if self.CONN.INFO["transferred"] == 131072: # 128 KB
|
||||
with open(self.CONN.INFO["last_path"], "rb") as file: temp = file.read()
|
||||
if temp[0x1FFB1:0x1FFB6] == b'Magic':
|
||||
cbCameraSavePopup = QtWidgets.QCheckBox("Don’t show this message again", checked=dontShowAgain)
|
||||
msgboxCameraPopup = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Question, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="Game Boy Camera save data was detected.\nWould you like to load it with the GB Camera Viewer now?")
|
||||
msgboxCameraPopup.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
||||
msgboxCameraPopup.setDefaultButton(QtWidgets.QMessageBox.Yes)
|
||||
msgboxCameraPopup.setCheckBox(cbCameraSavePopup)
|
||||
answer = msgboxCameraPopup.exec()
|
||||
dontShowAgainCameraSavePopup = cbCameraSavePopup.isChecked()
|
||||
if dontShowAgainCameraSavePopup: self.SETTINGS.setValue("SkipCameraSavePopup", "enabled")
|
||||
if answer == QtWidgets.QMessageBox.Yes:
|
||||
self.CAMWIN = None
|
||||
self.CAMWIN = PocketCameraWindow(self, icon=self.windowIcon(), file=self.CONN.INFO["last_path"], config_path=self.CONFIG_PATH)
|
||||
self.CAMWIN.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
|
||||
self.CAMWIN.setModal(True)
|
||||
self.CAMWIN.run()
|
||||
return
|
||||
|
||||
msgbox.setText("The save data backup is complete!")
|
||||
msgbox.setCheckBox(cb)
|
||||
@@ -1146,10 +1157,10 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
hdr = RomFileDMG(buffer).GetHeader()
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
hdr = RomFileAGB(buffer).GetHeader()
|
||||
if not hdr["logo_correct"] and mbc != 0x203:
|
||||
if not hdr["logo_correct"] 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)
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
if not hdr["header_checksum_correct"] and mbc != 0x203:
|
||||
if not hdr["header_checksum_correct"] and mbc not in (0x203, 0x205):
|
||||
msg_text = "Warning: The ROM file you selected will not boot on actual hardware due to an invalid header checksum (expected 0x{:02X} instead of 0x{:02X}).".format(hdr["header_checksum_calc"], hdr["header_checksum"])
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=msg_text)
|
||||
button_fix = msgbox.addButton(" &Fix and Continue ", QtWidgets.QMessageBox.ActionRole)
|
||||
@@ -1588,6 +1599,15 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
|
||||
self.lblHeaderROMChecksumResult.setText("")
|
||||
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
|
||||
elif data["mapper_raw"] == 0x205: # Datel Orbit V2
|
||||
self.lblHeaderRtcResult.setText("")
|
||||
self.lblHeaderRevisionResult.setText("")
|
||||
self.lblHeaderLogoValidResult.setText("")
|
||||
self.lblHeaderLogoValidResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
|
||||
self.lblHeaderChecksumResult.setText("")
|
||||
self.lblHeaderChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
|
||||
self.lblHeaderROMChecksumResult.setText("")
|
||||
self.lblHeaderROMChecksumResult.setStyleSheet(self.lblHeaderRevisionResult.styleSheet())
|
||||
elif data["mapper_raw"] == 0x204: # Sachen
|
||||
self.lblHeaderRtcResult.setText("")
|
||||
self.lblHeaderRevisionResult.setText("")
|
||||
@@ -1708,7 +1728,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
||||
self.grpStatus.setTitle("Transfer Status")
|
||||
self.FinishOperation()
|
||||
|
||||
if not data['logo_correct'] and data['empty'] == False and resetStatus and not (self.CONN.GetMode() == "DMG" and data["mapper_raw"] in (0x203, 0x204)):
|
||||
if not data['logo_correct'] and data['empty'] == False and resetStatus and not (self.CONN.GetMode() == "DMG" and data["mapper_raw"] in (0x203, 0x204, 0x205)):
|
||||
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The Nintendo Logo check failed which usually means that the cartridge can’t be read correctly. Please make sure you selected the correct mode and that the cartridge contacts are clean.", QtWidgets.QMessageBox.Ok)
|
||||
|
||||
if data['game_title'][:11] == "YJencrypted" and resetStatus:
|
||||
|
||||
@@ -17,6 +17,7 @@ class Flashcart:
|
||||
SECTOR_POS = 0
|
||||
SECTOR_MAP = None
|
||||
CFI = None
|
||||
LAST_SR = 0x00
|
||||
|
||||
def __init__(self, config=None, cart_write_fncptr=None, cart_write_fast_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, progress_fncptr=None):
|
||||
if config is None: config = {}
|
||||
@@ -165,6 +166,7 @@ class Flashcart:
|
||||
if full_reset and "power_cycle" in self.CONFIG:
|
||||
self.CART_POWERCYCLE_FNCPTR()
|
||||
time.sleep(0.001)
|
||||
self.Unlock()
|
||||
elif full_reset and "reset_every" in self.CONFIG:
|
||||
for j in range(0, self.CONFIG["flash_size"], self.CONFIG["reset_every"]):
|
||||
if j >= max_address: break
|
||||
@@ -264,6 +266,7 @@ class Flashcart:
|
||||
data = self.CONFIG["commands"]["chip_erase"][i][1]
|
||||
if not addr == None:
|
||||
self.CartWrite([[addr, data]])
|
||||
time.sleep(0.1)
|
||||
if self.CONFIG["commands"]["chip_erase_wait_for"][i][0] != None:
|
||||
addr = self.CONFIG["commands"]["chip_erase_wait_for"][i][0]
|
||||
data = self.CONFIG["commands"]["chip_erase_wait_for"][i][1]
|
||||
@@ -277,13 +280,14 @@ class Flashcart:
|
||||
self.CartWrite([[addr, sr_data]])
|
||||
self.CartRead(addr, 2) # dummy read (fixes some bootlegs)
|
||||
wait_for = struct.unpack("<H", self.CartRead(addr, 2))[0]
|
||||
self.LAST_SR = wait_for
|
||||
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(wait_for, self.CONFIG["commands"]["chip_erase_wait_for"][i][2], data, str((wait_for & self.CONFIG["commands"]["chip_erase_wait_for"][i][2]) == data)))
|
||||
wait_for = wait_for & self.CONFIG["commands"]["chip_erase_wait_for"][i][2]
|
||||
if wait_for == data: break
|
||||
time.sleep(0.5)
|
||||
timeout -= 0.5
|
||||
if timeout <= 0:
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the flash chip timed out. Please make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.", "abortable":False})
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the flash chip timed out. The last status register value was 0x{:X}.\n\nPlease make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.".format(self.LAST_SR), "abortable":False})
|
||||
return False
|
||||
self.Reset(full_reset=True)
|
||||
return True
|
||||
@@ -320,13 +324,14 @@ class Flashcart:
|
||||
self.CartWrite([[sr_addr, sr_data]])
|
||||
self.CartRead(addr, 2) # dummy read (fixes some bootlegs)
|
||||
wait_for = struct.unpack("<H", self.CartRead(addr, 2))[0]
|
||||
self.LAST_SR = wait_for
|
||||
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(wait_for, self.CONFIG["commands"]["sector_erase_wait_for"][i][2], data, str(wait_for & self.CONFIG["commands"]["sector_erase_wait_for"][i][2] == data)))
|
||||
wait_for = wait_for & self.CONFIG["commands"]["sector_erase_wait_for"][i][2]
|
||||
time.sleep(0.1)
|
||||
timeout -= 1
|
||||
if timeout < 1:
|
||||
dprint("Timeout error!")
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing a flash chip sector timed out. Please make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.", "abortable":False})
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The sector erase attempt timed out. The last status register value was 0x{:X}.\n\nPlease make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.".format(self.LAST_SR), "abortable":False})
|
||||
return False
|
||||
if wait_for == data: break
|
||||
self.PROGRESS_FNCPTR({"action":"SECTOR_ERASE", "sector_pos":buffer_pos, "time_start":time.time(), "abortable":True})
|
||||
@@ -558,12 +563,15 @@ class Flashcart_DMG_MMSA(Flashcart):
|
||||
while lives > 0:
|
||||
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"SECTOR_ERASE", "sector_pos":0, "time_start":time.time(), "abortable":False})
|
||||
sr = ord(self.CartRead(0))
|
||||
self.LAST_SR = sr
|
||||
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(sr, 0x80, 0x80, str(sr == 0x80)))
|
||||
if sr == 0x80: break
|
||||
time.sleep(0.5)
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
raise("Hidden Sector Erase Timeout Error")
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the hidden sector timed out. The last status register value was 0x{:X}.\n\nPlease make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.".format(self.LAST_SR), "abortable":False})
|
||||
return False
|
||||
#raise("Hidden Sector Erase Timeout Error")
|
||||
|
||||
# Write Hidden Sector
|
||||
cmds = [
|
||||
@@ -692,12 +700,15 @@ class Flashcart_DMG_MMSA(Flashcart):
|
||||
while lives > 0:
|
||||
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"ERASE", "time_start":time_start, "abortable":False})
|
||||
sr = ord(self.CartRead(0))
|
||||
self.LAST_SR = sr
|
||||
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(sr, 0x80, 0x80, str(sr == 0x80)))
|
||||
if sr == 0x80: break
|
||||
time.sleep(0.5)
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
raise Exception("Chip Erase Timeout Error")
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Erasing the flash chip timed out. The last status register value was 0x{:X}.\n\nPlease make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.".format(self.LAST_SR), "abortable":False})
|
||||
return False
|
||||
#raise Exception("Chip Erase Timeout Error")
|
||||
|
||||
# Reset flash to read mode
|
||||
cmds = [
|
||||
@@ -718,7 +729,7 @@ class Flashcart_DMG_MMSA(Flashcart):
|
||||
return True
|
||||
|
||||
def Unlock(self):
|
||||
self.UnlockForWriting()
|
||||
return self.UnlockForWriting()
|
||||
|
||||
def UnlockForWriting(self):
|
||||
time_start = time.time()
|
||||
@@ -813,10 +824,12 @@ class Flashcart_DMG_MMSA(Flashcart):
|
||||
lives = 10
|
||||
while lives > 0:
|
||||
sr = ord(self.CartRead(0))
|
||||
self.LAST_SR = sr
|
||||
dprint("Status Register Check: 0x{:X} & 0x{:X} == 0x{:X}? {:s}".format(sr, 0x80, 0x80, str(sr == 0x80)))
|
||||
if sr == 0x80: break
|
||||
if self.PROGRESS_FNCPTR is not None: self.PROGRESS_FNCPTR({"action":"UNLOCK", "time_start":time_start, "abortable":False})
|
||||
time.sleep(0.5)
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
raise Exception("Hidden Sector Unlock Timeout Error")
|
||||
self.PROGRESS_FNCPTR({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Unlocking the hidden sector timed out. The last status register value was 0x{:X}.\n\nPlease make sure that the cartridge contacts are clean, and that the selected cartridge type and settings are correct.".format(self.LAST_SR), "abortable":False})
|
||||
return False
|
||||
|
||||
@@ -71,6 +71,8 @@ class DMG_MBC:
|
||||
return DMG_Unlicensed_XploderGB(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
elif mbc_id == 0x204: # 0x204:'Sachen',
|
||||
return DMG_Unlicensed_Sachen(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
elif mbc_id == 0x205: # 0x205:'Datel Orbit V2',
|
||||
return DMG_Unlicensed_DatelOrbitV2(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
else:
|
||||
self.__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
return self
|
||||
@@ -190,10 +192,16 @@ class DMG_MBC1(DMG_MBC):
|
||||
|
||||
def EnableRAM(self, enable=True):
|
||||
dprint(self.GetName(), "|", enable)
|
||||
commands = [
|
||||
[ 0x6000, 0x01 if enable else 0x00 ],
|
||||
[ 0x0000, 0x0A if enable else 0x00 ],
|
||||
]
|
||||
if enable:
|
||||
commands = [
|
||||
[ 0x6000, 0x01 ],
|
||||
[ 0x0000, 0x0A ],
|
||||
]
|
||||
else:
|
||||
commands = [
|
||||
[ 0x0000, 0x00 ],
|
||||
[ 0x6000, 0x00 ],
|
||||
]
|
||||
self.CartWrite(commands)
|
||||
|
||||
def SelectBankROM(self, index):
|
||||
@@ -378,10 +386,16 @@ class DMG_MBC5(DMG_MBC):
|
||||
|
||||
def EnableRAM(self, enable=True):
|
||||
dprint(self.GetName(), "|", enable)
|
||||
commands = [
|
||||
[ 0x6000, 0x01 if enable else 0x00 ],
|
||||
[ 0x0000, 0x0A if enable else 0x00 ],
|
||||
]
|
||||
if enable:
|
||||
commands = [
|
||||
[ 0x6000, 0x01 ],
|
||||
[ 0x0000, 0x0A ],
|
||||
]
|
||||
else:
|
||||
commands = [
|
||||
[ 0x0000, 0x00 ],
|
||||
[ 0x6000, 0x00 ],
|
||||
]
|
||||
self.CartWrite(commands)
|
||||
|
||||
def SelectBankROM(self, index):
|
||||
@@ -524,18 +538,10 @@ class DMG_MBC7(DMG_MBC):
|
||||
]
|
||||
self.CartWrite(commands)
|
||||
|
||||
class DMG_MBC1M(DMG_MBC):
|
||||
class DMG_MBC1M(DMG_MBC1):
|
||||
def GetName(self):
|
||||
return "MBC1M"
|
||||
|
||||
def EnableRAM(self, enable=True):
|
||||
dprint(self.GetName(), "|", enable)
|
||||
commands = [
|
||||
[ 0x6000, 0x01 if enable else 0x00 ],
|
||||
[ 0x0000, 0x0A if enable else 0x00 ],
|
||||
]
|
||||
self.CartWrite(commands)
|
||||
|
||||
def SelectBankROM(self, index):
|
||||
dprint(self.GetName(), "|", index)
|
||||
if index < 10:
|
||||
@@ -1177,6 +1183,25 @@ class DMG_Unlicensed_Sachen(DMG_MBC):
|
||||
start_address = 0x4000
|
||||
return (start_address, self.ROM_BANK_SIZE)
|
||||
|
||||
class DMG_Unlicensed_DatelOrbitV2(DMG_MBC):
|
||||
def GetName(self):
|
||||
return "Datel Orbit V2"
|
||||
|
||||
def __init__(self, args=None, cart_write_fncptr=None, cart_read_fncptr=None, cart_powercycle_fncptr=None, clk_toggle_fncptr=None):
|
||||
if args is None: args = {}
|
||||
self.ROM_BANK_SIZE = 0x2000
|
||||
super().__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=None)
|
||||
|
||||
def SelectBankROM(self, index):
|
||||
dprint(self.GetName(), "|", index)
|
||||
if index == 0:
|
||||
self.CartRead(0x0101, 1)
|
||||
self.CartRead(0x0108, 1)
|
||||
self.CartRead(0x0101, 1)
|
||||
self.CartWrite([[ 0x7FE1, index & 0xFF ]])
|
||||
start_address = 0x4000
|
||||
return (start_address, self.ROM_BANK_SIZE)
|
||||
|
||||
|
||||
class AGB_GPIO:
|
||||
CART_WRITE_FNCPTR = None
|
||||
|
||||
@@ -116,7 +116,7 @@ class PocketCamera:
|
||||
pnginfo.add_text("Software", "FlashGBX")
|
||||
pnginfo.add_text("Creation Time", email.utils.formatdate())
|
||||
|
||||
if index == 0:
|
||||
if index == 30:
|
||||
pic = self.GetPicture(0)
|
||||
pnginfo.add_text("Title", "Game Face")
|
||||
elif index == 31:
|
||||
@@ -124,7 +124,7 @@ class PocketCamera:
|
||||
pnginfo.add_text("Title", "Last Seen Image")
|
||||
else:
|
||||
pic = self.GetPicture(index)
|
||||
pnginfo.add_text("Title", "Photo {:02d}".format(index))
|
||||
pnginfo.add_text("Title", "Photo {:02d}".format(index + 1))
|
||||
|
||||
if frame is not False:
|
||||
frame = Image.open(io.BytesIO(frame)).convert("RGB")
|
||||
|
||||
@@ -163,6 +163,36 @@ class RomFileDMG:
|
||||
pass
|
||||
data["version"] = "{:d}.{:d}.{:d}:{:c} ({:02d}:{:02d} {:02d}-{:02d}-{:02d} / {:04X})".format(buffer[0xD8], buffer[0xD9], buffer[0xDA], buffer[0xD7], buffer[0xD0], buffer[0xD1], buffer[0xD2], buffer[0xD3], buffer[0xD4], struct.unpack("<H", buffer[0xD5:0xD7])[0]).replace("\x00", "")
|
||||
|
||||
# Unlicensed Datel Orbit V2 Mapper
|
||||
elif hashlib.sha1(buffer[0x101:0x134]).digest() == bytearray([ 0xFA, 0x68, 0x5A, 0x37, 0x85, 0xEF, 0x65, 0x23, 0x2D, 0x6F, 0x23, 0xAC, 0x02, 0x05, 0x15, 0x20, 0x8B, 0xDE, 0xC5, 0x23 ]):
|
||||
data["rom_size_raw"] = 0x02
|
||||
data["ram_size_raw"] = 0
|
||||
data["mapper_raw"] = 0x205
|
||||
data["cgb"] = 0x80
|
||||
try:
|
||||
game_title = bytearray(buffer[0x134:0x150]).decode("ascii", "replace").replace("\xFF", "")
|
||||
game_title = re.sub(r"(\x00+)$", "", game_title)
|
||||
game_title = re.sub(r"((_)_+|(\x00)\x00+|(\s)\s+)", "\\2\\3\\4", game_title).replace("\x00", "")
|
||||
game_title = ''.join(filter(lambda x: x in set(string.printable), game_title))
|
||||
data["game_title"] = game_title
|
||||
except:
|
||||
pass
|
||||
|
||||
# Unlicensed Datel Orbit V2 Mapper (older firmware)
|
||||
elif hashlib.sha1(buffer[0x101:0x140]).digest() == bytearray([ 0xC1, 0xF4, 0x15, 0x4A, 0xEF, 0xCC, 0x5B, 0xE7, 0xEC, 0x83, 0xA8, 0xBB, 0x7B, 0xC0, 0x95, 0x83, 0x35, 0xEC, 0x9A, 0xF2 ]):
|
||||
data["rom_size_raw"] = 0x02
|
||||
data["ram_size_raw"] = 0
|
||||
data["mapper_raw"] = 0x205
|
||||
data["cgb"] = 0x80
|
||||
try:
|
||||
game_title = bytearray(buffer[0x134:0x140]).decode("ascii", "replace").replace("\xFF", "")
|
||||
game_title = re.sub(r"(\x00+)$", "", game_title)
|
||||
game_title = re.sub(r"((_)_+|(\x00)\x00+|(\s)\s+)", "\\2\\3\\4", game_title).replace("\x00", "")
|
||||
game_title = ''.join(filter(lambda x: x in set(string.printable), game_title))
|
||||
data["game_title"] = game_title
|
||||
except:
|
||||
pass
|
||||
|
||||
# Unlicensed Sachen MMC1/MMC2
|
||||
elif len(buffer) >= 0x280:
|
||||
sachen_hash = hashlib.sha1(buffer[0x200:0x280]).digest()
|
||||
|
||||
@@ -7,7 +7,7 @@ from enum import Enum
|
||||
|
||||
# Common constants
|
||||
APPNAME = "FlashGBX"
|
||||
VERSION_PEP440 = "3.9"
|
||||
VERSION_PEP440 = "3.10"
|
||||
VERSION = "v{:s}".format(VERSION_PEP440)
|
||||
DEBUG = False
|
||||
|
||||
@@ -19,7 +19,7 @@ AGB_Global_CRC32 = 0
|
||||
AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS" }
|
||||
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000 ]
|
||||
|
||||
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper' }
|
||||
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
|
||||
DMG_Header_ROM_Sizes = [ "32 KB", "64 KB", "128 KB", "256 KB", "512 KB", "1 MB", "2 MB", "4 MB", "8 MB", "16 MB", "32 MB" ]
|
||||
DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A ]
|
||||
DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"voltage":3.3,
|
||||
"flash_size":0x800000,
|
||||
"sector_size_from_cfi":true,
|
||||
"reset_every":0x100000,
|
||||
"reset_every":0x80000,
|
||||
"command_set":"INTEL",
|
||||
"commands":{
|
||||
"reset":[
|
||||
|
||||
64
FlashGBX/config/fc_DMG_ActionReplay.txt
Normal file
64
FlashGBX/config/fc_DMG_ActionReplay.txt
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"Action Replay (Datel Orbit V2)",
|
||||
"GameShark (Datel Orbit V2)"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0xBF, 0xB5, 0xFF, 0xFF ]
|
||||
],
|
||||
"voltage":5,
|
||||
"flash_size":0x20000,
|
||||
"first_bank":1,
|
||||
"start_addr":0x4000,
|
||||
"mbc":0x205,
|
||||
"write_pin":"WR",
|
||||
"chip_erase_timeout":10,
|
||||
"read_identifier_at":0x4000,
|
||||
"_first_bank":2,
|
||||
"_flash_commands_on_bank_1":true,
|
||||
"_power_cycle":true,
|
||||
"_sector_size":0x1000,
|
||||
"command_set":"DATEL_ORBITV2",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
],
|
||||
"unlock_read":[
|
||||
[ 0x0101, 1, 1 ],
|
||||
[ 0x0108, 1, 1 ],
|
||||
[ 0x0101, 1, 1 ]
|
||||
],
|
||||
"unlock":[
|
||||
[ 0x7FE1, 0x02, 1 ]
|
||||
],
|
||||
"bank_switch":[
|
||||
[ 0x7FE1, "ID" ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x7FE1, 0x02 ],
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x80 ],
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x10 ]
|
||||
],
|
||||
"chip_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ 0, 0xFF, 0xFF ]
|
||||
],
|
||||
"single_write":[],
|
||||
"single_write_wait_for":[]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"SD007_TSOP_29LV017D with S29GL032M90T"
|
||||
"SD007_TSOP_29LV017D with S29GL032M90T",
|
||||
"SD007_TSOP_48BALL_V9 with 32M29EWB"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x02, 0x02, 0x7D, 0x7D ]
|
||||
[ 0x02, 0x02, 0x7D, 0x7D ],
|
||||
[ 0x8A, 0x8A, 0x7D, 0x7D ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"flash_size":0x400000,
|
||||
|
||||
@@ -251,9 +251,9 @@ class GbxDevice:
|
||||
|
||||
def IsSupportedMbc(self, mbc):
|
||||
if self.CanPowerCycleCart():
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x201, 0x202, 0x203, 0x204 )
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x201, 0x202, 0x203, 0x204, 0x205 )
|
||||
else:
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x202 )
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x06, 0x0B, 0x0D, 0x10, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x202, 0x205 )
|
||||
|
||||
def IsSupported3dMemory(self):
|
||||
return True
|
||||
@@ -535,6 +535,8 @@ class GbxDevice:
|
||||
if self.FW["pcb_ver"] in (5, 6):
|
||||
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"])
|
||||
time.sleep(delay)
|
||||
else:
|
||||
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"])
|
||||
|
||||
def CartPowerOn(self, delay=0.1):
|
||||
if self.FW["pcb_ver"] in (5, 6):
|
||||
@@ -686,7 +688,8 @@ class GbxDevice:
|
||||
self.INFO["flash_type"] = 0
|
||||
self.INFO["last_action"] = 0
|
||||
|
||||
if self.MODE == "DMG" and setPinsAsInputs: self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"])
|
||||
if self.MODE == "DMG": #and setPinsAsInputs:
|
||||
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"])
|
||||
return data
|
||||
|
||||
def DetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=True):
|
||||
@@ -1321,6 +1324,20 @@ class GbxDevice:
|
||||
self._cart_write(address - 1, 0xFF)
|
||||
self.SKIPPING = skip_write
|
||||
|
||||
def WriteROM_DMG_DatelOrbitV2(self, address, buffer, bank):
|
||||
length = len(buffer)
|
||||
dprint("Writing 0x{:X} bytes to Datel Orbit V2 cartridge".format(length))
|
||||
for i in range(0, length):
|
||||
self._cart_write(0x7FE1, 2)
|
||||
self._cart_write(0x5555, 0xAA)
|
||||
self._cart_write(0x2AAA, 0x55)
|
||||
self._cart_write(0x5555, 0xA0)
|
||||
self._cart_write(0x7FE1, bank)
|
||||
self._cart_write(address + i, buffer[i])
|
||||
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
|
||||
self.SetProgress({"action":"WRITE", "bytes_added":1})
|
||||
return True
|
||||
|
||||
def WriteROM_DMG_EEPROM(self, address, buffer, bank, eeprom_buffer_size=0x80):
|
||||
length = len(buffer)
|
||||
if self.FW["pcb_ver"] not in (5, 6, 101):
|
||||
@@ -1401,7 +1418,7 @@ class GbxDevice:
|
||||
|
||||
flashcart = Flashcart(config=flashcart_meta, cart_write_fncptr=self._cart_write, cart_write_fast_fncptr=self._cart_write_flash, cart_read_fncptr=self.ReadROM, cart_powercycle_fncptr=self.CartPowerCycle)
|
||||
flashcart.Reset(full_reset=False)
|
||||
flashcart.Unlock()
|
||||
if flashcart.Unlock() is False: return False
|
||||
if "flash_ids" in flashcart_meta and len(flashcart_meta["flash_ids"]) > 0:
|
||||
vfid = flashcart.VerifyFlashID()
|
||||
if vfid is not False:
|
||||
@@ -1808,6 +1825,7 @@ class GbxDevice:
|
||||
(start_address, bank_size) = _mbc.SelectBankROM(bank)
|
||||
end_address = start_address + bank_size
|
||||
buffer_len = _mbc.GetROMBankSize()
|
||||
dprint("{:X}/{:X}/{:X}".format(start_address, bank_size, buffer_len))
|
||||
elif self.MODE == "AGB" and rom_banks > 1:
|
||||
if cart_type["flash_bank_select_type"] == 1:
|
||||
flashcart.SelectBankROM(bank)
|
||||
@@ -2352,6 +2370,7 @@ class GbxDevice:
|
||||
_mbc.EnableRAM(enable=False)
|
||||
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
||||
if audio_low: self._set_fw_variable("FLASH_WE_PIN", 0x02)
|
||||
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"]) # Prevent hotplugging corruptions on rare occasions
|
||||
|
||||
# Clean up
|
||||
self.INFO["last_action"] = self.INFO["action"]
|
||||
@@ -2540,7 +2559,7 @@ class GbxDevice:
|
||||
elif command_set_type in ("GBMEMORY", "DMG-MBC5-32M-FLASH"):
|
||||
temp = 0x00
|
||||
dprint("Using GB Memory command set")
|
||||
elif command_set_type == "BLAZE_XPLODER":
|
||||
elif command_set_type in ("BLAZE_XPLODER", "DATEL_ORBITV2"):
|
||||
temp = 0x00
|
||||
else:
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM flashing.", "abortable":False})
|
||||
@@ -2646,7 +2665,7 @@ class GbxDevice:
|
||||
# ↑↑↑ Load commands into firmware
|
||||
|
||||
# ↓↓↓ Unlock cartridge
|
||||
flashcart.Unlock()
|
||||
if flashcart.Unlock() is False: return False
|
||||
if self.MODE == "DMG" and "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
|
||||
dprint("Setting ROM bank 1")
|
||||
_mbc.SelectBankROM(1)
|
||||
@@ -2775,6 +2794,8 @@ class GbxDevice:
|
||||
status = self.WriteROM_DMG_MBC5_32M_FLASH(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
||||
elif command_set_type == "BLAZE_XPLODER":
|
||||
status = self.WriteROM_DMG_EEPROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
||||
elif command_set_type == "DATEL_ORBITV2":
|
||||
status = self.WriteROM_DMG_DatelOrbitV2(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
||||
else:
|
||||
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING), rumble_stop=rumble)
|
||||
if status is False:
|
||||
@@ -2805,7 +2826,7 @@ class GbxDevice:
|
||||
|
||||
# ↓↓↓ Reset flash
|
||||
flashcart.Reset(full_reset=True)
|
||||
self.SetMode(self.MODE)
|
||||
#self.SetMode(self.MODE)
|
||||
# ↑↑↑ Reset flash
|
||||
|
||||
# ↓↓↓ Flash verify
|
||||
|
||||
Binary file not shown.
@@ -173,6 +173,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
||||
- SD007_TSOP_48BALL with M29W160ET
|
||||
- SD007_TSOP_48BALL with L160DB12VI
|
||||
- SD007_TSOP_48BALL_V9 with 29LV160CBTC-70G
|
||||
- SD007_TSOP_48BALL_V9 with 32M29EWB
|
||||
- SD007_TSOP_48BALL_V10 with 29DL164BE-70P
|
||||
- SD007_TSOP_48BALL_V10 with 29DL32TF-70
|
||||
- SD007_TSOP_48BALL_V10 with 29LV320CTXEI
|
||||
@@ -299,6 +300,7 @@ The author would like to thank the following very kind people for their help and
|
||||
- iamevn (flash chip info)
|
||||
- Icesythe7 (feature suggestions, testing, bug reports)
|
||||
- Jayro (flash chip info)
|
||||
- Jenetrix (sample cartridge contribution)
|
||||
- JFox (help with properly packaging the app for pip, Linux help, bug reports)
|
||||
- joyrider3774 (flash chip info)
|
||||
- JS7457 (flash chip info)
|
||||
@@ -327,7 +329,7 @@ The author would like to thank the following very kind people for their help and
|
||||
- Super Maker (flash chip info, testing)
|
||||
- Tauwasser (research)
|
||||
- t5b6_de (flash chip info)
|
||||
- Timville (flash chip info)
|
||||
- Timville (sample cartridge contribution, flash chip info)
|
||||
- twitnic (flash chip info)
|
||||
- Veund (flash chip info)
|
||||
- Voultar (bug reports, feature suggestions)
|
||||
|
||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read(
|
||||
|
||||
setuptools.setup(
|
||||
name="FlashGBX",
|
||||
version="3.9",
|
||||
version="3.10",
|
||||
author="Lesserkuma",
|
||||
description="Reads and writes Game Boy and Game Boy Advance cartridge data. Supported hardware: GBxCart RW v1.3 and v1.4 by insideGadgets.",
|
||||
url="https://github.com/lesserkuma/FlashGBX",
|
||||
|
||||
Reference in New Issue
Block a user