mirror of
https://github.com/lesserkuma/FlashGBX.git
synced 2026-04-25 16:19:24 -05:00
4.3
This commit is contained in:
parent
8bebc6cf69
commit
c7f1688551
BIN
.github/01.png
vendored
BIN
.github/01.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 44 KiB |
BIN
.github/build/Ubuntu/builddata.tar.gz
vendored
Normal file
BIN
.github/build/Ubuntu/builddata.tar.gz
vendored
Normal file
Binary file not shown.
BIN
.github/build/Windows/Drivers/CH341/CH341SER.EXE
vendored
Normal file
BIN
.github/build/Windows/Drivers/CH341/CH341SER.EXE
vendored
Normal file
Binary file not shown.
BIN
.github/build/Windows/PySide2/Python_3.8.10.7z
vendored
Normal file
BIN
.github/build/Windows/PySide2/Python_3.8.10.7z
vendored
Normal file
Binary file not shown.
82
.github/build/Windows/PySide2/setup.iss
vendored
Normal file
82
.github/build/Windows/PySide2/setup.iss
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "FlashGBX"
|
||||
#define MyAppVersion "<APP_VERSION>"
|
||||
#define MyAppPublisher "Lesserkuma"
|
||||
#define MyAppURL "https://github.com/lesserkuma/FlashGBX"
|
||||
#define MyAppExeName "FlashGBX-app.exe"
|
||||
#define MyFilesDir "<FILES_DIR>"
|
||||
#define MyCH341Dir "<CH341_DIR>"
|
||||
#define MyOutputDir "<OUTPUT_DIR>"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{060FD4CF-E72F-497D-812E-EB5599D59A0A}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} v{#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
;PrivilegesRequiredOverridesAllowed=dialog
|
||||
OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename=Setup_FlashGBX
|
||||
Compression=lzma2/ultra64
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
UninstallDisplayIcon={app}\FlashGBX-app.exe
|
||||
UninstallDisplayName={#MyAppName} v{#MyAppVersion}
|
||||
PrivilegesRequired=lowest
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Types]
|
||||
Name: "full"; Description: "Install Application and Drivers"
|
||||
Name: "custom"; Description: "Custom Installation"; Flags: iscustom
|
||||
|
||||
[Components]
|
||||
Name: "program"; Description: "FlashGBX application"; Types: full custom; Flags: fixed
|
||||
Name: "driver_ch341"; Description: "CH340/CH341 driver v3.8.2023.02 for GBxCart RW and GBFlash (install/re-install)"; Types: full
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
|
||||
[Files]
|
||||
Source: "{#MyFilesDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: program
|
||||
Source: "{#MyCH341Dir}\*.*"; DestDir: "{app}\Drivers\CH341"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: driver_ch341
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\Drivers\CH341\CH341SER.EXE"; Description: "Install CH340/CH341 driver"; Flags: waituntilterminated; Components: driver_ch341
|
||||
Filename: "{app}\Python_3.8.10\python.exe"; Parameters: "-m pip install PySide2==5.15.2.1 --no-warn-script-location --isolated --cache-dir {app}\Python_3.8.10\cache"; StatusMsg: "Setting up prerequisites in embedded Python runtime (only used for FlashGBX)..."; Flags: waituntilterminated runminimized; Components: program
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[InstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\Python";
|
||||
Type: filesandordirs; Name: "{app}\Python_3.8.10";
|
||||
Type: filesandordirs; Name: "{app}\*.pyd";
|
||||
|
||||
[Code]
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}'), True, True, True);
|
||||
if MsgBox('Delete all config files of FlashGBX as well?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
|
||||
begin
|
||||
DelTree(ExpandConstant('{localappdata}\{#MyAppName}'), True, True, True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
BIN
.github/build/Windows/PySide6/Python_3.10.11.7z
vendored
Normal file
BIN
.github/build/Windows/PySide6/Python_3.10.11.7z
vendored
Normal file
Binary file not shown.
82
.github/build/Windows/PySide6/setup.iss
vendored
Normal file
82
.github/build/Windows/PySide6/setup.iss
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "FlashGBX"
|
||||
#define MyAppVersion "<APP_VERSION>"
|
||||
#define MyAppPublisher "Lesserkuma"
|
||||
#define MyAppURL "https://github.com/lesserkuma/FlashGBX"
|
||||
#define MyAppExeName "FlashGBX-app.exe"
|
||||
#define MyFilesDir "<FILES_DIR>"
|
||||
#define MyCH341Dir "<CH341_DIR>"
|
||||
#define MyOutputDir "<OUTPUT_DIR>"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{060FD4CF-E72F-497D-812E-EB5599D59A0A}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} v{#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
;PrivilegesRequiredOverridesAllowed=dialog
|
||||
OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename=Setup_FlashGBX
|
||||
Compression=lzma2/ultra64
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
UninstallDisplayIcon={app}\FlashGBX-app.exe
|
||||
UninstallDisplayName={#MyAppName} v{#MyAppVersion}
|
||||
PrivilegesRequired=lowest
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Types]
|
||||
Name: "full"; Description: "Install Application and Drivers"
|
||||
Name: "custom"; Description: "Custom Installation"; Flags: iscustom
|
||||
|
||||
[Components]
|
||||
Name: "program"; Description: "FlashGBX application"; Types: full custom; Flags: fixed
|
||||
Name: "driver_ch341"; Description: "CH340/CH341 driver v3.8.2023.02 for GBxCart RW and GBFlash (install/re-install)"; Types: full
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
|
||||
[Files]
|
||||
Source: "{#MyFilesDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: program
|
||||
Source: "{#MyCH341Dir}\*.*"; DestDir: "{app}\Drivers\CH341"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: driver_ch341
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\Drivers\CH341\CH341SER.EXE"; Description: "Install CH340/CH341 driver"; Flags: waituntilterminated; Components: driver_ch341
|
||||
Filename: "{app}\Python_3.10.11\python.exe"; Parameters: "-m pip install PySide6==6.5.0 --no-warn-script-location --isolated --cache-dir {app}\Python_3.10.11\cache"; StatusMsg: "Setting up prerequisites in embedded Python runtime (only used for FlashGBX)..."; Flags: waituntilterminated runminimized; Components: program
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[InstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\Python";
|
||||
Type: filesandordirs; Name: "{app}\Python_3.10.11";
|
||||
Type: filesandordirs; Name: "{app}\*.pyd";
|
||||
|
||||
[Code]
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}'), True, True, True);
|
||||
if MsgBox('Delete all config files of FlashGBX as well?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
|
||||
begin
|
||||
DelTree(ExpandConstant('{localappdata}\{#MyAppName}'), True, True, True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
51
.github/build/macOS/FlashGBX.spec
vendored
Normal file
51
.github/build/macOS/FlashGBX.spec
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
a = Analysis(
|
||||
['run.py'],
|
||||
pathex=[],
|
||||
binaries=[('FlashGBX/res/icon.ico', 'res')],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='FlashGBX',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True,
|
||||
icon=['FlashGBX/res/icon.ico'],
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='FlashGBX',
|
||||
)
|
||||
info_plist = {
|
||||
'CFBundleName': 'FlashGBX',
|
||||
'CFBundleDisplayName': 'FlashGBX',
|
||||
'CFBundleGetInfoString': 'Interface software for GB/GBC/GBA cart readers',
|
||||
'CFBundleShortVersionString': '<APP_VERSION>',
|
||||
'CFBundleIdentifier': 'com.lesserkuma.FlashGBX',
|
||||
}
|
||||
app = BUNDLE(
|
||||
coll,
|
||||
name='FlashGBX.app',
|
||||
icon='FlashGBX/res/icon.ico',
|
||||
bundle_identifier='com.lesserkuma.FlashGBX',
|
||||
info_plist=info_plist,
|
||||
)
|
||||
118
.github/workflows/build-macos.yaml
vendored
Normal file
118
.github/workflows/build-macos.yaml
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
name: macOS
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
arch: arm64
|
||||
- os: macos-13
|
||||
arch: x86_64
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create directory for Python packages
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/venv
|
||||
|
||||
- name: Cache Python packages
|
||||
id: cache-pip
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/venv
|
||||
key: flashgbx-macos-${{ matrix.arch }}-python-dependencies-2024110401
|
||||
restore-keys: |
|
||||
flashgbx-macos-${{ matrix.arch }}-python-dependencies-
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install Python packages
|
||||
if: steps.cache-pip.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv ${{ github.workspace }}/venv
|
||||
source ${{ github.workspace }}/venv/bin/activate
|
||||
python -m pip install pyinstaller==6.11.0 Pillow==10.3.0 PySide6==6.7.2 pyserial==3.5 python-dateutil==2.9.0.post0 requests==2.32.3
|
||||
|
||||
- name: Build FlashGBX
|
||||
run: |
|
||||
cd "${{ github.workspace }}"
|
||||
cp ".github/build/macOS/FlashGBX.spec" .
|
||||
sed -i '' 's/<APP_VERSION>/${{ env.VERSION }}/g' "./FlashGBX.spec"
|
||||
rm -r FlashGBX/config
|
||||
source ${{ github.workspace }}/venv/bin/activate
|
||||
pyinstaller FlashGBX.spec
|
||||
mkdir dist/FlashGBX.app/Contents/MacOS/config
|
||||
mkdir dist/FlashGBX.app/Contents/MacOS/res
|
||||
cp -r FlashGBX/res/* dist/FlashGBX.app/Contents/MacOS/res
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
brew install create-dmg
|
||||
mkdir -p "${{ github.workspace }}/dist/dmg"
|
||||
cp -r "${{ github.workspace }}/dist/FlashGBX.app" "${{ github.workspace }}/dist/dmg"
|
||||
|
||||
dmg_path="${{ github.workspace }}/dist/FlashGBX_${{ env.VERSION }}_macOS-${{ matrix.arch }}.dmg"
|
||||
|
||||
max_retries=5
|
||||
retry_delay=10
|
||||
|
||||
for attempt in $(seq 1 $max_retries); do
|
||||
if create-dmg \
|
||||
--volname "FlashGBX" \
|
||||
--volicon "${{ github.workspace }}/FlashGBX/res/icon.ico" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 600 300 \
|
||||
--icon-size 100 \
|
||||
--icon "FlashGBX.app" 175 120 \
|
||||
--hide-extension "FlashGBX.app" \
|
||||
--app-drop-link 425 120 \
|
||||
"$dmg_path" \
|
||||
"${{ github.workspace }}/dist/dmg/"; then
|
||||
echo "Successfully created image of FlashGBX v${{ env.VERSION }}."
|
||||
break
|
||||
else
|
||||
echo "Failed to create DMG (attempt $attempt/$max_retries). Retrying in $retry_delay seconds..."
|
||||
sleep $retry_delay
|
||||
fi
|
||||
|
||||
if [[ $attempt -eq $max_retries ]]; then
|
||||
echo "Error: Failed to create DMG after $max_retries attempts."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_macOS-${{ matrix.arch }}
|
||||
path: ${{ github.workspace }}/dist/FlashGBX_${{ env.VERSION }}_macOS-${{ matrix.arch }}.dmg
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: ${{ github.workspace }}/dist/FlashGBX_${{ env.VERSION }}_macOS-${{ matrix.arch }}.dmg
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
56
.github/workflows/build-ubuntu.yaml
vendored
Normal file
56
.github/workflows/build-ubuntu.yaml
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
name: Ubuntu
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract build data
|
||||
run: |
|
||||
mkdir -p "${{ github.workspace }}/dist/deb"
|
||||
tar xzf ".github/build/Ubuntu/builddata.tar.gz" -C "${{ github.workspace }}/dist/deb"
|
||||
sed -i 's/<APP_VERSION>/${{ env.VERSION }}/g' "${{ github.workspace }}/dist/deb/DEBIAN/control"
|
||||
|
||||
- name: Move FlashGBX files into deb build folder
|
||||
run: |
|
||||
rm -r "${{ github.workspace }}/FlashGBX/config"
|
||||
mv "${{ github.workspace }}/FlashGBX" "${{ github.workspace }}/dist/deb"
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt install devscripts dh-make debhelper
|
||||
|
||||
- name: Create DEB file
|
||||
run: |
|
||||
cd "${{ github.workspace }}/dist/deb"
|
||||
dpkg-deb --build . "../FlashGBX_${{ env.VERSION }}_Ubuntu-all.deb"
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_Ubuntu-all
|
||||
path: ${{ github.workspace }}/dist/FlashGBX_${{ env.VERSION }}_Ubuntu-all.deb
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: ${{ github.workspace }}/dist/FlashGBX_${{ env.VERSION }}_Ubuntu-all.deb
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
59
.github/workflows/build-windows-portable-qt5.yaml
vendored
Normal file
59
.github/workflows/build-windows-portable-qt5.yaml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Windows Portable (Qt5)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy files to temporary directory
|
||||
run: |
|
||||
$tempPath = "$env:RUNNER_TEMP\Temp"
|
||||
mkdir "$tempPath" | Out-Null
|
||||
|
||||
& "C:\Program Files\7-Zip\7z.exe" x .\.github\build\Windows\PySide2\Python_3.8.10.7z -o"$env:RUNNER_TEMP\Temp"
|
||||
xcopy ".\FlashGBX" "$tempPath\FlashGBX\" /s /i /y
|
||||
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$env:RUNNER_TEMP\Temp\" -Force
|
||||
}
|
||||
|
||||
if (Test-Path "$tempPath\FlashGBX\config") {
|
||||
Remove-Item "$tempPath\FlashGBX\config\*" -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Create zip archive
|
||||
run: |
|
||||
$zipFileName = "FlashGBX_${{ env.VERSION }}_Windows_Portable_Qt5.zip"
|
||||
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx=9 "$env:RUNNER_TEMP\$zipFileName" "$env:RUNNER_TEMP\Temp\*"
|
||||
echo "Created zip file: $zipFileName"
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_Windows_Portable_Qt5
|
||||
path: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Portable_Qt5.zip"
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Portable_Qt5.zip"
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
59
.github/workflows/build-windows-portable.yaml
vendored
Normal file
59
.github/workflows/build-windows-portable.yaml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Windows Portable
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy files to temporary directory
|
||||
run: |
|
||||
$tempPath = "$env:RUNNER_TEMP\Temp"
|
||||
mkdir "$tempPath" | Out-Null
|
||||
|
||||
& "C:\Program Files\7-Zip\7z.exe" x .\.github\build\Windows\PySide6\Python_3.10.11.7z -o"$env:RUNNER_TEMP\Temp"
|
||||
xcopy ".\FlashGBX" "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\" /s /i /y
|
||||
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$env:RUNNER_TEMP\Temp\" -Force
|
||||
}
|
||||
|
||||
if (Test-Path "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\config") {
|
||||
Remove-Item "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\config\*" -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Create zip archive
|
||||
run: |
|
||||
$zipFileName = "FlashGBX_${{ env.VERSION }}_Windows_Portable.zip"
|
||||
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx=9 "$env:RUNNER_TEMP\$zipFileName" "$env:RUNNER_TEMP\Temp\*"
|
||||
echo "Created zip file: $zipFileName"
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_Windows_Portable
|
||||
path: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Portable.zip"
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Portable.zip"
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
91
.github/workflows/build-windows-setup-qt5.yaml
vendored
Normal file
91
.github/workflows/build-windows-setup-qt5.yaml
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
name: Windows Setup (Qt5)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy files to temporary directory
|
||||
run: |
|
||||
$tempPath = "$env:RUNNER_TEMP\Temp"
|
||||
mkdir "$tempPath" | Out-Null
|
||||
|
||||
& "C:\Program Files\7-Zip\7z.exe" x .\.github\build\Windows\PySide2\Python_3.8.10.7z -o"$env:RUNNER_TEMP\Temp"
|
||||
xcopy ".\FlashGBX" "$tempPath\FlashGBX\" /s /i /y
|
||||
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$env:RUNNER_TEMP\Temp\" -Force
|
||||
}
|
||||
|
||||
if (Test-Path "$tempPath\FlashGBX\config") {
|
||||
Remove-Item "$tempPath\FlashGBX\config\*" -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Configure Setup
|
||||
run: |
|
||||
$myAppVersion = "${{ env.VERSION }}"
|
||||
$myFilesDir = "$env:RUNNER_TEMP\Temp"
|
||||
$myCH341Dir = "$env:GITHUB_WORKSPACE\.github\build\Windows\Drivers\CH341"
|
||||
$myOutputDir = "$env:RUNNER_TEMP\Setup"
|
||||
mkdir $myOutputDir | Out-Null
|
||||
|
||||
$issFilePath = "$env:GITHUB_WORKSPACE\.github\build\Windows\PySide2\setup.iss"
|
||||
(Get-Content $issFilePath) `
|
||||
-replace "<APP_VERSION>", "$myAppVersion" `
|
||||
-replace "<FILES_DIR>", "$myFilesDir" `
|
||||
-replace "<CH341_DIR>", "$myCH341Dir" `
|
||||
-replace "<OUTPUT_DIR>", "$myOutputDir" | Set-Content $issFilePath
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$myOutputDir" -Force
|
||||
}
|
||||
Rename-Item -Path "$myFilesDir\FlashGBX.exe" -NewName "FlashGBX-app.exe" -Force
|
||||
|
||||
- name: Install Inno Setup
|
||||
run: |
|
||||
$innoSetupUrl = "https://www.jrsoftware.org/download.php/is.exe"
|
||||
$installerPath = "$env:RUNNER_TEMP\InnoSetupInstaller.exe"
|
||||
Invoke-WebRequest -Uri $innoSetupUrl -OutFile $installerPath
|
||||
Start-Process -FilePath $installerPath -ArgumentList "/SILENT" -Wait
|
||||
|
||||
- name: Run Inno Setup
|
||||
run: |
|
||||
$issFilePath = "$env:GITHUB_WORKSPACE\.github\build\Windows\PySide2\setup.iss"
|
||||
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" $issFilePath
|
||||
|
||||
- name: Create zip archive
|
||||
run: |
|
||||
$zipFileName = "FlashGBX_${{ env.VERSION }}_Windows_Setup_Qt5.zip"
|
||||
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx=9 "$env:RUNNER_TEMP\$zipFileName" "$env:RUNNER_TEMP\Setup\*"
|
||||
echo "Created zip file: $zipFileName"
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_Windows_Setup_Qt5
|
||||
path: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Setup_Qt5.zip"
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Setup_Qt5.zip"
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
91
.github/workflows/build-windows-setup.yaml
vendored
Normal file
91
.github/workflows/build-windows-setup.yaml
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
name: Windows Setup
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name || '0.0' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy files to temporary directory
|
||||
run: |
|
||||
$tempPath = "$env:RUNNER_TEMP\Temp"
|
||||
mkdir "$tempPath" | Out-Null
|
||||
|
||||
& "C:\Program Files\7-Zip\7z.exe" x .\.github\build\Windows\PySide6\Python_3.10.11.7z -o"$env:RUNNER_TEMP\Temp"
|
||||
xcopy ".\FlashGBX" "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\" /s /i /y
|
||||
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$env:RUNNER_TEMP\Temp\" -Force
|
||||
}
|
||||
|
||||
if (Test-Path "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\config") {
|
||||
Remove-Item "$tempPath\Python_3.10.11\Lib\site-packages\FlashGBX\config\*" -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Configure Setup
|
||||
run: |
|
||||
$myAppVersion = "${{ env.VERSION }}"
|
||||
$myFilesDir = "$env:RUNNER_TEMP\Temp"
|
||||
$myCH341Dir = "$env:GITHUB_WORKSPACE\.github\build\Windows\Drivers\CH341"
|
||||
$myOutputDir = "$env:RUNNER_TEMP\Setup"
|
||||
mkdir $myOutputDir | Out-Null
|
||||
|
||||
$issFilePath = "$env:GITHUB_WORKSPACE\.github\build\Windows\PySide6\setup.iss"
|
||||
(Get-Content $issFilePath) `
|
||||
-replace "<APP_VERSION>", "$myAppVersion" `
|
||||
-replace "<FILES_DIR>", "$myFilesDir" `
|
||||
-replace "<CH341_DIR>", "$myCH341Dir" `
|
||||
-replace "<OUTPUT_DIR>", "$myOutputDir" | Set-Content $issFilePath
|
||||
$filesToCopy = @("CHANGES.md", "README.md", "LICENSE", "Third Party Notices.md")
|
||||
foreach ($file in $filesToCopy) {
|
||||
Copy-Item "$env:GITHUB_WORKSPACE\$file" "$myOutputDir" -Force
|
||||
}
|
||||
Rename-Item -Path "$myFilesDir\FlashGBX.exe" -NewName "FlashGBX-app.exe" -Force
|
||||
|
||||
- name: Install Inno Setup
|
||||
run: |
|
||||
$innoSetupUrl = "https://www.jrsoftware.org/download.php/is.exe"
|
||||
$installerPath = "$env:RUNNER_TEMP\InnoSetupInstaller.exe"
|
||||
Invoke-WebRequest -Uri $innoSetupUrl -OutFile $installerPath
|
||||
Start-Process -FilePath $installerPath -ArgumentList "/SILENT" -Wait
|
||||
|
||||
- name: Run Inno Setup
|
||||
run: |
|
||||
$issFilePath = "$env:GITHUB_WORKSPACE\.github\build\Windows\PySide6\setup.iss"
|
||||
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" $issFilePath
|
||||
|
||||
- name: Create zip archive
|
||||
run: |
|
||||
$zipFileName = "FlashGBX_${{ env.VERSION }}_Windows_Setup.zip"
|
||||
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx=9 "$env:RUNNER_TEMP\$zipFileName" "$env:RUNNER_TEMP\Setup\*"
|
||||
echo "Created zip file: $zipFileName"
|
||||
|
||||
- name: Create artifact
|
||||
if: env.VERSION == '0.0'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: FlashGBX_Windows_Setup
|
||||
path: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Setup.zip"
|
||||
|
||||
- name: Upload release asset
|
||||
if: env.VERSION != '0.0'
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: "${{ runner.temp }}\\FlashGBX_${{ env.VERSION }}_Windows_Setup.zip"
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
37
.github/workflows/trigger-remote-build-macos.yml
vendored
37
.github/workflows/trigger-remote-build-macos.yml
vendored
|
|
@ -1,37 +0,0 @@
|
|||
name: Trigger Remote Build macOS on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
trigger-release:
|
||||
if: github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger FlashGBX Remote Build
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: FlashGBX Remote Build
|
||||
token: ${{ secrets.FLASHGBX_MACOS_TOKEN }}
|
||||
repo: Cliffback/FlashGBX-macOS
|
||||
ref: main
|
||||
inputs: '{"create_release": "true", "latest_version": "${{ github.event.release.tag_name }}"}'
|
||||
trigger-manual:
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag != ''
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger FlashGBX Remote Build
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: FlashGBX Remote Build
|
||||
token: ${{ secrets.FLASHGBX_MACOS_TOKEN }}
|
||||
repo: Cliffback/FlashGBX-macOS
|
||||
ref: main
|
||||
inputs: '{"create_release": "true", "latest_version": "${{ github.event.inputs.release_tag }}"}'
|
||||
10
CHANGES.md
10
CHANGES.md
|
|
@ -1,4 +1,14 @@
|
|||
# Release notes
|
||||
### v4.3 (released 2024-11-07)
|
||||
- Added support for DVP DRV with MX29LV320CT *(thanks Mufsta)*
|
||||
- Added support for insideGadgets’ MegaDuck 32K flash cart
|
||||
- Added support for GBFlash 1M FLASH RTC (AGB-R1M-02V4)
|
||||
- Added support for the unlicensed MBCX Mapper by Geeksimon
|
||||
- Added support for GBFlash MBCX (8 MiB)
|
||||
- Added support for GBFlash MBCX (32 MiB)
|
||||
- Updated the Game Boy and Game Boy Advance lookup databases for save types, ROM sizes and checksums
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
### v4.2 (released 2024-08-04)
|
||||
- Fixed an issue with MBC2 save data on Nintendo Power GB-Memory Cartridges (DMG-MMSA-JPN) *(thanks iyatemu)*
|
||||
- Updated LK firmware to version L13 (fixes an issue with some FRAM-modded Game Boy cartridges) *(thanks jrharbort)*
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
import traceback
|
||||
from serial import SerialException
|
||||
from . import pyside as PySide2
|
||||
from . import Util
|
||||
from .Util import dprint
|
||||
|
||||
class DataTransfer(PySide2.QtCore.QThread):
|
||||
|
|
@ -54,5 +53,5 @@ class DataTransfer(PySide2.QtCore.QThread):
|
|||
if error is not None:
|
||||
print(tb)
|
||||
dprint(tb)
|
||||
self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "fatal":True, "info_msg":"An unresolvable error has occured. See console output for more information. Reconnect the device, restart the software and try again.\n\n{:s}: {:s}".format(type(error).__name__, str(error)), "abortable":False})
|
||||
self.updateProgress.emit({"action":"ABORT", "info_type":"msgbox_critical", "fatal":True, "info_msg":"An unresolvable error has occured. See the debug log file for more information. Reconnect the device, restart the software and try again.\n\n{:s}: {:s}".format(type(error).__name__, str(error)), "abortable":False})
|
||||
self.FINISHED = True
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ def main(portableMode=False):
|
|||
ap_cli1.add_argument("path", nargs="?", default="auto", help="target or source file path (optional when reading, required when writing)")
|
||||
|
||||
ap_cli2 = parser.add_argument_group('optional command line interface arguments')
|
||||
ap_cli2.add_argument("--dmg-romsize", choices=["auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb"], type=str.lower, default="auto", help="set size of Game Boy cartridge ROM data")
|
||||
ap_cli2.add_argument("--dmg-romsize", choices=["auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb"], type=str.lower, default="auto", help="set size of Game Boy cartridge ROM data")
|
||||
ap_cli2.add_argument("--dmg-mbc", type=str.lower, default="auto", help="set memory bank controller type of Game Boy cartridge")
|
||||
ap_cli2.add_argument("--dmg-savesize", choices=["auto", "4k", "16k", "64k", "256k", "512k", "1m", "eeprom2k", "eeprom4k", "tama5", "4m"], type=str.lower, default="auto", help="set size of Game Boy cartridge save data")
|
||||
ap_cli2.add_argument("--agb-romsize", choices=["auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb", "256mb", "512mb"], type=str.lower, default="auto", help="set size of Game Boy Advance cartridge ROM data")
|
||||
|
|
|
|||
|
|
@ -624,7 +624,8 @@ class FlashGBX_CLI():
|
|||
|
||||
header = self.CONN.ReadInfo()
|
||||
self.ReadCartridge(header)
|
||||
ret = self.CONN.DetectCartridge(limitVoltage=limitVoltage, checkSaveType=True)
|
||||
self.CONN._DetectCartridge(args={"limitVoltage":limitVoltage, "checkSaveType":True})
|
||||
ret = self.CONN.INFO["detect_cart"]
|
||||
(header, _, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, _, flash_id, detected_size) = ret
|
||||
|
||||
# Save Type
|
||||
|
|
@ -638,6 +639,8 @@ class FlashGBX_CLI():
|
|||
supp_cart_types = self.CONN.GetSupportedCartridgesDMG()
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
supp_cart_types = self.CONN.GetSupportedCartridgesAGB()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if len(cart_types) > 0:
|
||||
cart_type = cart_type_id
|
||||
|
|
@ -654,6 +657,7 @@ class FlashGBX_CLI():
|
|||
|
||||
# Save Type
|
||||
msg_save_type_s = ""
|
||||
temp = ""
|
||||
if save_chip is not None:
|
||||
temp = "{:s} ({:s})".format(Util.AGB_Header_Save_Types[save_type], save_chip)
|
||||
else:
|
||||
|
|
@ -695,6 +699,7 @@ class FlashGBX_CLI():
|
|||
else:
|
||||
if (len(flash_id.split("\n")) > 2) and ((self.CONN.GetMode() == "DMG") or ("dacs_8m" in header and header["dacs_8m"] is not True)):
|
||||
msg_cart_type_s = "Cartridge Type: Unknown flash cartridge."
|
||||
try_this = ""
|
||||
if ("[ 0/90]" in flash_id):
|
||||
try_this = "Generic Flash Cartridge (0/90)"
|
||||
elif ("[ AAA/AA]" in flash_id):
|
||||
|
|
@ -767,7 +772,7 @@ class FlashGBX_CLI():
|
|||
print("{:s}Couldn’t determine ROM size, will use 8 MiB. It can also be manually set with the “--dmg-romsize” command line switch.{:s}".format(ANSI.YELLOW, ANSI.RESET))
|
||||
rom_size = 8 * 1024 * 1024
|
||||
else:
|
||||
sizes = [ "auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb" ]
|
||||
sizes = [ "auto", "32kb", "64kb", "128kb", "256kb", "512kb", "1mb", "2mb", "4mb", "8mb", "16mb", "32mb", "64mb", "128mb" ]
|
||||
rom_size = Util.DMG_Header_ROM_Sizes_Flasher_Map[sizes.index(args.dmg_romsize) - 1]
|
||||
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
|
|
@ -820,6 +825,9 @@ class FlashGBX_CLI():
|
|||
carts = self.CONN.GetSupportedCartridgesDMG()[1]
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
carts = self.CONN.GetSupportedCartridgesAGB()[1]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
cart_type = 0
|
||||
for i in range(0, len(carts)):
|
||||
if not "names" in carts[i]: continue
|
||||
|
|
@ -978,6 +986,9 @@ class FlashGBX_CLI():
|
|||
s_mbc = " using Mapper Type 0x{:X}".format(mbc)
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
hdr = RomFileAGB(buffer).GetHeader()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if not hdr["logo_correct"] and (self.CONN.GetMode() == "AGB" or (self.CONN.GetMode() == "DMG" and mbc not in (0x203, 0x205))):
|
||||
print("{:s}Warning: The ROM file you selected will not boot on actual hardware due to invalid boot logo data.{:s}".format(ANSI.YELLOW, ANSI.RESET))
|
||||
bootlogo = None
|
||||
|
|
@ -1249,14 +1260,6 @@ class FlashGBX_CLI():
|
|||
print("\n{:s}Done! The writable save data size is {:s} out of {:s} checked.{:s}".format(ANSI.GREEN, Util.formatFileSize(size=found_length), Util.formatFileSize(size=Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(save_type)]), ANSI.RESET))
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
print("\n{:s}Done! The writable save data size using save type “{:s}” is {:s}.{:s}".format(ANSI.GREEN, Util.AGB_Header_Save_Types[save_type], Util.formatFileSize(size=found_length), ANSI.RESET))
|
||||
|
||||
try:
|
||||
(_, _, cfi) = self.CONN.CheckFlashChip(limitVoltage=False)
|
||||
if len(cfi["raw"]) > 0:
|
||||
with open(Util.CONFIG_PATH + "/cfi.bin", "wb") as f: f.write(cfi["raw"])
|
||||
print("CFI data was extracted to “cfi.bin”.")
|
||||
except:
|
||||
pass
|
||||
|
||||
def UpdateFirmware_PrintText(self, text, enableUI=False, setProgress=None):
|
||||
if setProgress is not None:
|
||||
|
|
@ -1415,8 +1418,8 @@ class FlashGBX_CLI():
|
|||
with zf.open("fw.ini") as f: ini_file = f.read()
|
||||
ini_file = ini_file.decode(encoding="utf-8")
|
||||
self.INI = Util.IniSettings(ini=ini_file, main_section="Firmware")
|
||||
fw_ver = self.INI.GetValue("fw_ver")
|
||||
fw_buildts = self.INI.GetValue("fw_buildts")
|
||||
#fw_ver = self.INI.GetValue("fw_ver")
|
||||
#fw_buildts = self.INI.GetValue("fw_buildts")
|
||||
|
||||
print("Select the firmware to install:\n1) Lesserkuma’s FlashGBX firmware\n2) BennVenn’s Drag’n’Drop firmware\n3) BennVenn’s JoeyGUI firmware\n")
|
||||
answer = input("Enter number 1-3: ").lower().strip()
|
||||
|
|
@ -1454,6 +1457,7 @@ class FlashGBX_CLI():
|
|||
FWUPD = FirmwareUpdater(port=port)
|
||||
file_name = Util.APP_PATH + "/res/fw_JoeyJr.zip"
|
||||
with zipfile.ZipFile(file_name) as archive:
|
||||
fw_data = None
|
||||
if fw_choice == 1:
|
||||
with archive.open("FIRMWARE_LK.JR") as f: fw_data = bytearray(f.read())
|
||||
elif fw_choice == 2:
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
TEXT_COLOR = (0, 0, 0, 255)
|
||||
|
||||
def __init__(self, args):
|
||||
sys.excepthook = Util.exception_hook
|
||||
|
||||
QtWidgets.QWidget.__init__(self)
|
||||
Util.CONFIG_PATH = args['config_path']
|
||||
Util.APP_PATH = args['app_path']
|
||||
|
|
@ -314,7 +316,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGGameNameResult = QtWidgets.QLabel("")
|
||||
rowDMGGameName.addWidget(self.lblDMGGameNameResult)
|
||||
rowDMGGameName.setStretch(0, 9)
|
||||
rowDMGGameName.setStretch(1, 11)
|
||||
rowDMGGameName.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGGameName)
|
||||
|
||||
rowDMGRomTitle = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -324,7 +326,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGRomTitleResult = QtWidgets.QLabel("")
|
||||
rowDMGRomTitle.addWidget(self.lblDMGRomTitleResult)
|
||||
rowDMGRomTitle.setStretch(0, 9)
|
||||
rowDMGRomTitle.setStretch(1, 11)
|
||||
rowDMGRomTitle.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGRomTitle)
|
||||
|
||||
rowDMGGameCodeRevision = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -334,7 +336,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGGameCodeRevisionResult = QtWidgets.QLabel("")
|
||||
rowDMGGameCodeRevision.addWidget(self.lblDMGGameCodeRevisionResult)
|
||||
rowDMGGameCodeRevision.setStretch(0, 9)
|
||||
rowDMGGameCodeRevision.setStretch(1, 11)
|
||||
rowDMGGameCodeRevision.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGGameCodeRevision)
|
||||
|
||||
rowDMGHeaderRtc = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -345,7 +347,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGHeaderRtcResult.mousePressEvent = lambda event: [ self.EditRTC(event) ]
|
||||
rowDMGHeaderRtc.addWidget(self.lblDMGHeaderRtcResult)
|
||||
rowDMGHeaderRtc.setStretch(0, 9)
|
||||
rowDMGHeaderRtc.setStretch(1, 11)
|
||||
rowDMGHeaderRtc.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderRtc)
|
||||
|
||||
rowDMGHeaderBootlogo = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -355,7 +357,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGHeaderBootlogoResult = QtWidgets.QLabel("")
|
||||
rowDMGHeaderBootlogo.addWidget(self.lblDMGHeaderBootlogoResult)
|
||||
rowDMGHeaderBootlogo.setStretch(0, 9)
|
||||
rowDMGHeaderBootlogo.setStretch(1, 11)
|
||||
rowDMGHeaderBootlogo.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderBootlogo)
|
||||
|
||||
rowDMGHeaderROMChecksum = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -365,7 +367,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGHeaderROMChecksumResult = QtWidgets.QLabel("")
|
||||
rowDMGHeaderROMChecksum.addWidget(self.lblDMGHeaderROMChecksumResult)
|
||||
rowDMGHeaderROMChecksum.setStretch(0, 9)
|
||||
rowDMGHeaderROMChecksum.setStretch(1, 11)
|
||||
rowDMGHeaderROMChecksum.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderROMChecksum)
|
||||
|
||||
rowDMGHeaderROMSize = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -376,7 +378,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.cmbDMGHeaderROMSizeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
rowDMGHeaderROMSize.addWidget(self.cmbDMGHeaderROMSizeResult)
|
||||
rowDMGHeaderROMSize.setStretch(0, 9)
|
||||
rowDMGHeaderROMSize.setStretch(1, 11)
|
||||
rowDMGHeaderROMSize.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderROMSize)
|
||||
|
||||
rowDMGHeaderSaveType = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -387,7 +389,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.cmbDMGHeaderSaveTypeResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
rowDMGHeaderSaveType.addWidget(self.cmbDMGHeaderSaveTypeResult)
|
||||
rowDMGHeaderSaveType.setStretch(0, 9)
|
||||
rowDMGHeaderSaveType.setStretch(1, 11)
|
||||
rowDMGHeaderSaveType.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderSaveType)
|
||||
|
||||
rowDMGHeaderMapper = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -398,7 +400,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.cmbDMGHeaderMapperResult.view().setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
rowDMGHeaderMapper.addWidget(self.cmbDMGHeaderMapperResult)
|
||||
rowDMGHeaderMapper.setStretch(0, 9)
|
||||
rowDMGHeaderMapper.setStretch(1, 11)
|
||||
rowDMGHeaderMapper.setStretch(1, 12)
|
||||
group_layout.addLayout(rowDMGHeaderMapper)
|
||||
|
||||
rowDMGCartridgeType = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -428,7 +430,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBGameNameResult = QtWidgets.QLabel("")
|
||||
rowAGBGameName.addWidget(self.lblAGBGameNameResult)
|
||||
rowAGBGameName.setStretch(0, 9)
|
||||
rowAGBGameName.setStretch(1, 11)
|
||||
rowAGBGameName.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBGameName)
|
||||
|
||||
rowAGBRomTitle = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -438,7 +440,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBRomTitleResult = QtWidgets.QLabel("")
|
||||
rowAGBRomTitle.addWidget(self.lblAGBRomTitleResult)
|
||||
rowAGBRomTitle.setStretch(0, 9)
|
||||
rowAGBRomTitle.setStretch(1, 11)
|
||||
rowAGBRomTitle.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBRomTitle)
|
||||
|
||||
rowAGBHeaderGameCodeRevision = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -448,7 +450,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBHeaderGameCodeRevisionResult = QtWidgets.QLabel("")
|
||||
rowAGBHeaderGameCodeRevision.addWidget(self.lblAGBHeaderGameCodeRevisionResult)
|
||||
rowAGBHeaderGameCodeRevision.setStretch(0, 9)
|
||||
rowAGBHeaderGameCodeRevision.setStretch(1, 11)
|
||||
rowAGBHeaderGameCodeRevision.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderGameCodeRevision)
|
||||
|
||||
rowAGBGpioRtc = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -459,7 +461,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBGpioRtcResult.mousePressEvent = lambda event: [ self.EditRTC(event) ]
|
||||
rowAGBGpioRtc.addWidget(self.lblAGBGpioRtcResult)
|
||||
rowAGBGpioRtc.setStretch(0, 9)
|
||||
rowAGBGpioRtc.setStretch(1, 11)
|
||||
rowAGBGpioRtc.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBGpioRtc)
|
||||
|
||||
rowAGBHeaderBootlogo = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -469,7 +471,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBHeaderBootlogoResult = QtWidgets.QLabel("")
|
||||
rowAGBHeaderBootlogo.addWidget(self.lblAGBHeaderBootlogoResult)
|
||||
rowAGBHeaderBootlogo.setStretch(0, 9)
|
||||
rowAGBHeaderBootlogo.setStretch(1, 11)
|
||||
rowAGBHeaderBootlogo.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderBootlogo)
|
||||
|
||||
rowAGBHeaderChecksum = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -479,7 +481,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBHeaderChecksumResult = QtWidgets.QLabel("")
|
||||
rowAGBHeaderChecksum.addWidget(self.lblAGBHeaderChecksumResult)
|
||||
rowAGBHeaderChecksum.setStretch(0, 9)
|
||||
rowAGBHeaderChecksum.setStretch(1, 11)
|
||||
rowAGBHeaderChecksum.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderChecksum)
|
||||
|
||||
rowAGBHeaderROMChecksum = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -489,7 +491,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBHeaderROMChecksumResult = QtWidgets.QLabel("")
|
||||
rowAGBHeaderROMChecksum.addWidget(self.lblAGBHeaderROMChecksumResult)
|
||||
rowAGBHeaderROMChecksum.setStretch(0, 9)
|
||||
rowAGBHeaderROMChecksum.setStretch(1, 11)
|
||||
rowAGBHeaderROMChecksum.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderROMChecksum)
|
||||
|
||||
rowAGBHeaderROMSize = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -502,7 +504,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.cmbAGBHeaderROMSizeResult.setCurrentIndex(self.cmbAGBHeaderROMSizeResult.count() - 1)
|
||||
rowAGBHeaderROMSize.addWidget(self.cmbAGBHeaderROMSizeResult)
|
||||
rowAGBHeaderROMSize.setStretch(0, 9)
|
||||
rowAGBHeaderROMSize.setStretch(1, 11)
|
||||
rowAGBHeaderROMSize.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderROMSize)
|
||||
|
||||
rowAGBHeaderSaveType = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -515,7 +517,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.cmbAGBSaveTypeResult.setCurrentIndex(self.cmbAGBSaveTypeResult.count() - 1)
|
||||
rowAGBHeaderSaveType.addWidget(self.cmbAGBSaveTypeResult)
|
||||
rowAGBHeaderSaveType.setStretch(0, 9)
|
||||
rowAGBHeaderSaveType.setStretch(1, 11)
|
||||
rowAGBHeaderSaveType.setStretch(1, 12)
|
||||
group_layout.addLayout(rowAGBHeaderSaveType)
|
||||
|
||||
rowAGBCartridgeType = QtWidgets.QHBoxLayout()
|
||||
|
|
@ -716,7 +718,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
def AboutGameDB(self):
|
||||
msg = f"{APPNAME} uses a game database that is based on the ongoing efforts of the No-Intro project. Visit <a href=\"https://no-intro.org/\">https://no-intro.org/</a> for more information.<br><br>"
|
||||
msg += f"No-Intro databases referenced for this version of {APPNAME}:<br>"
|
||||
msg += "• Nintendo - Game Boy (20240713-090345)<br>• Nintendo - Game Boy Advance (20240803-104002)<br>• Nintendo - Game Boy Advance (Video) (20240727-194101)<br>• Nintendo - Game Boy Color (20240801-100010)" # No-Intro DBs
|
||||
msg += "• Nintendo - Game Boy (20241003-140822)<br>• Nintendo - Game Boy Advance (20241102-092521)<br>• Nintendo - Game Boy Advance (Video) (20241021-195439)<br>• Nintendo - Game Boy Color (20241105-004534)" # No-Intro DBs
|
||||
QtWidgets.QMessageBox.information(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
|
||||
|
||||
def OpenPath(self, path=None):
|
||||
|
|
@ -740,26 +742,19 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if isinstance(event, QtGui.QMouseEvent):
|
||||
if event.button() in (QtCore.Qt.MouseButton.MiddleButton, QtCore.Qt.MouseButton.RightButton): return
|
||||
|
||||
device = False
|
||||
try:
|
||||
Util.dprint("{:s} version: {:s} ({:d})".format(Util.APPNAME, Util.VERSION_PEP440, Util.VERSION_TIMESTAMP))
|
||||
Util.dprint("Platform: {:s}".format(platform.platform()))
|
||||
if self.CONN is not None:
|
||||
Util.dprint("Connected device: {:s}".format(self.CONN.GetFullNameExtended(more=True)))
|
||||
else:
|
||||
Util.dprint("No device connected")
|
||||
Util.dprint("Now writing debug log file")
|
||||
device = self.CONN.GetFullNameExtended(more=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
Util.write_debug_log(device)
|
||||
try:
|
||||
fn = Util.CONFIG_PATH + "/debug.log"
|
||||
with open(fn, "wb") as f:
|
||||
f.write("\n".join(Util.DEBUG_LOG).encode("UTF-8-SIG"))
|
||||
print("debug.log written")
|
||||
if open_log is True:
|
||||
fn = Util.CONFIG_PATH + "/debug.log"
|
||||
self.OpenPath(fn)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
pass
|
||||
|
||||
def ConnectDevice(self):
|
||||
if self.CONN is not None:
|
||||
|
|
@ -872,6 +867,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if dev.FirmwareUpdateAvailable():
|
||||
dontShowAgain = str(self.SETTINGS.value("SkipFirmwareUpdate", default="disabled")).lower() == "enabled"
|
||||
if not dontShowAgain or dev.FW_UPDATE_REQ:
|
||||
cb = None
|
||||
if dev.FW_UPDATE_REQ is True:
|
||||
text = "A firmware update for your {:s} device is required to use this software. Do you want to update now?".format(dev.GetFullName())
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=text, standardButtons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes)
|
||||
|
|
@ -889,8 +885,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if not Util.DEBUG:
|
||||
self.DisconnectDevice()
|
||||
else:
|
||||
dontShowAgain = cb.isChecked()
|
||||
if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
|
||||
if cb is not None:
|
||||
dontShowAgain = cb.isChecked()
|
||||
if dontShowAgain: self.SETTINGS.setValue("SkipFirmwareUpdate", "enabled")
|
||||
if answer == QtWidgets.QMessageBox.Yes:
|
||||
self.ShowFirmwareUpdateWindow()
|
||||
|
||||
|
|
@ -899,8 +896,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if not Util.DEBUG:
|
||||
self.DisconnectDevice()
|
||||
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
|
||||
|
||||
if dev.IsUnregistered():
|
||||
try:
|
||||
text = dev.GetRegisterInformation()
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok)
|
||||
except:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def FindDevices(self, connectToFirst=False, port=None, mode=None, firstRun=False):
|
||||
|
|
@ -959,7 +964,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
msg += message + "\n\n"
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg[:-2], QtWidgets.QMessageBox.Ok)
|
||||
elif not firstRun:
|
||||
QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="No compatible devices found. Please ensure the device is connected properly.\n\nTroubleshooting advice:\n- Reconnect the device, try different USB ports/cables, avoid passive USB hubs\n- Use a USB data cable (battery charging cables may not work)\n- Check if the operating system detects the device (if not, reboot your machine)\n- Ensure your user account has permissions to use the device\n- Refer to the device compatibility list on the <a href=\"https://github.com/lesserkuma/FlashGBX/#compatible-cartridge-readerwriter-hardware\">GitHub page</a>".replace("\n", "<br>"), standardButtons=QtWidgets.QMessageBox.Ok).exec()
|
||||
QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="No compatible devices found. Please ensure the device is connected properly.\n\nTroubleshooting advice:\n- Reconnect the device, try different USB ports/cables, avoid passive USB hubs\n- Use a USB data cable (battery charging cables may not work)\n- Check if the operating system detects the device (if not, reboot your machine)\n- Ensure your user account has permissions to use the device\n- Refer to the device compatibility list on the <a href=\"https://github.com/lesserkuma/FlashGBX/#compatible-cartridge-readerwriter-hardware\">GitHub page</a>\n- Update the firmware manually through Options → Tools → Firmware Updater".replace("\n", "<br>"), standardButtons=QtWidgets.QMessageBox.Ok).exec()
|
||||
|
||||
self.lblDevice.setText("No devices found.")
|
||||
self.lblDevice.setStyleSheet("")
|
||||
|
|
@ -1080,7 +1085,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
else:
|
||||
msg += " This may indicate a bad dump, however this can be normal for some reproduction cartridges, unlicensed games, prototypes, patched games and intentional overdumps. You can also try to change the read mode in the options."
|
||||
if self.CONN.GetMode() == "DMG" and self.cmbDMGHeaderMapperResult.currentText() == "MBC1":
|
||||
msg += "\n\nIf this is a NP GB-Memory Cartridge, please use the “Retry as G-MMC1” button."
|
||||
msg += "\n\nIf this is a NP GB-Memory Cartridge, try the “Retry as G-MMC1” option."
|
||||
button_gmmc1 = msgbox.addButton(" Retry as G-MMC1 ", QtWidgets.QMessageBox.ActionRole)
|
||||
msgbox.setText(msg + msg_te)
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
|
|
@ -1244,6 +1249,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.CONN.INFO["dump_info"]["batteryless_sram"] = temp3
|
||||
else:
|
||||
self.ReadCartridge(resetStatus=False)
|
||||
|
||||
elif self.CONN.INFO["last_action"] == 6: # Detect Cartridge
|
||||
self.lblStatus4a.setText("Ready.")
|
||||
self.CONN.INFO["last_action"] = 0
|
||||
self.FinishDetectCartridge(self.CONN.INFO["detect_cart"])
|
||||
|
||||
else:
|
||||
self.lblStatus4a.setText("Ready.")
|
||||
|
|
@ -1375,11 +1385,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
if cart_type == 0:
|
||||
cart_type = self.DetectCartridge(canSkipMessage=True)
|
||||
if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
|
||||
if self.STATUS["detected_cart_type"] == "":
|
||||
self.STATUS["detected_cart_type"] = "WAITING_FLASH"
|
||||
self.STATUS["detect_cartridge_args"] = { "dpath":path }
|
||||
self.STATUS["can_skip_message"] = True
|
||||
self.DetectCartridge(checkSaveType=False)
|
||||
return
|
||||
cart_type = self.STATUS["detected_cart_type"]
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if cart_type is False: # clicked Cancel button
|
||||
return
|
||||
elif cart_type is None or cart_type == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
if self.CONN.GetMode() == "DMG":
|
||||
|
|
@ -1387,6 +1406,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
elif self.CONN.GetMode() == "AGB":
|
||||
self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
|
||||
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if self.CONN.GetMode() == "DMG":
|
||||
self.SetDMGMapperResult(carts[cart_type])
|
||||
mbc = Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex())
|
||||
|
|
@ -1467,7 +1488,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if not Util.compare_mbc(hdr["mapper_raw"], mbc):
|
||||
mbc1 = Util.get_mbc_name(mbc)
|
||||
mbc2 = Util.get_mbc_name(hdr["mapper_raw"])
|
||||
compatible_mbc = [ "None", "MBC2", "MBC3", "MBC30", "MBC5", "MBC7", "MAC-GBD", "G-MMC1", "HuC-1", "HuC-3" ]
|
||||
compatible_mbc = [ "None", "MBC2", "MBC3", "MBC30", "MBC5", "MBC7", "MAC-GBD", "G-MMC1", "HuC-1", "HuC-3", "Unlicensed MBCX Mapper" ]
|
||||
if (mbc2 == "None") or (mbc1 == "G-MMC1" and mbc2 == "MBC1") or (mbc2 == "G-MMC1" and mbc1 == "MBC1"):
|
||||
pass
|
||||
elif mbc2 != "None" and not (mbc1 in compatible_mbc and mbc2 in compatible_mbc):
|
||||
|
|
@ -1640,14 +1661,25 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
return
|
||||
cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
|
||||
if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
|
||||
cart_type = self.DetectCartridge()
|
||||
if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
|
||||
if self.STATUS["detected_cart_type"] == "":
|
||||
self.STATUS["detected_cart_type"] = "WAITING_SAVE_READ"
|
||||
self.STATUS["detect_cartridge_args"] = { "dpath":path }
|
||||
self.STATUS["can_skip_message"] = True
|
||||
self.DetectCartridge(checkSaveType=True)
|
||||
return
|
||||
cart_type = self.STATUS["detected_cart_type"]
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if cart_type is False: # clicked Cancel button
|
||||
return
|
||||
elif cart_type is None or cart_type == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
|
||||
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
|
||||
detected = self.CONN.INFO["dump_info"]["batteryless_sram"]
|
||||
else:
|
||||
|
|
@ -1678,10 +1710,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.STATUS["last_path"] = path
|
||||
self.STATUS["args"] = args
|
||||
|
||||
def WriteRAM(self, dpath="", erase=False, test=False):
|
||||
def WriteRAM(self, dpath="", erase=False, test=False, skip_warning=False):
|
||||
if not self.CheckDeviceAlive(): return
|
||||
mode = self.CONN.GetMode()
|
||||
|
||||
if erase is True: dpath = ""
|
||||
|
||||
if dpath == "":
|
||||
path = Util.GenerateFileName(mode=mode, header=self.CONN.INFO, settings=self.SETTINGS)
|
||||
path = os.path.splitext(path)[0]
|
||||
|
|
@ -1716,14 +1750,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
|
||||
filesize = 0
|
||||
if dpath != "":
|
||||
text = "The following save data file will now be written to the cartridge:\n" + dpath
|
||||
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
if not skip_warning:
|
||||
text = "The following save data file will now be written to the cartridge:\n" + dpath
|
||||
answer = QtWidgets.QMessageBox.question(self, "{:s} {:s}".format(APPNAME, VERSION), text, QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
path = dpath
|
||||
self.SETTINGS.setValue(setting_name, os.path.dirname(path))
|
||||
elif erase:
|
||||
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
|
||||
if not skip_warning:
|
||||
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
|
||||
elif test:
|
||||
path = None
|
||||
if self.CONN.GetFWBuildDate() == "": # Legacy Mode
|
||||
|
|
@ -2020,14 +2056,25 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
return
|
||||
cart_type = self.cmbAGBCartridgeTypeResult.currentIndex()
|
||||
if cart_type == 0 or ("dump_info" not in self.CONN.INFO or "batteryless_sram" not in self.CONN.INFO["dump_info"]):
|
||||
cart_type = self.DetectCartridge()
|
||||
if "detected_cart_type" not in self.STATUS: self.STATUS["detected_cart_type"] = ""
|
||||
if self.STATUS["detected_cart_type"] == "":
|
||||
self.STATUS["detected_cart_type"] = "WAITING_SAVE_WRITE"
|
||||
self.STATUS["detect_cartridge_args"] = { "dpath":path, "erase":erase }
|
||||
self.STATUS["can_skip_message"] = True
|
||||
self.DetectCartridge(checkSaveType=True)
|
||||
return
|
||||
cart_type = self.STATUS["detected_cart_type"]
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if cart_type is False: # clicked Cancel button
|
||||
return
|
||||
elif cart_type is None or cart_type == 0:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge type could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "A compatible flash cartridge profile could not be auto-detected.", QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
self.cmbAGBCartridgeTypeResult.setCurrentIndex(cart_type)
|
||||
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
|
||||
if "dump_info" in self.CONN.INFO and "batteryless_sram" in self.CONN.INFO["dump_info"]:
|
||||
detected = self.CONN.INFO["dump_info"]["batteryless_sram"]
|
||||
else:
|
||||
|
|
@ -2087,6 +2134,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
except:
|
||||
pass
|
||||
|
||||
intro_msg = ""
|
||||
if detected is not False:
|
||||
try:
|
||||
loc_index = locs.index(detected["bl_offset"])
|
||||
|
|
@ -2154,7 +2202,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
|
||||
if self.CONN.GetMode() == "DMG":
|
||||
mbc = Util.get_mbc_name(Util.ConvertMapperTypeToMapper(self.cmbDMGHeaderMapperResult.currentIndex()))
|
||||
if mbc in ("MBC3", "MBC30"):
|
||||
if mbc in ("MBC3", "MBC30", "Unlicensed MBCX Mapper"):
|
||||
dlg_args = {
|
||||
"title":"MBC3/MBC30 Real Time Clock Editor",
|
||||
"intro":"Enter the number of days, hours, minutes and seconds that passed since the RTC initially started.\n\nPlease note that all values are internal values. The game may use these only as a relative reference.",
|
||||
|
|
@ -2379,10 +2427,16 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
|
||||
if not self.CheckDeviceAlive(setMode=setTo): return
|
||||
|
||||
if self.optDMG.isChecked() and (mode == "AGB" or mode == None):
|
||||
self.CONN.SetMode("DMG")
|
||||
elif self.optAGB.isChecked() and (mode == "DMG" or mode == None):
|
||||
self.CONN.SetMode("AGB")
|
||||
try:
|
||||
if self.optDMG.isChecked() and (mode == "AGB" or mode == None):
|
||||
self.CONN.SetMode("DMG")
|
||||
elif self.optAGB.isChecked() and (mode == "DMG" or mode == None):
|
||||
self.CONN.SetMode("AGB")
|
||||
except BrokenPipeError:
|
||||
msg = "Failed to turn on the cartridge power.\n\nAs a workaround, try to disable the \"Automatic cartridge power off\" setting and then hotplug the cartridge after clicking \"Refresh\"."
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), msg, QtWidgets.QMessageBox.Ok)
|
||||
self.DisconnectDevice()
|
||||
return False
|
||||
|
||||
ok = self.ReadCartridge()
|
||||
qt_app.processEvents()
|
||||
|
|
@ -2422,7 +2476,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
return False
|
||||
|
||||
if self.CONN.CheckROMStable() is False and resetStatus:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge and then try again.", QtWidgets.QMessageBox.Ok)
|
||||
try:
|
||||
if data != bytearray(data[0] * len(data)):
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge and then try again.", QtWidgets.QMessageBox.Ok)
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.CONN.GetMode() == "DMG":
|
||||
self.cmbDMGHeaderMapperResult.clear()
|
||||
|
|
@ -2447,7 +2505,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblDMGGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
|
||||
temp = data["db"]["gn"]
|
||||
self.lblDMGGameNameResult.setText(temp)
|
||||
while self.lblDMGGameNameResult.fontMetrics().boundingRect(self.lblDMGGameNameResult.text()).width() > 200:
|
||||
while self.lblDMGGameNameResult.fontMetrics().boundingRect(self.lblDMGGameNameResult.text()).width() > 240:
|
||||
temp = temp[:-1]
|
||||
self.lblDMGGameNameResult.setText(temp + "…")
|
||||
if temp != data["db"]["gn"]:
|
||||
|
|
@ -2569,7 +2627,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblAGBHeaderGameCodeRevisionResult.setText("{:s}-{:s}".format(data["db"]["gc"], str(data["version"])))
|
||||
temp = data["db"]["gn"]
|
||||
self.lblAGBGameNameResult.setText(temp)
|
||||
while self.lblAGBGameNameResult.fontMetrics().boundingRect(self.lblAGBGameNameResult.text()).width() > 200:
|
||||
while self.lblAGBGameNameResult.fontMetrics().boundingRect(self.lblAGBGameNameResult.text()).width() > 240:
|
||||
temp = temp[:-1]
|
||||
self.lblAGBGameNameResult.setText(temp + "…")
|
||||
if temp != data["db"]["gn"]:
|
||||
|
|
@ -2680,13 +2738,12 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.btnRestoreRAM.setEnabled(True)
|
||||
self.btnHeaderRefresh.setFocus()
|
||||
self.SetProgressBars(min=0, max=100, value=0)
|
||||
self.lblStatus4a.setText("Ready.")
|
||||
qt_app.processEvents()
|
||||
|
||||
if data['game_title'][:11] == "YJencrypted" and resetStatus:
|
||||
QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "This cartridge may be protected against reading or writing a ROM. If you don’t want to risk this cartridge to render itself unusable, please do not try to write a new ROM to it.", QtWidgets.QMessageBox.Ok)
|
||||
|
||||
def DetectCartridge(self, canSkipMessage=False):
|
||||
def DetectCartridge(self, checkSaveType=True):
|
||||
if not self.CheckDeviceAlive(): return
|
||||
if not self.CONN.CheckROMStable():
|
||||
answer = QtWidgets.QMessageBox.warning(self, "{:s} {:s}".format(APPNAME, VERSION), "The cartridge connection is unstable!\nPlease clean the cartridge pins, carefully realign the cartridge for best results.\n\nContinue anyway?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
|
||||
|
|
@ -2702,12 +2759,20 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblStatus2aResult.setText("–")
|
||||
self.lblStatus3aResult.setText("–")
|
||||
self.lblStatus4aResult.setText("")
|
||||
self.lblStatus4a.setText("Analyzing Cartridge...")
|
||||
# self.lblStatus4a.setText("Analyzing Cartridge...")
|
||||
self.SetProgressBars(min=0, max=0, value=1)
|
||||
qt_app.processEvents()
|
||||
|
||||
if "can_skip_message" not in self.STATUS: self.STATUS["can_skip_message"] = False
|
||||
limitVoltage = str(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled")).lower() == "enabled"
|
||||
self.CONN.DetectCartridge(fncSetProgress=self.PROGRESS.SetProgress, args={"limitVoltage":limitVoltage, "checkSaveType":checkSaveType})
|
||||
|
||||
def FinishDetectCartridge(self, ret):
|
||||
self.lblStatus1aResult.setText("–")
|
||||
self.lblStatus2aResult.setText("–")
|
||||
self.lblStatus3aResult.setText("–")
|
||||
|
||||
limitVoltage = str(self.SETTINGS.value("AutoDetectLimitVoltage", default="disabled")).lower() == "enabled"
|
||||
ret = self.CONN.DetectCartridge(limitVoltage=limitVoltage, checkSaveType=not canSkipMessage)
|
||||
if ret is False:
|
||||
QtWidgets.QMessageBox.critical(self, "{:s} {:s}".format(APPNAME, VERSION), "An error occured while trying to analyze the cartridge and you may need to physically reconnect the device.\n\nThis cartridge may not be auto-detectable, please select the cartridge type manually.", QtWidgets.QMessageBox.Ok)
|
||||
self.DisconnectDevice()
|
||||
|
|
@ -2716,7 +2781,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
(header, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, _, flash_id, detected_size) = ret
|
||||
|
||||
# Save Type
|
||||
if not canSkipMessage:
|
||||
if not self.STATUS["can_skip_message"]:
|
||||
try:
|
||||
if save_type is not None and save_type is not False:
|
||||
if self.CONN.GetMode() == "DMG":
|
||||
|
|
@ -2735,6 +2800,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
supp_cart_types = self.CONN.GetSupportedCartridgesDMG()
|
||||
elif self.CONN.GetMode() == "AGB":
|
||||
supp_cart_types = self.CONN.GetSupportedCartridgesAGB()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
except Exception as e:
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text="An unknown error occured. Please try again.\n\n" + str(e), standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
msgbox.exec()
|
||||
|
|
@ -2768,7 +2835,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
# Save Type
|
||||
msg_save_type_s = ""
|
||||
temp = ""
|
||||
if not canSkipMessage and save_type is not False and save_type is not None:
|
||||
if not self.STATUS["can_skip_message"] and save_type is not False and save_type is not None:
|
||||
if save_chip is not None:
|
||||
temp = "{:s} ({:s})".format(Util.AGB_Header_Save_Types[save_type], save_chip)
|
||||
else:
|
||||
|
|
@ -2916,7 +2983,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
msg = "The following cartridge configuration was detected:<br><br>"
|
||||
if found_supported:
|
||||
dontShowAgain = str(self.SETTINGS.value("SkipAutodetectMessage", default="disabled")).lower() == "enabled"
|
||||
if not dontShowAgain or not canSkipMessage:
|
||||
if not dontShowAgain or not self.STATUS["can_skip_message"]:
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_cart_type_s, msg_gbmem)
|
||||
temp = temp[:-4]
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION), text=temp)
|
||||
|
|
@ -2926,7 +2993,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
button_cancel = None
|
||||
msgbox.setDefaultButton(button_ok)
|
||||
cb = QtWidgets.QCheckBox("Always skip this message", checked=False)
|
||||
if canSkipMessage:
|
||||
if self.STATUS["can_skip_message"]:
|
||||
button_cancel = msgbox.addButton("&Cancel", QtWidgets.QMessageBox.RejectRole)
|
||||
msgbox.setEscapeButton(button_cancel)
|
||||
msgbox.setCheckBox(cb)
|
||||
|
|
@ -2935,7 +3002,7 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
|
||||
msgbox.exec()
|
||||
dontShowAgain = cb.isChecked()
|
||||
if dontShowAgain and canSkipMessage: self.SETTINGS.setValue("SkipAutodetectMessage", "enabled")
|
||||
if dontShowAgain and self.STATUS["can_skip_message"]: self.SETTINGS.setValue("SkipAutodetectMessage", "enabled")
|
||||
|
||||
if msgbox.clickedButton() == button_details:
|
||||
show_details = True
|
||||
|
|
@ -2950,7 +3017,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.btnHeaderRefresh.setFocus()
|
||||
self.SetProgressBars(min=0, max=100, value=0)
|
||||
self.lblStatus4a.setText("Ready.")
|
||||
return False
|
||||
self.STATUS["can_skip_message"] = False
|
||||
if "detected_cart_type" in self.STATUS: del(self.STATUS["detected_cart_type"])
|
||||
return
|
||||
|
||||
if not found_supported or show_details is True:
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Information, windowTitle="{:s} {:s}".format(APPNAME, VERSION))
|
||||
|
|
@ -2976,7 +3045,9 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
if answer == QtWidgets.QMessageBox.Yes:
|
||||
self.SETTINGS.setValue("AutoDetectLimitVoltage", "disabled")
|
||||
self.mnuConfig.actions()[4].setChecked(False)
|
||||
return self.DetectCartridge()
|
||||
self.STATUS["can_skip_message"] = False
|
||||
self.DetectCartridge()
|
||||
return
|
||||
|
||||
temp = "{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}{:s}".format(msg, msg_header_s, msg_flash_size_s, msg_save_type_s, msg_flash_mapper_s, msg_flash_id_s, msg_cfi_s, msg_cart_type_s_detail, msg_gbmem, msg_fw)
|
||||
temp = temp[:-4]
|
||||
|
|
@ -3006,7 +3077,27 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.btnHeaderRefresh.setFocus()
|
||||
self.SetProgressBars(min=0, max=100, value=0)
|
||||
self.lblStatus4a.setText("Ready.")
|
||||
return cart_type
|
||||
|
||||
waiting = None
|
||||
if "detected_cart_type" in self.STATUS and self.STATUS["detected_cart_type"] in ("WAITING_FLASH", "WAITING_SAVE_READ", "WAITING_SAVE_WRITE"):
|
||||
waiting = self.STATUS["detected_cart_type"]
|
||||
self.STATUS["detected_cart_type"] = cart_type
|
||||
self.STATUS["can_skip_message"] = False
|
||||
|
||||
if waiting == "WAITING_FLASH":
|
||||
if "detect_cartridge_args" in self.STATUS:
|
||||
self.FlashROM(dpath=self.STATUS["detect_cartridge_args"]["dpath"])
|
||||
del(self.STATUS["detect_cartridge_args"])
|
||||
else:
|
||||
self.FlashROM()
|
||||
elif waiting == "WAITING_SAVE_READ":
|
||||
self.BackupRAM()
|
||||
elif waiting == "WAITING_SAVE_WRITE":
|
||||
if "detect_cartridge_args" in self.STATUS:
|
||||
self.WriteRAM(dpath=self.STATUS["detect_cartridge_args"]["dpath"], erase=self.STATUS["detect_cartridge_args"]["erase"], skip_warning=True)
|
||||
del(self.STATUS["detect_cartridge_args"])
|
||||
else:
|
||||
self.WriteRAM()
|
||||
|
||||
def WaitProgress(self, args):
|
||||
if args["user_action"] == "REINSERT_CART":
|
||||
|
|
@ -3037,6 +3128,8 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.grpStatus.setTitle("Transfer Status (Write Save Data)")
|
||||
elif args["method"] == "SAVE_WRITE_VERIFY":
|
||||
self.grpStatus.setTitle("Transfer Status (Verify Save Data)")
|
||||
elif args["method"] == "DETECT_CART":
|
||||
self.grpStatus.setTitle("Transfer Status (Analyze Cartridge)")
|
||||
|
||||
if "error" in args:
|
||||
self.lblStatus4a.setText("Failed!")
|
||||
|
|
@ -3120,6 +3213,11 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
self.lblStatus4aResult.setText("")
|
||||
self.btnCancel.setEnabled(args["abortable"])
|
||||
self.SetProgressBars(min=0, max=size, value=pos)
|
||||
elif args["action"] == "UPDATE_INFO":
|
||||
self.lblStatus4a.setText(args["text"])
|
||||
self.lblStatus4aResult.setText("")
|
||||
self.btnCancel.setEnabled(args["abortable"])
|
||||
self.SetProgressBars(min=0, max=size, value=pos)
|
||||
elif args["action"] == "FINISHED":
|
||||
if pos > 0:
|
||||
self.lblStatus1aResult.setText(Util.formatFileSize(size=pos))
|
||||
|
|
@ -3338,18 +3436,6 @@ class FlashGBX_GUI(QtWidgets.QWidget):
|
|||
except:
|
||||
pass
|
||||
qt_app.exec()
|
||||
# # Taskbar Progress on Windows only
|
||||
# try:
|
||||
# from PySide6.QtWin import QtWinTaskbarButton, QtWin
|
||||
# myappid = 'lesserkuma.flashgbx'
|
||||
# QtWin.setAppUserModelId(myappid)
|
||||
# taskbar_button = QtWinTaskbarButton()
|
||||
# self.TBPROG = taskbar_button.progress()
|
||||
# self.TBPROG.setRange(0, 100)
|
||||
# taskbar_button.setWindow(self.windowHandle())
|
||||
# self.TBPROG.setVisible(False)
|
||||
# except ImportError:
|
||||
# pass
|
||||
|
||||
else: # PySide2
|
||||
qt_app.exec_()
|
||||
|
|
|
|||
|
|
@ -708,6 +708,7 @@ class Flashcart_DMG_BUNG_16M(Flashcart):
|
|||
self.CartWrite([[0x2000, 0x02]], fast_write=False)
|
||||
self.CartWrite([[0x6AAA, 0x90]], fast_write=True)
|
||||
cart_flash_id = list(self.CartRead(0, 4))
|
||||
verified = False
|
||||
if rom != cart_flash_id and cart_flash_id == self.CONFIG["flash_ids"][0]:
|
||||
self.Reset()
|
||||
verified = True
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class LK_Device(ABC):
|
|||
"AGB_IRQ_ENABLED":[8, 0x10],
|
||||
}
|
||||
|
||||
ACTIONS = {"ROM_READ":1, "SAVE_READ":2, "SAVE_WRITE":3, "ROM_WRITE":4, "ROM_WRITE_VERIFY":4, "SAVE_WRITE_VERIFY":3, "RTC_WRITE":5}
|
||||
ACTIONS = {"ROM_READ":1, "SAVE_READ":2, "SAVE_WRITE":3, "ROM_WRITE":4, "ROM_WRITE_VERIFY":4, "SAVE_WRITE_VERIFY":3, "RTC_WRITE":5, "DETECT_CART":6}
|
||||
SUPPORTED_CARTS = {}
|
||||
|
||||
FW = {}
|
||||
|
|
@ -229,8 +229,14 @@ class LK_Device(ABC):
|
|||
#################################################################
|
||||
|
||||
def IsSupportedMbc(self, mbc):
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205 )
|
||||
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206 )
|
||||
|
||||
def IsUnregistered(self):
|
||||
if "unregistered" in self.FW:
|
||||
return self.FW["unregistered"]
|
||||
else:
|
||||
return False
|
||||
|
||||
def TryConnect(self, port, baudrate):
|
||||
dprint("Trying to connect to {:s} at baud rate {:d} ({:s})".format(port, baudrate, type(self).__module__))
|
||||
try:
|
||||
|
|
@ -635,11 +641,11 @@ class LK_Device(ABC):
|
|||
ret = self._read(1)
|
||||
if ret != 0x01:
|
||||
if ret is False:
|
||||
msg = "Error: No response while trying to write to the device."
|
||||
msg = "Error: No response while trying to communicate with the device."
|
||||
time.sleep(0.5)
|
||||
self.DEVICE.reset_input_buffer()
|
||||
else:
|
||||
msg = "Error: Bad response “{:s}” while trying to write to the device.".format(str(ret))
|
||||
msg = "Error: Bad response “{:s}” while trying to communicate with the device.".format(str(ret))
|
||||
print(f"{ANSI.RED}{msg}{ANSI.RESET}")
|
||||
dprint(msg)
|
||||
#self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A critical communication error occured during a write. Please avoid passive USB hubs, try different USB ports/cables and re-connect the device."})
|
||||
|
|
@ -741,7 +747,7 @@ class LK_Device(ABC):
|
|||
if self.DEVICE.in_waiting == 0:
|
||||
dprint("Waiting for ACK...")
|
||||
hp -= 1
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
temp = self.DEVICE.read(self.DEVICE.in_waiting)
|
||||
if len(temp) >= 1: temp = temp[len(temp) - 1]
|
||||
|
|
@ -751,6 +757,13 @@ class LK_Device(ABC):
|
|||
self._write(self.DEVICE_CMD["QUERY_CART_PWR"])
|
||||
time.sleep(0.05)
|
||||
hp -= 1
|
||||
|
||||
if hp == 0:
|
||||
self.DEVICE.close()
|
||||
self.DEVICE = None
|
||||
self.ERROR = True
|
||||
raise BrokenPipeError("Couldn’t power on the cartridge.")
|
||||
|
||||
self.DEVICE.timeout = self.DEVICE_TIMEOUT
|
||||
|
||||
self._write(self.DEVICE_CMD["QUERY_CART_PWR"])
|
||||
|
|
@ -854,21 +867,25 @@ class LK_Device(ABC):
|
|||
def GetSupportedCartridgesAGB(self):
|
||||
return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values()))
|
||||
|
||||
def SetProgress(self, args):
|
||||
def SetProgress(self, args, signal=None):
|
||||
if self.CANCEL and args["action"] not in ("ABORT", "FINISHED", "ERROR"): return
|
||||
if "pos" in args: self.POS = args["pos"]
|
||||
if args["action"] == "UPDATE_POS": self.INFO["transferred"] = args["pos"]
|
||||
if signal is None:
|
||||
signal = self.SIGNAL
|
||||
|
||||
try:
|
||||
self.SIGNAL.emit(args)
|
||||
signal.emit(args)
|
||||
except AttributeError:
|
||||
if self.SIGNAL is not None:
|
||||
self.SIGNAL(args)
|
||||
if signal is not None:
|
||||
signal(args)
|
||||
|
||||
if args["action"] == "INITIALIZE":
|
||||
if self.CanPowerCycleCart(): self.CartPowerOn() # Ensure cart is powered
|
||||
self.POS = 0
|
||||
elif args["action"] == "FINISHED":
|
||||
self.POS = 0
|
||||
signal = None
|
||||
self.SIGNAL = None
|
||||
|
||||
def Debug(self):
|
||||
|
|
@ -905,7 +922,7 @@ class LK_Device(ABC):
|
|||
header = self.ReadROM(0, 0x180)
|
||||
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
with open("debug_header.bin", "wb") as f: f.write(header)
|
||||
with open(Util.CONFIG_PATH + "/debug_header.bin", "wb") as f: f.write(header)
|
||||
|
||||
# Parse ROM header
|
||||
if self.MODE == "DMG":
|
||||
|
|
@ -1069,7 +1086,19 @@ class LK_Device(ABC):
|
|||
|
||||
return data
|
||||
|
||||
def DetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=True):
|
||||
def _DetectCartridge(self, args): # Wrapper for thread call
|
||||
self.SetProgress({"action":"INITIALIZE", "abortable":False, "method":"DETECT_CART"})
|
||||
signal = self.SIGNAL
|
||||
self.SIGNAL = None
|
||||
ret = self.DoDetectCartridge(mbc=None, limitVoltage=args["limitVoltage"], checkSaveType=args["checkSaveType"], signal=signal)
|
||||
self.INFO["detect_cart"] = ret
|
||||
self.INFO["last_action"] = self.ACTIONS["DETECT_CART"]
|
||||
self.INFO["action"] = None
|
||||
self.SIGNAL = signal
|
||||
self.SetProgress({"action":"FINISHED"})
|
||||
return True
|
||||
|
||||
def DoDetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=True, signal=None):
|
||||
self.SIGNAL = None
|
||||
self.CANCEL = False
|
||||
self.ERROR = False
|
||||
|
|
@ -1079,8 +1108,10 @@ class LK_Device(ABC):
|
|||
sram_unstable = None
|
||||
save_size = None
|
||||
checkBatterylessSRAM = False
|
||||
_apot = 0
|
||||
|
||||
# Header
|
||||
if signal is not None: self.SetProgress({"action":"UPDATE_INFO", "text":"Detecting ROM..."}, signal=signal)
|
||||
info = self.ReadInfo(checkRtc=True)
|
||||
if self.MODE == "DMG" and mbc is None:
|
||||
mbc = info["mapper_raw"]
|
||||
|
|
@ -1097,6 +1128,7 @@ class LK_Device(ABC):
|
|||
self._set_fw_variable("AUTO_POWEROFF_TIME", 5000)
|
||||
|
||||
# Detect Flash Cart
|
||||
if signal is not None: self.SetProgress({"action":"UPDATE_INFO", "text":"Detecting Flash..."}, signal=signal)
|
||||
ret = self.DetectFlash(limitVoltage=limitVoltage)
|
||||
if ret is False: return False
|
||||
(cart_types, cart_type_id, flash_id, cfi_s, cfi, detected_size) = ret
|
||||
|
|
@ -1113,6 +1145,7 @@ class LK_Device(ABC):
|
|||
elif self.MODE == "AGB" and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 1:
|
||||
checkSaveType = False
|
||||
elif self.MODE == "DMG" and "mbc" in cart_type and cart_type["mbc"] == 0x105: # G-MMC1
|
||||
if signal is not None: self.SetProgress({"action":"UPDATE_INFO", "text":"Detecting GB-Memory..."}, signal=signal)
|
||||
header = self.ReadROM(0, 0x180)
|
||||
data = RomFileDMG(header).GetHeader()
|
||||
_mbc = DMG_MBC().GetInstance(args={"mbc":cart_type["mbc"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
||||
|
|
@ -1131,6 +1164,7 @@ class LK_Device(ABC):
|
|||
|
||||
# Save Type and Size
|
||||
if checkSaveType:
|
||||
if signal is not None: self.SetProgress({"action":"UPDATE_INFO", "text":"Detecting save type..."}, signal=signal)
|
||||
if self.MODE == "DMG":
|
||||
save_size = 131072
|
||||
save_type = 0x04
|
||||
|
|
@ -1148,6 +1182,8 @@ class LK_Device(ABC):
|
|||
args = { 'mode':2, 'path':None, 'mbc':mbc, 'save_type':save_type, 'rtc':False, 'detect':True }
|
||||
elif self.MODE == "AGB":
|
||||
args = { 'mode':2, 'path':None, 'mbc':mbc, 'save_type':8, 'rtc':False, 'detect':True }
|
||||
else:
|
||||
return
|
||||
|
||||
ret = self._BackupRestoreRAM(args=args)
|
||||
|
||||
|
|
@ -1175,6 +1211,10 @@ class LK_Device(ABC):
|
|||
if self.INFO["data"][i:i+3] != bytearray([self.INFO["data"][i]] * 3):
|
||||
check = False
|
||||
break
|
||||
|
||||
if self.INFO["data"][0:0x8000] == self.INFO["data"][0x8000:0x10000]: # MBCX
|
||||
check = True
|
||||
|
||||
if check:
|
||||
save_size = 32768
|
||||
save_type = 0x03
|
||||
|
|
@ -1201,7 +1241,13 @@ class LK_Device(ABC):
|
|||
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)]
|
||||
save_chip = Util.AGB_Flash_Save_Chips[flash_save_id]
|
||||
if save_size == 131072:
|
||||
|
||||
if flash_save_id in (0xBF5B, 0xFFFF): # Bootlegs
|
||||
if self.INFO["data"][0:0x10000] == self.INFO["data"][0x10000:0x20000]:
|
||||
save_type = 4
|
||||
else:
|
||||
save_type = 5
|
||||
elif save_size == 131072:
|
||||
save_type = 5
|
||||
elif save_size == 65536:
|
||||
save_type = 4
|
||||
|
|
@ -1395,6 +1441,8 @@ class LK_Device(ABC):
|
|||
command = "DMG_CART_READ"
|
||||
elif self.MODE == "AGB":
|
||||
command = "AGB_CART_READ"
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
for n in range(0, num):
|
||||
self._write(self.DEVICE_CMD[command])
|
||||
|
|
@ -1944,7 +1992,7 @@ class LK_Device(ABC):
|
|||
chunk_len = max_length if left > max_length else left
|
||||
chunk_from = offset + chunk_pos
|
||||
chunk_to = offset + chunk_pos + chunk_len
|
||||
dprint(f"Running CRC32 verification, comparing between source 0x{chunk_from:x}~0x{chunk_to:x} and target 0x{address+chunk_pos:x}~0x{address+chunk_pos+chunk_len:x}")
|
||||
dprint(f"Running CRC32 verification, comparing between source 0x{chunk_from:X}~0x{chunk_to:X} and target 0x{address+chunk_pos:X}~0x{address+chunk_pos+chunk_len:X}")
|
||||
crc32_expected = zlib.crc32(buffer[chunk_from:chunk_to])
|
||||
|
||||
for i in range(0, 2 if (reset is True and flashcart is not None) else 1): # for retrying with reset
|
||||
|
|
@ -2008,6 +2056,9 @@ class LK_Device(ABC):
|
|||
self.SetAGBReadMethod(0)
|
||||
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
||||
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
rom = self._cart_read(0, 8)
|
||||
|
||||
dprint("Resetting and unlocking all cart types")
|
||||
|
|
@ -2182,7 +2233,7 @@ class LK_Device(ABC):
|
|||
flash_id_methods.append([we - 1, i, list(cmp), cfi_buffer, flash_id_cmds[i]["read_identifier"]])
|
||||
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
with open("debug_cfi.bin", "wb") as f: f.write(cfi_buffer)
|
||||
with open(Util.CONFIG_PATH + "/debug_cfi.bin", "wb") as f: f.write(cfi_buffer)
|
||||
try:
|
||||
magic = "{:s}{:s}{:s}".format(chr(cfi_buffer[0x20]), chr(cfi_buffer[0x22]), chr(cfi_buffer[0x24]))
|
||||
d_swap = (0, 0)
|
||||
|
|
@ -2199,7 +2250,7 @@ class LK_Device(ABC):
|
|||
for j in range(0, len(cfi_buffer)):
|
||||
cfi_buffer[j] = bitswap(cfi_buffer[j], d_swap[j2])
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
with open("debug_cfi_d0d1+d6d7.bin", "wb") as f: f.write(cfi_buffer)
|
||||
with open(Util.CONFIG_PATH + "/debug_cfi_d0d1+d6d7.bin", "wb") as f: f.write(cfi_buffer)
|
||||
else:
|
||||
cfi_buffer = None
|
||||
except:
|
||||
|
|
@ -2281,6 +2332,8 @@ class LK_Device(ABC):
|
|||
supp_flash_types = self.GetSupportedCartridgesDMG()
|
||||
elif self.MODE == "AGB":
|
||||
supp_flash_types = self.GetSupportedCartridgesAGB()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if "flash_size" in supp_flash_types[1][flash_types[0]]:
|
||||
size = supp_flash_types[1][flash_types[0]]["flash_size"]
|
||||
|
|
@ -2381,6 +2434,9 @@ class LK_Device(ABC):
|
|||
def FlashROM(self, fncSetProgress=None, args=None):
|
||||
self.DoTransfer(4, fncSetProgress, args)
|
||||
|
||||
def DetectCartridge(self, fncSetProgress=None, args=None):
|
||||
self.DoTransfer(5, fncSetProgress, args)
|
||||
|
||||
#################################################################
|
||||
|
||||
def _BackupROM(self, args):
|
||||
|
|
@ -2416,8 +2472,10 @@ class LK_Device(ABC):
|
|||
|
||||
# Firmware check L8
|
||||
if self.FW["fw_ver"] < 8 and flashcart and "enable_pullups" in cart_type:
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
||||
return False
|
||||
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
||||
#return False
|
||||
print("{:s}Note: This cartridge may not be fully compatible with your {:s} device running an old or legacy firmware version.{:s}".format(ANSI.YELLOW, self.FW["pcb_name"], ANSI.RESET))
|
||||
del(cart_type["enable_pullups"])
|
||||
# Firmware check L8
|
||||
|
||||
buffer_len = 0x4000
|
||||
|
|
@ -2712,12 +2770,11 @@ class LK_Device(ABC):
|
|||
pos_total += len(temp)
|
||||
|
||||
if "verify_write" in args:
|
||||
#if pos_total >= len(args["verify_write"]): break
|
||||
check = args["verify_write"][pos_total-len(temp):pos_total]
|
||||
if temp[:len(check)] != check:
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
dprint("Writing 0x{:X} bytes to debug_verify_0x{:X}.bin".format(len(temp), pos_total-len(temp)))
|
||||
with open("debug_verify_0x{:X}.bin".format(pos_total-len(temp)), "ab") as f: f.write(temp)
|
||||
with open(Util.CONFIG_PATH + "/debug_verify_0x{:X}.bin".format(pos_total-len(temp)), "ab") as f: f.write(temp)
|
||||
|
||||
for i in range(0, pos_total):
|
||||
if (i < len(args["verify_write"]) - 1) and (i < pos_total - 1) and args["verify_write"][i] != buffer[i]:
|
||||
|
|
@ -2818,7 +2875,7 @@ class LK_Device(ABC):
|
|||
|
||||
# Check for ROM loops
|
||||
self.INFO["loop_detected"] = False
|
||||
temp = min(0x2000000, len(buffer))
|
||||
temp = len(buffer)
|
||||
while temp > 0x4000:
|
||||
temp = temp >> 1
|
||||
if (buffer[0:0x4000] == buffer[temp:temp+0x4000]):
|
||||
|
|
@ -2861,6 +2918,8 @@ class LK_Device(ABC):
|
|||
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
||||
if _agb_gpio.HasRTC() is not True: return False
|
||||
ret = _agb_gpio.WriteRTCDict(args["rtc_dict"])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return ret
|
||||
|
||||
def _BackupRestoreRAM(self, args):
|
||||
|
|
@ -2872,6 +2931,12 @@ class LK_Device(ABC):
|
|||
empty_data_byte = 0x00
|
||||
extra_size = 0
|
||||
audio_low = False
|
||||
|
||||
# Initialization
|
||||
ram_banks = 0
|
||||
buffer_len = 0
|
||||
sram_5 = 0
|
||||
temp = None
|
||||
|
||||
cart_type = None
|
||||
if "cart_type" in args and args["cart_type"] >= 0:
|
||||
|
|
@ -3052,6 +3117,7 @@ class LK_Device(ABC):
|
|||
if args["rtc"] is True:
|
||||
extra_size = 0x10
|
||||
|
||||
action = None
|
||||
if args["mode"] == 2: # Backup
|
||||
action = "SAVE_READ"
|
||||
buffer = bytearray()
|
||||
|
|
@ -3467,7 +3533,14 @@ class LK_Device(ABC):
|
|||
return True
|
||||
|
||||
def _FlashROM(self, args):
|
||||
# Initialization
|
||||
self.FAST_READ = True
|
||||
temp = None
|
||||
rom_bank_size = 0
|
||||
end_bank = 0
|
||||
pos_from = 0
|
||||
verify_len = 0
|
||||
|
||||
if "buffer" in args:
|
||||
data_import = args["buffer"]
|
||||
else:
|
||||
|
|
@ -3549,13 +3622,17 @@ class LK_Device(ABC):
|
|||
# Firmware check L5
|
||||
# Firmware check L8
|
||||
if (self.FW["fw_ver"] < 8 and "enable_pullups" in cart_type and cart_type["enable_pullups"] is True):
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
||||
return False
|
||||
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
||||
#return False
|
||||
print("{:s}Note: This cartridge may not be fully compatible with your {:s} device running an old or legacy firmware version.{:s}".format(ANSI.YELLOW, self.FW["pcb_name"], ANSI.RESET))
|
||||
del(cart_type["enable_pullups"])
|
||||
# Firmware check L8
|
||||
# Firmware check L12
|
||||
if (self.FW["fw_ver"] < 12 and "set_irq_high" in cart_type and cart_type["set_irq_high"] is True):
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L12.", "abortable":False})
|
||||
return False
|
||||
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L12.", "abortable":False})
|
||||
#return False
|
||||
print("{:s}Note: This cartridge may not be fully compatible with your {:s} device running an old or legacy firmware version.{:s}".format(ANSI.YELLOW, self.FW["pcb_name"], ANSI.RESET))
|
||||
del(cart_type["set_irq_high"])
|
||||
if (self.FW["fw_ver"] < 12 and "status_register_mask" in cart_type):
|
||||
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L12.", "abortable":False})
|
||||
return False
|
||||
|
|
@ -3704,7 +3781,7 @@ class LK_Device(ABC):
|
|||
else:
|
||||
dprint("Hidden sector data:", args["buffer_map"])
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
with open("debug_mmsa_map.bin", "wb") as f: f.write(args["buffer_map"])
|
||||
with open(Util.CONFIG_PATH + "/debug_mmsa_map.bin", "wb") as f: f.write(args["buffer_map"])
|
||||
data_map_import = copy.copy(args["buffer_map"])
|
||||
data_map_import = bytearray(data_map_import)
|
||||
dprint("Hidden sector data loaded")
|
||||
|
|
@ -4030,9 +4107,6 @@ class LK_Device(ABC):
|
|||
sector_pos = sector_offsets.index(sector[:2])
|
||||
start_bank = math.floor(buffer_pos / rom_bank_size)
|
||||
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
||||
# print(hex(start_address), hex(end_address), start_bank, end_bank)
|
||||
# print(hex(sector_offsets[sector_pos][0]), sector_pos)
|
||||
# print("")
|
||||
elif self.MODE == "DMG":
|
||||
dprint("Writing sector:", hex(sector[0]), hex(sector[1]))
|
||||
buffer_pos = sector[0]
|
||||
|
|
@ -4043,12 +4117,88 @@ class LK_Device(ABC):
|
|||
sector_pos = sector_offsets.index(sector[:2])
|
||||
start_bank = math.floor(buffer_pos / rom_bank_size)
|
||||
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
||||
# print(hex(start_address), hex(end_address), start_bank, end_bank)
|
||||
# print(hex(sector_offsets[sector_pos][0]), sector_pos)
|
||||
# print("")
|
||||
|
||||
#for bank in range(start_bank, end_bank):
|
||||
bank = start_bank
|
||||
|
||||
# ↓↓↓ Check if data matches already
|
||||
if self.FW["fw_ver"] >= 10 and not (flashcart and cart_type["command_set"] == "GBAMP"):
|
||||
if "verify_write" in args and args["verify_write"] is True:
|
||||
buffer_pos_matchcheck = buffer_pos
|
||||
verified = False
|
||||
ts_se_start = time.time()
|
||||
while bank < end_bank:
|
||||
status = None
|
||||
# ↓↓↓ Switch ROM bank
|
||||
if self.MODE == "DMG":
|
||||
if _mbc.ResetBeforeBankChange(bank) is True:
|
||||
dprint("Resetting the MBC")
|
||||
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
||||
(start_address, bank_size) = _mbc.SelectBankROM(bank)
|
||||
if flashcart.PulseResetAfterWrite():
|
||||
if bank == 0:
|
||||
if self.FW["fw_ver"] < 2:
|
||||
self._write(self.DEVICE_CMD["OFW_GB_CART_MODE"])
|
||||
else:
|
||||
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
||||
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
||||
# else:
|
||||
# self._write(self.DEVICE_CMD["OFW_GB_FLASH_BANK_1_COMMAND_WRITES"])
|
||||
self._set_fw_variable("DMG_ROM_BANK", bank)
|
||||
|
||||
buffer_len = min(buffer_len, bank_size)
|
||||
if "start_addr" in flashcart.CONFIG and bank == 0: start_address = flashcart.CONFIG["start_addr"]
|
||||
end_address = start_address + bank_size
|
||||
start_address += (buffer_pos % rom_bank_size)
|
||||
if end_address > start_address + sector[1]:
|
||||
end_address = start_address + sector[1]
|
||||
|
||||
elif self.MODE == "AGB":
|
||||
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
||||
if bank != current_bank:
|
||||
flashcart.Reset(full_reset=True)
|
||||
flashcart.SelectBankROM(bank)
|
||||
temp = end_address - start_address
|
||||
start_address %= cart_type["flash_bank_size"]
|
||||
end_address = min(cart_type["flash_bank_size"], start_address + temp)
|
||||
current_bank = bank
|
||||
# ↑↑↑ Switch ROM bank
|
||||
|
||||
pos = start_address
|
||||
#dprint("pos=0x{:X}, buffer_pos=0x{:X}, start_address=0x{:X}, end_address=0x{:X}".format(pos, buffer_pos_matchcheck, start_address, end_address))
|
||||
verified = self.CompareCRC32(buffer=data_import, offset=buffer_pos_matchcheck, length=end_address - pos, address=pos, flashcart=flashcart, reset=True)
|
||||
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos_matchcheck})
|
||||
if verified is not True:
|
||||
break
|
||||
buffer_pos_matchcheck += (end_address - pos)
|
||||
bank += 1
|
||||
|
||||
if verified is True:
|
||||
dprint("Skipping sector #{:d}, because the CRC32 matched".format(sector_pos))
|
||||
if flashcart.FlashCommandsOnBank1(): _mbc.SelectBankROM(bank)
|
||||
self.NO_PROG_UPDATE = True
|
||||
se_ret = flashcart.SectorErase(pos=pos, buffer_pos=buffer_pos, skip=verified)
|
||||
self.NO_PROG_UPDATE = False
|
||||
|
||||
if self.CANCEL:
|
||||
cancel_args = {"action":"ABORT", "abortable":False}
|
||||
cancel_args.update(self.CANCEL_ARGS)
|
||||
self.CANCEL_ARGS = {}
|
||||
self.ERROR_ARGS = {}
|
||||
self.SetProgress(cancel_args)
|
||||
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
||||
return
|
||||
|
||||
ts_se_elapsed = time.time() - ts_se_start
|
||||
if se_ret:
|
||||
sector_size = se_ret
|
||||
dprint("Next sector size: 0x{:X}".format(sector_size))
|
||||
buffer_pos += sector_size
|
||||
continue
|
||||
else:
|
||||
verified = False
|
||||
bank = start_bank
|
||||
# ↑↑↑ Check if data matches already
|
||||
|
||||
while bank < end_bank:
|
||||
if self.CANCEL:
|
||||
cancel_args = {"action":"ABORT", "abortable":False}
|
||||
|
|
@ -4116,44 +4266,20 @@ class LK_Device(ABC):
|
|||
se_ret = None
|
||||
if chip_erase is False:
|
||||
if sector_pos < len(sector_offsets) and buffer_pos == sector_offsets[sector_pos][0]:
|
||||
len_rest = end_address - pos
|
||||
skip_se = False
|
||||
ts_se_start = time.time()
|
||||
if self.FW["fw_ver"] >= 12 and "compare_sectors" in args and args["compare_sectors"] is True and sector[1] <= len_rest and not (flashcart and cart_type["command_set"] == "GBAMP"):
|
||||
verified = self.CompareCRC32(buffer=data_import, offset=sector[0], length=sector[1], address=start_address, flashcart=flashcart, reset=True)
|
||||
if verified is True:
|
||||
skip_se = True
|
||||
elif verified is not True and len(verified) == 2:
|
||||
dprint("Writing sector #{:d}, because the CRC32 didn’t match: 0x{:X} ≠ 0x{:X}".format(sector_pos, verified[0], verified[1]))
|
||||
verified = False
|
||||
skip_se = False
|
||||
|
||||
if self.CANCEL:
|
||||
cancel_args = {"action":"ABORT", "abortable":False}
|
||||
cancel_args.update(self.CANCEL_ARGS)
|
||||
self.CANCEL_ARGS = {}
|
||||
self.ERROR_ARGS = {}
|
||||
self.SetProgress(cancel_args)
|
||||
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
||||
return
|
||||
|
||||
if skip_se is True:
|
||||
dprint("Skipping sector #{:d}, because the CRC32 matched".format(sector_pos))
|
||||
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "sector_pos":sector_pos, "force_update":True})
|
||||
else:
|
||||
if sector not in verify_sectors:
|
||||
verify_sectors.append(sector)
|
||||
dprint("Erasing sector #{:d} at position 0x{:X} (0x{:X})".format(sector_pos, buffer_pos, pos))
|
||||
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "force_update":True})
|
||||
if self.CanPowerCycleCart(): self.CartPowerOn()
|
||||
dprint("Erasing sector #{:d} at position 0x{:X} (0x{:X})".format(sector_pos, buffer_pos, pos))
|
||||
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "force_update":True})
|
||||
if self.CanPowerCycleCart(): self.CartPowerOn()
|
||||
|
||||
sector_pos += 1
|
||||
if flashcart.FlashCommandsOnBank1(): _mbc.SelectBankROM(bank)
|
||||
self.NO_PROG_UPDATE = True
|
||||
se_ret = flashcart.SectorErase(pos=pos, buffer_pos=buffer_pos, skip=skip_se)
|
||||
se_ret = flashcart.SectorErase(pos=pos, buffer_pos=buffer_pos, skip=False)
|
||||
self.NO_PROG_UPDATE = False
|
||||
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
||||
continue
|
||||
if sector not in verify_sectors:
|
||||
verify_sectors.append(sector)
|
||||
|
||||
ts_se_elapsed = time.time() - ts_se_start
|
||||
if se_ret:
|
||||
|
|
@ -4161,11 +4287,6 @@ class LK_Device(ABC):
|
|||
sector_size = se_ret
|
||||
dprint("Next sector size: 0x{:X}".format(sector_size))
|
||||
skip_init = False
|
||||
|
||||
if skip_se:
|
||||
buffer_pos += sector_size
|
||||
pos += sector_size
|
||||
continue
|
||||
# ↑↑↑ Sector erase
|
||||
|
||||
if se_ret is not False:
|
||||
|
|
@ -4313,7 +4434,7 @@ class LK_Device(ABC):
|
|||
if "verify_write" in args and args["verify_write"] is True:
|
||||
self.SetProgress({"action":"INITIALIZE", "method":"ROM_WRITE_VERIFY", "size":len(data_import), "flash_offset":flash_offset})
|
||||
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
||||
with open("debug_verify.bin", "wb") as f: pass
|
||||
with open(Util.CONFIG_PATH + "/debug_verify.bin", "wb") as f: pass
|
||||
|
||||
current_bank = None
|
||||
broken_sectors = []
|
||||
|
|
@ -4502,6 +4623,8 @@ class LK_Device(ABC):
|
|||
self.CANCEL_ARGS = {}
|
||||
self.READ_ERRORS = 0
|
||||
self.WRITE_ERRORS = 0
|
||||
_apot = 0
|
||||
|
||||
if self.IsConnected():
|
||||
_apoe = False
|
||||
if self.CanPowerCycleCart():
|
||||
|
|
@ -4524,6 +4647,7 @@ class LK_Device(ABC):
|
|||
elif args['mode'] == 2: ret = self._BackupRestoreRAM(args)
|
||||
elif args['mode'] == 3: ret = self._BackupRestoreRAM(args)
|
||||
elif args['mode'] == 4: ret = self._FlashROM(args)
|
||||
elif args['mode'] == 5: ret = self._DetectCartridge(args)
|
||||
elif args['mode'] == 0xFF: self.Debug()
|
||||
if self.FW is None: return False
|
||||
if self.FW["fw_ver"] >= 2 and self.FW["pcb_name"] == "GBxCart RW":
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ class DMG_MBC:
|
|||
return DMG_Unlicensed_Sachen(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
elif mbc_id == 0x205: # 0x205:'Datel Orbit V2',
|
||||
return DMG_Unlicensed_DatelOrbitV2(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
# elif mbc_id == 0x206: # 0x206:'Datel MegaMem',
|
||||
# return DMG_Unlicensed_DatelMegaMem(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
elif mbc_id == 0x206: # 0x206:'MBCX',
|
||||
return DMG_Unlicensed_MBCX(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
else:
|
||||
self.__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
|
||||
return self
|
||||
|
|
@ -269,7 +269,7 @@ class DMG_MBC3(DMG_MBC):
|
|||
|
||||
def HasRTC(self):
|
||||
dprint("Checking for RTC")
|
||||
if self.MBC_ID not in (0x0F, 0x10, 0x110):
|
||||
if self.MBC_ID not in (0x0F, 0x10, 0x110, 0x206):
|
||||
dprint("No RTC because mapper value is not used for RTC:", self.MBC_ID)
|
||||
return False
|
||||
self.EnableRAM(enable=False)
|
||||
|
|
@ -831,6 +831,7 @@ class DMG_GMMC1(DMG_MBC5):
|
|||
def CalcChecksum(self, buffer):
|
||||
header = RomFileDMG(buffer[:0x180]).GetHeader()
|
||||
target_chk_value = 0
|
||||
target_sha1_value = 0
|
||||
if header["game_title"] == "NP M-MENU MENU":
|
||||
target_sha1_value = "15f5d445c0b2fdf4221cf2a986a4a5cb8dfda131"
|
||||
target_chk_value = 0x19E8
|
||||
|
|
@ -1531,6 +1532,44 @@ class DMG_Unlicensed_DatelOrbitV2(DMG_MBC):
|
|||
def GetMaxROMSize(self):
|
||||
return 128*1024
|
||||
|
||||
class DMG_Unlicensed_MBCX(DMG_MBC3):
|
||||
def GetName(self):
|
||||
return "MBCX"
|
||||
|
||||
def HasFlashBanks(self):
|
||||
return True
|
||||
|
||||
def SelectBankFlash(self, index):
|
||||
dprint(self.GetName(), "|SelectBankFlash()|", index)
|
||||
|
||||
commands = [
|
||||
[ 0x0000, 0x05 ],
|
||||
[ 0x4000, 0x82 ],
|
||||
[ 0xA000, index ],
|
||||
[ 0x0000, 0x00 ]
|
||||
]
|
||||
self.CURRENT_FLASH_BANK = index
|
||||
self.CartWrite(commands, delay=0.1)
|
||||
|
||||
def SelectBankROM(self, index):
|
||||
dprint(self.GetName(), index)
|
||||
|
||||
if (index % 512 == 0) or (self.CURRENT_FLASH_BANK != math.floor(index / 512)):
|
||||
self.SelectBankFlash(math.floor(index / 512))
|
||||
self.CURRENT_ROM_BANK = index
|
||||
index = index % 512
|
||||
|
||||
commands = [
|
||||
[ 0x3000, ((index >> 8) & 0xFF) ],
|
||||
[ 0x2100, index & 0xFF ],
|
||||
]
|
||||
|
||||
self.CartWrite(commands)
|
||||
return (0x4000, self.ROM_BANK_SIZE)
|
||||
|
||||
def GetMaxROMSize(self):
|
||||
return 32*1024*1024
|
||||
|
||||
|
||||
class AGB_GPIO:
|
||||
CART_WRITE_FNCPTR = None
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class PocketCamera:
|
|||
offset = 0x2000 + (index * 0x1000)
|
||||
elif index == 30:
|
||||
offset = 0x11FC
|
||||
elif index == 31:
|
||||
else:
|
||||
offset = 0
|
||||
imgbuffer = self.DATA[offset:offset+0x1000]
|
||||
return self.ConvertPicture(imgbuffer)
|
||||
|
|
@ -138,7 +138,7 @@ class PocketCamera:
|
|||
frame.paste(pic, (left, top))
|
||||
pic = frame
|
||||
|
||||
pic = pic.resize((pic.width * scale, pic.height * scale), Image.NEAREST)
|
||||
pic = pic.resize((pic.width * scale, pic.height * scale), Image.Resampling.NEAREST)
|
||||
|
||||
ext = os.path.splitext(path)[1]
|
||||
if ext == "" or ext.lower() == ".png":
|
||||
|
|
|
|||
|
|
@ -309,13 +309,13 @@ class PocketCameraWindow(QtWidgets.QDialog):
|
|||
draw.line([0, 112, 128, 0], fill=(255, 0, 0, 192), width=8)
|
||||
pic.paste(draw_bg, mask=draw_bg)
|
||||
self.lblPhoto[i].setToolTip("This picture was marked as “deleted” and may be overwritten when you take new pictures.")
|
||||
self.CUR_THUMBS[i] = ImageQt(pic.resize((47, 41), Image.HAMMING))
|
||||
self.CUR_THUMBS[i] = ImageQt(pic.resize((47, 41), Image.Resampling.HAMMING))
|
||||
qpixmap = QtGui.QPixmap.fromImage(self.CUR_THUMBS[i])
|
||||
self.lblPhoto[i].setPixmap(qpixmap)
|
||||
|
||||
def UpdateViewer(self, index):
|
||||
resampler = Image.NEAREST
|
||||
if self.CUR_BICUBIC: resampler = Image.BICUBIC
|
||||
resampler = Image.Resampling.NEAREST
|
||||
if self.CUR_BICUBIC: resampler = Image.Resampling.BICUBIC
|
||||
cam = self.CUR_PC
|
||||
if cam is None: return
|
||||
|
||||
|
|
@ -323,7 +323,7 @@ class PocketCameraWindow(QtWidgets.QDialog):
|
|||
self.lblPhoto[i].setStyleSheet("border-top: 1px solid #adadad; border-left: 1px solid #adadad; border-bottom: 1px solid #ffffff; border-right: 1px solid #ffffff;")
|
||||
|
||||
if index >= 30:
|
||||
self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), Image.BICUBIC if index == 31 else resampler))
|
||||
self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), Image.Resampling.BICUBIC if index == 31 else resampler))
|
||||
else:
|
||||
self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), resampler))
|
||||
self.lblPhoto[index].setStyleSheet("border: 3px solid green; padding: 1px;")
|
||||
|
|
|
|||
|
|
@ -431,6 +431,12 @@ class RomFileDMG:
|
|||
temp = self.LogoToImage(buffer[0x104:0x104+0x30])
|
||||
if temp is not False: data["logo_sachen"] = temp
|
||||
|
||||
# GBFlash MBCX
|
||||
if data["game_title"] == "MBCX_MENU":
|
||||
data["rom_size_raw"] = 0x0A
|
||||
data["ram_size_raw"] = 0x03
|
||||
data["mapper_raw"] = 0x206
|
||||
|
||||
if data["mapper_raw"] in Util.DMG_Header_Mapper:
|
||||
data["mapper"] = Util.DMG_Header_Mapper[data["mapper_raw"]]
|
||||
elif data["logo_correct"]:
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
# FlashGBX
|
||||
# Author: Lesserkuma (github.com/lesserkuma)
|
||||
|
||||
import math, time, datetime, copy, configparser, threading, os, platform, traceback, io, struct, re, statistics, random
|
||||
import math, time, datetime, copy, configparser, threading, os, platform, traceback, io, struct, re, statistics, random, sys
|
||||
from io import StringIO
|
||||
from enum import Enum
|
||||
|
||||
# Common constants
|
||||
APPNAME = "FlashGBX"
|
||||
VERSION_PEP440 = "4.2"
|
||||
VERSION_PEP440 = "4.3"
|
||||
VERSION = "v{:s}".format(VERSION_PEP440)
|
||||
VERSION_TIMESTAMP = 1722797669
|
||||
VERSION_TIMESTAMP = 1731015769
|
||||
DEBUG = False
|
||||
DEBUG_LOG = []
|
||||
APP_PATH = ""
|
||||
|
|
@ -23,11 +23,11 @@ AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1048576, 65536, 13
|
|||
AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS", 0xBF5B:"Unlicensed SST49LF080A", 0xFFFF:"Unlicensed 0xFFFF" }
|
||||
AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000, 0x20000, 0x20000 ]
|
||||
|
||||
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x0F:'MBC3+RTC+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'MAC-GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
|
||||
DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x0F, 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ] }
|
||||
DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB" ]
|
||||
DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A ]
|
||||
DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]
|
||||
DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x0F:'MBC3+RTC+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'MAC-GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper', 0x206:'Unlicensed MBCX Mapper' }
|
||||
DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x0F, 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ], "Unlicensed MBCX Mapper":[ 0x206 ] }
|
||||
DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB", "64 MiB", "128 MiB" ]
|
||||
DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C ]
|
||||
DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000 ]
|
||||
DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KiB)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)" ]
|
||||
DMG_Header_RAM_Sizes_Map = [ 0x00, 0x100, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204 ]
|
||||
DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000, 0x80000 ] # RAM size in bytes
|
||||
|
|
@ -169,6 +169,10 @@ class Progress():
|
|||
self.PROGRESS["time_start"] = args["time_start"]
|
||||
else:
|
||||
self.PROGRESS["time_start"] = now
|
||||
if "abortable" in args:
|
||||
self.PROGRESS["abortable"] = args["abortable"]
|
||||
else:
|
||||
self.PROGRESS["abortable"] = True
|
||||
self.PROGRESS["time_last_emit"] = now
|
||||
self.PROGRESS["time_last_update_speed"] = now
|
||||
self.PROGRESS["time_left"] = 0
|
||||
|
|
@ -218,7 +222,7 @@ class Progress():
|
|||
self.PROGRESS["sector_pos"] = args["sector_pos"]
|
||||
if "abortable" in args:
|
||||
self.PROGRESS["abortable"] = args["abortable"]
|
||||
|
||||
|
||||
if ((now - self.PROGRESS["time_last_emit"]) > 0.06) or "force_update" in args and args["force_update"] is True:
|
||||
self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"]
|
||||
time_delta = now - self.PROGRESS["time_last_update_speed"]
|
||||
|
|
@ -254,6 +258,11 @@ class Progress():
|
|||
self.UPDATER(self.PROGRESS)
|
||||
self.PROGRESS["time_last_emit"] = now
|
||||
|
||||
elif args["action"] == "UPDATE_INFO":
|
||||
self.PROGRESS["text"] = args["text"]
|
||||
self.PROGRESS["action"] = args["action"]
|
||||
self.UPDATER(self.PROGRESS)
|
||||
|
||||
elif args["action"] == "FINISHED":
|
||||
self.PROGRESS["pos"] = self.PROGRESS["size"]
|
||||
self.UPDATER(self.PROGRESS)
|
||||
|
|
@ -535,6 +544,8 @@ def GetDumpReport(di, device):
|
|||
di["rom_size"] = "{:s}".format(AGB_Header_ROM_Sizes[AGB_Header_ROM_Sizes_Map.index(di["rom_size"])])
|
||||
else:
|
||||
di["rom_size"] = "{:,} bytes".format(di["rom_size"])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
di["cart_type"] = list(device.SUPPORTED_CARTS[mode].keys())[di["cart_type"]]
|
||||
if di["file_name"] is None:
|
||||
|
|
@ -795,7 +806,10 @@ def GetDumpReport(di, device):
|
|||
if "st" in db: s += "* Save Type: {:s}\n".format(AGB_Header_Save_Types[db["st"]])
|
||||
#if "ss" in db: s += "* Save Size: {:s}\n".format(formatFileSize(size=db["ss"], asInt=True))
|
||||
|
||||
return s
|
||||
if platform.system() == "Windows":
|
||||
return s.replace("\n", "\r\n")
|
||||
else:
|
||||
return s
|
||||
|
||||
def GenerateFileName(mode, header, settings=None):
|
||||
fe_ni = True
|
||||
|
|
@ -921,3 +935,37 @@ def dprint(*args, **kwargs):
|
|||
if DEBUG:
|
||||
msg = "{:s}{:s}".format(ANSI.CLEAR_LINE, msg)
|
||||
print(msg)
|
||||
|
||||
def write_debug_log(device=False):
|
||||
dprint("{:s} version: {:s} ({:d})".format(APPNAME, VERSION_PEP440, VERSION_TIMESTAMP))
|
||||
dprint("Platform: {:s}".format(platform.platform()))
|
||||
if device is not False:
|
||||
if device is not None:
|
||||
dprint("Connected device: {:s}".format(device))
|
||||
else:
|
||||
dprint("No device connected")
|
||||
dprint("Now writing debug log file")
|
||||
try:
|
||||
fn = CONFIG_PATH + "/debug.log"
|
||||
with open(fn, "wb") as f:
|
||||
if platform.system() == "Windows":
|
||||
f.write("\r\n".join(DEBUG_LOG).encode("UTF-8-SIG"))
|
||||
else:
|
||||
f.write("\n".join(DEBUG_LOG).encode("UTF-8-SIG"))
|
||||
print("debug.log written")
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def exception_hook(exc_type, exc_value, exc_traceback):
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||
return
|
||||
|
||||
s = "⚠️ EXCEPTION OCCURED ⚠️\n"
|
||||
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
||||
for line in lines:
|
||||
s += f"{line:s}"
|
||||
print(s)
|
||||
dprint(s)
|
||||
write_debug_log()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"buffer_size":64,
|
||||
"sector_size":0x20000,
|
||||
"reset_every":0x400000,
|
||||
"chip_erase_timeout":2000,
|
||||
"command_set":"AMD",
|
||||
"commands":{
|
||||
"reset":[
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"names":[
|
||||
"insideGadgets 32 MiB (S29GL512N) + RTC",
|
||||
"AGB-E20-30 with S29GL256N10TFI01",
|
||||
"GBFlash 1M FLASH RTC (AGB-R1M-02V3)"
|
||||
"GBFlash 1M FLASH RTC (AGB-R1M-02V3)",
|
||||
"GBFlash 1M FLASH RTC (AGB-R1M-02V4)"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x01, 0x00, 0x7E, 0x22 ],
|
||||
|
|
|
|||
53
FlashGBX/config/fc_DMG_39SF020A.txt
Normal file
53
FlashGBX/config/fc_DMG_39SF020A.txt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"insideGadgets MegaDuck 32K"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0xBF, 0xB6 ]
|
||||
],
|
||||
"voltage":5,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"WR",
|
||||
"chip_erase_timeout":30,
|
||||
"command_set":"AMD",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x80 ],
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0x10 ]
|
||||
],
|
||||
"chip_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ 0, 0xFF, 0xFF ]
|
||||
],
|
||||
"single_write":[
|
||||
[ 0x5555, 0xAA ],
|
||||
[ 0x2AAA, 0x55 ],
|
||||
[ 0x5555, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
88
FlashGBX/config/fc_DMG_GBFlash_MBCX_256M.txt
Normal file
88
FlashGBX/config/fc_DMG_GBFlash_MBCX_256M.txt
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"GBFlash MBCX (32 MiB)"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x01, 0x01, 0x7E, 0x7E ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"flash_size":0x2000000,
|
||||
"start_addr":0x4000,
|
||||
"first_bank":0,
|
||||
"write_pin":"WR",
|
||||
"sector_size_from_cfi":true,
|
||||
"chip_erase_timeout":120,
|
||||
"mbc":0x206,
|
||||
"command_set":"AMD",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0x0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x80 ],
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x10 ]
|
||||
],
|
||||
"chip_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ 0, 0xFF, 0xFF ]
|
||||
],
|
||||
"sector_erase":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x80 ],
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ "SA", 0x30 ]
|
||||
],
|
||||
"sector_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ "SA", 0xFF, 0xFF ]
|
||||
],
|
||||
"buffer_write":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ "SA", 0x25 ],
|
||||
[ "SA", "BS" ],
|
||||
[ "PA", "PD" ],
|
||||
[ "SA", 0x29 ]
|
||||
],
|
||||
"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, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
88
FlashGBX/config/fc_DMG_GBFlash_MBCX_64M.txt
Normal file
88
FlashGBX/config/fc_DMG_GBFlash_MBCX_64M.txt
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"GBFlash MBCX (8 MiB)"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x01, 0x01, 0x7E, 0x7E ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"flash_size":0x800000,
|
||||
"start_addr":0x4000,
|
||||
"first_bank":0,
|
||||
"write_pin":"WR",
|
||||
"sector_size_from_cfi":true,
|
||||
"chip_erase_timeout":60,
|
||||
"mbc":0x206,
|
||||
"command_set":"AMD",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0x0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x80 ],
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x10 ]
|
||||
],
|
||||
"chip_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ 0, 0xFF, 0xFF ]
|
||||
],
|
||||
"sector_erase":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0x80 ],
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ "SA", 0x30 ]
|
||||
],
|
||||
"sector_erase_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ "SA", 0xFF, 0xFF ]
|
||||
],
|
||||
"buffer_write":[
|
||||
[ 0xAAA, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ "SA", 0x25 ],
|
||||
[ "SA", "BS" ],
|
||||
[ "PA", "PD" ],
|
||||
[ "SA", 0x29 ]
|
||||
],
|
||||
"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, 0xAA ],
|
||||
[ 0x555, 0x55 ],
|
||||
[ 0xAAA, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"DVP DRV with MX29LV320CB"
|
||||
"DVP DRV with MX29LV320CB",
|
||||
"DVP DRV with MX29LV320CT"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0xC2, 0xC2, 0xA8, 0xA8 ]
|
||||
[ 0xC2, 0xC2, 0xA8, 0xA8 ],
|
||||
[ 0xC2, 0xC2, 0xA7, 0xA7 ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"flash_size":0x400000,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
"type":"DMG",
|
||||
"names":[
|
||||
"SD008-6810-512S with MSP55LV512",
|
||||
"SD008_512ND_4M with epoxy flash chip"
|
||||
"SD008_512ND_4M with epoxy flash chip",
|
||||
"Nameless PCB with epoxy flash chip"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x02, 0x7D, 0x00, 0x08 ],
|
||||
[ 0x02, 0x7D, 0x00, 0x98 ]
|
||||
[ 0x02, 0x7D, 0x00, 0x98 ],
|
||||
[ 0x04, 0x7D, 0x00, 0x98 ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"voltage_variants":true,
|
||||
|
|
|
|||
|
|
@ -230,11 +230,6 @@ try:
|
|||
self.DEVICE = device
|
||||
else:
|
||||
self.APP.QT_APP.processEvents()
|
||||
text = "This Firmware Updater is for GBFlash devices only. Please only proceed if your device is a GBFlash."
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
|
||||
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
|
||||
answer = msgbox.exec()
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
self.FWUPD = FirmwareUpdater(app_path, None)
|
||||
|
||||
self.layout = QtWidgets.QGridLayout()
|
||||
|
|
|
|||
|
|
@ -110,11 +110,6 @@ try:
|
|||
self.DEVICE = device
|
||||
else:
|
||||
self.APP.QT_APP.processEvents()
|
||||
text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nOlder GBxCart RW revisions can be updated only after connecting to them first."
|
||||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
|
||||
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
|
||||
answer = msgbox.exec()
|
||||
if answer == QtWidgets.QMessageBox.Cancel: return
|
||||
self.FWUPD = FirmwareUpdater(app_path, None)
|
||||
|
||||
self.layout = QtWidgets.QGridLayout()
|
||||
|
|
@ -217,8 +212,10 @@ try:
|
|||
self.lblDeviceFWVerResult.setText(self.FW_VER)
|
||||
if self.PCB_VER == "v1.4":
|
||||
self.optDevicePCBVer14.setChecked(True)
|
||||
self.optDevicePCBVer14a.setEnabled(False)
|
||||
elif self.PCB_VER == "v1.4a/b/c":
|
||||
self.optDevicePCBVer14a.setChecked(True)
|
||||
self.optDevicePCBVer14.setEnabled(False)
|
||||
self.SetPCBVersion()
|
||||
|
||||
def SetPCBVersion(self):
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ class FirmwareUpdater():
|
|||
path = os.path.dirname(path) + "/"
|
||||
fncSetStatus(text="Connecting... This may take a moment.")
|
||||
|
||||
with open(file, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
|
||||
filename = os.path.split(file)[1]
|
||||
filepath = os.path.split(file)[0]
|
||||
with open(filepath + "/" + filename, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
|
||||
if not temp.startswith("UPDATE"):
|
||||
with open(file, "wb") as f:
|
||||
temp = bytearray(b"UPDATE")
|
||||
|
|
@ -46,10 +48,16 @@ class FirmwareUpdater():
|
|||
return 2
|
||||
|
||||
try:
|
||||
with open(file, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
|
||||
except FileNotFoundError:
|
||||
fncSetStatus(text="Couldn’t access MODE.TXT. Remove cartridge and try again.")
|
||||
return 2
|
||||
with open(filepath + "/" + filename, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
|
||||
except FileNotFoundError as e:
|
||||
try:
|
||||
if filename == "MODE.TXT":
|
||||
with open(filepath + "/" + "MODE!.TXT", "rb") as f: temp = f.read().decode("UTF-8", "ignore")
|
||||
else:
|
||||
raise FileNotFoundError from e
|
||||
except FileNotFoundError:
|
||||
fncSetStatus(text="Couldn’t access MODE.TXT. Remove cartridge and try again.")
|
||||
return 2
|
||||
|
||||
if not temp.startswith("UPDATE"):
|
||||
fncSetStatus(text="Couldn’t enter UPDATE mode, please try again.")
|
||||
|
|
@ -272,7 +280,7 @@ try:
|
|||
self.rowUpdate.addStretch()
|
||||
|
||||
self.rowUpdate2 = QtWidgets.QHBoxLayout()
|
||||
self.lblUpdateDisclaimer = QtWidgets.QLabel("Please note that FlashGBX is not officially supported by BennVenn, so please use this firmware updater at your own risk.")
|
||||
self.lblUpdateDisclaimer = QtWidgets.QLabel("Please note that FlashGBX is not officially supported by BennVenn.")
|
||||
self.lblUpdateDisclaimer.setWordWrap(True)
|
||||
self.lblUpdateDisclaimer.setAlignment(QtGui.Qt.AlignmentFlag.AlignCenter)
|
||||
self.rowUpdate2.addWidget(self.lblUpdateDisclaimer)
|
||||
|
|
@ -467,7 +475,7 @@ try:
|
|||
msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
|
||||
answer = msgbox.exec()
|
||||
return False
|
||||
answer = QtWidgets.QMessageBox.information(self, "FlashGBX", "If your Joey Jr device is currently running the Drag'n'Drop firmware, please continue and choose its <b>MODE.TXT</b> file.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
|
||||
answer = QtWidgets.QMessageBox.information(self, "FlashGBX", "If your Joey Jr device is currently running the Drag'n'Drop firmware, please continue and choose its <b>MODE.TXT</b> (or <b>MODE!.TXT</b>) file.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
|
||||
if answer == QtWidgets.QMessageBox.Cancel:
|
||||
self.SetStatus("No device found.", enableUI=True)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class GbxDevice(LK_Device):
|
|||
DEVICE_NAME = "GBFlash"
|
||||
DEVICE_MIN_FW = 1
|
||||
DEVICE_MAX_FW = 12
|
||||
DEVICE_LATEST_FW_TS = { 5:1722774120, 10:1722774120, 11:1722774120, 12:1722774120, 13:1722774120 }
|
||||
DEVICE_LATEST_FW_TS = { 5:1730731680, 10:1730731680, 11:1730731680, 12:1730731680, 13:1730731680 }
|
||||
PCB_VERSIONS = { 5:'', 12:'v1.2', 13:'v1.3' }
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -111,8 +111,10 @@ class GbxDevice(LK_Device):
|
|||
self.FW["cart_power_ctrl"] = True if self._read(1) == 1 else False
|
||||
|
||||
# Reset to bootloader support
|
||||
self.FW["bootloader_reset"] = True if self._read(1) == 1 else False
|
||||
|
||||
temp = self._read(1)
|
||||
self.FW["bootloader_reset"] = True if temp & 1 == 1 else False
|
||||
self.FW["unregistered"] = True if temp >> 7 == 1 else False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -192,7 +194,7 @@ class GbxDevice(LK_Device):
|
|||
return True
|
||||
|
||||
def FirmwareUpdateAvailable(self):
|
||||
if self.FW["pcb_ver"] == 5: # unofficial firmware
|
||||
if self.FW["pcb_ver"] == 5 or self.FW["fw_ts"] < 1730592000: # unofficial firmware
|
||||
self.FW_UPDATE_REQ = True
|
||||
return True
|
||||
if self.FW["fw_ts"] < self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]:
|
||||
|
|
@ -239,6 +241,13 @@ class GbxDevice(LK_Device):
|
|||
|
||||
def GetFullName(self):
|
||||
if self.FW["pcb_ver"] < 13 and self.CanPowerCycleCart():
|
||||
return "{:s} {:s} + PLUGIN 01".format(self.GetName(), self.GetPCBVersion())
|
||||
s = "{:s} {:s} + PLUGIN 01".format(self.GetName(), self.GetPCBVersion())
|
||||
else:
|
||||
return "{:s} {:s}".format(self.GetName(), self.GetPCBVersion())
|
||||
s = "{:s} {:s}".format(self.GetName(), self.GetPCBVersion())
|
||||
if self.IsUnregistered():
|
||||
s += " (unregistered)"
|
||||
return s
|
||||
|
||||
def GetRegisterInformation(self):
|
||||
text = f"Your GBFlash device reported a registration error, which means it may be an illegitimate clone.\n\nThe device’s integrated piracy detection may limit the device in performance and functionality until proper registration. The {Util.APPNAME:s} software has no control over this.\n\nPlease visit <a href=\"https://gbflash.geeksimon.com/\">https://gbflash.geeksimon.com/</a> for more information.".replace("\n", "<br>")
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class GbxDevice(LK_Device):
|
|||
MAX_BUFFER_READ = 0x1000
|
||||
MAX_BUFFER_WRITE = 0x400
|
||||
|
||||
def Initialize(self, flashcarts, port=None, max_baud=2000000):
|
||||
def Initialize(self, flashcarts=None, port=None, max_baud=2000000):
|
||||
if self.IsConnected(): self.DEVICE.close()
|
||||
if platform.system() == "Darwin": max_baud = 1000000
|
||||
|
||||
|
|
@ -81,7 +81,8 @@ class GbxDevice(LK_Device):
|
|||
self.DEVICE.timeout = self.DEVICE_TIMEOUT
|
||||
|
||||
# Load Flash Cartridge Handlers
|
||||
self.UpdateFlashCarts(flashcarts)
|
||||
if flashcarts is not None:
|
||||
self.UpdateFlashCarts(flashcarts)
|
||||
|
||||
# Stop after first found device
|
||||
break
|
||||
|
|
@ -177,6 +178,7 @@ class GbxDevice(LK_Device):
|
|||
elif baudrate == 1000000:
|
||||
self._write(self.DEVICE_CMD["OFW_USART_1_0M_SPEED"])
|
||||
self.BAUDRATE = baudrate
|
||||
self.DEVICE.close()
|
||||
|
||||
def CheckActive(self):
|
||||
if time.time() < self.LAST_CHECK_ACTIVE + 1: return True
|
||||
|
|
|
|||
|
|
@ -11,6 +11,27 @@
|
|||
<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>
|
||||
<p>Qt is The Qt Company Ltd product developed as an open source project. See <a href="https://qt.io/">qt.io</a> for more information.</p>
|
||||
|
||||
# Python
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using Python software in source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright © 2001-2021 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement.
|
||||
|
||||
# Pillow
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
|
@ -108,7 +129,6 @@ IN THE SOFTWARE.
|
|||
|
||||
# requests
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
52
README.md
52
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# FlashGBX (by Lesserkuma)
|
||||
|
||||
for [Windows](https://github.com/lesserkuma/FlashGBX/releases), [Linux](https://github.com/lesserkuma/FlashGBX#run-using-python-linux-macos-windows), [macOS](https://github.com/lesserkuma/FlashGBX#run-using-python-linux-macos-windows)
|
||||
for Windows, Linux, macOS (→ [Download](https://github.com/lesserkuma/FlashGBX/releases))
|
||||
|
||||
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/01.png" alt="FlashGBX on Windows 11" width="500"><br><img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/02.png" alt="GB Camera Album Viewer" width="500">
|
||||
|
||||
|
|
@ -15,37 +15,44 @@ for [Windows](https://github.com/lesserkuma/FlashGBX/releases), [Linux](https://
|
|||
- 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
|
||||
- Generate ROM dump reports for game preservation purposes
|
||||
- Delta flashing support, useful for development by writing only differences between two ROMs (if named `rom.gba` and `rom.delta.gba`)
|
||||
|
||||
### Compatible cartridge reader/writer hardware
|
||||
|
||||
- [GBxCart RW](https://www.gbxcart.com/) (tested with v1.3, v1.4, v1.4a and v1.4c)
|
||||
- [GBxCart RW](https://www.gbxcart.com/) (tested with v1.4, v1.4a and v1.4c)
|
||||
- [GBFlash](https://github.com/simonkwng/GBFlash) (tested with v1.2 and v1.3)
|
||||
- [Joey Jr](https://bennvenn.myshopify.com/collections/game-cart-to-pc-interface/products/usb-gb-c-cart-dumper-the-joey-jr) (tested with V2++)
|
||||
|
||||
## Installing and running
|
||||
|
||||
### Windows Packages
|
||||
### Pre-compiled binaries and packages
|
||||
|
||||
Available in the GitHub [Releases](https://github.com/lesserkuma/FlashGBX/releases) section:
|
||||
Available in the GitHub [Releases](https://github.com/lesserkuma/FlashGBX/releases) section are pre-compiled downloads available for:
|
||||
|
||||
* Windows Setup: An installer that will add the application to the start menu and optionally create a desktop icon
|
||||
* Windows Portable: Have everything in one place including the config files
|
||||
* **Windows (64-bit)**
|
||||
* Setup: An installer that will add the application to the start menu and optionally create a desktop icon
|
||||
* Portable: Have everything in one place including the config files
|
||||
|
||||
*(For users of Windows 7, legacy “Qt5” versions are provided as well.)*
|
||||
|
||||
These work for installing fresh and upgrading from an older version.
|
||||
* **Linux**
|
||||
* Ubuntu (.deb file): Install using `dpkg -i /path/to/FlashGBX_x.x_Ubuntu-all.deb`.<br>*(Based on a contribution by [JJ-Fox](https://github.com/JJ-Fox))*
|
||||
* Other distributions: Pre-made packages are contributed by JJ-Fox [here](https://github.com/JJ-Fox/FlashGBX-Linux-builds/releases/latest).
|
||||
|
||||
### Run using Python (Linux, macOS, Windows)
|
||||
* **macOS**
|
||||
* x86-64/arm64 (.dmg file): Install by opening the .dmg file and copying over the “FlashGBX” application to the desktop.<br>If it doesn’t run, it probably got quarantined during download. Run the following command in a Terminal window to unquarantine it: `xattr -d com.apple.quarantine /path/to/FlashGBX.app`.<br>*(Based on a contribution by [Cliffback](https://github.com/Cliffback))*
|
||||
|
||||
#### Installing
|
||||
*(If you have a Joey Jr and use macOS, please run the [Joey Jr Firmware Updater](https://github.com/lesserkuma/JoeyJr_FWUpdater) before using FlashGBX.)*
|
||||
|
||||
### Run via Python
|
||||
|
||||
FlashGBX can also be run in a Python environment like so:
|
||||
|
||||
1. Download and install [Python](https://www.python.org/downloads/) (version 3.10.11 is recommended)
|
||||
2. Open a Terminal or Command Prompt window
|
||||
3. Install FlashGBX with this command:<br>`pip3 install "FlashGBX[qt6]"`
|
||||
* If installation fails, try this command instead:<br>`pip3 install "FlashGBX[qt5]"`
|
||||
* 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), contributed by JJ-Fox.
|
||||
* Pre-made macOS packages and instructions are available [here](https://github.com/Cliffback/FlashGBX-macOS) (in the “Releases” section), contributed by Cliffback.
|
||||
* Update to the latest version by replacing `install` with `install -U`.
|
||||
|
||||
#### Running
|
||||
Use this command in a Terminal or Command Prompt window to launch the installed FlashGBX application:
|
||||
|
|
@ -56,11 +63,6 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
|
||||
*Note: On Linux systems, the `brltty` module may render serial communication devices non-accessible. See the troubleshooting section for details.*
|
||||
|
||||
#### 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
|
||||
- Game Boy
|
||||
|
|
@ -85,6 +87,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
- Unlicensed Xploder GB Mapper
|
||||
- Unlicensed Sachen Mapper
|
||||
- Unlicensed Datel Orbit V2 Mapper
|
||||
- Unlicensed MBCX Mapper
|
||||
|
||||
- Game Boy Advance
|
||||
- All cartridges without memory mapping
|
||||
|
|
@ -127,6 +130,8 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
- GameShark Pro
|
||||
- GB-CART32K-A with SST39SF020A
|
||||
- GB Smart 32M
|
||||
- GBFlash MBCX (8 MiB)
|
||||
- GBFlash MBCX (32 MiB)
|
||||
- GBFlash RTC with MX29LV320EB
|
||||
- HDR Game Boy Camera Flashcart
|
||||
- insideGadgets 32 KiB
|
||||
|
|
@ -139,6 +144,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
- insideGadgets 4 MiB, 128 KiB SRAM/FRAM
|
||||
- insideGadgets 4 MiB, 32 KiB FRAM, MBC3+RTC
|
||||
- insideGadgets 4 MiB (2× 2 MiB), 32 KiB FRAM, MBC5
|
||||
- insideGadgets MegaDuck 32K
|
||||
- Mr Flash 64M
|
||||
- Sillyhatday MBC5-DUAL-FLASH-4/8MB
|
||||
- Squareboi 4 MiB (2× 2 MiB)
|
||||
|
|
@ -163,6 +169,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
- FunnyPlaying MidnightTrace 32 MiB Flash Cart
|
||||
- GBA Movie Player v2 CF (with SST39VF400A)¹
|
||||
- GBFlash 1M FLASH RTC (AGB-R1M-02V3)
|
||||
- GBFlash 1M FLASH RTC (AGB-R1M-02V4)
|
||||
- insideGadgets 16 MiB, 64K EEPROM with Solar Sensor and RTC options
|
||||
- insideGadgets 32 MiB, 1M FLASH with RTC option
|
||||
- insideGadgets 32 MiB, 512K FLASH
|
||||
|
|
@ -185,6 +192,7 @@ Use this command in a Terminal or Command Prompt window to launch the installed
|
|||
- DRV with AM29LV160DB and ALTERA CPLD
|
||||
- DRV with AM29LV160DT and ALTERA CPLD
|
||||
- DVP DRV with MX29LV320CB
|
||||
- DVP DRV with MX29LV320CT
|
||||
- ES29LV160_DRV with 29DL32TF-70
|
||||
- GB-M968 with 29LV160DB
|
||||
- GB-M968 with M29W160EB
|
||||
|
|
@ -328,21 +336,23 @@ Many different reproduction cartridges share their flash chip command set, so ev
|
|||
|
||||
* On some Linux systems like Fedora, you may need to install the `python3-pillow-qt` package manually in order for the GUI mode to work.
|
||||
|
||||
* On some Linux systems you may see the message “No devices found.” with the GBxCart RW hardware device, even though you’re using a USB cable capable of data transfers. This may be caused by a module called `brltty` (a driver for refreshable braille displays) that is erroneously interfering and taking over control of any connected USB device that uses the CH340/341 chipset. The solution would be to uninstall or blacklist the `brltty` driver and then reboot the system.
|
||||
* On some Linux systems you may see the message “No devices found.” with the GBxCart RW or GBFlash hardware device, even though you’re using a USB cable capable of data transfers. This may be caused by a module called `brltty` (a driver for refreshable braille displays) that is erroneously interfering and taking over control of any connected USB device that uses the CH340/341 chipset. The solution would be to uninstall or blacklist the `brltty` driver and then reboot the system. This is not an issue with Joey Jr devices.
|
||||
|
||||
* If you’re using macOS version 10.13 or older, there may be no driver for serial communication devices 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’re using macOS and get a “Segmentation Fault: 11.” error, try uninstalling Python, reinstalling Python version 3.10.11 and then try again.
|
||||
* If you’re using macOS and get a “Segmentation Fault: 11.” error, try the “Run using Python” method with Python version 3.10.11.
|
||||
|
||||
## 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)
|
||||
|
||||
* To write only the differences between two ROMs, name the original one `<name>.gba` and the edited one `<name>.delta.gba`.
|
||||
|
||||
## Contributors
|
||||
|
||||
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, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, olDirdey, orangeglo, paarongiroux, Paradoxical, 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, ccs21, chobby, ClassicOldSong, Cliffback, CodyWick13, Corborg, Cristóbal, crizzlycruz, Crystal, Därk, Davidish, DevDavisNunez, Diddy_Kong, djedditt, Dr-InSide, dyf2007, easthighNerd, EchelonPrime, edo999, Eldram, Ell, EmperorOfTigers, endrift, Erba Verde, ethanstrax, eveningmoose, Falknör, FerrantePescara, frarees, Frost Clock, gandalf1980, gboh, gekkio, Godan, Grender, HDR, Herax, Hiccup, hiks, howie0210, iamevn, Icesythe7, ide, inYourBackline, iyatemu, Jayro, Jenetrix, JFox, joyrider3774, jrharbort, JS7457, julgr, Kaede, kane159, KOOORAY, kscheel, kyokohunter, Leitplanke, litlemoran, LovelyA72, Lu, Luca DS, LucentW, manuelcm1, marv17, Merkin, metroid-maniac, Mr_V, Mufsta, olDirdey, orangeglo, paarongiroux, Paradoxical, 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
|
||||
|
||||
## Third Party Notices and Licenses
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,27 @@
|
|||
<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>
|
||||
<p>Qt is The Qt Company Ltd product developed as an open source project. See <a href="https://qt.io/">qt.io</a> for more information.</p>
|
||||
|
||||
# Python
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using Python software in source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright © 2001-2021 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement.
|
||||
|
||||
# Pillow
|
||||
|
||||
The Python Imaging Library (PIL) is
|
||||
|
|
@ -108,7 +129,6 @@ IN THE SOFTWARE.
|
|||
|
||||
# requests
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -8,7 +8,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read(
|
|||
|
||||
setuptools.setup(
|
||||
name="FlashGBX",
|
||||
version="4.2",
|
||||
version="4.3",
|
||||
author="Lesserkuma",
|
||||
description="Reads and writes Game Boy and Game Boy Advance cartridge data",
|
||||
url="https://github.com/lesserkuma/FlashGBX",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user