This commit is contained in:
Lesserkuma
2026-06-05 19:43:16 +02:00
parent 935f95ce87
commit 3cf83f3ac9
16 changed files with 1219 additions and 1170 deletions

View File

@@ -1,5 +1,5 @@
# Release notes
### v5.0 (released 2026-06-03)
### v5.0/v5.0.1 (released 2026-06-06)
- Added support for the [Game Bub](https://gamebub.net/) hardware device *(thanks Eli)*
- Added a new launcher for the Windows pre-built package that hides the terminal window
- Now supports gettext-style i18n localizations (see locale folder for the template)
@@ -12,7 +12,10 @@
- Added support for ACAM_GL04G10_2G_204 with GL04GR00FHCR2 *(thanks CaptainBean)*
- Added Dark Mode for Windows users *(thanks marv17 for the suggestion)*
- Updated the Game Boy and Game Boy Advance lookup databases for save types, ROM sizes and checksums
- Lots of minor improvements and some bug fixes
- Added an option to force-disable dark mode via settings.ini `AllowDarkMode`
- Fixed an issue with the Interactive Console (the `ws` command did not work) *(thanks Goon)*
- Fixed an issue with some GBxCart RW devices not being able to read save data correctly *(thanks Boeuffy)*
- Lots of minor improvements and bug fixes
### v4.6 (released 2026-01-27)
- Added support for a new Game Boy Advance bootleg flash save chip (SST39VF6401B) used on “Quetzal” bootlegs *(thanks Grender, marv17, Pese)*

View File

@@ -177,14 +177,14 @@ class FlashGBX_CLI():
pc = PocketCamera()
if pc.LoadFile(args.path) != False:
pc.SetPalette(PocketCamera.PALETTE_NAMES.index(args.gbcamera_palette))
file = os.path.splitext(args.path)[0] + "/IMG_PC00.png"
file = os.path.splitext(args.path)[0] + os.sep + "IMG_PC00.png"
if os.path.isfile(os.path.dirname(file)):
print("\n" + ANSI.RED + __("Cant save pictures at location “{path}”.", path=os.path.abspath(os.path.dirname(file))) + ANSI.RESET)
return 1
if not os.path.isdir(os.path.dirname(file)):
os.makedirs(os.path.dirname(file))
for i in range(0, 32):
file = os.path.splitext(args.path)[0] + "/IMG_PC{:02d}".format(i+1) + "." + args.gbcamera_outfile_format
file = os.path.splitext(args.path)[0] + os.sep + "IMG_PC{:02d}".format(i+1) + "." + args.gbcamera_outfile_format
pc.ExportPicture(i, file, scale=1)
print(__("The pictures from “{save_file}” were extracted to “{destination}”.", save_file=os.path.abspath(args.path), destination=os.path.abspath(os.path.dirname(file)) + os.sep + "IMG_PC**.{:s}".format(args.gbcamera_outfile_format)))
else:
@@ -965,7 +965,7 @@ class FlashGBX_CLI():
if args.path != "auto":
if os.path.isdir(args.path):
path = args.path + "/" + path
path = args.path + os.sep + path
else:
path = args.path
@@ -1303,7 +1303,7 @@ class FlashGBX_CLI():
if args.path != "auto":
if os.path.isdir(args.path):
path = args.path + "/" + path
path = args.path + os.sep + path
else:
path = args.path

View File

@@ -21,8 +21,8 @@ from .Formatter import Formatter
from .IniSettings import IniSettings
from .Flashcart import empty_flashcarts_map, has_3v_compatible_profile
from .RomFileDMG import from_isx
from .Mapper import (ConvertMapperToMapperType, ConvertMapperTypeToMapper, get_mbc_name, save_size_includes_rtc, compare_mbc)
from .pyside import bitmap2pixmap
from .Mapper import ConvertMapperToMapperType, ConvertMapperTypeToMapper, get_mbc_name, save_size_includes_rtc, compare_mbc
from .pyside import bitmap2pixmap, GetQtVersion, IsDarkMode
SAVE_EXTS = (".sav", ".srm", ".fla", ".eep")
ROM_EXTS_DMG = (".gb", ".sgb", ".gbc", ".bin", ".isx")
@@ -50,27 +50,22 @@ class FlashGBX_GUI(QtWidgets.QMainWindow):
def __init__(self, args):
sys.excepthook = Logger.exception_hook
self.SETTINGS = IniSettings(path=args["config_path"] + os.sep + "settings.ini")
self.FLASHCARTS = args["flashcarts"]
self.PROGRESS = Progress(self.UpdateProgress, self.WaitProgress)
try:
if GetQtVersion() >= (6, 5, 0):
if self.SETTINGS.value("AllowDarkMode", default="enabled") == "disabled":
QtGui.QGuiApplication.styleHints().setColorScheme(QtCore.Qt.ColorScheme.Light)
if platform.system() == "Windows":
_use_dark_mode = False
try:
import winreg
_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize")
_use_dark_mode = winreg.QueryValueEx(_key, "AppsUseLightTheme")[0] == 0
winreg.CloseKey(_key)
except Exception:
pass
qt_app.setStyle("fusion" if _use_dark_mode else "windowsvista")
qt_app.setStyle("fusion" if IsDarkMode() else "windowsvista")
except:
pass
QtWidgets.QMainWindow.__init__(self)
AppContext.CONFIG_PATH = args['config_path']
AppContext.APP_PATH = args['app_path']
self.SETTINGS = IniSettings(path=args["config_path"] + "/settings.ini")
self.FLASHCARTS = args["flashcarts"]
self.PROGRESS = Progress(self.UpdateProgress, self.WaitProgress)
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle("{:s} {:s}".format(AppInfo.NAME, AppInfo.VERSION))
@@ -1226,13 +1221,13 @@ class FlashGBX_GUI(QtWidgets.QMainWindow):
if isinstance(lang_name, tuple):
lang_name = lang_name[0]
msg += f"Translated to {lang_name} by {i18n.TRANSLATION_AUTHOR}<br><br>"
msg += "Acknowledgments and Contributors:<br><small>2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, CaptainBean, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, delibird_deals, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, Duckman, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Eli, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, fredemmott, Frost Clock, Gahr, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, infinest, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, luxkiller65, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, numma_cway, olDirdey, orangeglo, paarongiroux, Paradoxical, Pese, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr</small>"
msg += "Acknowledgments and Contributors:<br><small>2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, Boeuffy, CaptainBean, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, delibird_deals, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, Duckman, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Eli, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, fredemmott, Frost Clock, Gahr, gandalf1980, gboh, gekkio, Godan, Goon, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, infinest, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, luxkiller65, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, numma_cway, olDirdey, orangeglo, paarongiroux, Paradoxical, Pese, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr</small>"
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(AppInfo.NAME, AppInfo.VERSION), msg, QtWidgets.QMessageBox.Ok)
def AboutGameDB(self):
msg = __("FlashGBX uses a game database that is based on the ongoing efforts of the No-Intro project. Visit {url} for more information.", url="<a href=\"https://no-intro.org/\">https://no-intro.org/</a>") + "<br><br>"
msg += __("No-Intro databases referenced for this version of FlashGBX:") + "<br>"
msg += "• Nintendo - Game Boy (20260602-070215)<br>• Nintendo - Game Boy Advance (20260602-094414)<br>• Nintendo - Game Boy Advance (Video) (20260522-144016)<br>• Nintendo - Game Boy Color (20260602-232612)" # No-Intro DBs
msg += "• Nintendo - Game Boy (20260605-002844)<br>• Nintendo - Game Boy Advance (20260602-094414)<br>• Nintendo - Game Boy Advance (Video) (20260522-144016)<br>• Nintendo - Game Boy Color (20260605-010629)" # No-Intro DBs
QtWidgets.QMessageBox.information(self, "FlashGBX {:s}".format(AppInfo.VERSION), msg, QtWidgets.QMessageBox.Ok)
def _GetDeviceSupportData(self):

View File

@@ -57,6 +57,12 @@ class InteractiveConsole:
return True
def execute_command(self, cmdline):
try:
return self._execute_command_inner(cmdline)
except Exception:
return False
def _execute_command_inner(self, cmdline):
try:
parts = shlex.split(cmdline)
except ValueError:
@@ -150,6 +156,20 @@ class InteractiveConsole:
self.hexdump(address, data)
return True
if command == "ws" and len(parts) == 3:
try:
address = int(parts[1], 16)
if re.fullmatch(r"[01]{8}", parts[2]):
value = int(parts[2], 2)
else:
value = int(parts[2], 16)
except ValueError:
self.on_output(__("Invalid input. Use hexadecimal or 8-bit binary for the value."))
return True
self.CONN._cart_write(address, value, sram=True)
self.on_output(__("OK"))
return True
if command == "wf" and len(parts) == 3:
try:
address = int(parts[1], 16)

View File

@@ -3512,6 +3512,7 @@ class LK_Device(ABC):
self.SetProgress({"action":"INITIALIZE", "method":action, "size":save_size+extra_size})
buffer_offset = 0
max_length = 64 if self.FW["pcb_name"] in ("GBxCart RW", "") else self.MAX_BUFFER_READ
for bank in range(0, ram_banks):
if self.MODE == "DMG":
if _mbc.GetName() == "MBC6" and bank > 7:
@@ -3557,7 +3558,6 @@ class LK_Device(ABC):
dprint("Unknown bank switching method")
time.sleep(0.05)
max_length = self.MAX_BUFFER_READ # I dont remember why this was hardcoded to 64 before
dprint("start_address=0x{:X}, end_address=0x{:X}, buffer_len=0x{:X}, buffer_offset=0x{:X}".format(start_address, end_address, buffer_len, buffer_offset))
pos = start_address
while pos < end_address:
@@ -3576,6 +3576,7 @@ class LK_Device(ABC):
xe = 2
else:
xe = 1
_read_failed = False
for x in range(0, xe):
if x == 1:
self.NO_PROG_UPDATE = True
@@ -3608,9 +3609,14 @@ class LK_Device(ABC):
else:
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}! Decreasing maximum transfer buffer length to 0x{:X}.".format(len(in_temp[x]), buffer_len, len(buffer), max_length >> 1))
max_length >>= 1
_read_failed = True
self.DEVICE.reset_input_buffer()
self.DEVICE.reset_output_buffer()
continue
if _read_failed:
break
if _read_failed:
continue
if xe == 2 and in_temp[0] != in_temp[1]:
print(ANSI.RED + __("Error: Inconsistent reads detected.") + ANSI.RESET)

View File

@@ -34,7 +34,7 @@ class PocketCameraWindow(QtWidgets.QDialog):
]
def __init__(self, app, file=None, icon=None, config_path=".", app_path="."):
QtWidgets.QDialog.__init__(self)
QtWidgets.QDialog.__init__(self, app)
self.setAcceptDrops(True)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))

View File

@@ -6,9 +6,9 @@ import platform, importlib, re, sys
class AppInfo:
NAME = "FlashGBX"
VERSION_PEP440 = "5.0"
VERSION_PEP440 = "5.0.1"
VERSION = "v{:s}".format(VERSION_PEP440)
VERSION_TIMESTAMP = 1780515968
VERSION_TIMESTAMP = 1780697375
@classmethod
def os_string(cls) -> str:

View File

@@ -473,7 +473,7 @@ try:
PCB_VER = ""
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
QtWidgets.QDialog.__init__(self, app)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle(AppInfo.NAME + " " + __("Firmware Updater for {device_name}", device_name="GBFlash"))

View File

@@ -462,7 +462,7 @@ try:
PCB_VER = ""
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
QtWidgets.QDialog.__init__(self, app)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle(AppInfo.NAME + " " + __("Firmware Updater for {device_name}", device_name="GBxCart RW"))
@@ -724,7 +724,7 @@ try:
FW_FILES = {"v1.1/v1.2":"fw_GBxCart_RW_v1_1_v1_2.zip", "v1.3":"fw_GBxCart_RW_v1_3.zip", "XMAS v1.0":"fw_GBxCart_RW_XMAS_v1_0.zip", "Mini v1.0":"fw_GBxCart_RW_Mini_v1_0.zip"}
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
QtWidgets.QDialog.__init__(self, app)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.APP = app

View File

@@ -483,7 +483,7 @@ try:
PCB_VER = ""
def __init__(self, app, app_path, file=None, icon=None, device=None):
QtWidgets.QDialog.__init__(self)
QtWidgets.QDialog.__init__(self, app)
if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
self.setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }")
self.setWindowTitle("FlashGBX " + __("Firmware Updater for {device_name}", device_name="Joey Jr"))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -253,6 +253,19 @@ elif platform.system() == "Linux":
__all__ = ['QtCore', 'QtWidgets', 'QtGui', 'QApplication', 'QDesktopWidget', 'QActionGroup']
def GetQtVersion():
return tuple(map(int, QtCore.qVersion().split(".")))
def IsDarkMode():
if GetQtVersion() < (6, 5, 0):
return False
try:
scheme = QtGui.QGuiApplication.styleHints().colorScheme()
if scheme == QtCore.Qt.ColorScheme.Dark:
return True
except Exception:
pass
return False
def bitmap2pixmap(data, scale_factor=4):
try:

Binary file not shown.

View File

@@ -398,7 +398,7 @@ FlashGBX can also be run in a local Python environment like so:
The author would like to thank the following very kind people for their help, contributions or documentation (in alphabetical order):
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, CaptainBean, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, delibird_deals, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, Duckman, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Eli, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, fredemmott, Frost Clock, Gahr, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, infinest, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, luxkiller65, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, numma_cway, olDirdey, orangeglo, paarongiroux, Paradoxical, Pese, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr
2358, 90sFlav, AcoVanConis, AdmirtheSableye, AlexiG, ALXCO-Hardware, AndehX, antPL, aronson, Ausar, bbsan, BennVenn, Boeuffy, CaptainBean, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, delibird_deals, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, Duckman, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Eli, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, fredemmott, Frost Clock, Gahr, gandalf1980, gboh, gekkio, Godan, Goon, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, infinest, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, luxkiller65, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, numma_cway, olDirdey, orangeglo, paarongiroux, Paradoxical, Pese, Rairch, Raphaël BOICHOT, redalchemy, RetroGorek, RevZ, RibShark, s1cp, Satumox, Sgt.DoudouMiel, SH, Shinichi999, Sillyhatday, simonK, Sithdown, skite2001, Smelly-Ghost, Sonikks, Squiddy, Stitch, Super Maker, t5b6_de, Tauwasser, TheNFCookie, Timville, twitnic, velipso, Veund, voltagex, Voultar, Warez Waldo, wickawack, Winter1760, Wkr, x7l7j8cc, xactoes, xukkorz, yosoo, Zeii, Zelante, zipplet, Zoo, zvxr
Thanks to the No-Intro project for their game databases which FlashGBXs databases are partly based on.

View File

@@ -1,6 +1,6 @@
[project]
name = "FlashGBX"
version = "5.0"
version = "5.0.1"
description = "Reads and writes Game Boy and Game Boy Advance cartridge data"
readme = "README.md"
requires-python = ">=3.7"