From f9da20f7c5f414a7b12d246217536cb73666c93f Mon Sep 17 00:00:00 2001 From: Lesserkuma Date: Tue, 17 May 2022 12:07:16 +0200 Subject: [PATCH] 3.10 --- CHANGES.md | 6 +++ FlashGBX/FlashGBX.py | 1 + FlashGBX/FlashGBX_GUI.py | 50 ++++++++++++------ FlashGBX/Flashcart.py | 25 ++++++--- FlashGBX/Mapper.py | 59 +++++++++++++++------- FlashGBX/PocketCamera.py | 4 +- FlashGBX/RomFileDMG.py | 30 +++++++++++ FlashGBX/Util.py | 4 +- FlashGBX/config/fc_AGB_M36W0R603.txt | 2 +- FlashGBX/config/fc_DMG_ActionReplay.txt | 64 ++++++++++++++++++++++++ FlashGBX/config/fc_DMG_S29GL032M90T.txt | 6 ++- FlashGBX/hw_GBxCartRW.py | 35 ++++++++++--- FlashGBX/res/config.zip | Bin 146685 -> 147301 bytes README.md | 4 +- setup.py | 2 +- 15 files changed, 238 insertions(+), 54 deletions(-) create mode 100644 FlashGBX/config/fc_DMG_ActionReplay.txt diff --git a/CHANGES.md b/CHANGES.md index 8e96838..92fcd1f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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)* diff --git a/FlashGBX/FlashGBX.py b/FlashGBX/FlashGBX.py index 3ea433b..05eadc3 100644 --- a/FlashGBX/FlashGBX.py +++ b/FlashGBX/FlashGBX.py @@ -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 diff --git a/FlashGBX/FlashGBX_GUI.py b/FlashGBX/FlashGBX_GUI.py index db59239..d4e87a9 100644 --- a/FlashGBX/FlashGBX_GUI.py +++ b/FlashGBX/FlashGBX_GUI.py @@ -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: diff --git a/FlashGBX/Flashcart.py b/FlashGBX/Flashcart.py index 2274b10..4c474c1 100644 --- a/FlashGBX/Flashcart.py +++ b/FlashGBX/Flashcart.py @@ -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(" 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 diff --git a/FlashGBX/Mapper.py b/FlashGBX/Mapper.py index 0264494..a706018 100644 --- a/FlashGBX/Mapper.py +++ b/FlashGBX/Mapper.py @@ -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 diff --git a/FlashGBX/PocketCamera.py b/FlashGBX/PocketCamera.py index dc0b2bf..f207d5f 100644 --- a/FlashGBX/PocketCamera.py +++ b/FlashGBX/PocketCamera.py @@ -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") diff --git a/FlashGBX/RomFileDMG.py b/FlashGBX/RomFileDMG.py index bbdcf5c..905fb71 100644 --- a/FlashGBX/RomFileDMG.py +++ b/FlashGBX/RomFileDMG.py @@ -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("= 0x280: sachen_hash = hashlib.sha1(buffer[0x200:0x280]).digest() diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py index 265394c..9954a11 100644 --- a/FlashGBX/Util.py +++ b/FlashGBX/Util.py @@ -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 ] diff --git a/FlashGBX/config/fc_AGB_M36W0R603.txt b/FlashGBX/config/fc_AGB_M36W0R603.txt index b93b445..780e7ac 100644 --- a/FlashGBX/config/fc_AGB_M36W0R603.txt +++ b/FlashGBX/config/fc_AGB_M36W0R603.txt @@ -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":[ diff --git a/FlashGBX/config/fc_DMG_ActionReplay.txt b/FlashGBX/config/fc_DMG_ActionReplay.txt new file mode 100644 index 0000000..26a010c --- /dev/null +++ b/FlashGBX/config/fc_DMG_ActionReplay.txt @@ -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":[] + } +} diff --git a/FlashGBX/config/fc_DMG_S29GL032M90T.txt b/FlashGBX/config/fc_DMG_S29GL032M90T.txt index dc9d198..b737710 100644 --- a/FlashGBX/config/fc_DMG_S29GL032M90T.txt +++ b/FlashGBX/config/fc_DMG_S29GL032M90T.txt @@ -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, diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index 8f93d29..c2270d9 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -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 diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index 1bfab2eb42bc6977d2e1aa7904cdfc92baf6b8a6..9c03784527a5ac8f8a0192585ea77f180933f2ad 100644 GIT binary patch delta 3604 zcmZ{m2{@E%8^`B;rUqG3$i9!=jD1(gkSr}oDo%?ed)CU*l!{JrqI^AwLkU?D5}g+M z@>!w~=}d7_lx!ipgM80BW-iY4UEjFox^BPwfB)}io9BHO=QzLqy;=NJ<>AA}(sR-3{?rn!lDEB3rI`G)Lll z^vqw;GYXj*;qm?L;)&Sykyvv1l2J<*`Fze?KoWVN?NV9og7>ESnxn?Nzd4S^#NA!m znmMm4m+Ny;H)%Y${!3rnW1jxVtFelXSKO}zNfb3FTPFEb>DAD)3mXP@H?F^~^YHB! z&g7DJ7q?#E+H=&8LKZ##kmG_-a+ausc}6pB%{!|bKcZ@Q{9K;W-LB;Q2P<+Nvgo+w z7;q>1ov&EYRs*fntW(ZcC91TzEsJv!vrMNcVV7IP&-N%RxLiBE)i0>~xAEZYG`IW3 z#i#jAg7Z_ACH&{MI9NI<9jT7z-)reM*K0KSSQrSJsOJ0Lkg7i;ubnQSWKh%PNBenf z$Ha-2OQeCNKr5Z5&6=r!PGb%)s0KfIhue%JeLm5?a|Z$G;DSnax?8o|%yt^e@W`=i@)EA}jm?%1f7U#O%(JO_~u!!|Em(b)+)=QXrRYNJw7!6ii@ zi+-%AB1Vr<-74ed=-PN_TY9d)^M=ckA?+3R2EU0(ww04DOYAlnW$e2p(tSd~k-^1R zcV@?dfXX@k8q-;w!oU{lz|@z?p6Oya70iD+Zm84RXVNr9%8eS+bUY+W{h@Z0#o}46 zAopaeRIf9hHukN(YrcofH|F=hndRB%x-0tNSCVg+Ey$eVZm7LP9{%WkM~E1;uqiAf z+_fVtBToJH=8^4#Hh+spb(`2O_&n+Dsa*GUCvUsZb#pJpQkurU8n0i9pes7vIZ5{- zD@|SBsN3b3o^y5N%Ai^|-*nJ3Vr5(frn{R@;mSSdyiZ>W9-v)THYZXfOy8ghrSKE zn#%H7_)^L|3Is?6-##`?h9%{?IWCIn`Q~z1{;1ULum5yhK085<5v1Q`(56D{c;cFM z^J>0wuBe${{t($@&VR;OOf94J^H7gOb$2D53runli{rB^!#98-jC(z~x8|)k19M$) zfvo~S2%ZxFG*VS=AEK~H0Q|y^H3WeNJ5CV0 zMw0VkYT`xFC|U8ODDY&*2gO$6Mls+{(oACNDdNb76{n$?dpTxc_sv6>XCf;B?76KU z;c5m(dwyz}CP)Bl(lUkbCD3ZHVlzo_NM&dDvRbw+IRFkMawr1|F(^}roDa*P zOhP2nCK6>Z0xXdORPJTOz@~CjmdEo`4vptQF$UihW5Q5dex=M_9+f%4bMmPB&D%_; zQXD;peeyt!jfBYvxj}pKiYf|6bFh>Q)ahj}@j?a`Gf<$zPh|XKqyG27Mn)Tme`L@% zwgUTqkGXYB0fB%KL`cw1N&MSrM#khfV1v8IG~JrOmAWOiK`&?X9L`= zg2uvPv4|198ZnsJfVS?f3fekWW&~G{H$k#0a^N_|bZ}8c?80k!MHT3{S8`V0hF#R%d_I((3XrrGXi;;8? ziy48YIzWZZd~wDz25;%C)HWkVto(t&(NRnkO6j5{58lOeHPl6FpK%y~&Mcc{;!I!? zcC2C~J;WYQK~p_6a#pP$V#L!k7%ZK_gy17ROrNy|NZt7pni!z9W-))nh*bmt?--yVvtS1< zJPaVWArcnUGK)7GBDP@-3^7FaDvRaftaL5xTBYMNLWV~<08C#C#Ni<$)Sjh|Kp8O0 z2}_Jnr>DbA*Uv^^lk7iV+3*+h)%Vv8E&!W3fC$ty20VP0znWElJjTFS^|r>y^9(mU zYmCk$i#@`bA}<^<2KsEKYJ$#m6?|iYd`_AGX;O>85Pq!E5eM%4@Q?`*h6Ae-e5Pm` zV;cbE<3}a#ra(}7xrBk)j^-rAZD8Mw%g;*mmI4z^fjBrW0PmRsIqIm_=rw?@Q+Cmt boa49q?eU_uB>ZOfLVu>J1cHDNK>z(0u*&uk delta 3023 zcmY+G2|Sf~7sv1Ozj-LSDc5?l@B4O1Ld)0+Gt*7#t*%#Nra_X*)F_2^p}kHrnU-6r zYQ`zAyt{Ip0yr&%39NBZj|43E(r_O*W#|?`d zu8Owyl?`;14eadv{=CB5cgc4)y_?S|`&_+CC7{jz?h556ZU%;jy6;yCx6oZbKhZtg zyz+7bSaGyi@Ob0`k$5L6cn|d7Y6s{W`7VToz*#fQ~$=He%)}3w#l*o`CpI4QbBnb z9TF3@w9fEj^&ih4Zyi0dHh-~?bETI<)?!gykdwHhS)jGueN|MWOI}dgb*s=br|GzN z?M3wi$|X(vsbWFN^0SoHbe?-ewJ6W1#w!DN{*Z>e;Y2TShIrv+2mb1;mPCvG(llvR zZ+>>2a;~k~LhGd$9xIhrNxz%1{cdSqkVus0 zm^!N0S^3TOaQ*N^`#6Zaonl?#u4j{M@av!%6FH*SH&1=GM5i9>V6DPeKC zMboFb*FWxC=dQC(zpB3e<9Z9uhSIjH!Q-N{_YdS8PqmtAapUE=BJPLsPV2MrQN zYrkgWPZ%p34KT5kTBsWai6-c>T9f$EgA{IRlKHfk(K^P_QH%T}k2AiBDQ)Ecoyuw= zwMnErmNIczEX)0LNQ^u_uS0&7#})!Ik6s3>W{Uv3l*Qcw@{2qU9gFdbGFDSDmWbuC zjV}7gcm~GGDXok~O65XGR0&0{j5^~ zgUX@RlvvA=h8ZF;1egt}Rx#)U6qpek4WZr!cJj6DC!JNfQtHP5icI7{>{;wF#geFy zO=lq4oCxVbA)LpUsGo%{b7C)NW){fym%=;?+^(q)V+dOr7IGf0TA)tG-ZM;a5$r98 zbn`8d6_)|vm7WmI*48l0_Yx>t5l1=mvcm0Ems3!4h0=$YoS`j7McNbfd$E1I~ z!lpGK@ft+~WsR$nr3KdL_~9x{x5kqfoMTJYG3@PiD7D58Wvqi?YySchn;~6@4O)-9 zNx`UF_yI_4&^7rn>rlpI{;Z_n&}~W&)NF?`wzlZwe;dMW(dQ4Pjm$>&Fsz~qD)B03 z3U=K^H8L#eIj9Qp3qGK| ziCOg|A+q)tuv~~I$(V#;)7zm@h<-Ar=z>h?6?nSf{lc5f*2XaGz-y4W5KB3$W0-jd z40pv^!w6RA>WZwk6QW&lLK!>Aunm91Jy-OTF~xB>q1{^w9C|1X2p)$M$}%ySA@$uL z8Hb&_ZDU>UGHiD*jBp!b?rz9-^h2y0ewqBvu)4!6dk<1KoKVK-@u>T006fOy=`yy6 zVSOLr$at)s^MI|bN5;MXnF1Ae6h;i&%&g4Q9bp28z#2j{A=Mq*lx2=G85=^tZWz&m z9wrlS&DQWHVCEK=K$Qa4*cuZRp1RKzqMF`7$rQ^OMWODpB zf(oLh2wEP*+9OrO_(~}!zA$0)+y>dhh|l40lsNx>q2h2fIVQ-N9|Qb5tq6%8#FV%G zk%P2;egie=1ce^V`NIihDid9xJ+b$ac(!)~PdwLL8A3dXtDNmb#{ItvfhHa?WV-#T z9_EGWP#(B@p}OuM>#&?*U85k!3-3l5d*nql4Dcl)SZ{8ASv