This commit is contained in:
Lesserkuma
2022-06-11 02:01:46 +02:00
parent f417cede30
commit 9b44a9959b
20 changed files with 380 additions and 211 deletions

View File

@@ -1,4 +1,11 @@
# Release notes
### v3.14 (released 2022-06-11)
- Fixed a bug with extracting the Game Boy Cameras 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 Yoshis 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

40
FlashGBX/pyside.py Normal file
View File

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

Binary file not shown.

View File

@@ -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:<br>`pip3 install --ignore-requires-python -U PySide2`
4. Install or upgrade FlashGBX with this command:<br>`pip3 install -U FlashGBX`
* If installation fails and you see an error about a conflict involving PySide2, try these commands instead:<br>`pip3 install pyserial Pillow setuptools requests python-dateutil`<br>`pip3 install --no-deps -U FlashGBX`
3. Install FlashGBX with this command:<br>`pip3 install FlashGBX[qt5]`
* If installation fails, use this command instead:<br>`pip3 install FlashGBX[qt6]`
* If installation still fails, you can install the minimal version (command line interface) with this command:<br>`pip3 install FlashGBX`
* Pre-made Linux packages and instructions for select distributions are available [here](https://github.com/JJ-Fox/FlashGBX-Linux-builds/releases/latest).
*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:<br>`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 doesnt 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 its 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 youre 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 its 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)

View File

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