This commit is contained in:
Lesserkuma
2020-10-03 18:56:01 +02:00
parent eed9d2ea5d
commit a779a3d8cd
48 changed files with 21281 additions and 525 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
.github/FlashGBX_macOS.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

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

File diff suppressed because it is too large Load Diff

0
FlashGBX/__init__.py Normal file
View File

2
FlashGBX/__main__.py Normal file
View File

@@ -0,0 +1,2 @@
from . import FlashGBX
FlashGBX.main()

20281
FlashGBX/config/db_AGB.json Normal file

File diff suppressed because it is too large Load Diff

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

View File

@@ -7,6 +7,7 @@
[ 0xBF, 0x00, 0x53, 0x73 ]
],
"voltage":3.3,
"voltage_variants":true,
"flash_size":0x400000,
"start_addr":0,
"first_bank":1,

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

View File

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

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

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

View File

@@ -11,7 +11,7 @@
"sector_size":0x20000,
"start_addr":0,
"first_bank":1,
"write_pin":"VIN",
"write_pin":"AUDIO",
"commands":{
"reset":[
[ 0, 0xFF ]

View File

@@ -11,7 +11,7 @@
"sector_size":0x20000,
"start_addr":0,
"first_bank":1,
"write_pin":"VIN",
"write_pin":"AUDIO",
"commands":{
"reset":[
[ 0, 0xFF ]

View File

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

View File

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

View File

@@ -1,12 +1,11 @@
# -*- coding: 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, "Couldnt 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, "Couldnt 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] + " couldnt be accessed. Make sure your user account has permission to use it and its 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("\nCouldnt 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("Cant 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("Cant 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("Couldnt access the the device.")
if not self.IsConnected() and not self.Initialize(): raise Exception("Couldnt 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("Couldnt access the the device.")
if not self.IsConnected() and not self.Initialize(): raise Exception("Couldnt 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 doesnt 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

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

1
MANIFEST.in Normal file
View File

@@ -0,0 +1 @@
recursive-include FlashGBX/res *

118
README.md
View File

@@ -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 doesnt 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 dont 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
View 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
View 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,
)

File diff suppressed because one or more lines are too long