diff --git a/CHANGES.md b/CHANGES.md index fb8d4f0..e296afc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,11 @@ # Release notes +### v3.14 (released 2022-06-11) +- Fixed a bug with extracting the Game Boy Camera’s Game Face image *(thanks 2358)* +- Improved the default file names, now includes the ROM revision information as well +- The default file name format can now be configured within the `settings.ini` file +- Updated the Game Boy Advance lookup database for save types, ROM sizes and checksums (fixes some games such as Yoshi’s Universal Gravitation) *(thanks 2358)* +- FlashGBX now experimentally supports both PySide2 and PySide6 GUI frameworks which should make it easier to use on Apple M1 macOS systems *(thanks JFox)* + ### v3.13 (released 2022-05-30) - Bundles GBxCart RW v1.4 firmware version R36+L7 (fixes a problem with insideGadgets’ GBxCam application) - Added smaller selectable ROM sizes for Game Boy Advance ROM backups diff --git a/FlashGBX/DataTransfer.py b/FlashGBX/DataTransfer.py index b15b760..aaa1677 100644 --- a/FlashGBX/DataTransfer.py +++ b/FlashGBX/DataTransfer.py @@ -3,7 +3,7 @@ # Author: Lesserkuma (github.com/lesserkuma) import traceback -import PySide2 +from . import pyside as PySide2 class DataTransfer(PySide2.QtCore.QThread): CONFIG = None diff --git a/FlashGBX/FlashGBX.py b/FlashGBX/FlashGBX.py index 07f0fa1..b117614 100644 --- a/FlashGBX/FlashGBX.py +++ b/FlashGBX/FlashGBX.py @@ -89,7 +89,7 @@ def main(portableMode=False): app_path = os.path.dirname(os.path.abspath(__file__)) try: - from PySide2 import QtCore + from .pyside import QtCore cp = { "subdir":app_path + "/config", "appdata":QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppConfigLocation) + '/FlashGBX' } except: cp = { "subdir":app_path + "/config", "appdata":os.path.expanduser('~') + '/FlashGBX' } diff --git a/FlashGBX/FlashGBX_CLI.py b/FlashGBX/FlashGBX_CLI.py index 96591c9..c09d0dd 100644 --- a/FlashGBX/FlashGBX_CLI.py +++ b/FlashGBX/FlashGBX_CLI.py @@ -749,6 +749,7 @@ class FlashGBX_CLI(): mbc = 1 rom_size = 0 + path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=None) if self.CONN.GetMode() == "DMG": if args.dmg_mbc == "auto": try: @@ -780,16 +781,6 @@ class FlashGBX_CLI(): else: sizes = [ "auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb" ] rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[sizes.index(args.dmg_romsize) - 1] - - path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = "ROM" - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) - if self.CONN.INFO["cgb"] == 0xC0 or self.CONN.INFO["cgb"] == 0x80: - path = path + ".gbc" - elif self.CONN.INFO["old_lic"] == 0x33 and self.CONN.INFO["sgb"] == 0x03: - path = path + ".sgb" - else: - path = path + ".gb" elif self.CONN.GetMode() == "AGB": if args.agb_romsize == "auto": @@ -797,12 +788,6 @@ class FlashGBX_CLI(): else: sizes = [ "auto", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb" ] rom_size = Util.AGB_Header_ROM_Sizes_Map[sizes.index(args.agb_romsize) - 1] - - path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = header["game_code"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = "ROM" - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) - path = path + ".gba" if args.path != "auto": if os.path.isdir(args.path): @@ -867,7 +852,6 @@ class FlashGBX_CLI(): print("Selected cartridge type: {:s}\n".format(cart_types[0][i])) cart_type = i break - self.CONN.TransferData(args={ 'mode':1, 'path':path, 'mbc':mbc, 'rom_size':rom_size, 'agb_rom_size':rom_size, 'start_addr':0, 'fast_read_mode':True, 'cart_type':cart_type }, signal=self.PROGRESS.SetProgress) def FlashROM(self, args, header): @@ -996,6 +980,14 @@ class FlashGBX_CLI(): add_date_time = args.save_filename_add_datetime is True rtc = args.store_rtc is True + path_datetime = "" + if add_date_time: + path_datetime = "_{:s}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) + + path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=None) + path = os.path.splitext(path)[0] + path += "{:s}.sav".format(path_datetime) + if self.CONN.GetMode() == "DMG": if args.dmg_mbc == "auto": try: @@ -1038,9 +1030,6 @@ class FlashGBX_CLI(): sizes = [ "auto", "4k", "16k", "64k", "256k", "512k", "1m", "eeprom2k", "eeprom4k", "tama5", "4m" ] save_type = Util.DMG_Header_RAM_Sizes_Flasher_Map[sizes.index(args.dmg_savesize)] - path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = "ROM" - if save_type == 0: print("{:s}Unable to auto-detect the save size. Please use the “--dmg-savesize” command line switch to manually select it.{:s}".format(ANSI.RED, ANSI.RESET)) return @@ -1052,10 +1041,6 @@ class FlashGBX_CLI(): sizes = [ "auto", "eeprom4k", "eeprom64k", "sram256k", "flash512k", "flash1m", "dacs8m", "sram512k", "sram1m" ] save_type = sizes.index(args.agb_savetype) - path = header["game_title"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = header["game_code"].strip().encode('ascii', 'ignore').decode('ascii') - if path == "": path = "ROM" - mbc = 0 if save_type == 0 or save_type == None: print("{:s}Unable to auto-detect the save type. Please use the “--agb-savetype” command line switch to manually select it.{:s}".format(ANSI.RED, ANSI.RESET)) @@ -1064,11 +1049,6 @@ class FlashGBX_CLI(): else: return - if add_date_time: - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + "_" + datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".sav" - else: - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav" - if args.path != "auto": if os.path.isdir(args.path): path = args.path + "/" + path diff --git a/FlashGBX/FlashGBX_GUI.py b/FlashGBX/FlashGBX_GUI.py index 2fe4e87..eccbbe4 100644 --- a/FlashGBX/FlashGBX_GUI.py +++ b/FlashGBX/FlashGBX_GUI.py @@ -3,7 +3,7 @@ # Author: Lesserkuma (github.com/lesserkuma) import sys, os, time, datetime, re, json, platform, subprocess, requests, webbrowser, pkg_resources, struct, math -from PySide2 import QtCore, QtWidgets, QtGui +from .pyside import QtCore, QtWidgets, QtGui, QApplication from .RomFileDMG import RomFileDMG from .RomFileAGB import RomFileAGB from .PocketCameraWindow import PocketCameraWindow @@ -991,8 +991,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): def DMGMapperTypeChanged(self, index): if index in (-1, 0): return - #if ((list(Util.DMG_Header_Mapper.items())[index])[0]) == 0x203: # Xploder GB - # self.cmbHeaderROMSizeResult.setCurrentIndex(Util.DMG_Header_ROM_Sizes_Flasher_Map.index(0x40000)) def CartridgeTypeChanged(self, index): if index in (-1, 0): return @@ -1028,19 +1026,12 @@ class FlashGBX_GUI(QtWidgets.QWidget): rom_size = 0 cart_type = 0 + path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS) if self.CONN.GetMode() == "DMG": setting_name = "LastDirRomDMG" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - path = self.lblHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "" or self.lblHeaderTitleResult.styleSheet() == "QLabel { color: red; }": path = "ROM" - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) - if self.CONN.INFO["cgb"] == 0xC0 or self.CONN.INFO["cgb"] == 0x80: - path = path + ".gbc" - elif self.CONN.INFO["old_lic"] == 0x33 and self.CONN.INFO["sgb"] == 0x03: - path = path + ".sgb" - else: - path = path + ".gb" + path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup ROM", last_dir + "/" + path, "Game Boy ROM File (*.gb *.sgb *.gbc);;All Files (*.*)")[0] cart_type = self.cmbDMGCartridgeTypeResult.currentIndex() rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[self.cmbHeaderROMSizeResult.currentIndex()] @@ -1049,12 +1040,8 @@ class FlashGBX_GUI(QtWidgets.QWidget): setting_name = "LastDirRomAGB" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - path = self.lblAGBHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') + "_" + self.lblAGBHeaderCodeResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "_": path = self.lblAGBHeaderCodeResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "" or self.lblAGBHeaderTitleResult.styleSheet() == "QLabel { color: red; }": path = "ROM" - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + rom_size = Util.AGB_Header_ROM_Sizes_Map[self.cmbAGBHeaderROMSizeResult.currentIndex()] - path = path + ".gba" path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup ROM", last_dir + "/" + path, "Game Boy Advance ROM File (*.gba *.srl);;All Files (*.*)")[0] cart_type = self.cmbAGBCartridgeTypeResult.currentIndex() @@ -1246,13 +1233,19 @@ class FlashGBX_GUI(QtWidgets.QWidget): if not self.CheckDeviceAlive(): return rtc = False features = [] + add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled") + path_datetime = "" + if add_date_time and add_date_time.lower() == "enabled": + path_datetime = "_{:s}".format(datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) + path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS) + path = os.path.splitext(path)[0] + path += "{:s}.sav".format(path_datetime) if self.CONN.GetMode() == "DMG": setting_name = "LastDirSaveDataDMG" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - path = self.lblHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "" or self.lblHeaderTitleResult.styleSheet() == "QLabel { color: red; }": path = "ROM" + mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderFeaturesResult.currentIndex()])[0] try: features = list(Util.DMG_Header_Mapper.keys())[self.cmbHeaderFeaturesResult.currentIndex()] @@ -1266,9 +1259,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): setting_name = "LastDirSaveDataAGB" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - path = self.lblAGBHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') + "_" + self.lblAGBHeaderCodeResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "_": path = self.lblAGBHeaderCodeResult.text().strip().encode('ascii', 'ignore').decode('ascii') - if path == "" or self.lblAGBHeaderTitleResult.styleSheet() == "QLabel { color: red; }": path = "ROM" + mbc = 0 save_type = self.cmbAGBSaveTypeResult.currentIndex() if save_type == 0: @@ -1277,12 +1268,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): else: return - add_date_time = self.SETTINGS.value("SaveFileNameAddDateTime", default="disabled") - if add_date_time and add_date_time.lower() == "enabled": - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + "_" + datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".sav" - else: - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav" - path = QtWidgets.QFileDialog.getSaveFileName(self, "Backup Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0] if (path == ""): return @@ -1318,11 +1303,16 @@ class FlashGBX_GUI(QtWidgets.QWidget): def WriteRAM(self, dpath="", erase=False): if not self.CheckDeviceAlive(): return features = 0 + + if dpath == "": + path = Util.GenerateFileName(mode=self.CONN.GetMode(), header=self.CONN.INFO, settings=self.SETTINGS) + path = os.path.splitext(path)[0] + path += ".sav" + if self.CONN.GetMode() == "DMG": setting_name = "LastDirSaveDataDMG" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - if dpath == "": path = self.lblHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') mbc = (list(Util.DMG_Header_Mapper.items())[self.cmbHeaderFeaturesResult.currentIndex()])[0] try: features = list(Util.DMG_Header_Mapper.keys())[self.cmbHeaderFeaturesResult.currentIndex()] @@ -1337,8 +1327,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): setting_name = "LastDirSaveDataAGB" last_dir = self.SETTINGS.value(setting_name) if last_dir is None: last_dir = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DocumentsLocation) - if dpath == "": - path = self.lblAGBHeaderTitleResult.text().strip().encode('ascii', 'ignore').decode('ascii') + "_" + self.lblAGBHeaderCodeResult.text().strip().encode('ascii', 'ignore').decode('ascii') mbc = 0 save_type = self.cmbAGBSaveTypeResult.currentIndex() if save_type == 0: @@ -1358,7 +1346,6 @@ class FlashGBX_GUI(QtWidgets.QWidget): answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The save data on your cartridge will now be erased.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Cancel) if answer == QtWidgets.QMessageBox.Cancel: return else: - path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + ".sav" path = QtWidgets.QFileDialog.getOpenFileName(self, "Restore Save Data", last_dir + "/" + path, "Save Data File (*.sav);;All Files (*.*)")[0] if not path == "": self.SETTINGS.setValue(setting_name, os.path.dirname(path)) if (path == ""): return @@ -1532,17 +1519,18 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.cmbHeaderFeaturesResult.clear() self.cmbHeaderFeaturesResult.addItems(list(Util.DMG_Header_Mapper.values())) self.cmbHeaderFeaturesResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - self.cmbDMGCartridgeTypeResult.clear() - self.cmbDMGCartridgeTypeResult.addItems(self.CONN.GetSupportedCartridgesDMG()[0]) - self.cmbDMGCartridgeTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + if resetStatus: + self.cmbDMGCartridgeTypeResult.clear() + self.cmbDMGCartridgeTypeResult.addItems(self.CONN.GetSupportedCartridgesDMG()[0]) + self.cmbDMGCartridgeTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + if "flash_type" in data: + self.cmbDMGCartridgeTypeResult.setCurrentIndex(data["flash_type"]) self.cmbHeaderROMSizeResult.clear() self.cmbHeaderROMSizeResult.addItems(Util.DMG_Header_ROM_Sizes) self.cmbHeaderROMSizeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.cmbHeaderRAMSizeResult.clear() self.cmbHeaderRAMSizeResult.addItems(Util.DMG_Header_RAM_Sizes) self.cmbHeaderRAMSizeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - if "flash_type" in data: - self.cmbDMGCartridgeTypeResult.setCurrentIndex(data["flash_type"]) self.lblHeaderTitleResult.setText(data['game_title']) self.lblHeaderRevisionResult.setText(str(data['version'])) @@ -1667,11 +1655,12 @@ class FlashGBX_GUI(QtWidgets.QWidget): self.grpDMGCartridgeInfo.setVisible(True) elif self.CONN.GetMode() == "AGB": - self.cmbAGBCartridgeTypeResult.clear() - self.cmbAGBCartridgeTypeResult.addItems(self.CONN.GetSupportedCartridgesAGB()[0]) - self.cmbAGBCartridgeTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) - if "flash_type" in data: - self.cmbAGBCartridgeTypeResult.setCurrentIndex(data["flash_type"]) + if resetStatus: + self.cmbAGBCartridgeTypeResult.clear() + self.cmbAGBCartridgeTypeResult.addItems(self.CONN.GetSupportedCartridgesAGB()[0]) + self.cmbAGBCartridgeTypeResult.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + if "flash_type" in data: + self.cmbAGBCartridgeTypeResult.setCurrentIndex(data["flash_type"]) self.lblAGBHeaderTitleResult.setText(data['game_title']) self.lblAGBHeaderCodeResult.setText(data['game_code']) @@ -2297,7 +2286,7 @@ class FlashGBX_GUI(QtWidgets.QWidget): # Taskbar Progress on Windows only try: - from PySide2.QtWinExtras import QWinTaskbarButton, QtWin + from PySide2.QtWinExtras import QWinTaskbarButton, QtWin myappid = 'lesserkuma.flashgbx' QtWin.setCurrentProcessExplicitAppUserModelID(myappid) taskbar_button = QWinTaskbarButton() @@ -2308,7 +2297,10 @@ class FlashGBX_GUI(QtWidgets.QWidget): except ImportError: pass - qt_app.exec_() + try: + qt_app.exec() # PySide6 + except AttributeError: + qt_app.exec_() # PySide2 -qt_app = QtWidgets.QApplication(sys.argv) +qt_app = QApplication(sys.argv) qt_app.setApplicationName(APPNAME) diff --git a/FlashGBX/PocketCamera.py b/FlashGBX/PocketCamera.py index f207d5f..58f4ea1 100644 --- a/FlashGBX/PocketCamera.py +++ b/FlashGBX/PocketCamera.py @@ -103,9 +103,11 @@ class PocketCamera: return self.ConvertPicture(imgbuffer, lastseen=True) def ExtractPicture(self, index): - if index <= 30: + if index < 30: index = self.ORDER[index] offset = 0x2000 + (index * 0x1000) + elif index == 30: + offset = 0x11FC elif index == 31: offset = 0 imgbuffer = self.DATA[offset:offset+0x1000] @@ -117,7 +119,7 @@ class PocketCamera: pnginfo.add_text("Creation Time", email.utils.formatdate()) if index == 30: - pic = self.GetPicture(0) + pic = self.GetPicture(30) pnginfo.add_text("Title", "Game Face") elif index == 31: pic = self.GetPicture(31) diff --git a/FlashGBX/PocketCameraWindow.py b/FlashGBX/PocketCameraWindow.py index fea7be2..0dc00ad 100644 --- a/FlashGBX/PocketCameraWindow.py +++ b/FlashGBX/PocketCameraWindow.py @@ -2,10 +2,10 @@ # FlashGBX # Author: Lesserkuma (github.com/lesserkuma) -import functools, os, json, platform, shutil, hashlib +import functools, os, json, platform, shutil from PIL.ImageQt import ImageQt from PIL import Image, ImageDraw -from PySide2 import QtCore, QtWidgets, QtGui +from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget from .PocketCamera import PocketCamera class PocketCameraWindow(QtWidgets.QDialog): @@ -204,7 +204,7 @@ class PocketCameraWindow(QtWidgets.QDialog): def run(self): self.layout.update() self.layout.activate() - screenGeometry = QtWidgets.QDesktopWidget().screenGeometry() + screenGeometry = QDesktopWidget().screenGeometry(self) x = (screenGeometry.width() - self.width()) / 2 y = (screenGeometry.height() - self.height()) / 2 self.move(x, y) diff --git a/FlashGBX/Util.py b/FlashGBX/Util.py index 32d7b74..890617e 100644 --- a/FlashGBX/Util.py +++ b/FlashGBX/Util.py @@ -2,12 +2,12 @@ # FlashGBX # Author: Lesserkuma (github.com/lesserkuma) -import math, time, datetime, copy, configparser, threading, statistics, os, platform, traceback, io, struct +import math, time, datetime, copy, configparser, threading, statistics, os, platform, traceback, io, struct, re from enum import Enum # Common constants APPNAME = "FlashGBX" -VERSION_PEP440 = "3.13" +VERSION_PEP440 = "3.14" VERSION = "v{:s}".format(VERSION_PEP440) DEBUG = False @@ -520,7 +520,13 @@ def GetDumpReport(di, device): di["hdr_logo"] = "OK" if di["header"]["logo_correct"] else "Invalid" di["header"]["game_title_raw"] = di["header"]["game_title_raw"].replace("\0", "␀") if mode == "DMG": + game_title = di["header"]["game_title_raw"] + game_code = "" if di["header"]["cgb"] == 0xC0 or di["header"]["cgb"] == 0x80: + if len(di["header"]["game_title_raw"].rstrip("\x00")) == 15: + if game_title[-4:][0] in ("A", "B", "H", "K", "V") and game_title[-4:][3] in ("A", "B", "D", "E", "F", "I", "J", "K", "P", "S", "U", "X", "Y"): + game_code = "* Game Code: {:s}\n".format(game_title[-4:]) + game_title = game_title[:-4].rstrip("_") di["hdr_target_platform"] = "Game Boy Color" elif di["header"]["old_lic"] == 0x33 and di["header"]["sgb"] == 0x03: di["hdr_target_platform"] = "Super Game Boy" @@ -563,7 +569,8 @@ def GetDumpReport(di, device): s += "" \ "\n== Parsed Data ==\n" \ - "* Game Title/Code: {hdr_game_title:s}\n" \ + "* Game Title: {hdr_game_title:s}\n" \ + "{hdr_game_code:s}" \ "* Revision: {hdr_revision:s}\n" \ "* Super Game Boy: {hdr_sgb:s}\n" \ "* Game Boy Color: {hdr_cgb:s}\n" \ @@ -574,7 +581,7 @@ def GetDumpReport(di, device): "* SRAM Size: {hdr_save_type:s}\n" \ "* Mapper Type: {hdr_mapper_type:s}\n" \ "* Target Platform: {hdr_target_platform:s}\n" \ - .format(hdr_game_title=di["header"]["game_title_raw"], hdr_revision=str(di["header"]["version"]), hdr_sgb=di["hdr_sgb"], hdr_cgb=di["hdr_cgb"], hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], hdr_rom_checksum=di["hdr_rom_checksum"], hdr_rom_size=di["hdr_rom_size"], hdr_save_type=di["hdr_save_type"], hdr_mapper_type=di["hdr_mapper_type"], hdr_target_platform=di["hdr_target_platform"]) + .format(hdr_game_title=game_title, hdr_game_code=game_code, hdr_revision=str(di["header"]["version"]), hdr_sgb=di["hdr_sgb"], hdr_cgb=di["hdr_cgb"], hdr_logo=di["hdr_logo"], hdr_header_checksum=di["hdr_header_checksum"], hdr_rom_checksum=di["hdr_rom_checksum"], hdr_rom_size=di["hdr_rom_size"], hdr_save_type=di["hdr_save_type"], hdr_mapper_type=di["hdr_mapper_type"], hdr_target_platform=di["hdr_target_platform"]) if "gbmem" in di and di["gbmem"] is not None: s += "" \ "* Map Parameters: {gbmem:s}\n" \ @@ -613,10 +620,58 @@ def GetDumpReport(di, device): if "agb_save_flash_id" in di and di["agb_save_flash_id"] is not None: s += "" \ "* Save Flash Chip: {agb_save_flash_chip_name:s} (0x{agb_save_flash_chip_id:04X})\n" \ - .format(agb_save_flash_chip_name=di["agb_save_flash_id"][2], agb_save_flash_chip_id=di["agb_save_flash_id"][1]) + .format(agb_save_flash_chip_name=di["agb_save_flash_id"][1], agb_save_flash_chip_id=di["agb_save_flash_id"][0]) return s +def GenerateFileName(mode, header, settings): + path = "ROM" + if mode == "DMG": + path_title = header["game_title"] + path_code = "" + path_revision = str(header["version"]) + path_extension = "bin" + path = "%TITLE%" + if settings is not None: + path = settings.value(key="FileNameFormatDMG", default=path) + if header["cgb"] == 0xC0 or header["cgb"] == 0x80: + if len(header["game_title_raw"].rstrip("\x00")) == 15: + if path_title[-4:][0] in ("A", "B", "H", "K", "V") and path_title[-4:][3] in ("A", "B", "D", "E", "F", "I", "J", "K", "P", "S", "U", "X", "Y"): + path_code = path_title[-4:] + path_title = path_title[:-4].rstrip("_") + path = "%TITLE%_%CODE%-%REVISION%" + if settings is not None: + path = settings.value(key="FileNameFormatCGB", default=path) + path_extension = "gbc" + elif header["old_lic"] == 0x33 and header["sgb"] == 0x03: + path_extension = "sgb" + else: + path_extension = "gb" + if path_title == "": + path = "ROM.{:s}".format(path_extension) + else: + path = path.replace("%TITLE%", path_title.strip()) + path = path.replace("%CODE%", path_code.strip()) + path = path.replace("%REVISION%", path_revision) + path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + path += ".{:s}".format(path_extension) + elif mode == "AGB": + path = "%TITLE%_%CODE%-%REVISION%" + if settings is not None: + path = settings.value(key="FileNameFormatAGB", default=path) + path_title = header["game_title"] + path_code = header["game_code"] + path_revision = str(header["version"]) + if (path_title == "" and path_code == ""): + path = "ROM.gba" + else: + path = path.replace("%TITLE%", path_title.strip()) + path = path.replace("%CODE%", path_code.strip()) + path = path.replace("%REVISION%", path_revision) + path = re.sub(r"[<>:\"/\\|\?\*]", "_", path) + path += ".gba" + return path + def validate_datetime_format(string, format): try: if string != datetime.datetime.strptime(string, format).strftime(format): diff --git a/FlashGBX/config/db_AGB.json b/FlashGBX/config/db_AGB.json index 6097dff..dfeccf1 100644 --- a/FlashGBX/config/db_AGB.json +++ b/FlashGBX/config/db_AGB.json @@ -422,9 +422,9 @@ "07748c5c0edcd6890aba301b5cfb882d1c7b61cc": { "rs": 16777216, "rc": 3479774578, - "gc": "A3IJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "A3IJ" }, "e2f12287081274a4219484e1011df55fe128464f": { "rs": 8388608, @@ -926,9 +926,9 @@ "6f5857fcd4bfbf4faa4663c5264a82e3b227c8c9": { "rs": 8388608, "rc": 1058406058, - "gc": "A5KJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "A5KJ" }, "0af2969144a1b23ec55accd861b8b654b888a35c": { "rs": 8388608, @@ -2214,9 +2214,9 @@ "a4512fdd8e2a3578bb845c3eacbc4ce00929862d": { "rs": 4194304, "rc": 1726573365, - "gc": "ABFJ", - "ss": 8192, - "st": 2 + "ss": 32768, + "st": 3, + "gc": "ABFJ" }, "bb1a5113a8c79949aa021befa000edac151ea835": { "rs": 4194304, @@ -2795,9 +2795,9 @@ "b51cbeb1518b0b82f3c6f2793728ee80184a87b8": { "rs": 4194304, "rc": 2670723119, - "gc": "ACZP", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "ACZP" }, "b6d80926c21f87b37bc8ee48a1e9d318349b1504": { "rs": 4194304, @@ -6393,9 +6393,9 @@ "a831603d7b6eb2c1218bee8325ac8b9b1af3f150": { "rs": 4194304, "rc": 140251524, - "gc": "ANTJ", - "ss": 8192, - "st": 2 + "ss": 32768, + "st": 3, + "gc": "ANTJ" }, "4ff2e579ae5644d40dffd9f4ed001ac49dbab21f": { "rs": 4194304, @@ -7695,9 +7695,9 @@ "28ff9cc06162a4b205f13f6cd44829aa226fbb61": { "rs": 16777216, "rc": 3508024577, - "gc": "ASIE", "ss": 8192, - "st": 2 + "st": 2, + "gc": "ASIE" }, "9f19f09f2379b8c0ae95b3c7b2225c27de435f39": { "rs": 8388608, @@ -7744,9 +7744,9 @@ "4b5e58bdda76ffc76631a908c2180bdc0d4cf182": { "rs": 8388608, "rc": 2811785982, - "gc": "ASNJ", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "ASNJ" }, "b37ebb42082531a8b19b4135b418b1a0f379777e": { "rs": 8388608, @@ -9326,9 +9326,9 @@ "90b3f47c4786796dc677df692cf1bdb060b6bc5a": { "rs": 8388608, "rc": 1167458473, - "gc": "AXRP", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "AXRP" }, "a3407a821d70ca993070de023978f6fad99ce168": { "rs": 8388608, @@ -9795,9 +9795,9 @@ "1403d29d785921ef64c937a425480cc3ac55994b": { "rs": 8388608, "rc": 302064698, - "gc": "AZ8E", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "AZ8E" }, "1692bac2caaf63b913c737ac76321a4f94b7364a": { "rs": 8388608, @@ -10558,16 +10558,16 @@ "a055f008d019434c18c4ecab172033897de643f8": { "rs": 4194304, "rc": 4139076061, - "gc": "B3JE", - "ss": 8192, - "st": 2 + "ss": 0, + "st": 0, + "gc": "B3JE" }, "dfeb4fc4c55d9f60c360a82bb704c06b09391091": { "rs": 4194304, "rc": 4008772784, - "gc": "B3JP", - "ss": 8192, - "st": 2 + "ss": 0, + "st": 0, + "gc": "B3JP" }, "5231d174ba03d16626e55ef442798149024991be": { "rs": 16777216, @@ -13225,9 +13225,9 @@ "c35514b185c9dfe221477015834c982416e6a468": { "rs": 8388608, "rc": 2853482904, - "gc": "BFDJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "BFDJ" }, "229cd73d98ea89354eac7ced19610aed65a299a1": { "rs": 8388608, @@ -13414,9 +13414,9 @@ "cc2420acfeb107bca88b8c95e3c6b2dbfb312b44": { "rs": 8388608, "rc": 310993820, - "gc": "BFUE", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "BFUE" }, "c9ed82d3960e1d50c3af095217c5fcd6939223a8": { "rs": 16777216, @@ -13988,9 +13988,9 @@ "da9474bc0ffd4b1a37de8342ca256c1e2792629a": { "rs": 8388608, "rc": 1083542572, - "gc": "BHDJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "BHDJ" }, "191a5f32084d68ca0ab9ae699559409c4df9e44c": { "rs": 8388608, @@ -14464,9 +14464,9 @@ "3cb020218f7d0df5aac338b2997a94ff832057ad": { "rs": 16777216, "rc": 2699800977, - "gc": "BIME", "ss": 8192, - "st": 2 + "st": 2, + "gc": "BIME" }, "586016806d38ed555396f0961f3cb2ad71134046": { "rs": 16777216, @@ -14639,9 +14639,9 @@ "0d25f426bef88a6394573aabf77898a629d0625b": { "rs": 4194304, "rc": 1839427202, - "gc": "BJCJ", - "ss": 8192, - "st": 2 + "ss": 0, + "st": 0, + "gc": "BJCJ" }, "9af90f3edc8743bd4aca79699a76008df1cfc957": { "rs": 4194304, @@ -14891,9 +14891,9 @@ "87f3a23a874b49678842a68ca0e0b5ff133fa0c8": { "rs": 8388608, "rc": 3160063349, - "gc": "BKGJ", - "ss": 8192, - "st": 2 + "ss": 0, + "st": 0, + "gc": "BKGJ" }, "6c700a54826d353a7a8387ab9c3a95049b0e929f": { "rs": 4194304, @@ -14996,9 +14996,9 @@ "5972b4dda23cd79dab8e62935a2c49a15f57abee": { "rs": 8388608, "rc": 1134203627, - "gc": "BKQX", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "BKQX" }, "751f9d8707c1e2ab5fd055316ecaec9cff190b6f": { "rs": 8388608, @@ -15010,9 +15010,9 @@ "19d474e909cb9afa323ede14f350d030e57a6892": { "rs": 16777216, "rc": 746883250, - "gc": "BKSJ", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "BKSJ" }, "1aa2e24d3e05e80c71d4ca848c39579b273abfe5": { "rs": 16777216, @@ -15913,9 +15913,9 @@ "46e8087672b8e6e55c76a9bc3ae90dd82a4a0efb": { "rs": 8388608, "rc": 1383063551, - "gc": "BNBJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "BNBJ" }, "27290ef11179368455585d3160d3607425cd4bff": { "rs": 8388608, @@ -17586,9 +17586,9 @@ "2f4d0ab318819219b0c80cd61d40148b58b04df3": { "rs": 4194304, "rc": 682155056, - "gc": "BSHJ", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "BSHJ" }, "185f7d5786cdb36f11705d89c97df9bcc0b21a73": { "rs": 8388608, @@ -19875,30 +19875,30 @@ "0480b99a83e6920d1fed9e846ff5ea80fbd442e7": { "rs": 4194304, "rc": 201213673, - "gc": "KHPJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "KHPJ" }, "c7d56c91f302e86650b2fdf52827a04f522fbf1b": { "rs": 8388608, "rc": 3863750236, - "gc": "KYGE", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "KYGE" }, "50779ae9f99b2e279254850f2964de8ebe7516f9": { "rs": 8388608, "rc": 827935610, - "gc": "KYGJ", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "KYGJ" }, "8321af4b0785defa6b5c8671106564551e69b521": { "rs": 8388608, "rc": 99930282, - "gc": "KYGP", - "ss": 8192, - "st": 2 + "ss": 512, + "st": 1, + "gc": "KYGP" }, "8f889007a7be21f1014b0fb65e9259dc3b832121": { "rs": 67108864, @@ -20253,65 +20253,65 @@ "569244c74f0f1c6d9bc1efefb6a2a1d0a5bdd77b": { "rs": 8388608, "rc": 3494379487, - "gc": "TCHK", - "ss": 8192, - "st": 2 + "ss": 0, + "st": 0, + "gc": "TCHK" }, "3fc931b3150d91bca7b4d525fc2894814d51a08c": { "rs": 16777216, "rc": 3791631057, - "gc": "U32E", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U32E" }, "7e7fdea619a9715ac7a2948521174ff4816f9f6d": { "rs": 16777216, "rc": 1910689025, - "gc": "U32J", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U32J" }, "0694b93592f3a49af4f90db935923c50efb88c6e": { "rs": 16777216, "rc": 671409545, - "gc": "U32J", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U32J" }, "cd1945cc9ddf12b1f907d5a9511d95b50abbdb53": { "rs": 16777216, "rc": 1954646453, - "gc": "U32P", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U32P" }, "f347b99107e83fce5c6d072594d019651c0b3e2f": { "rs": 16777216, "rc": 2940547426, - "gc": "U33J", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U33J" }, "0c9b3e738df537c04ca2f107092acfa4344c8739": { "rs": 16777216, "rc": 3876957253, - "gc": "U3IE", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U3IE" }, "f8d27f3c59c550c486a800d0416334f584ea8f4b": { "rs": 16777216, "rc": 2694580231, - "gc": "U3IJ", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U3IJ" }, "8b686cee5bf0a1482003b33ab17fe5dc277a1ff3": { "rs": 16777216, "rc": 2525414251, - "gc": "U3IP", "ss": 8192, - "st": 2 + "st": 2, + "gc": "U3IP" }, "a073bd1aa042ea65b5959225e312b4b857476154": { "rs": 8388608, diff --git a/FlashGBX/config/fc_AGB_M29W640.txt b/FlashGBX/config/fc_AGB_M29W640.txt new file mode 100644 index 0000000..0535c2b --- /dev/null +++ b/FlashGBX/config/fc_AGB_M29W640.txt @@ -0,0 +1,89 @@ +{ + "type":"AGB", + "names":[ + "BX2006_TSOPBGA_0106 with M29W640", + "BX2006_TSOPBGA_6108 with M29W640" + ], + "flash_ids":[ + [ 0x20, 0x00, 0x7D, 0x22 ], + [ 0x20, 0x00, 0x7D, 0x22 ] + ], + "voltage":3.3, + "flash_size":0x800000, + "sector_size_from_cfi":true, + "chip_erase_timeout":120, + "command_set":"AMD", + "commands":{ + "reset":[ + [ 0, 0xF0 ] + ], + "read_identifier":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x90 ] + ], + "read_cfi":[ + [ 0xAA, 0x98 ] + ], + "sector_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ "SA", 0x30 ] + ], + "sector_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", 0xFFFF, 0xFFFF ] + ], + "chip_erase":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x80 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0x10 ] + ], + "chip_erase_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ 0, 0xFFFF, 0xFFFF ] + ], + "buffer_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ "SA", 0x26 ], + [ "SA", "BS" ], + [ "PA", "PD" ], + [ "SA", 0x2A ] + ], + "buffer_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ "SA", "PD", 0xFFFF ] + ], + "single_write":[ + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], + [ 0xAAA, 0xA0 ], + [ "PA", "PD" ] + ], + "single_write_wait_for":[ + [ null, null, null ], + [ null, null, null ], + [ null, null, null ], + [ null, null, null ] + ] + } +} diff --git a/FlashGBX/config/fc_AGB_MSP55LV128M.txt b/FlashGBX/config/fc_AGB_MSP55LV128M.txt index 3208d11..47e9769 100644 --- a/FlashGBX/config/fc_AGB_MSP55LV128M.txt +++ b/FlashGBX/config/fc_AGB_MSP55LV128M.txt @@ -5,7 +5,6 @@ "AGB-E05-01 with GL128S", "BX2006_0106_NEW with S29GL128N10TFI01", "BX2006_TSOP_64BALL with GL128S", - "BX2006_TSOPBGA_0106 with M29W640GB6AZA6", "AGB-E05-02 with M29W128GH", "AGB-E05-02 with M29W128FH", "2006_TSOP_64BALL_6106 with W29GL128SH9B", @@ -19,7 +18,6 @@ [ 0x02, 0x00, 0x7D, 0x22 ], [ 0x20, 0x00, 0x7D, 0x22 ], [ 0x20, 0x00, 0x7D, 0x22 ], - [ 0x20, 0x00, 0x7D, 0x22 ], [ 0xEF, 0x00, 0x7D, 0x22 ], [ 0x8A, 0x00, 0x7D, 0x22 ], [ 0x20, 0x00, 0x7D, 0x22 ] diff --git a/FlashGBX/config/fc_AGB_S29GL032.txt b/FlashGBX/config/fc_AGB_S29GL032.txt index 743095a..5080290 100644 --- a/FlashGBX/config/fc_AGB_S29GL032.txt +++ b/FlashGBX/config/fc_AGB_S29GL032.txt @@ -24,11 +24,11 @@ [ 0xAA, 0x98 ] ], "chip_erase":[ - [ 0xAAA, 0xAA ], - [ 0x555, 0x55 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], [ 0xAAA, 0x80 ], - [ 0xAAA, 0xAA ], - [ 0x555, 0x55 ], + [ 0xAAA, 0xA9 ], + [ 0x555, 0x56 ], [ 0xAAA, 0x10 ] ], "chip_erase_wait_for":[ diff --git a/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt b/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt index 3ccfadf..78f018b 100644 --- a/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt +++ b/FlashGBX/config/fc_DMG_GL032M11BAIR4.txt @@ -3,12 +3,14 @@ "names":[ "SD007_48BALL_64M with GL032M11BAIR4", "SD007_48BALL_64M_V2 with GL032M11BAIR4", - "S29GL032N90T and ALTERA CPLD (MBC5)" + "S29GL032N90T and ALTERA CPLD (MBC5)", + "29LV320TE and ALTERA CPLD (no PCB text)" ], "flash_ids":[ [ 0x02, 0x02, 0x7D, 0x7D ], [ 0x02, 0x02, 0x7D, 0x7D ], - [ 0x02, 0x02, 0x7D, 0x7D ] + [ 0x02, 0x02, 0x7D, 0x7D ], + [ 0x04, 0x04, 0xF5, 0xF5 ] ], "voltage":3.3, "flash_size":0x400000, diff --git a/FlashGBX/fw_GBxCartRW_v1_3.py b/FlashGBX/fw_GBxCartRW_v1_3.py index 2f71627..40a471b 100644 --- a/FlashGBX/fw_GBxCartRW_v1_3.py +++ b/FlashGBX/fw_GBxCartRW_v1_3.py @@ -3,7 +3,7 @@ # Author: Lesserkuma (github.com/lesserkuma) import zipfile, os, serial, struct, time, re, math, platform -from PySide2 import QtCore, QtWidgets, QtGui +from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget from . import Util class FirmwareUpdaterWindow(QtWidgets.QDialog): @@ -141,7 +141,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog): def run(self): self.layout.update() self.layout.activate() - screenGeometry = QtWidgets.QDesktopWidget().screenGeometry() + screenGeometry = QDesktopWidget().screenGeometry(self) x = (screenGeometry.width() - self.width()) / 2 y = (screenGeometry.height() - self.height()) / 2 self.move(x, y) diff --git a/FlashGBX/fw_GBxCartRW_v1_4.py b/FlashGBX/fw_GBxCartRW_v1_4.py index 1d7f0fc..51814dc 100644 --- a/FlashGBX/fw_GBxCartRW_v1_4.py +++ b/FlashGBX/fw_GBxCartRW_v1_4.py @@ -3,7 +3,7 @@ # Author: Lesserkuma (github.com/lesserkuma) import zipfile, serial, struct, time, random, hashlib, datetime -from PySide2 import QtCore, QtWidgets, QtGui +from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget try: from . import Util except ImportError: @@ -242,7 +242,7 @@ class FirmwareUpdaterWindow(QtWidgets.QDialog): try: self.layout.update() self.layout.activate() - screenGeometry = QtWidgets.QDesktopWidget().screenGeometry() + screenGeometry = QDesktopWidget().screenGeometry(self) x = (screenGeometry.width() - self.width()) / 2 y = (screenGeometry.height() - self.height()) / 2 self.move(x, y) diff --git a/FlashGBX/hw_GBxCartRW.py b/FlashGBX/hw_GBxCartRW.py index 2722445..4433c30 100644 --- a/FlashGBX/hw_GBxCartRW.py +++ b/FlashGBX/hw_GBxCartRW.py @@ -764,7 +764,7 @@ class GbxDevice: # Check for FLASH ret = self.ReadFlashSaveID() if ret is not False: - (_, flash_save_id, _) = ret + (flash_save_id, _) = ret try: if flash_save_id != 0 and flash_save_id in Util.AGB_Flash_Save_Chips: save_size = Util.AGB_Flash_Save_Chips_Sizes[list(Util.AGB_Flash_Save_Chips).index(flash_save_id)] @@ -849,11 +849,6 @@ class GbxDevice: time.sleep(0.01) self._cart_write_flash([ [ 0, 0xF0 ] ]) time.sleep(0.01) - if agb_flash_chip == 0x1F3D: - buffer_len = 128 - else: - buffer_len = 0x1000 - buffer_len = 0x1000 if agb_flash_chip not in Util.AGB_Flash_Save_Chips: # Restore SRAM values @@ -868,7 +863,7 @@ class GbxDevice: agb_flash_chip_name = Util.AGB_Flash_Save_Chips[agb_flash_chip] dprint(agb_flash_chip_name) - return (buffer_len, agb_flash_chip, agb_flash_chip_name) + return (agb_flash_chip, agb_flash_chip_name) def ReadROM(self, address, length, skip_init=False, max_length=64): num = math.ceil(length / max_length) @@ -1961,7 +1956,7 @@ class GbxDevice: self.INFO["dump_info"]["agb_save_flash_id"] = None if "FLASH" in temp_ver: agb_save_flash_id = self.ReadFlashSaveID() - if agb_save_flash_id is not False and len(agb_save_flash_id) == 3: + if agb_save_flash_id is not False and len(agb_save_flash_id) == 2: self.INFO["dump_info"]["agb_save_flash_id"] = agb_save_flash_id self.INFO["rom_checksum_calc"] = chk @@ -2097,7 +2092,8 @@ class GbxDevice: if ret is False: self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Couldn’t detect the save data flash chip.", "abortable":False}) return False - (buffer_len, agb_flash_chip, _) = ret + buffer_len = 0x1000 + (agb_flash_chip, _) = ret elif args["save_type"] == 6: # DACS empty_data_byte = 0xFF # Read Chip ID diff --git a/FlashGBX/pyside.py b/FlashGBX/pyside.py new file mode 100644 index 0000000..f95c39a --- /dev/null +++ b/FlashGBX/pyside.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# FlashGBX +# Author: Lesserkuma (github.com/lesserkuma) +# +# PySide abstraction layer contributed by J-Fox +# + +from .Util import dprint +import importlib + +try: + import PySide2 + psversion = 2 +except ImportError as err: + try: + import PySide6 + except ImportError: + raise err + dprint('Using PySide6 code path.') + psversion = 6 + from PySide6 import QtCore + from PySide6 import QtWidgets + from PySide6 import QtGui + from PySide6.QtWidgets import QApplication +else: + dprint('Using PySide2 code path.') + from PySide2 import QtCore + from PySide2 import QtWidgets + from PySide2 import QtGui + from PySide2.QtWidgets import QApplication + +__all__ = ['QtCore', 'QtWidgets', 'QtGui', 'QApplication', 'QDesktopWidget'] + + +class QDesktopWidget(object): + def screenGeometry(self, widget): + if psversion == 2: + return QtWidgets.QDesktopWidget().screenGeometry() + else: + return widget.screen().geometry() diff --git a/FlashGBX/res/config.zip b/FlashGBX/res/config.zip index 777d7db..42df668 100644 Binary files a/FlashGBX/res/config.zip and b/FlashGBX/res/config.zip differ diff --git a/README.md b/README.md index 9cd9bf5..945f2ac 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ for [Windows](https://github.com/lesserkuma/FlashGBX/releases), [Linux](https:// - Many reproduction cartridges and flash cartridges can be auto-detected - A flash chip query (including Common Flash Interface information) can be performed for flash cartridges - Decode and extract Game Boy Camera photos from save data -- Update firmware of insideGadgets GBxCart RW v1.3 and v1.4 devices +- Generate ROM dump reports for game preservation purposes +- Update firmware of most insideGadgets GBxCart RW devices ### Confirmed working reader/writer hardware and firmware versions @@ -33,24 +34,27 @@ Available in the GitHub [Releases](https://github.com/lesserkuma/FlashGBX/releas ### Run using Python (Linux, macOS, Windows) -#### Installing or upgrading from an older version +#### Installing -1. Download and install [Python](https://www.python.org/downloads/) (version 3.7 or higher) +1. Download and install [Python](https://www.python.org/downloads/) (version 3.7 or newer) 2. Open a Terminal or Command Prompt window -3. If your Python version is 3.10 or newer, first run this command:
`pip3 install --ignore-requires-python -U PySide2` -4. Install or upgrade FlashGBX with this command:
`pip3 install -U FlashGBX` -* If installation fails and you see an error about a conflict involving PySide2, try these commands instead:
`pip3 install pyserial Pillow setuptools requests python-dateutil`
`pip3 install --no-deps -U FlashGBX` +3. Install FlashGBX with this command:
`pip3 install FlashGBX[qt5]` +* If installation fails, use this command instead:
`pip3 install FlashGBX[qt6]` +* If installation still fails, you can install the minimal version (command line interface) with this command:
`pip3 install FlashGBX` * Pre-made Linux packages and instructions for select distributions are available [here](https://github.com/JJ-Fox/FlashGBX-Linux-builds/releases/latest). -*FlashGBX should work on pretty much any operating system that supports Qt-GUI applications built using [Python](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/), [pyserial](https://pypi.org/project/pyserial/), [Pillow](https://pypi.org/project/Pillow/), [setuptools](https://pypi.org/project/setuptools/), [requests](https://pypi.org/project/requests/) and [python-dateutil](https://pypi.org/project/python-dateutil/) packages.* - #### Running Use this command in a Terminal or Command Prompt window to launch the installed FlashGBX application: `python3 -m FlashGBX` -*To run FlashGBX in portable mode without installing, you can also download the source code archive and call `python3 run.py` after installing the prerequisites yourself.* +*FlashGBX should work on pretty much any operating system that supports Qt-GUI applications built using [Python](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/) or [PySide6](https://pypi.org/project/PySide6/), [pyserial](https://pypi.org/project/pyserial/), [Pillow](https://pypi.org/project/Pillow/), [setuptools](https://pypi.org/project/setuptools/), [requests](https://pypi.org/project/requests/) and [python-dateutil](https://pypi.org/project/python-dateutil/) packages. To run FlashGBX in portable mode without installing, you can also download the source code archive and call `python3 run.py` after installing the prerequisites yourself.* + +#### Upgrading from an older version + +1. Open a Terminal or Command Prompt window +2. Enter this command:
`pip3 install -U FlashGBX` ## Cartridge Compatibility ### Supported cartridge memory mappers @@ -231,8 +235,9 @@ Use this command in a Terminal or Command Prompt window to launch the installed - BX2006_0106_NEW with S29GL128N10TFI01 - BX2006_TSOP_64BALL with GL128S - BX2006_TSOP_64BALL with GL256S - - BX2006_TSOPBGA_0106 with M29W640GB6AZA6 + - BX2006_TSOPBGA_0106 with M29W640 - BX2006_TSOPBGA_0106 with K8D6316UTM-PI07 + - BX2006_TSOPBGA_6108 with M29W640 - DV15 with MSP55LV100G - GA-07 with unlabeled flash chip - GE28F128W30 with 128W30B0 @@ -247,6 +252,10 @@ Many different reproduction cartridges share their flash chip command set, so ev * If something doesn’t work as expected, first try to clean the game cartridge contacts (best with IPA 99.9%+ on a cotton swab) and reconnect the device. An unstable cartridge connection is the most common reason for read or write errors. +* If your Game Boy Camera cartridge is not reading, make sure it’s connected the correct way around; screws go up. + +* For save data backup/restore on Game Boy Advance reproduction cartridges, depending on how it was built, you may have to manually select the save type for it to work properly. However, the save data backup/restore feature may not work on certain reproduction cartridges with batteryless-patched ROMs. As those cartridges use the same flash chip for both ROM and save data storage, a full ROM backup will usually include the save data. Also, when flashing a new unpatched ROM to a cartridge like this, the game may not be able to save progress without soldering in a battery. See the [Flash Cart DB website](https://flashcartdb.com/index.php/Clone_and_Repo_Cart_Problems) for more information. + * Depending on your system configuration, you may have to use `pip` and `python` commands instead of `pip3` and `python3`. * On Linux systems, you may run into a *Permission Error* problem when trying to connect to USB devices without *sudo* privileges. To grant yourself the necessary permissions temporarily, you can run `sudo chmod 0666 /dev/ttyUSB0` (replace with actual device path) before running the app. For a permanent solution, add yourself to the usergroup that has access to serial devices by default (e.g. *dialout* on Debian-based distros; `sudo adduser $USER dialout`) and then reboot the system. @@ -257,12 +266,6 @@ Many different reproduction cartridges share their flash chip command set, so ev * If you’re using macOS version 10.13 or older, there may be no driver for the *insideGadgets GBxCart RW* device installed on your system. You can either upgrade your macOS version to 10.14+ or manually install a driver which is available [here](https://github.com/adrianmihalko/ch340g-ch34g-ch34x-mac-os-x-driver). -* If you use Python 3.10+ and see the error `Type Error: 'PySide2.QtCore.Qt.WindowType' object cannot be interpreted as an integer` or can only use CLI mode, try to install or update the PySide2 package by running `pip3 install -U PySide2 --ignore-requires-python` or try the older [Python version 3.9.9](https://www.python.org/downloads/release/python-399/). - -* If your Game Boy Camera cartridge is not reading, make sure it’s connected the correct way around; screws go up. - -* For save data backup/restore on Game Boy Advance reproduction cartridges, depending on how it was built, you may have to manually select the save type for it to work properly. However, the save data backup/restore feature may not work on certain reproduction cartridges with batteryless-patched ROMs. As those cartridges use the same flash chip for both ROM and save data storage, a full ROM backup will usually include the save data. Also, when flashing a new unpatched ROM to a cartridge like this, the game may not be able to save progress without soldering in a battery. See the [Flash Cart DB website](https://flashcartdb.com/index.php/Clone_and_Repo_Cart_Problems) for more information. - ## Miscellaneous * To use your own frame around extracted Game Boy Camera pictures, place a file called `pc_frame.png` (must be at least 160×144 pixels) into the configuration directory. (GUI mode only) @@ -271,6 +274,7 @@ Many different reproduction cartridges share their flash chip command set, so ev The author would like to thank the following very kind people for their help and contributions (in alphabetical order): +- 2358 (bug reports) - 90sFlav (flash chip info) - AcoVanConis (bug reports, flash chip info) - AdmirtheSableye (bug reports) diff --git a/setup.py b/setup.py index a246e60..3a566ce 100644 --- a/setup.py +++ b/setup.py @@ -4,12 +4,16 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read( setuptools.setup( name="FlashGBX", - version="3.13", + version="3.14", 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", packages=setuptools.find_packages(), - install_requires=['PySide2', 'pyserial', 'Pillow', 'setuptools', 'requests', 'python-dateutil'], + install_requires=['pyserial', 'Pillow', 'setuptools', 'requests', 'python-dateutil'], + extras_require={ + "qt5":["PySide2"], + "qt6":["PySide6"] + }, include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable",