This commit is contained in:
Lesserkuma
2022-05-17 12:07:16 +02:00
parent 681cfcd1de
commit f9da20f7c5
15 changed files with 238 additions and 54 deletions

View File

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

View File

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

View File

@@ -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("Dont 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 cant 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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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":[

View 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":[]
}
}

View File

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

View File

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

View File

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

View File

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