mirror of
https://github.com/lesserkuma/FlashGBX.git
synced 2026-08-25 02:05:14 -05:00
3.14
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
89
FlashGBX/config/fc_AGB_M29W640.txt
Normal file
89
FlashGBX/config/fc_AGB_M29W640.txt
Normal 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 ]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 ]
|
||||
|
||||
@@ -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":[
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
40
FlashGBX/pyside.py
Normal file
40
FlashGBX/pyside.py
Normal 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.
36
README.md
36
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:<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 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)
|
||||
|
||||
8
setup.py
8
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",
|
||||
|
||||
Reference in New Issue
Block a user