FlashGBX 4.2__py3-none-any.whl → 4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
FlashGBX/Mapper.py CHANGED
@@ -75,8 +75,8 @@ class DMG_MBC:
75
75
  return DMG_Unlicensed_Sachen(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
76
76
  elif mbc_id == 0x205: # 0x205:'Datel Orbit V2',
77
77
  return DMG_Unlicensed_DatelOrbitV2(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
78
- # elif mbc_id == 0x206: # 0x206:'Datel MegaMem',
79
- # return DMG_Unlicensed_DatelMegaMem(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
78
+ elif mbc_id == 0x206: # 0x206:'MBCX',
79
+ return DMG_Unlicensed_MBCX(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
80
80
  else:
81
81
  self.__init__(args=args, cart_write_fncptr=cart_write_fncptr, cart_read_fncptr=cart_read_fncptr, cart_powercycle_fncptr=cart_powercycle_fncptr, clk_toggle_fncptr=clk_toggle_fncptr)
82
82
  return self
@@ -269,7 +269,7 @@ class DMG_MBC3(DMG_MBC):
269
269
 
270
270
  def HasRTC(self):
271
271
  dprint("Checking for RTC")
272
- if self.MBC_ID not in (0x0F, 0x10, 0x110):
272
+ if self.MBC_ID not in (0x0F, 0x10, 0x110, 0x206):
273
273
  dprint("No RTC because mapper value is not used for RTC:", self.MBC_ID)
274
274
  return False
275
275
  self.EnableRAM(enable=False)
@@ -831,6 +831,7 @@ class DMG_GMMC1(DMG_MBC5):
831
831
  def CalcChecksum(self, buffer):
832
832
  header = RomFileDMG(buffer[:0x180]).GetHeader()
833
833
  target_chk_value = 0
834
+ target_sha1_value = 0
834
835
  if header["game_title"] == "NP M-MENU MENU":
835
836
  target_sha1_value = "15f5d445c0b2fdf4221cf2a986a4a5cb8dfda131"
836
837
  target_chk_value = 0x19E8
@@ -1531,6 +1532,44 @@ class DMG_Unlicensed_DatelOrbitV2(DMG_MBC):
1531
1532
  def GetMaxROMSize(self):
1532
1533
  return 128*1024
1533
1534
 
1535
+ class DMG_Unlicensed_MBCX(DMG_MBC3):
1536
+ def GetName(self):
1537
+ return "MBCX"
1538
+
1539
+ def HasFlashBanks(self):
1540
+ return True
1541
+
1542
+ def SelectBankFlash(self, index):
1543
+ dprint(self.GetName(), "|SelectBankFlash()|", index)
1544
+
1545
+ commands = [
1546
+ [ 0x0000, 0x05 ],
1547
+ [ 0x4000, 0x82 ],
1548
+ [ 0xA000, index ],
1549
+ [ 0x0000, 0x00 ]
1550
+ ]
1551
+ self.CURRENT_FLASH_BANK = index
1552
+ self.CartWrite(commands, delay=0.1)
1553
+
1554
+ def SelectBankROM(self, index):
1555
+ dprint(self.GetName(), index)
1556
+
1557
+ if (index % 512 == 0) or (self.CURRENT_FLASH_BANK != math.floor(index / 512)):
1558
+ self.SelectBankFlash(math.floor(index / 512))
1559
+ self.CURRENT_ROM_BANK = index
1560
+ index = index % 512
1561
+
1562
+ commands = [
1563
+ [ 0x3000, ((index >> 8) & 0xFF) ],
1564
+ [ 0x2100, index & 0xFF ],
1565
+ ]
1566
+
1567
+ self.CartWrite(commands)
1568
+ return (0x4000, self.ROM_BANK_SIZE)
1569
+
1570
+ def GetMaxROMSize(self):
1571
+ return 32*1024*1024
1572
+
1534
1573
 
1535
1574
  class AGB_GPIO:
1536
1575
  CART_WRITE_FNCPTR = None
@@ -1608,7 +1647,7 @@ class AGB_GPIO:
1608
1647
  temp = self.CartRead(self.GPIO_REG_DAT) & 0xFF
1609
1648
  bit = (temp & 2) >> 1
1610
1649
  data = (data >> 1) | (bit << 7)
1611
- # print("RTCReadData(): i={:d}/temp={:X}/bit={:x}/data={:x}".format(i, temp, bit, data))
1650
+ # dprint("RTCReadData(): i={:d}/temp={:X}/bit={:x}/data={:x}".format(i, temp, bit, data))
1612
1651
  return data
1613
1652
 
1614
1653
  def RTCWriteData(self, data):
@@ -1664,7 +1703,8 @@ class AGB_GPIO:
1664
1703
  status = self.RTCReadStatus()
1665
1704
  else:
1666
1705
  status = buffer[0]
1667
-
1706
+
1707
+ dprint("Status:", bin(status))
1668
1708
  if (status >> 7) == 1:
1669
1709
  dprint("No RTC because of set RTC Status Register Power Flag:", status >> 7 & 1)
1670
1710
  return 1
@@ -1684,7 +1724,7 @@ class AGB_GPIO:
1684
1724
  else:
1685
1725
  rom2 = buffer[1:7]
1686
1726
 
1687
- dprint(' '.join(format(x, '02X') for x in rom1), "/", ' '.join(format(x, '02X') for x in rom2))
1727
+ dprint("RTC Data:", ' '.join(format(x, '02X') for x in rom1), "/", ' '.join(format(x, '02X') for x in rom2))
1688
1728
  if (rom1 == rom2):
1689
1729
  dprint("No RTC because ROM data didn’t change:", rom1, rom2)
1690
1730
  return 3
@@ -1869,7 +1909,6 @@ class AGB_GPIO:
1869
1909
  }
1870
1910
 
1871
1911
  if rtc_y == 0 and rtc_m == 0 and rtc_d == 0 and rtc_h == 0 and rtc_i == 0 and rtc_s == 0:
1872
- #raise ValueError("Invalid RTC data")
1873
1912
  d["string"] = "Invalid RTC data"
1874
1913
  d["rtc_valid"] = False
1875
1914
  else:
FlashGBX/PocketCamera.py CHANGED
@@ -110,7 +110,7 @@ class PocketCamera:
110
110
  offset = 0x2000 + (index * 0x1000)
111
111
  elif index == 30:
112
112
  offset = 0x11FC
113
- elif index == 31:
113
+ else:
114
114
  offset = 0
115
115
  imgbuffer = self.DATA[offset:offset+0x1000]
116
116
  return self.ConvertPicture(imgbuffer)
@@ -138,7 +138,7 @@ class PocketCamera:
138
138
  frame.paste(pic, (left, top))
139
139
  pic = frame
140
140
 
141
- pic = pic.resize((pic.width * scale, pic.height * scale), Image.NEAREST)
141
+ pic = pic.resize((pic.width * scale, pic.height * scale), Image.Resampling.NEAREST)
142
142
 
143
143
  ext = os.path.splitext(path)[1]
144
144
  if ext == "" or ext.lower() == ".png":
@@ -7,6 +7,7 @@ from PIL.ImageQt import ImageQt
7
7
  from PIL import Image, ImageDraw
8
8
  from .pyside import QtCore, QtWidgets, QtGui, QDesktopWidget
9
9
  from .PocketCamera import PocketCamera
10
+ from .UserInputDialog import UserInputDialog
10
11
 
11
12
  class PocketCameraWindow(QtWidgets.QDialog):
12
13
  CUR_PIC = None
@@ -20,6 +21,7 @@ class PocketCameraWindow(QtWidgets.QDialog):
20
21
  APP_PATH = "."
21
22
  CONFIG_PATH = "."
22
23
  APP = None
24
+ FORCE_EXIT = False
23
25
  PALETTES = [
24
26
  [ 255, 255, 255, 176, 176, 176, 104, 104, 104, 0, 0, 0 ], # Grayscale
25
27
  [ 208, 217, 60, 120, 164, 106, 84, 88, 84, 36, 70, 36 ], # Game Boy
@@ -33,6 +35,8 @@ class PocketCameraWindow(QtWidgets.QDialog):
33
35
  QtWidgets.QDialog.__init__(self)
34
36
  self.setAcceptDrops(True)
35
37
  if icon is not None: self.setWindowIcon(QtGui.QIcon(icon))
38
+
39
+ self.FORCE_EXIT = False
36
40
  self.CUR_FILE = file
37
41
  self.CONFIG_PATH = config_path
38
42
  self.APP_PATH = app_path
@@ -188,7 +192,9 @@ class PocketCameraWindow(QtWidgets.QDialog):
188
192
  self.CUR_PALETTE = len(self.PALETTES) - 1
189
193
 
190
194
  if self.CUR_FILE is not None:
191
- self.OpenFile(self.CUR_FILE)
195
+ if self.OpenFile(self.CUR_FILE) is False:
196
+ self.FORCE_EXIT = True
197
+ return
192
198
 
193
199
  self.CUR_EXPORT_PATH = self.APP.SETTINGS.value("LastDirPocketCamera")
194
200
  if self.CUR_EXPORT_PATH is None:
@@ -201,6 +207,9 @@ class PocketCameraWindow(QtWidgets.QDialog):
201
207
  self.btnSaveAll.setFocus()
202
208
 
203
209
  def run(self):
210
+ if self.FORCE_EXIT:
211
+ self.reject()
212
+ return
204
213
  self.layout.update()
205
214
  self.layout.activate()
206
215
  screenGeometry = QDesktopWidget().screenGeometry(self)
@@ -217,6 +226,26 @@ class PocketCameraWindow(QtWidgets.QDialog):
217
226
  self.UpdateViewer(self.CUR_INDEX)
218
227
 
219
228
  def OpenFile(self, file):
229
+ if isinstance(file, bytearray) and len(file) == 0x100000 or isinstance(file, str) and os.path.getsize(file) == 0x100000:
230
+ dlg_args = {
231
+ "title":"Photo!",
232
+ "intro":"A Photo! save file was detected.\n\nPlease select the roll of pictures that you would like to load.",
233
+ "params": [
234
+ [ "index", "cmb", "Roll:", [ "Current Save Data" ] + [ "Flash Directory Slot {:d}".format(l) for l in range(1, 8) ], 0 ],
235
+ ]
236
+ }
237
+ dlg = UserInputDialog(self, icon=self.windowIcon(), args=dlg_args)
238
+ if dlg.exec_() == 1:
239
+ result = dlg.GetResult()
240
+ index = result["index"].currentIndex()
241
+ if isinstance(file, str):
242
+ with open(file, "rb") as f:
243
+ file = bytearray(f.read())
244
+ file = file[0x20000 * index:][:0x20000]
245
+ else:
246
+ self.CUR_PC = None
247
+ return False
248
+
220
249
  try:
221
250
  self.CUR_PC = PocketCamera()
222
251
  if self.CUR_PC.LoadFile(file) == False:
@@ -285,6 +314,7 @@ class PocketCameraWindow(QtWidgets.QDialog):
285
314
  self.SavePicture(self.CUR_INDEX)
286
315
 
287
316
  def btnClose_Clicked(self, event):
317
+ self.FORCE_EXIT = True
288
318
  self.reject()
289
319
 
290
320
  def hideEvent(self, event):
@@ -309,13 +339,13 @@ class PocketCameraWindow(QtWidgets.QDialog):
309
339
  draw.line([0, 112, 128, 0], fill=(255, 0, 0, 192), width=8)
310
340
  pic.paste(draw_bg, mask=draw_bg)
311
341
  self.lblPhoto[i].setToolTip("This picture was marked as “deleted” and may be overwritten when you take new pictures.")
312
- self.CUR_THUMBS[i] = ImageQt(pic.resize((47, 41), Image.HAMMING))
342
+ self.CUR_THUMBS[i] = ImageQt(pic.resize((47, 41), Image.Resampling.HAMMING))
313
343
  qpixmap = QtGui.QPixmap.fromImage(self.CUR_THUMBS[i])
314
344
  self.lblPhoto[i].setPixmap(qpixmap)
315
345
 
316
346
  def UpdateViewer(self, index):
317
- resampler = Image.NEAREST
318
- if self.CUR_BICUBIC: resampler = Image.BICUBIC
347
+ resampler = Image.Resampling.NEAREST
348
+ if self.CUR_BICUBIC: resampler = Image.Resampling.BICUBIC
319
349
  cam = self.CUR_PC
320
350
  if cam is None: return
321
351
 
@@ -323,7 +353,7 @@ class PocketCameraWindow(QtWidgets.QDialog):
323
353
  self.lblPhoto[i].setStyleSheet("border-top: 1px solid #adadad; border-left: 1px solid #adadad; border-bottom: 1px solid #ffffff; border-right: 1px solid #ffffff;")
324
354
 
325
355
  if index >= 30:
326
- self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), Image.BICUBIC if index == 31 else resampler))
356
+ self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), Image.Resampling.BICUBIC if index == 31 else resampler))
327
357
  else:
328
358
  self.CUR_PIC = ImageQt(cam.GetPicture(index).convert("RGBA").resize((256, 224), resampler))
329
359
  self.lblPhoto[index].setStyleSheet("border: 3px solid green; padding: 1px;")
FlashGBX/RomFileAGB.py CHANGED
@@ -121,6 +121,7 @@ class RomFileAGB:
121
121
  def GetHeader(self, unchanged=False):
122
122
  buffer = bytearray(self.ROMFILE)
123
123
  data = {}
124
+ if len(buffer) < 0x180: return {}
124
125
  hash = hashlib.sha1(buffer[0:0x180]).digest()
125
126
  nocart_hashes = []
126
127
  nocart_hashes.append(bytearray([ 0x4F, 0xE9, 0x3E, 0xEE, 0xBC, 0x55, 0x93, 0xFE, 0x2E, 0x23, 0x1A, 0x39, 0x86, 0xCE, 0x86, 0xC9, 0x5C, 0x11, 0x00, 0xDD ])) # Method 0
@@ -187,7 +188,9 @@ class RomFileAGB:
187
188
  (data["game_title"] == "CARDE READER" and data["game_code"] == "PSAE" and data["header_checksum"] == 0x95):
188
189
  data["ereader"] = True
189
190
 
190
- data["unchanged"] = copy.copy(data)
191
+ if unchanged:
192
+ data["unchanged"] = copy.copy(data)
193
+
191
194
  self.DATA = data
192
195
  data["db"] = self.GetDatabaseEntry()
193
196
 
FlashGBX/RomFileDMG.py CHANGED
@@ -140,8 +140,9 @@ class RomFileDMG:
140
140
  data["rom_checksum_calc"] = self.CalcChecksumGlobal()
141
141
  data["rom_checksum_correct"] = data["rom_checksum"] == data["rom_checksum_calc"]
142
142
 
143
- data["unchanged"] = copy.copy(data)
144
- if not unchanged:
143
+ if unchanged:
144
+ data["unchanged"] = copy.copy(data)
145
+ else:
145
146
  # MBC2
146
147
  if data["mapper_raw"] == 0x06:
147
148
  data["ram_size_raw"] = 0x100
@@ -431,6 +432,16 @@ class RomFileDMG:
431
432
  temp = self.LogoToImage(buffer[0x104:0x104+0x30])
432
433
  if temp is not False: data["logo_sachen"] = temp
433
434
 
435
+ # GBFlash MBCX
436
+ if data["game_title"] == "MBCX_MENU":
437
+ data["rom_size_raw"] = 0x0A
438
+ data["ram_size_raw"] = 0x03
439
+ data["mapper_raw"] = 0x206
440
+
441
+ # Photo!
442
+ if data["game_title"] == "PHOTO":
443
+ data["ram_size_raw"] = 0x204
444
+
434
445
  if data["mapper_raw"] in Util.DMG_Header_Mapper:
435
446
  data["mapper"] = Util.DMG_Header_Mapper[data["mapper_raw"]]
436
447
  elif data["logo_correct"]:
FlashGBX/Util.py CHANGED
@@ -2,15 +2,15 @@
2
2
  # FlashGBX
3
3
  # Author: Lesserkuma (github.com/lesserkuma)
4
4
 
5
- import math, time, datetime, copy, configparser, threading, os, platform, traceback, io, struct, re, statistics, random
5
+ import math, time, datetime, copy, configparser, threading, os, platform, traceback, io, struct, re, statistics, random, sys
6
6
  from io import StringIO
7
7
  from enum import Enum
8
8
 
9
9
  # Common constants
10
10
  APPNAME = "FlashGBX"
11
- VERSION_PEP440 = "4.2"
11
+ VERSION_PEP440 = "4.4"
12
12
  VERSION = "v{:s}".format(VERSION_PEP440)
13
- VERSION_TIMESTAMP = 1722797669
13
+ VERSION_TIMESTAMP = 1748007939
14
14
  DEBUG = False
15
15
  DEBUG_LOG = []
16
16
  APP_PATH = ""
@@ -23,14 +23,14 @@ AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1048576, 65536, 13
23
23
  AGB_Flash_Save_Chips = { 0xBFD4:"SST 39VF512", 0x1F3D:"Atmel AT29LV512", 0xC21C:"Macronix MX29L512", 0x321B:"Panasonic MN63F805MNP", 0xC209:"Macronix MX29L010", 0x6213:"SANYO LE26FV10N1TS", 0xBF5B:"Unlicensed SST49LF080A", 0xFFFF:"Unlicensed 0xFFFF" }
24
24
  AGB_Flash_Save_Chips_Sizes = [ 0x10000, 0x10000, 0x10000, 0x10000, 0x20000, 0x20000, 0x20000, 0x20000 ]
25
25
 
26
- DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x0F:'MBC3+RTC+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'MAC-GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper' }
27
- DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x0F, 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ] }
28
- DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB" ]
29
- DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A ]
30
- DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000 ]
31
- DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KiB)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)" ]
32
- DMG_Header_RAM_Sizes_Map = [ 0x00, 0x100, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204 ]
33
- DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000, 0x80000 ] # RAM size in bytes
26
+ DMG_Header_Mapper = { 0x00:'None', 0x01:'MBC1', 0x02:'MBC1+SRAM', 0x03:'MBC1+SRAM+BATTERY', 0x06:'MBC2+SRAM+BATTERY', 0x0F:'MBC3+RTC+BATTERY', 0x10:'MBC3+RTC+SRAM+BATTERY', 0x110:'MBC30+RTC+SRAM+BATTERY', 0x12:'MBC3+SRAM', 0x13:'MBC3+SRAM+BATTERY', 0x19:'MBC5', 0x1A:'MBC5+SRAM', 0x1B:'MBC5+SRAM+BATTERY', 0x1C:'MBC5+RUMBLE', 0x1E:'MBC5+RUMBLE+SRAM+BATTERY', 0x20:'MBC6+SRAM+FLASH+BATTERY', 0x22:'MBC7+ACCELEROMETER+EEPROM', 0x101:'MBC1M', 0x103:'MBC1M+SRAM+BATTERY', 0x0B:'MMM01', 0x0D:'MMM01+SRAM+BATTERY', 0xFC:'MAC-GBD+SRAM+BATTERY', 0x105:'G-MMC1+SRAM+BATTERY', 0x104:'M161', 0xFF:'HuC-1+IR+SRAM+BATTERY', 0xFE:'HuC-3+RTC+SRAM+BATTERY', 0xFD:'TAMA5+RTC+EEPROM', 0x201:'Unlicensed 256M Mapper', 0x202:'Unlicensed Wisdom Tree Mapper', 0x203:'Unlicensed Xploder GB Mapper', 0x204:'Unlicensed Sachen Mapper', 0x205:'Unlicensed Datel Orbit V2 Mapper', 0x206:'Unlicensed MBCX Mapper' }
27
+ DMG_Mapper_Types = { "None":[ 0x00, 0x08, 0x09 ], "MBC1":[ 0x01, 0x02, 0x03 ], "MBC2":[ 0x05, 0x06 ], "MBC3":[ 0x0F, 0x10, 0x11, 0x12, 0x13 ], "MBC30":[ 0x110 ], "MBC5":[ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E ], "MBC6":[ 0x20 ], "MBC7":[ 0x22 ], "MBC1M":[ 0x101, 0x103 ], "MMM01":[ 0x0B, 0x0D ], "MAC-GBD":[ 0xFC ], "G-MMC1":[ 0x105 ], "M161":[ 0x104 ], "HuC-1":[ 0xFF ], "HuC-3":[ 0xFE ], "TAMA5":[ 0xFD ], "Unlicensed 256M Multi Cart Mapper":[ 0x201 ], "Unlicensed Wisdom Tree Mapper":[ 0x202 ], "Unlicensed Xploder GB Mapper":[ 0x203 ], "Unlicensed Sachen Mapper":[ 0x204 ], "Unlicensed Datel Orbit V2 Mapper":[ 0x205 ], "Unlicensed MBCX Mapper":[ 0x206 ] }
28
+ DMG_Header_ROM_Sizes = [ "32 KiB", "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB", "64 MiB", "128 MiB" ]
29
+ DMG_Header_ROM_Sizes_Map = [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C ]
30
+ DMG_Header_ROM_Sizes_Flasher_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000 ]
31
+ DMG_Header_RAM_Sizes = [ "None", "4K SRAM (512 Bytes)", "16K SRAM (2 KiB)", "64K SRAM (8 KiB)", "256K SRAM (32 KiB)", "512K SRAM (64 KiB)", "1M SRAM (128 KiB)", "MBC6 SRAM+FLASH (1.03 MiB)", "MBC7 2K EEPROM (256 Bytes)", "MBC7 4K EEPROM (512 Bytes)", "TAMA5 EEPROM (32 Bytes)", "Unlicensed 4M SRAM (512 KiB)", "Unlicensed 1M EEPROM (128 KiB)", "Unlicensed Photo! Directory (1 MiB)", "Unlicensed Batteryless SRAM" ]
32
+ DMG_Header_RAM_Sizes_Map = [ 0x00, 0x100, 0x01, 0x02, 0x03, 0x05, 0x04, 0x104, 0x101, 0x102, 0x103, 0x201, 0x203, 0x204, 0x205 ]
33
+ DMG_Header_RAM_Sizes_Flasher_Map = [ 0, 0x200, 0x800, 0x2000, 0x8000, 0x10000, 0x20000, 0x108000, 0x100, 0x200, 0x20, 0x80000, 0x20000, 0x100000, 0x80000 ] # RAM size in bytes
34
34
  DMG_Header_SGB = { 0x00:'No support', 0x03:'Supported' }
35
35
  DMG_Header_CGB = { 0x00:'No support', 0x80:'Supported', 0xC0:'Required' }
36
36
 
@@ -169,6 +169,10 @@ class Progress():
169
169
  self.PROGRESS["time_start"] = args["time_start"]
170
170
  else:
171
171
  self.PROGRESS["time_start"] = now
172
+ if "abortable" in args:
173
+ self.PROGRESS["abortable"] = args["abortable"]
174
+ else:
175
+ self.PROGRESS["abortable"] = True
172
176
  self.PROGRESS["time_last_emit"] = now
173
177
  self.PROGRESS["time_last_update_speed"] = now
174
178
  self.PROGRESS["time_left"] = 0
@@ -218,7 +222,7 @@ class Progress():
218
222
  self.PROGRESS["sector_pos"] = args["sector_pos"]
219
223
  if "abortable" in args:
220
224
  self.PROGRESS["abortable"] = args["abortable"]
221
-
225
+
222
226
  if ((now - self.PROGRESS["time_last_emit"]) > 0.06) or "force_update" in args and args["force_update"] is True:
223
227
  self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"]
224
228
  time_delta = now - self.PROGRESS["time_last_update_speed"]
@@ -254,6 +258,11 @@ class Progress():
254
258
  self.UPDATER(self.PROGRESS)
255
259
  self.PROGRESS["time_last_emit"] = now
256
260
 
261
+ elif args["action"] == "UPDATE_INFO":
262
+ self.PROGRESS["text"] = args["text"]
263
+ self.PROGRESS["action"] = args["action"]
264
+ self.UPDATER(self.PROGRESS)
265
+
257
266
  elif args["action"] == "FINISHED":
258
267
  self.PROGRESS["pos"] = self.PROGRESS["size"]
259
268
  self.UPDATER(self.PROGRESS)
@@ -401,8 +410,14 @@ def EncodeBCD(value):
401
410
  def ParseCFI(buffer):
402
411
  buffer = copy.copy(buffer)
403
412
  info = {}
404
- magic = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24]))
405
- if magic != "QRY": # nothing swapped
413
+ magic_8bit = "{:s}{:s}{:s}".format(chr(buffer[0x10]), chr(buffer[0x11]), chr(buffer[0x12]))
414
+ magic_16bit = "{:s}{:s}{:s}".format(chr(buffer[0x20]), chr(buffer[0x22]), chr(buffer[0x24]))
415
+ buffer_conv = bytearray()
416
+ if magic_8bit == "QRY":
417
+ buffer_conv = bytearray(b for x in buffer for b in (x, x))
418
+ buffer = buffer_conv
419
+
420
+ if magic_8bit != "QRY" and magic_16bit != "QRY": # nothing swapped
406
421
  return False
407
422
 
408
423
  try:
@@ -513,7 +528,10 @@ def ConvertMapperTypeToMapper(mapper_type):
513
528
  return 0
514
529
 
515
530
  def GetDumpReport(di, device):
516
- header = di["header"]["unchanged"]
531
+ header = di["header"]
532
+ if "unchanged" in di["header"]:
533
+ header = di["header"]["unchanged"]
534
+
517
535
  if "db" in di["header"]: header["db"] = di["header"]["db"]
518
536
  if di["system"] == "DMG":
519
537
  mode = "DMG"
@@ -535,6 +553,8 @@ def GetDumpReport(di, device):
535
553
  di["rom_size"] = "{:s}".format(AGB_Header_ROM_Sizes[AGB_Header_ROM_Sizes_Map.index(di["rom_size"])])
536
554
  else:
537
555
  di["rom_size"] = "{:,} bytes".format(di["rom_size"])
556
+ else:
557
+ raise NotImplementedError
538
558
 
539
559
  di["cart_type"] = list(device.SUPPORTED_CARTS[mode].keys())[di["cart_type"]]
540
560
  if di["file_name"] is None:
@@ -795,7 +815,10 @@ def GetDumpReport(di, device):
795
815
  if "st" in db: s += "* Save Type: {:s}\n".format(AGB_Header_Save_Types[db["st"]])
796
816
  #if "ss" in db: s += "* Save Size: {:s}\n".format(formatFileSize(size=db["ss"], asInt=True))
797
817
 
798
- return s
818
+ if platform.system() == "Windows":
819
+ return s.replace("\n", "\r\n")
820
+ else:
821
+ return s
799
822
 
800
823
  def GenerateFileName(mode, header, settings=None):
801
824
  fe_ni = True
@@ -911,6 +934,19 @@ def find_size(data, max_size, min_size=0x20):
911
934
  break
912
935
  return offset
913
936
 
937
+ def bitmap2pixmap(data, scale_factor=4):
938
+ try:
939
+ from .pyside import QtGui
940
+ from PIL.ImageQt import ImageQt
941
+ from PIL import Image
942
+ data_converted = data.convert("RGBA")
943
+ pixmap = QtGui.QPixmap.fromImage(ImageQt(data_converted.resize((data_converted.width * scale_factor, data_converted.height * scale_factor), Image.NEAREST)))
944
+ pixmap.setDevicePixelRatio(scale_factor)
945
+ return pixmap
946
+ except:
947
+ print("Couldn’t convert bitmap to pixmap.")
948
+ return False
949
+
914
950
  def dprint(*args, **kwargs):
915
951
  stack = traceback.extract_stack()
916
952
  stack = stack[len(stack)-2]
@@ -921,3 +957,37 @@ def dprint(*args, **kwargs):
921
957
  if DEBUG:
922
958
  msg = "{:s}{:s}".format(ANSI.CLEAR_LINE, msg)
923
959
  print(msg)
960
+
961
+ def write_debug_log(device=False):
962
+ dprint("{:s} version: {:s} ({:d})".format(APPNAME, VERSION_PEP440, VERSION_TIMESTAMP))
963
+ dprint("Platform: {:s}".format(platform.platform()))
964
+ if device is not False:
965
+ if device is not None:
966
+ dprint("Connected device: {:s}".format(device))
967
+ else:
968
+ dprint("No device connected")
969
+ dprint("Now writing debug log file")
970
+ try:
971
+ fn = CONFIG_PATH + "/debug.log"
972
+ with open(fn, "wb") as f:
973
+ if platform.system() == "Windows":
974
+ f.write("\r\n".join(DEBUG_LOG).encode("UTF-8-SIG"))
975
+ else:
976
+ f.write("\n".join(DEBUG_LOG).encode("UTF-8-SIG"))
977
+ print("debug.log written")
978
+ return True
979
+ except:
980
+ return False
981
+
982
+ def exception_hook(exc_type, exc_value, exc_traceback):
983
+ if issubclass(exc_type, KeyboardInterrupt):
984
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
985
+ return
986
+
987
+ s = "⚠️ EXCEPTION OCCURED ⚠️\n"
988
+ lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
989
+ for line in lines:
990
+ s += f"{line:s}"
991
+ print(s)
992
+ dprint(s)
993
+ write_debug_log()
FlashGBX/fw_GBFlash.py CHANGED
@@ -230,11 +230,6 @@ try:
230
230
  self.DEVICE = device
231
231
  else:
232
232
  self.APP.QT_APP.processEvents()
233
- text = "This Firmware Updater is for GBFlash devices only. Please only proceed if your device is a GBFlash."
234
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
235
- msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
236
- answer = msgbox.exec()
237
- if answer == QtWidgets.QMessageBox.Cancel: return
238
233
  self.FWUPD = FirmwareUpdater(app_path, None)
239
234
 
240
235
  self.layout = QtWidgets.QGridLayout()
@@ -110,11 +110,6 @@ try:
110
110
  self.DEVICE = device
111
111
  else:
112
112
  self.APP.QT_APP.processEvents()
113
- text = "This Firmware Updater is for insideGadgets GBxCart RW v1.4 devices only. Please only proceed if your device matches this hardware revision.\n\nOlder GBxCart RW revisions can be updated only after connecting to them first."
114
- msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Warning, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
115
- msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
116
- answer = msgbox.exec()
117
- if answer == QtWidgets.QMessageBox.Cancel: return
118
113
  self.FWUPD = FirmwareUpdater(app_path, None)
119
114
 
120
115
  self.layout = QtWidgets.QGridLayout()
@@ -217,8 +212,10 @@ try:
217
212
  self.lblDeviceFWVerResult.setText(self.FW_VER)
218
213
  if self.PCB_VER == "v1.4":
219
214
  self.optDevicePCBVer14.setChecked(True)
215
+ self.optDevicePCBVer14a.setEnabled(False)
220
216
  elif self.PCB_VER == "v1.4a/b/c":
221
217
  self.optDevicePCBVer14a.setChecked(True)
218
+ self.optDevicePCBVer14.setEnabled(False)
222
219
  self.SetPCBVersion()
223
220
 
224
221
  def SetPCBVersion(self):
FlashGBX/fw_JoeyJr.py CHANGED
@@ -3,6 +3,7 @@
3
3
  # Author: Lesserkuma (github.com/lesserkuma)
4
4
 
5
5
  import zipfile, time, os, struct, serial, platform
6
+ from serial import SerialException
6
7
  try:
7
8
  from . import Util
8
9
  except ImportError:
@@ -30,7 +31,9 @@ class FirmwareUpdater():
30
31
  path = os.path.dirname(path) + "/"
31
32
  fncSetStatus(text="Connecting... This may take a moment.")
32
33
 
33
- with open(file, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
34
+ filename = os.path.split(file)[1]
35
+ filepath = os.path.split(file)[0]
36
+ with open(filepath + "/" + filename, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
34
37
  if not temp.startswith("UPDATE"):
35
38
  with open(file, "wb") as f:
36
39
  temp = bytearray(b"UPDATE")
@@ -46,10 +49,16 @@ class FirmwareUpdater():
46
49
  return 2
47
50
 
48
51
  try:
49
- with open(file, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
50
- except FileNotFoundError:
51
- fncSetStatus(text="Couldn’t access MODE.TXT. Remove cartridge and try again.")
52
- return 2
52
+ with open(filepath + "/" + filename, "rb") as f: temp = f.read().decode("UTF-8", "ignore")
53
+ except FileNotFoundError as e:
54
+ try:
55
+ if filename == "MODE.TXT":
56
+ with open(filepath + "/" + "MODE!.TXT", "rb") as f: temp = f.read().decode("UTF-8", "ignore")
57
+ else:
58
+ raise FileNotFoundError from e
59
+ except FileNotFoundError:
60
+ fncSetStatus(text="Couldn’t access MODE.TXT. Remove cartridge and try again.")
61
+ return 2
53
62
 
54
63
  if not temp.startswith("UPDATE"):
55
64
  fncSetStatus(text="Couldn’t enter UPDATE mode, please try again.")
@@ -112,8 +121,14 @@ class FirmwareUpdater():
112
121
  fncSetStatus(text="Connecting...")
113
122
  try:
114
123
  dev = serial.Serial(port, 2000000, timeout=0.2)
124
+ except SerialException as e:
125
+ if "Errno 13" in str(e) and platform.system() == "Linux":
126
+ fncSetStatus(text="No permission to use device! See README file.", enableUI=True)
127
+ else:
128
+ fncSetStatus(text="Device not accessible.", enableUI=True)
129
+ return 2
115
130
  except:
116
- fncSetStatus(text="Device not accessible.", enableUI=True)
131
+ fncSetStatus(text="Unknown error while accessing the device.", enableUI=True)
117
132
  return 2
118
133
  dev.reset_input_buffer()
119
134
 
@@ -272,7 +287,7 @@ try:
272
287
  self.rowUpdate.addStretch()
273
288
 
274
289
  self.rowUpdate2 = QtWidgets.QHBoxLayout()
275
- self.lblUpdateDisclaimer = QtWidgets.QLabel("Please note that FlashGBX is not officially supported by BennVenn, so please use this firmware updater at your own risk.")
290
+ self.lblUpdateDisclaimer = QtWidgets.QLabel("Please note that FlashGBX is not officially supported by BennVenn.")
276
291
  self.lblUpdateDisclaimer.setWordWrap(True)
277
292
  self.lblUpdateDisclaimer.setAlignment(QtGui.Qt.AlignmentFlag.AlignCenter)
278
293
  self.rowUpdate2.addWidget(self.lblUpdateDisclaimer)
@@ -467,7 +482,7 @@ try:
467
482
  msgbox = QtWidgets.QMessageBox(parent=self, icon=QtWidgets.QMessageBox.Critical, windowTitle="FlashGBX", text=text, standardButtons=QtWidgets.QMessageBox.Ok)
468
483
  answer = msgbox.exec()
469
484
  return False
470
- answer = QtWidgets.QMessageBox.information(self, "FlashGBX", "If your Joey Jr device is currently running the Drag'n'Drop firmware, please continue and choose its <b>MODE.TXT</b> file.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
485
+ answer = QtWidgets.QMessageBox.information(self, "FlashGBX", "If your Joey Jr device is currently running the Drag'n'Drop firmware, please continue and choose its <b>MODE.TXT</b> (or <b>MODE!.TXT</b>) file.", QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Ok)
471
486
  if answer == QtWidgets.QMessageBox.Cancel:
472
487
  self.SetStatus("No device found.", enableUI=True)
473
488
  return False
FlashGBX/hw_GBFlash.py CHANGED
@@ -9,7 +9,7 @@ class GbxDevice(LK_Device):
9
9
  DEVICE_NAME = "GBFlash"
10
10
  DEVICE_MIN_FW = 1
11
11
  DEVICE_MAX_FW = 12
12
- DEVICE_LATEST_FW_TS = { 5:1722774120, 10:1722774120, 11:1722774120, 12:1722774120, 13:1722774120 }
12
+ DEVICE_LATEST_FW_TS = { 5:1747991884, 10:1747991884, 11:1747991884, 12:1747991884, 13:1747991884 }
13
13
  PCB_VERSIONS = { 5:'', 12:'v1.2', 13:'v1.3' }
14
14
 
15
15
  def __init__(self):
@@ -111,8 +111,10 @@ class GbxDevice(LK_Device):
111
111
  self.FW["cart_power_ctrl"] = True if self._read(1) == 1 else False
112
112
 
113
113
  # Reset to bootloader support
114
- self.FW["bootloader_reset"] = True if self._read(1) == 1 else False
115
-
114
+ temp = self._read(1)
115
+ self.FW["bootloader_reset"] = True if temp & 1 == 1 else False
116
+ self.FW["unregistered"] = True if temp >> 7 == 1 else False
117
+
116
118
  return True
117
119
 
118
120
  except Exception as e:
@@ -192,10 +194,10 @@ class GbxDevice(LK_Device):
192
194
  return True
193
195
 
194
196
  def FirmwareUpdateAvailable(self):
195
- if self.FW["pcb_ver"] == 5: # unofficial firmware
197
+ if self.FW["pcb_ver"] == 5 or self.FW["fw_ts"] < 1730592000: # unofficial firmware
196
198
  self.FW_UPDATE_REQ = True
197
199
  return True
198
- if self.FW["fw_ts"] < self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]:
200
+ if self.FW["fw_ts"] != self.DEVICE_LATEST_FW_TS[self.FW["pcb_ver"]]:
199
201
  return True
200
202
  self.FW_UPDATE_REQ = False
201
203
  return False
@@ -239,6 +241,13 @@ class GbxDevice(LK_Device):
239
241
 
240
242
  def GetFullName(self):
241
243
  if self.FW["pcb_ver"] < 13 and self.CanPowerCycleCart():
242
- return "{:s} {:s} + PLUGIN 01".format(self.GetName(), self.GetPCBVersion())
244
+ s = "{:s} {:s} + PLUGIN 01".format(self.GetName(), self.GetPCBVersion())
243
245
  else:
244
- return "{:s} {:s}".format(self.GetName(), self.GetPCBVersion())
246
+ s = "{:s} {:s}".format(self.GetName(), self.GetPCBVersion())
247
+ if self.IsUnregistered():
248
+ s += " (unregistered)"
249
+ return s
250
+
251
+ def GetRegisterInformation(self):
252
+ text = f"Your GBFlash device reported a registration error, which means it may be an illegitimate clone.\n\nThe device’s integrated piracy detection may limit the device in performance and functionality until proper registration. The {Util.APPNAME:s} software has no control over this.\n\nPlease visit <a href=\"https://gbflash.geeksimon.com/\">https://gbflash.geeksimon.com/</a> for more information.".replace("\n", "<br>")
253
+ return text