0.8β
BIN
.github/FlashGBX_Ubuntu.png
vendored
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
BIN
.github/FlashGBX_Windows.png
vendored
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
BIN
.github/FlashGBX_macOS.png
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
@@ -30,11 +30,11 @@ class DataTransfer(QThread):
|
||||
self.RUNNING = False
|
||||
elif self.CONFIG['mode'] == 3:
|
||||
self.RUNNING = True
|
||||
self.CONFIG['port']._TransferData(3, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['mbc'], self.CONFIG['save_type'] ])
|
||||
self.CONFIG['port']._TransferData(3, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['mbc'], self.CONFIG['save_type'], self.CONFIG['erase'] ])
|
||||
self.RUNNING = False
|
||||
elif self.CONFIG['mode'] == 4:
|
||||
self.RUNNING = True
|
||||
self.CONFIG['port']._TransferData(4, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['cart_type'] ])
|
||||
self.CONFIG['port']._TransferData(4, self.updateProgress, [ self.CONFIG['path'], self.CONFIG['cart_type'], self.CONFIG['trim_rom'], self.CONFIG['override_voltage'] ])
|
||||
self.RUNNING = False
|
||||
|
||||
except Exception as e:
|
||||
0
FlashGBX/__init__.py
Normal file
2
FlashGBX/__main__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import FlashGBX
|
||||
FlashGBX.main()
|
||||
20281
FlashGBX/config/db_AGB.json
Normal file
@@ -2,10 +2,12 @@
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"SD007_48BALL_64M_V3 with 29DL161TD-90",
|
||||
"SD007_48BALL_64M_V5 with 29DL163BD-90",
|
||||
"SD007_48BALL_64M_V6 with 29DL163BD-90"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x04, 0x04, 0x35, 0x35 ],
|
||||
[ 0x04, 0x04, 0x2B, 0x2B ],
|
||||
[ 0x04, 0x04, 0x2B, 0x2B ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
@@ -13,7 +15,7 @@
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"WR",
|
||||
"chip_erase_timeout":50,
|
||||
"chip_erase_timeout":120,
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
@@ -7,6 +7,7 @@
|
||||
[ 0xBF, 0x00, 0x53, 0x73 ]
|
||||
],
|
||||
"voltage":3.3,
|
||||
"voltage_variants":true,
|
||||
"flash_size":0x400000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
53
FlashGBX/config/fc_DMG_AM29F016_AUDIO.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"AM29F016/AM29F016B (DIY cart) @ AUDIO"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x01, 0xAD, 0x00, 0x00 ]
|
||||
],
|
||||
"voltage":5,
|
||||
"flash_size":0x200000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"AUDIO",
|
||||
"chip_erase_timeout":60,
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x80 ],
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 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":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"AM29F016/AM29F016B (DIY cart)"
|
||||
"AM29F016/AM29F016B (DIY cart) @ WR"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x01, 0xAD, 0x00, 0x00 ]
|
||||
],
|
||||
"voltage":5,
|
||||
"flash_size":0x400000,
|
||||
"flash_size":0x200000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"WR",
|
||||
53
FlashGBX/config/fc_DMG_AM29F032_AUDIO.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"AM29F032/AM29F032B (DIY cart) @ AUDIO"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x04, 0xD4, 0x00, 0x00 ]
|
||||
],
|
||||
"voltage":5,
|
||||
"flash_size":0x400000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"AUDIO",
|
||||
"chip_erase_timeout":60,
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x80 ],
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 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":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
53
FlashGBX/config/fc_DMG_AM29F032_WR.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type":"DMG",
|
||||
"names":[
|
||||
"AM29F032/AM29F032B (DIY cart) @ WR"
|
||||
],
|
||||
"flash_ids":[
|
||||
[ 0x04, 0xD4, 0x00, 0x00 ]
|
||||
],
|
||||
"voltage":5,
|
||||
"flash_size":0x400000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"WR",
|
||||
"chip_erase_timeout":60,
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xF0 ]
|
||||
],
|
||||
"read_identifier":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x90 ]
|
||||
],
|
||||
"chip_erase":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0x80 ],
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 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":[
|
||||
[ 0x555, 0xAA ],
|
||||
[ 0x2AA, 0x55 ],
|
||||
[ 0x555, 0xA0 ],
|
||||
[ "PA", "PD" ]
|
||||
],
|
||||
"single_write_wait_for":[
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ],
|
||||
[ null, null, null ]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"sector_size":0x20000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"VIN",
|
||||
"write_pin":"AUDIO",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xFF ]
|
||||
@@ -11,7 +11,7 @@
|
||||
"sector_size":0x20000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"VIN",
|
||||
"write_pin":"AUDIO",
|
||||
"commands":{
|
||||
"reset":[
|
||||
[ 0, 0xFF ]
|
||||
@@ -10,7 +10,7 @@
|
||||
"flash_size":0x400000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"VIN",
|
||||
"write_pin":"AUDIO",
|
||||
"chip_erase_timeout":120,
|
||||
"commands":{
|
||||
"reset":[
|
||||
@@ -10,7 +10,7 @@
|
||||
"flash_size":0x80000,
|
||||
"start_addr":0,
|
||||
"first_bank":1,
|
||||
"write_pin":"VIN",
|
||||
"write_pin":"AUDIO",
|
||||
"chip_erase_timeout":30,
|
||||
"commands":{
|
||||
"reset":[
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# UTF-8
|
||||
import serial, serial.tools.list_ports
|
||||
import serial, os, serial.tools.list_ports
|
||||
from serial import SerialException
|
||||
import sys, time, re, glob, math, struct, json, statistics
|
||||
import zlib
|
||||
from DataTransfer import *
|
||||
from RomFileDMG import *
|
||||
from RomFileAGB import *
|
||||
import sys, time, re, glob, math, struct, json, statistics, traceback, zlib, random
|
||||
from .DataTransfer import *
|
||||
from .RomFileDMG import *
|
||||
from .RomFileAGB import *
|
||||
|
||||
class GbxDevice:
|
||||
DEVICE_NAME = "GBxCart RW"
|
||||
@@ -77,7 +76,6 @@ class GbxDevice:
|
||||
"GBA_FLASH_WRITE_SHARP_64BYTE":'x',
|
||||
"GBA_SAVEDATA_DETECT":'z',
|
||||
# General commands
|
||||
"CART_MODE":'C',
|
||||
"SET_INPUT":'I',
|
||||
"SET_OUTPUT":'O',
|
||||
"SET_OUTPUT_LOW":'L',
|
||||
@@ -94,10 +92,11 @@ class GbxDevice:
|
||||
"XMAS_VALUE":0x7690FCD,
|
||||
"READ_BUFFER":0
|
||||
}
|
||||
PCB_VERSIONS = {1:'v1.0', 2:'v1.1', 4:'v1.3', 90:'XMAS'}
|
||||
|
||||
SUPPORTED_CARTS_DMG = { "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" }
|
||||
SUPPORTED_CARTS_AGB = { "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" }
|
||||
PCB_VERSIONS = {1:'v1.0', 2:'v1.1', 4:'v1.3', 90:'XMAS', 100:'Mini'}
|
||||
SUPPORTED_CARTS = {
|
||||
"DMG":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" },
|
||||
"AGB":{ "Generic ROM Cartridge":"RETAIL", "● Auto-Detect Flash Cartridge ●":"AUTODETECT" }
|
||||
}
|
||||
|
||||
FW = []
|
||||
MODE = None
|
||||
@@ -110,7 +109,7 @@ class GbxDevice:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def Initialize(self):
|
||||
def Initialize(self, flashcarts):
|
||||
if self.IsConnected(): self.DEVICE.close()
|
||||
|
||||
conn_msg = []
|
||||
@@ -125,51 +124,49 @@ class GbxDevice:
|
||||
|
||||
for i in range(0, len(ports)):
|
||||
try:
|
||||
dev = serial.Serial(ports[i], 1000000, timeout=1)
|
||||
dev = serial.Serial(ports[i], 1000000, timeout=0.5)
|
||||
self.DEVICE = dev
|
||||
self.LoadFirmwareVersion()
|
||||
try:
|
||||
self.LoadFirmwareVersion()
|
||||
except:
|
||||
self.DEVICE = None
|
||||
|
||||
if self.DEVICE is None or not self.IsConnected() or self.FW[0] == b'':
|
||||
dev.close()
|
||||
self.DEVICE = None
|
||||
conn_msg.append([3, "Couldn’t communicate with the GBxCart RW device on port " + ports[i] + ". Please disconnect and reconnect the device, then try again."])
|
||||
continue
|
||||
if self.FW[0] < self.DEVICE_MIN_FW:
|
||||
dev.close()
|
||||
self.DEVICE = None
|
||||
conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " requires a firmware update to work with this software. Please try again after updating it to version R" + str(self.DEVICE_MIN_FW) + " or higher."])
|
||||
continue
|
||||
elif self.FW[0] > self.DEVICE_MAX_FW:
|
||||
conn_msg.append([0, "NOTE: The GBxCart RW device on port " + ports[i] + " is running a firmware version that is newer than what this software was developed to work with. Use with caution."])
|
||||
conn_msg.append([0, "NOTE: The GBxCart RW device on port " + ports[i] + " is running a firmware version that is newer than what this version of FlashGBX was developed to work with, so errors may occur."])
|
||||
|
||||
if (self.FW[1] != 4):
|
||||
conn_msg.append([2, "This tool was developed to be used with GBxCart RW v1.3 and v1.3 Pro. Other revisions are untested and may not be compatible."])
|
||||
conn_msg.append([0, "NOTE: FlashGBX was developed to be used with GBxCart RW v1.3 and v1.3 Pro. Other revisions are untested and may not be fully compatible."])
|
||||
|
||||
self.PORT = ports[i]
|
||||
|
||||
# Load Flash Cartridge Handlers
|
||||
flashcarts_dmg = {}
|
||||
flashcarts_agb = {}
|
||||
for file in glob.glob("./config/fc_*.txt"):
|
||||
with open(file, encoding='utf-8') as f:
|
||||
data = f.read()
|
||||
specs_int = re.sub("(0x[\dA-F]+)", lambda m: str(int(m.group(1), 16)), data)
|
||||
specs = json.loads(specs_int)
|
||||
|
||||
for name in specs["names"]:
|
||||
if specs["type"] == "DMG":
|
||||
flashcarts_dmg[name] = specs
|
||||
elif specs["type"] == "AGB":
|
||||
flashcarts_agb[name] = specs
|
||||
|
||||
for key in sorted(flashcarts_dmg.keys(), key=str.casefold):
|
||||
self.SUPPORTED_CARTS_DMG[key] = flashcarts_dmg[key]
|
||||
for key in sorted(flashcarts_agb.keys(), key=str.casefold):
|
||||
self.SUPPORTED_CARTS_AGB[key] = flashcarts_agb[key]
|
||||
for mode in flashcarts.keys():
|
||||
for key in sorted(flashcarts[mode].keys(), key=str.casefold):
|
||||
self.SUPPORTED_CARTS[mode][key] = flashcarts[mode][key]
|
||||
|
||||
# Stop after first found device
|
||||
break
|
||||
|
||||
except SerialException as e:
|
||||
conn_msg.append([3, "Couldn’t communicate with the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)])
|
||||
if "Permission" in str(e):
|
||||
conn_msg.append([3, "The GBxCart RW device on port " + ports[i] + " couldn’t be accessed. Make sure your user account has permission to use it and it’s not already in use by another application.\n\n" + str(e)])
|
||||
else:
|
||||
conn_msg.append([3, "A critical error occured while trying to access the GBxCart RW device on port " + ports[i] + ".\n\n" + str(e)])
|
||||
continue
|
||||
|
||||
conn_msg.append([0, "NOTE: This is an unofficial tool for GBxCart RW. Visit https://www.gbxcart.com/ for officially supported options."])
|
||||
return conn_msg
|
||||
|
||||
|
||||
def IsConnected(self):
|
||||
if self.DEVICE is None: return False
|
||||
if not self.DEVICE.isOpen(): return False
|
||||
@@ -194,85 +191,147 @@ class GbxDevice:
|
||||
|
||||
def GetFullName(self):
|
||||
fw, pcb = self.FW
|
||||
self.DEVICE_NAME = "GBxCart RW " + self.PCB_VERSIONS[pcb]
|
||||
return self.DEVICE_NAME + " – Firmware R" + str(fw)
|
||||
if pcb in self.PCB_VERSIONS:
|
||||
self.DEVICE_NAME = "GBxCart RW " + self.PCB_VERSIONS[pcb]
|
||||
else:
|
||||
self.DEVICE_NAME = "GBxCart RW (unknown revision)"
|
||||
return self.DEVICE_NAME + " – Firmware R" + str(fw) + " (" + str(self.PORT) + ")"
|
||||
|
||||
def GetPort(self):
|
||||
return self.PORT
|
||||
|
||||
def LoadFirmwareVersion(self):
|
||||
self.write(self.DEVICE_CMD['READ_FIRMWARE_VERSION'])
|
||||
fw = self.read(1)
|
||||
fw = bytearray(self.read(1))[0]
|
||||
self.write(self.DEVICE_CMD['READ_PCB_VERSION'])
|
||||
pcb = self.read(1)
|
||||
pcb = bytearray(self.read(1))[0]
|
||||
self.FW = [fw, pcb]
|
||||
|
||||
def PingDevice(self):
|
||||
if self.DEVICE != None:
|
||||
try:
|
||||
self.GetFullName()
|
||||
return True
|
||||
except:
|
||||
self.DEVICE = None
|
||||
return False
|
||||
return False
|
||||
|
||||
def CanSetVoltageManually(self):
|
||||
fw, pcb = self.FW
|
||||
if not pcb in (4, 100):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def CanSetVoltageAutomatically(self):
|
||||
fw, pcb = self.FW
|
||||
if pcb in (1, 2, 100):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def GetSupprtedModes(self):
|
||||
fw, pcb = self.FW
|
||||
if pcb == 100:
|
||||
return ["DMG"]
|
||||
else:
|
||||
return ["DMG", "AGB"]
|
||||
|
||||
def GetMode(self):
|
||||
return self.MODE
|
||||
|
||||
def GetSupportedCartridgesDMG(self):
|
||||
return (list(self.SUPPORTED_CARTS_DMG.keys()), list(self.SUPPORTED_CARTS_DMG.values()))
|
||||
return (list(self.SUPPORTED_CARTS['DMG'].keys()), list(self.SUPPORTED_CARTS['DMG'].values()))
|
||||
|
||||
def GetSupportedCartridgesAGB(self):
|
||||
return (list(self.SUPPORTED_CARTS_AGB.keys()), list(self.SUPPORTED_CARTS_AGB.values()))
|
||||
return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values()))
|
||||
|
||||
def wait_for_ack(self, timeout=1):
|
||||
temp = self.DEVICE.timeout
|
||||
self.DEVICE.timeout = timeout
|
||||
while True:
|
||||
buffer = self.DEVICE.read(1)
|
||||
if buffer == b'':
|
||||
print("wait_for_ack(): Timeout -", buffer)
|
||||
def wait_for_ack(self):
|
||||
buffer = self.read(1)
|
||||
timeout = 5
|
||||
while buffer != b'1':
|
||||
print("wait_for_ack(): No valid acknowledgement received (", buffer, "). ", end="")
|
||||
timeout -= 1
|
||||
if timeout < 1:
|
||||
print("Failed.")
|
||||
traceback.print_stack()
|
||||
print("\nwait_for_ack(): Skipping...")
|
||||
return False
|
||||
elif buffer == b'1':
|
||||
self.DEVICE.timeout = temp
|
||||
return True
|
||||
time.sleep(0.1)
|
||||
|
||||
def read(self, length=64, timeout=1):
|
||||
temp = self.DEVICE.timeout
|
||||
self.DEVICE.timeout = timeout
|
||||
currAddr = 0x0000
|
||||
endAddr = length
|
||||
response = bytearray()
|
||||
while (currAddr < endAddr):
|
||||
count = length
|
||||
if count > 1: count = min(length, 64)
|
||||
buffer = self.DEVICE.read(count)
|
||||
if len(buffer) == count:
|
||||
response += buffer
|
||||
currAddr += count
|
||||
if (currAddr < endAddr):
|
||||
self.write('1') # continue read
|
||||
else:
|
||||
self.write('0') # stop read
|
||||
else:
|
||||
print("read(): Timeout -", buffer)
|
||||
self.DEVICE.timeout = temp
|
||||
return b''
|
||||
if length == 1: return buffer[0]
|
||||
|
||||
self.DEVICE.timeout = temp
|
||||
return response[:length]
|
||||
print("Retrying...")
|
||||
time.sleep(0.2)
|
||||
buffer = self.read(1)
|
||||
return True
|
||||
|
||||
def read(self, length=64, last=False):
|
||||
readlen = length
|
||||
if readlen > 64: readlen = 64
|
||||
lives = 5
|
||||
while True:
|
||||
if length <= 64:
|
||||
buffer = self.DEVICE.read(readlen)
|
||||
if len(buffer) != readlen:
|
||||
print("read(): Failed to receive {:d} byte(s) from the device. Discarding {:d} byte(s).".format(readlen, len(buffer)))
|
||||
self.write('0')
|
||||
self.DEVICE.reset_input_buffer()
|
||||
self.DEVICE.reset_output_buffer()
|
||||
return False
|
||||
|
||||
if length == 1:
|
||||
return buffer
|
||||
else:
|
||||
if last:
|
||||
self.write('0')
|
||||
if not last:
|
||||
self.write('1')
|
||||
return buffer[:length]
|
||||
|
||||
else: # length > 64
|
||||
mbuffer = bytearray()
|
||||
for i in range(0, length, 64):
|
||||
buffer = self.DEVICE.read(readlen)
|
||||
if len(buffer) != readlen:
|
||||
print("read(): Failed to receive {:d} byte(s) from the device during iteration {:d}. Discarding {:d} byte(s).".format(readlen, i, len(buffer)))
|
||||
self.write('0')
|
||||
self.DEVICE.reset_input_buffer()
|
||||
self.DEVICE.reset_output_buffer()
|
||||
return False
|
||||
|
||||
mbuffer += buffer
|
||||
if not (i + 64 >= length):
|
||||
self.write('1')
|
||||
|
||||
if last:
|
||||
self.write('0')
|
||||
|
||||
return mbuffer[:length]
|
||||
|
||||
def write(self, data, wait_for_ack=False):
|
||||
if not isinstance(data, bytearray):
|
||||
data = bytearray(data, 'ascii')
|
||||
#print(data)
|
||||
self.DEVICE.write(data)
|
||||
self.DEVICE.flush()
|
||||
|
||||
if wait_for_ack:
|
||||
return self.wait_for_ack()
|
||||
|
||||
def ReadROM(self, offset, length, set_address=True):
|
||||
buffer = False
|
||||
lives = 10
|
||||
while buffer == False:
|
||||
if self.MODE == "DMG":
|
||||
if set_address:
|
||||
self.set_number(offset, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
self.set_mode(self.DEVICE_CMD['READ_ROM_RAM'])
|
||||
buffer = self.read(length, last=True)
|
||||
elif self.MODE == "AGB":
|
||||
if set_address:
|
||||
self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
self.set_mode(self.DEVICE_CMD['GBA_READ_ROM'])
|
||||
buffer = self.read(length, last=True)
|
||||
|
||||
if buffer == False:
|
||||
lives -= 1
|
||||
if lives == 0:
|
||||
self.CANCEL = True
|
||||
print("\nCouldn’t recover from the error. Please try again from the beginning.")
|
||||
return False
|
||||
print("\nStarting over segment at 0x{:06X}...".format(offset))
|
||||
set_address = True
|
||||
|
||||
return buffer
|
||||
|
||||
def gbx_flash_write_address_byte(self, address, data):
|
||||
if self.MODE == "DMG":
|
||||
return self.gb_flash_write_address_byte(address, data)
|
||||
@@ -289,7 +348,11 @@ class GbxDevice:
|
||||
buffer = self.DEVICE_CMD['GBA_FLASH_CART_WRITE_BYTE'] + data + '\x00'
|
||||
self.write(buffer)
|
||||
time.sleep(0.001)
|
||||
return self.wait_for_ack()
|
||||
ack = self.wait_for_ack()
|
||||
if ack == False:
|
||||
print("Can’t continue. Please try again.")
|
||||
self.CANCEL = True
|
||||
return False
|
||||
|
||||
def gb_flash_write_address_byte(self, address, data):
|
||||
address = format(address, 'x')
|
||||
@@ -297,7 +360,11 @@ class GbxDevice:
|
||||
self.write(buffer)
|
||||
buffer = format(data, 'x') + '\x00'
|
||||
self.write(buffer)
|
||||
return self.wait_for_ack()
|
||||
ack = self.wait_for_ack()
|
||||
if ack == False:
|
||||
print("Can’t continue. Please try again.")
|
||||
self.CANCEL = True
|
||||
return False
|
||||
|
||||
def gbx_flash_write_data_bytes(self, command, data):
|
||||
buffer = bytearray(self.DEVICE_CMD[command], "ascii") + bytearray(data)
|
||||
@@ -321,22 +388,36 @@ class GbxDevice:
|
||||
def set_number(self, number, command):
|
||||
buffer = format(command, 's') + format(int(number), 'x') + '\x00'
|
||||
self.write(buffer)
|
||||
if command in ("SET_START_ADDRESS"):
|
||||
time.sleep(0.005)
|
||||
elif command in ("VOLTAGE_3_3V", "VOLTAGE_5V"):
|
||||
time.sleep(0.3)
|
||||
|
||||
def EnableRAM(self, mbc=1, enable=True):
|
||||
self.ReadROM(0, 64)
|
||||
#self.ReadROM(0, 64)
|
||||
if enable:
|
||||
if mbc <= 4:
|
||||
self.set_bank(0x6000, 1)
|
||||
self.set_bank(0x0000, 0x0A)
|
||||
else:
|
||||
if mbc <= 4:
|
||||
self.set_bank(0x6000, 0)
|
||||
self.set_bank(0x0000, 0x00)
|
||||
time.sleep(0.2)
|
||||
|
||||
def SetBankROM(self, bank):
|
||||
self.set_bank(0x2100, (bank & 0xFF))
|
||||
if bank > 255:
|
||||
self.set_bank(0x3000, ((bank >> 8) & 0xFF))
|
||||
def SetBankROM(self, bank, mbc=5):
|
||||
if mbc == 1: # MBC1
|
||||
self.set_bank(0x6000, 0)
|
||||
self.set_bank(0x4000, bank >> 5)
|
||||
self.set_bank(0x2000, bank & 0x1F)
|
||||
elif mbc == 1.1: # Hudson MBC1
|
||||
self.set_bank(0x4000, bank >> 4)
|
||||
if (bank < 10):
|
||||
self.set_bank(0x2000, bank & 0x1F)
|
||||
else:
|
||||
self.set_bank(0x2000, 0x10 | (bank & 0x1F))
|
||||
else:
|
||||
self.set_bank(0x2100, (bank & 0xFF))
|
||||
if bank > 255:
|
||||
self.set_bank(0x3000, ((bank >> 8) & 0xFF))
|
||||
|
||||
def SetBankRAM(self, bank):
|
||||
self.set_bank(0x4000, (bank & 0xFF))
|
||||
@@ -344,46 +425,13 @@ class GbxDevice:
|
||||
def ReadFlashSaveID(self):
|
||||
makers = { 0x1F:"ATMEL", 0xBF:"SST/SANYO", 0xC2:"MACRONIX", 0x32:"PANASONIC", 0x62:"SANYO" }
|
||||
self.set_mode(self.DEVICE_CMD['GBA_FLASH_READ_ID'])
|
||||
time.sleep(0.2)
|
||||
time.sleep(0.02)
|
||||
buffer = self.DEVICE.read(2)
|
||||
if buffer[0] in makers.keys():
|
||||
return makers[buffer[0]]
|
||||
else:
|
||||
return buffer[0]
|
||||
|
||||
def ReadSaveAGB(self, command, eeprom_size, offset, length):
|
||||
if eeprom_size > 0:
|
||||
if offset == 0:
|
||||
self.set_number(eeprom_size, self.DEVICE_CMD['GBA_SET_EEPROM_SIZE'])
|
||||
self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
#time.sleep(0.02)
|
||||
self.set_mode(self.DEVICE_CMD[command])
|
||||
else:
|
||||
self.DEVICE.write(b'1')
|
||||
buffer = self.DEVICE.read(length)
|
||||
else:
|
||||
self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
#time.sleep(0.02)
|
||||
self.set_mode(self.DEVICE_CMD[command])
|
||||
buffer = self.read(length)
|
||||
|
||||
return buffer
|
||||
|
||||
def ReadROM(self, offset, length, cont=False):
|
||||
if self.MODE == "DMG":
|
||||
if not cont:
|
||||
self.set_number(offset, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
#time.sleep(0.02)
|
||||
self.set_mode(self.DEVICE_CMD['READ_ROM_RAM'])
|
||||
buffer = self.read(length)
|
||||
elif self.MODE == "AGB":
|
||||
if not cont:
|
||||
self.set_number(math.floor(offset / 2), self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
#time.sleep(0.02)
|
||||
self.set_mode(self.DEVICE_CMD['GBA_READ_ROM'])
|
||||
buffer = self.read(length)
|
||||
return buffer
|
||||
|
||||
def SetMode(self, mode):
|
||||
if mode == "DMG":
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_5V'])
|
||||
@@ -391,17 +439,16 @@ class GbxDevice:
|
||||
elif mode == "AGB":
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V'])
|
||||
self.MODE = "AGB"
|
||||
time.sleep(0.2)
|
||||
self.set_number(0, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
|
||||
def AutoDetectFlash(self, limitVoltage=False):
|
||||
flash_types = []
|
||||
flash_type = 0
|
||||
if limitVoltage:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V'])
|
||||
time.sleep(0.1)
|
||||
|
||||
if self.MODE == "DMG":
|
||||
supported_carts = list(self.SUPPORTED_CARTS_DMG.values())
|
||||
supported_carts = list(self.SUPPORTED_CARTS['DMG'].values())
|
||||
for f in range(2, len(supported_carts)):
|
||||
flashcart_meta = supported_carts[f]
|
||||
|
||||
@@ -412,7 +459,7 @@ class GbxDevice:
|
||||
if flashcart_meta["write_pin"] == "WR":
|
||||
self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN'])
|
||||
self.set_mode(self.DEVICE_CMD['WE_AS_WR_PIN'])
|
||||
elif flashcart_meta["write_pin"] == "VIN":
|
||||
elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"):
|
||||
self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN'])
|
||||
self.set_mode(self.DEVICE_CMD['WE_AS_AUDIO_PIN'])
|
||||
|
||||
@@ -452,10 +499,9 @@ class GbxDevice:
|
||||
|
||||
if not flash_id_found:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_5V'])
|
||||
time.sleep(0.1)
|
||||
|
||||
elif self.MODE == "AGB":
|
||||
supported_carts = list(self.SUPPORTED_CARTS_AGB.values())
|
||||
supported_carts = list(self.SUPPORTED_CARTS['AGB'].values())
|
||||
for f in range(2, len(supported_carts)):
|
||||
flashcart_meta = supported_carts[f]
|
||||
|
||||
@@ -466,7 +512,7 @@ class GbxDevice:
|
||||
data_ = flashcart_meta["commands"]["unlock"][i][1]
|
||||
count = flashcart_meta["commands"]["unlock"][i][2]
|
||||
for j in range(0, count):
|
||||
self.gbx_flash_write_address_byte(addr, data_ )
|
||||
self.gbx_flash_write_address_byte(addr, data_)
|
||||
|
||||
# Reset Flash
|
||||
if "reset" in flashcart_meta["commands"]:
|
||||
@@ -498,7 +544,6 @@ class GbxDevice:
|
||||
def CheckFlashID(self, limitVoltage=False):
|
||||
if limitVoltage:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V'])
|
||||
time.sleep(0.1)
|
||||
|
||||
flash_id_lines = []
|
||||
if self.MODE == "DMG":
|
||||
@@ -577,12 +622,11 @@ class GbxDevice:
|
||||
|
||||
if limitVoltage and self.MODE == "DMG":
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_5V'])
|
||||
time.sleep(0.1)
|
||||
|
||||
return ret
|
||||
|
||||
def ReadInfo(self):
|
||||
if not self.PingDevice() and not self.Initialize(): raise Exception("Couldn’t access the the device.")
|
||||
if not self.IsConnected() and not self.Initialize(): raise Exception("Couldn’t access the the device.")
|
||||
data = {}
|
||||
header = self.ReadROM(0x000000, 0x000180)
|
||||
|
||||
@@ -617,20 +661,20 @@ class GbxDevice:
|
||||
self.WORKER.updateProgress.connect(fncSetProgress)
|
||||
self.WORKER.start()
|
||||
|
||||
def RestoreRAM(self, fncSetProgress, path="ROM.sav", mbc=0x00, save_type=0):
|
||||
config = { 'mode':3, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type }
|
||||
def RestoreRAM(self, fncSetProgress, path="ROM.sav", mbc=0x00, save_type=0, erase=False):
|
||||
config = { 'mode':3, 'port':self, 'path':path, 'mbc':mbc, 'save_type':save_type, 'erase':erase }
|
||||
self.WORKER = DataTransfer(config)
|
||||
self.WORKER.updateProgress.connect(fncSetProgress)
|
||||
self.WORKER.start()
|
||||
|
||||
def FlashROM(self, fncSetProgress, path="ROM.gb", cart_type=0):
|
||||
config = { 'mode':4, 'port':self, 'path':path, 'cart_type':cart_type }
|
||||
def FlashROM(self, fncSetProgress, path="ROM.gb", cart_type=0, trim_rom=False, override_voltage=False):
|
||||
config = { 'mode':4, 'port':self, 'path':path, 'cart_type':cart_type, 'trim_rom':trim_rom, 'override_voltage':override_voltage }
|
||||
self.WORKER = DataTransfer(config)
|
||||
self.WORKER.updateProgress.connect(fncSetProgress)
|
||||
self.WORKER.start()
|
||||
|
||||
def _TransferData(self, mode, signal, args): # called by thread
|
||||
if not self.PingDevice() and not self.Initialize(): raise Exception("Couldn’t access the the device.")
|
||||
if not self.IsConnected() and not self.Initialize(): raise Exception("Couldn’t access the the device.")
|
||||
|
||||
path = args[0]
|
||||
if self.INFO == None: self.ReadInfo()
|
||||
@@ -679,31 +723,35 @@ class GbxDevice:
|
||||
if bank > 0:
|
||||
startAddr = bank_size
|
||||
endAddr = startAddr + bank_size
|
||||
self.SetBankROM(bank)
|
||||
self.SetBankROM(bank, mbc)
|
||||
|
||||
buffer_len = 1024
|
||||
buffer_len = 0x1000 # debug todo
|
||||
for currAddr in range(startAddr, endAddr, buffer_len):
|
||||
if self.CANCEL:
|
||||
signal.emit({"action":"ABORT", "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return
|
||||
|
||||
if currAddr == startAddr:
|
||||
cont = False
|
||||
buffer = self.ReadROM(currAddr, buffer_len, True)
|
||||
else:
|
||||
cont = True
|
||||
buffer = self.ReadROM(currAddr, buffer_len, cont)
|
||||
buffer = self.ReadROM(currAddr, buffer_len, False)
|
||||
|
||||
if buffer == False:
|
||||
signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An I/O error occured. Please try again from the beginning.", "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return False
|
||||
|
||||
data_dump.extend(buffer)
|
||||
file.write(buffer)
|
||||
recvBytes += buffer_len
|
||||
|
||||
# Report back to GUI
|
||||
if recvBytes % 4096 == 0:
|
||||
if recvBytes % buffer_len == 0:
|
||||
now = time.time()
|
||||
measure_points.append(now - last_emit)
|
||||
last_emit = now
|
||||
if len(measure_points) >= 16:
|
||||
time_delta = statistics.fmean(measure_points)
|
||||
speed = 4096 / time_delta
|
||||
time_delta = statistics.mean(measure_points)
|
||||
speed = buffer_len / time_delta
|
||||
time_elapsed = time.time() - time_start
|
||||
time_left = (time_elapsed / (recvBytes/rom_size)) - time_elapsed
|
||||
if len(measure_points) >= 512:
|
||||
@@ -711,6 +759,16 @@ class GbxDevice:
|
||||
|
||||
if not recvBytes == rom_size: signal.emit(None, recvBytes, rom_size, speed/1024, time.time()-time_start, time_left)
|
||||
|
||||
# check for flashgbx trimmed rom
|
||||
if not os.path.splitext(path)[0].endswith("_notrimfix"): # debug
|
||||
rom_size = len(data_dump)
|
||||
trm_index = data_dump.find(b'TRIMFLASHGBX')
|
||||
if trm_index != -1:
|
||||
free_byte = data_dump[trm_index-4]
|
||||
data_dump = data_dump[0:trm_index-4] + bytes([free_byte] * (rom_size - (trm_index-4)))
|
||||
file.seek(0)
|
||||
file.write(data_dump)
|
||||
|
||||
file.close()
|
||||
|
||||
# calculate global checksum
|
||||
@@ -792,15 +850,19 @@ class GbxDevice:
|
||||
transfer_size = 128
|
||||
|
||||
# Prepare some stuff
|
||||
if mode == 2:
|
||||
if mode == 2: # Backup
|
||||
try:
|
||||
file = open(path, "wb")
|
||||
except PermissionError as e:
|
||||
signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"FlashGBX doesn’t have permission to access this file for writing:\n" + path, "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return False
|
||||
|
||||
elif mode == 3:
|
||||
with open(path, "rb") as file: data_import = file.read()
|
||||
elif mode == 3: # Restore
|
||||
if args[3]: # Erase
|
||||
data_import = save_size * b'\xFF'
|
||||
else:
|
||||
with open(path, "rb") as file: data_import = file.read()
|
||||
|
||||
if save_size > len(data_import):
|
||||
data_import += b'\xFF' * (save_size - len(data_import))
|
||||
|
||||
@@ -815,13 +877,16 @@ class GbxDevice:
|
||||
endAddr = startAddr + bank_size
|
||||
if endAddr > (startAddr + save_size): endAddr = startAddr + save_size
|
||||
self.SetBankRAM(bank)
|
||||
|
||||
elif self.MODE == "AGB":
|
||||
endAddr = startAddr + min(save_size, bank_size)
|
||||
if endAddr > (startAddr + save_size): endAddr = startAddr + save_size
|
||||
if bank > 0:
|
||||
if save_type == 1 or save_type == 2: # EEPROM
|
||||
self.set_number(eeprom_size, self.DEVICE_CMD['GBA_SET_EEPROM_SIZE'])
|
||||
elif (save_type == 5 or save_type == 6) and bank > 0: # FLASH
|
||||
self.set_number(bank, self.DEVICE_CMD['GBA_FLASH_SET_BANK'])
|
||||
|
||||
self.set_number(startAddr, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
buffer_len = transfer_size
|
||||
sector = 0
|
||||
@@ -840,7 +905,16 @@ class GbxDevice:
|
||||
if self.MODE == "DMG":
|
||||
buffer = self.ReadROM(currAddr, buffer_len)
|
||||
elif self.MODE == "AGB":
|
||||
buffer = self.ReadSaveAGB(read_command, eeprom_size, currAddr*2, buffer_len)
|
||||
if currAddr + buffer_len < save_size:
|
||||
last = False
|
||||
else:
|
||||
last = True
|
||||
self.set_mode(self.DEVICE_CMD[read_command])
|
||||
buffer = self.read(buffer_len, last)
|
||||
if buffer == False:
|
||||
signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Backup failed, please try again.", "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return False
|
||||
|
||||
data_dump.extend(buffer)
|
||||
file.write(buffer)
|
||||
|
||||
@@ -859,15 +933,16 @@ class GbxDevice:
|
||||
self.wait_for_ack()
|
||||
sector += 1
|
||||
while True: # wait for 0xFF (= erase done)
|
||||
buffer = self.ReadSaveAGB(read_command, eeprom_size, currAddr*2, buffer_len)
|
||||
self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
self.set_mode(self.DEVICE_CMD[read_command])
|
||||
buffer = self.read(buffer_len, last=True)
|
||||
if buffer[0] == 0xFF: break
|
||||
time.sleep(0.01)
|
||||
self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
self.gbx_flash_write_data_bytes("GBA_FLASH_WRITE_BYTE", data)
|
||||
|
||||
else: # SRAM / EEPROM
|
||||
else: # EEPROM / SRAM
|
||||
self.gbx_flash_write_data_bytes(write_command, data)
|
||||
|
||||
self.wait_for_ack()
|
||||
@@ -884,7 +959,6 @@ class GbxDevice:
|
||||
|
||||
if mode == 2: file.close()
|
||||
|
||||
self.ReadInfo()
|
||||
self.INFO["last_action"] = mode
|
||||
signal.emit(None, pos, pos, speed/1024, time.time()-time_start, 0)
|
||||
|
||||
@@ -893,10 +967,25 @@ class GbxDevice:
|
||||
elif mode == 4: # Flash ROM
|
||||
with open(path, "rb") as file: data_import = file.read()
|
||||
|
||||
if len(data_import) < 0x8000: # smallest flashable ROM is 32 KB
|
||||
data_import += b'\xFF' * (0x8000 - len(data_import))
|
||||
|
||||
if args[2]: # trim_rom
|
||||
free_byte = data_import[len(data_import)-1]
|
||||
if free_byte == 0xFF: # or free_byte == 0x00:
|
||||
marker = bytes([free_byte] * 4) + b'TRIMFLASHGBX' # a trim marker, so dumps will still be valid
|
||||
for i in range(len(data_import)-1, -1, -1):
|
||||
if data_import[i] != free_byte: break
|
||||
rom_size_trimmed = i
|
||||
rom_size_trimmed += len(marker)
|
||||
rom_size_trimmed = 0x1000 * (math.ceil(rom_size_trimmed / 0x1000))
|
||||
if rom_size_trimmed < len(data_import):
|
||||
data_import = data_import[0:rom_size_trimmed-len(marker)] + marker
|
||||
|
||||
if self.MODE == "DMG":
|
||||
supported_carts = list(self.SUPPORTED_CARTS_DMG.values())
|
||||
supported_carts = list(self.SUPPORTED_CARTS['DMG'].values())
|
||||
elif self.MODE == "AGB":
|
||||
supported_carts = list(self.SUPPORTED_CARTS_AGB.values())
|
||||
supported_carts = list(self.SUPPORTED_CARTS['AGB'].values())
|
||||
|
||||
cart_type = "RETAIL"
|
||||
for i in range(0, len(supported_carts)):
|
||||
@@ -911,11 +1000,14 @@ class GbxDevice:
|
||||
time_left = 0
|
||||
|
||||
# Set Voltage
|
||||
if flashcart_meta["voltage"] == 3.3:
|
||||
if args[3] == 3.3:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V'])
|
||||
elif args[3] == 5:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_5V'])
|
||||
elif flashcart_meta["voltage"] == 3.3:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_3_3V'])
|
||||
elif flashcart_meta["voltage"] == 5:
|
||||
self.set_mode(self.DEVICE_CMD['VOLTAGE_5V'])
|
||||
time.sleep(1)
|
||||
|
||||
if self.MODE == "DMG":
|
||||
self.set_mode(self.DEVICE_CMD['GB_CART_MODE'])
|
||||
@@ -925,9 +1017,9 @@ class GbxDevice:
|
||||
self.set_mode(self.DEVICE_CMD['GB_FLASH_WE_PIN'])
|
||||
if flashcart_meta["write_pin"] == "WR":
|
||||
self.set_mode(self.DEVICE_CMD['WE_AS_WR_PIN'])
|
||||
elif flashcart_meta["write_pin"] == "VIN":
|
||||
elif flashcart_meta["write_pin"] in ("AUDIO", "VIN"):
|
||||
self.set_mode(self.DEVICE_CMD['WE_AS_AUDIO_PIN'])
|
||||
|
||||
|
||||
if "single_write" in flashcart_meta["commands"] and len(flashcart_meta["commands"]["single_write"]) == 4:
|
||||
# Submit flash program commands to firmware
|
||||
self.set_mode(self.DEVICE_CMD['GB_FLASH_PROGRAM_METHOD'])
|
||||
@@ -963,7 +1055,7 @@ class GbxDevice:
|
||||
if flash_id in flashcart_meta["flash_ids"]: flash_id_found = True
|
||||
if not flash_id_found and len(flashcart_meta["flash_ids"]) > 0:
|
||||
pass
|
||||
|
||||
|
||||
# Reset Flash
|
||||
if "reset" in flashcart_meta["commands"]:
|
||||
for i in range(0, len(flashcart_meta["commands"]["reset"])):
|
||||
@@ -1003,7 +1095,6 @@ class GbxDevice:
|
||||
self.gbx_flash_write_address_byte(flashcart_meta["commands"]["reset"][i][0], flashcart_meta["commands"]["reset"][i][1])
|
||||
|
||||
self.set_number(0, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
# Write Flash
|
||||
pos = 0
|
||||
@@ -1012,7 +1103,7 @@ class GbxDevice:
|
||||
if "first_bank" in flashcart_meta: first_bank = flashcart_meta["first_bank"]
|
||||
if "start_addr" in flashcart_meta: currAddr = flashcart_meta["start_addr"]
|
||||
endAddr = 0x7FFF
|
||||
bank_count = math.floor(len(data_import) / bank_size)
|
||||
bank_count = math.ceil(len(data_import) / bank_size)
|
||||
self.SetBankROM(0)
|
||||
elif self.MODE == "AGB":
|
||||
currAddr = 0
|
||||
@@ -1035,9 +1126,9 @@ class GbxDevice:
|
||||
if self.MODE == "DMG":
|
||||
if bank > first_bank: currAddr = bank_size
|
||||
self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
while (currAddr < endAddr):
|
||||
if pos == len(data_import): break
|
||||
if self.CANCEL:
|
||||
# Reset Flash
|
||||
if "reset" in flashcart_meta["commands"]:
|
||||
@@ -1114,7 +1205,6 @@ class GbxDevice:
|
||||
self.set_number(currAddr, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
elif self.MODE == "AGB":
|
||||
self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
if sectCount is not None:
|
||||
sectCount -= 1
|
||||
@@ -1131,8 +1221,8 @@ class GbxDevice:
|
||||
pos += 32
|
||||
|
||||
else:
|
||||
print("FAILURE: Buffer writing for this flash method is not implemented yet.")
|
||||
self.CANCEL = True
|
||||
signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return False
|
||||
# TODO
|
||||
|
||||
elif self.MODE == "AGB":
|
||||
@@ -1158,7 +1248,6 @@ class GbxDevice:
|
||||
|
||||
if int(currAddr % 0x8000) == 0:
|
||||
self.set_number(currAddr / 2, self.DEVICE_CMD['SET_START_ADDRESS'])
|
||||
time.sleep(0.02)
|
||||
|
||||
else:
|
||||
data = data_import[pos:pos+64]
|
||||
@@ -1168,8 +1257,8 @@ class GbxDevice:
|
||||
pos += 64
|
||||
|
||||
else:
|
||||
print("FAILURE: Buffer writing for this flash method is not implemented yet.")
|
||||
self.CANCEL = True
|
||||
signal.emit({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Buffer writing for this flash chip is not implemented yet.", "abortable":False}, 0, 0, 0, 0, 0)
|
||||
return False
|
||||
# TODO
|
||||
|
||||
elif "single_write" in flashcart_meta["commands"]:
|
||||
@@ -1225,7 +1314,7 @@ class GbxDevice:
|
||||
measure_points = measure_points[-512:]
|
||||
|
||||
if not pos == len(data_import): signal.emit(None, pos, len(data_import), speed/1024, time.time()-time_start, time_left)
|
||||
|
||||
|
||||
# Reset Flash
|
||||
if "reset" in flashcart_meta["commands"]:
|
||||
for i in range(0, len(flashcart_meta["commands"]["reset"])):
|
||||
BIN
FlashGBX/res/config.zip
Normal file
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
1
MANIFEST.in
Normal file
@@ -0,0 +1 @@
|
||||
recursive-include FlashGBX/res *
|
||||
118
README.md
@@ -2,16 +2,19 @@
|
||||
|
||||
by Lesserkuma
|
||||
|
||||
<img src="/.github/FlashGBX_Windows.png" alt="Screenshot" width="500">
|
||||
<img src="/.github/FlashGBX_Ubuntu.png" alt="Screenshot" width="500">
|
||||
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/FlashGBX_Windows.png" alt="FlashGBX on Windows" width="500">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/FlashGBX_Ubuntu.png" alt="FlashGBX on Ubuntu" width="500">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/lesserkuma/FlashGBX/master/.github/FlashGBX_macOS.png" alt="FlashGBX on macOS" width="500">
|
||||
|
||||
## Introduction
|
||||
|
||||
### Software features
|
||||
|
||||
- Backup and restore save data from Game Boy and Game Boy Advance game cartridges
|
||||
- Backup, restore and erase save data from Game Boy and Game Boy Advance game cartridges
|
||||
- Backup ROM data from Game Boy and Game Boy Advance game cartridges
|
||||
- Flash new ROMs to a wide variety of Game Boy and Game Boy Advance flash cartridges
|
||||
- Write new ROMs to a wide variety of Game Boy and Game Boy Advance flash cartridges
|
||||
- Many flash cartridges can be auto-detected
|
||||
- A Flash ID check can be performed for unsupported flash cartridges
|
||||
|
||||
@@ -23,14 +26,15 @@ by Lesserkuma
|
||||
|
||||
- Game Boy
|
||||
|
||||
- BUNG Doctor GB Card 64M
|
||||
- DIY cart with AM29F016/AM29F016B *(thanks RevZ, AndehX)*
|
||||
- GB Smart 32M
|
||||
- insideGadgets 32 KB *(thanks AlexiG)*
|
||||
- insideGadgets 512 KB *(thanks AlexiG)*
|
||||
- insideGadgets 1 MB, 128 KB SRAM *(thanks AlexiG)*
|
||||
- insideGadgets 2 MB, 128 KB SRAM/32 KB FRAM *(thanks AlexiG)*
|
||||
- insideGadgets 4 MB, 128 KB SRAM/FRAM *(thanks AlexiG)*
|
||||
- insideGadgets 4 MB, 32 KB FRAM, MBC3+RTC *(thanks AlexiG)*
|
||||
- BUNG Doctor GB Card 64M
|
||||
- GB Smart 32M
|
||||
|
||||
- Game Boy Advance
|
||||
|
||||
@@ -42,7 +46,6 @@ by Lesserkuma
|
||||
|
||||
- Game Boy
|
||||
|
||||
- DIY cart with AM29F016/AM29F016B *(thanks RevZ)*
|
||||
- ES29LV160_DRV with 29DL32TF-70
|
||||
- GB-M968 with M29W160EB *(thanks RevZ)*
|
||||
- GB-M968 with MX29LV320ABTC
|
||||
@@ -64,64 +67,52 @@ by Lesserkuma
|
||||
- AGB-SD-E05 with MSP55LV128 *(thanks RevZ)*
|
||||
- BX2006_0106_NEW with S29GL128N10TFI01 *(thanks litlemoran)*
|
||||
|
||||
Many different bootleg cartridges share their command set, so even if yours is not on this list, it may still work fine or even be detected as another one. Support for more cartridges can also be added by creating external config files that include the necessary flash chip commands.
|
||||
Many different bootleg cartridges share their flash chip command set, so even if yours is not on this list, it may still work fine or even be detected as another one. Support for more cartridges can also be added by creating external config files that include the necessary flash chip commands.
|
||||
|
||||
## Installing and running
|
||||
|
||||
The application should work on pretty much every operating system that supports Qt-GUI applications built using [Python 3](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/) and [pyserial](https://pypi.org/project/pyserial/) packages.
|
||||
|
||||
If you have Python and pip installed, you can use `pip install FlashGBX` to download and install the application.
|
||||
|
||||
To run it in portable mode, you can also download the source code archive and call `python run.py` after installing the prerequisites yourself.
|
||||
|
||||
### Windows binaries
|
||||
|
||||
Available in the GitHub [Releases](https://github.com/lesserkuma/FlashGBX/releases) section:
|
||||
|
||||
* 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
|
||||
|
||||
These executables have been created using *PyInstaller* and *Inno Setup*.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* If something doesn’t work as expected during data transfer, first try to clean the game cartridge contacts, wiggle the cartridge a bit and reconnect the USB device.
|
||||
|
||||
* On Linux systems, you may run into a *Permission Error* problem when trying to connect to USB devices without *sudo* privileges. To grant yourself the necessary permissions temporarily, you can run `sudo chmod 0666 /dev/ttyUSB0` (replace with actual device path) before running the app. For a permanent solution, add yourself to the usergroup that has access to serial devices by default (e.g. *dialout* on Debian-based distros; `sudo adduser $USER dialout`) and then reboot the system.
|
||||
|
||||
* On some Linux systems, you may need the *XCB Xinerama package* if you see an error regarding failed Qt platform plugin initialization. You can install it with `sudo apt install libxcb-xinerama0` etc.
|
||||
|
||||
## DISCLAIMER
|
||||
|
||||
This software is provided as-is and the developer is not responsible for any damage that is caused by the use of it. Use at your own risk!
|
||||
|
||||
## System Requirements and Setup Instructions
|
||||
|
||||
Requires [Python 3.8+](https://www.python.org/downloads/) with [PySide2](https://pypi.org/project/PySide2/) and [pyserial](https://pypi.org/project/pyserial/) packages. If these are available for your Operating System, it should be compatible.
|
||||
|
||||
Developed and tested using the *insideGadgets GBxCart RW v1.3 Pro* hardware device on Windows 10. Also confirmed working on most Debian-based Linux distributions including Ubuntu.
|
||||
|
||||
### Windows
|
||||
|
||||
There are multiple options to run this application on Windows:
|
||||
|
||||
* You can install the app with the Setup program. This will add the app to the start menu and optionally create a desktop icon.
|
||||
* Or, you can extract the Portable package and run the precompiled `FlashGBX.exe` file (created with *PyInstaller*) which includes the Python interpreter, required packages and libraries.
|
||||
* Or, you can install Python and run the app directly from source code:
|
||||
1. Install [Python 3.8+](https://www.python.org/downloads/)
|
||||
2. Open a Command Prompt window and install PySide2 and pyserial like so:<br>`pip install PySide2 pyserial`
|
||||
3. Run `FlashGBX.py`
|
||||
|
||||
### Linux
|
||||
|
||||
#### Manual setup procedure tested on Ubuntu 20.04.1 LTS
|
||||
|
||||
1. Update the package list:<br>`sudo apt update`
|
||||
2. Install Python3, pip for Python 3 and the XCB library package (required by PySide2/Qt5):<br>`sudo apt install python3 python3-pip libxcb-xinerama0`<br>If it says “Package 'python3-pip' has no installation candidate”, you may need to run this command first and then try again:<br>`sudo add-apt-repository universe`
|
||||
3. Install PySide2 and pyserial using pip:<br>`sudo pip3 install PySide2 pyserial`
|
||||
4. At this point you could run the app with `sudo` privileges, but to avoid that you will need access permissions for your cart reader/writer hardware. Here are some ways to do that:
|
||||
* Add yourself to the *dialout* usergroup and then reboot the system:<br>`sudo adduser $USER dialout`<br>`sudo reboot`
|
||||
* Or, add permanent user permissions to your device. (No reboot required.)
|
||||
1. Create a udev rules file:<br>`sudoedit /etc/udev/rules.d/50-ttyusb.rules`
|
||||
2. Add the following line to this file (GBxCart RW uses Vendor ID *1a86* and Product ID *7523*):<br>`KERNEL=="ttyUSB[0-9]*",ATTRS{idVendor}=="1a86",ATTRS{idProduct}=="7523",MODE="0666"`
|
||||
3. Reload your udev configuration:<br>`sudo udevadm control --reload`
|
||||
4. Connect your cart reader/writer to a USB port. (If it was already connected, unplug and reconnect it.)
|
||||
6. Run the app:<br>`python3 FlashGBX.py`
|
||||
|
||||
#### Automated install script for Debian-based distributions
|
||||
|
||||
A third-party install script contributed by *RevZ* for your convenience is also available. This is not maintained by me, but this should take care of installing all the prerequisites and copy everything to a subdirectory in your home directory. It also adds a shortcut to your desktop.
|
||||
|
||||
After extracting all FlashGBX files into a temporary directory, get the installer [here](https://pastebin.com/fDsYh1Eb), place it into the same directory, adjust permissions (`chmod +x installer.sh`) and run it (`./installer.sh`). After the install is complete you may still have to reboot, and right click the Desktop icon to “Allow Launching”.
|
||||
|
||||
## Contributions
|
||||
|
||||
The author would like to thank the following kind people for their help and contributions:
|
||||
The author would like to thank the following very kind people for their help and contributions (in alphabetical order):
|
||||
|
||||
- AlexiG (GBxCart RW hardware, bug reports, flash chip info)
|
||||
- RevZ (install script for Linux, testing, bug reports, flash chip info)
|
||||
- AndehX (app icon)
|
||||
- LovelyA72 (flash chip info)
|
||||
- JFox (help with properly packaging the app for pip)
|
||||
- julgr (macOS help, testing)
|
||||
- litlemoran (flash chip info)
|
||||
- LovelyA72 (flash chip info)
|
||||
- RevZ (Linux help, testing, bug reports, flash chip info)
|
||||
|
||||
## Changes
|
||||
|
||||
### v0.7β
|
||||
### v0.7β (2020-09-25)
|
||||
|
||||
- First started tracking changes
|
||||
- Added a way to launch the flash cartridge type auto-detection process from the type list
|
||||
@@ -130,7 +121,7 @@ The author would like to thank the following kind people for their help and cont
|
||||
- Fixed config file for 4455_4400_4000_4350_36L0R_V3 with M36L0R705 (sector size map was incomplete)
|
||||
- Renamed some labels of flash cartridge types
|
||||
- Made config files UTF-8 compatible
|
||||
- File open and save dialogs will now remember the last used directory for ROM files and save data files respectively (stored in config.ini in AppData/Roaming), clearable with the `--resetconfig` switch
|
||||
- File open and save dialogs will now remember the last used directory for ROM files and save data files respectively (stored in config.ini in AppData/Roaming), clearable with the `--reset` switch or by editing/deleting config.ini
|
||||
- Added CRC32 checksum comparison for Game Boy Advance games by using a database based on header SHA1 hashes
|
||||
- Fixed a bug with save data restore and improved save data backup speed
|
||||
- Fixed a few status message errors
|
||||
@@ -138,3 +129,26 @@ The author would like to thank the following kind people for their help and cont
|
||||
- Made some messages suppressible (stored in config.ini)
|
||||
- Added detection of ungraceful device disconnects when starting a new task, with optional automatic reconnect
|
||||
- First public beta release
|
||||
|
||||
### v0.8β (2020-10-03)
|
||||
|
||||
- Added support for the DIY cart with AM29F016/AM29F016B with AUDIO as WE *(thanks AndehX)*
|
||||
- Renamed `VIN` to `AUDIO` in config files and the command line switch `--resetconfig` to `--reset`
|
||||
- Added experimental support for GBxCart RW revisions other than v1.3 and fixed a crash when connecting to unknown revisions of the GBxCart RW
|
||||
- The app is now available as a package and can be installed directly through *pip* *(thanks JFox)*
|
||||
- Changed the way configuration files are stored (for details call with `--help` command line switch)
|
||||
- Added the option to write an automatically trimmed ROM file which can reduce flashing time, especially in Game Boy Advance mode (note that not all ROMs can be trimmed)
|
||||
- When dumping a flash cartridge that has been flashed with a trimmed ROM, the ROM will be fixed so checksums will still match up (can be disabled for debugging by adding `_notrimfix` to the file name)
|
||||
- Added a button that opens a file browser to the currently used config directory for easy access
|
||||
- Added the option to erase/wipe the save data on a cartridge
|
||||
- Rearranged some buttons on the main window so that the newly added button popup menus don’t block anything
|
||||
- Improved the database for Game Boy Advance titles (now detects SRAM_F_V### save types properly)
|
||||
- Rewrote some of the save data handling for Game Boy Advance cartridges to speed transfers up a bit
|
||||
- Fixed a couple of instability issues that used to caused timeouts on macOS *(thanks julgr)*
|
||||
- Confirmed support for SD007_48BALL_64M_V5 with 29DL163BD-90 *(thanks julgr)*
|
||||
- Added an option for flashing at 5V for a few flash cartridge types that sometimes require this; if you think your cartridge is affected, you can add `"voltage_variants":true,` to the config file
|
||||
- Fixed support for Windows 7 when using pre-compiled exe file packages
|
||||
- Reduced size and decompression time of the pre-compiled exe file packages by excluding unnecessary DLL files
|
||||
- Fixed a timing issue that could sometimes cause a loss of save data when hot-swapping Game Boy cartridges
|
||||
- Added some warnings that help with troubleshooting, for example that manually setting the feature box to a MBC5 option may be necessary for a clean dump when dumping ROMs from flash cartridges
|
||||
- Added taskbar progress visualization on Windows systems
|
||||
|
||||
4
run.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Note: This file runs FlashGBX in portable mode and is also used to build the portable Windows archive.
|
||||
|
||||
from FlashGBX import FlashGBX
|
||||
FlashGBX.main(portableMode=True)
|
||||
36
setup.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="FlashGBX",
|
||||
version="0.8b0",
|
||||
author="Lesserkuma",
|
||||
description="A GUI application that can read and write Game Boy and Game Boy Advance cartridge data. Currently supports the GBxCart RW hardware device by insideGadgets.",
|
||||
url="https://github.com/lesserkuma/FlashGBX",
|
||||
packages=setuptools.find_packages(),
|
||||
install_requires=['PySide2', 'pyserial'],
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: X11 Applications :: Qt",
|
||||
"Topic :: Terminals :: Serial",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Developers',
|
||||
],
|
||||
project_urls={
|
||||
'Source': 'https://github.com/lesserkuma/FlashGBX/',
|
||||
'Tracker': 'https://github.com/lesserkuma/FlashGBX/issues',
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': (
|
||||
'flashgbx = FlashGBX.FlashGBX:main',
|
||||
)
|
||||
},
|
||||
python_requires='>=3.7',
|
||||
long_description_content_type="text/markdown",
|
||||
long_description=long_description,
|
||||
)
|
||||