FlashGBX 3.37__py3-none-any.whl → 4.0.1__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/RomFileAGB.py CHANGED
@@ -2,9 +2,15 @@
2
2
  # FlashGBX
3
3
  # Author: Lesserkuma (github.com/lesserkuma)
4
4
 
5
- import hashlib, re, zlib, string, os, json, copy
5
+ import hashlib, re, zlib, string, os, json, copy, struct
6
6
  from . import Util
7
7
 
8
+ try:
9
+ Image = None
10
+ from PIL import Image
11
+ except:
12
+ pass
13
+
8
14
  class RomFileAGB:
9
15
  ROMFILE_PATH = None
10
16
  ROMFILE = bytearray()
@@ -41,6 +47,77 @@ class RomFileAGB:
41
47
  self.CalcChecksumHeader(True)
42
48
  return self.ROMFILE[0:0x200]
43
49
 
50
+ def LogoToImage(self, data, valid=True):
51
+ if Image is None: return False
52
+ if data in (bytearray([0] * len(data)), bytearray([0xFF] * len(data))): return False
53
+
54
+ # HuffUnComp function provided by Winter1760, thank you!
55
+ def HuffUnComp(data):
56
+ bits = data[0] & 15
57
+ out_size = int.from_bytes(data[1:4], "little") & 0xFFFF
58
+ i = 6 + data[4] * 2
59
+ node_offs = 5
60
+ out_units = 0
61
+ out_ready = 0
62
+ out = b""
63
+ while len(out) < out_size:
64
+ in_unit = int.from_bytes(data[i:i+2], "little") | int.from_bytes(data[i^2:(i^2)+2], "little") << 16
65
+ i += 4
66
+ for b in range(31, -1, -1):
67
+ node = data[node_offs]
68
+ node_offs &= ~1
69
+ node_offs += (node & 0x3F) * 2 + 2 + (in_unit >> b & 1)
70
+ if node << (in_unit >> b & 1) & 0x80:
71
+ out_ready >>= bits
72
+ out_ready |= data[node_offs] << 32 - bits
73
+ out_ready &= 0xFFFFFFFF
74
+ out_units += 1
75
+ if out_units == bits % 8 + 4:
76
+ out += out_ready.to_bytes(4, "little")
77
+ if len(out) >= out_size:
78
+ return out
79
+ out_units = 0
80
+ out_ready = 0
81
+ node_offs = 5
82
+ return out
83
+
84
+ def Diff16BitUnFilter(data):
85
+ header = struct.unpack("I", data[0:4])[0]
86
+ out_size = (header >> 8) & 0xFFFF
87
+ pos = 4
88
+ prev = 0
89
+ dest = bytearray()
90
+ while pos < out_size:
91
+ if pos+2 > len(data): break
92
+ temp = (struct.unpack("H", data[pos:pos+2])[0] + prev) & 0xFFFF
93
+ dest += struct.pack("H", temp)
94
+ pos += 2
95
+ prev = temp
96
+ return dest
97
+
98
+ temp = bytearray.fromhex("09050A060B07C20DC2020E08C30483018303C30C830F8382828101000000400F0000D424")
99
+ temp.reverse()
100
+ data = temp + data
101
+ data = HuffUnComp(data)
102
+ data = Diff16BitUnFilter(data)
103
+
104
+ img = Image.new(mode='P', size=(104, 16))
105
+ img.info["transparency"] = 0
106
+ img.putpalette([ 255, 255, 255, 0, 0, 0 ])
107
+ pixels = img.load()
108
+
109
+ for tile_row in range(0, 2):
110
+ for tile_w in range(0, 13):
111
+ for tile_h in range(0, 8):
112
+ for bit in range(0, 8):
113
+ pos = (tile_row * 13 * 8) + (tile_w * 8) + tile_h
114
+ if pos >= len(data): break
115
+ pixel = (data[pos] >> bit) & 1
116
+ x = tile_w * 8 + bit
117
+ y = tile_row * 8 + tile_h
118
+ pixels[x, y] = pixel
119
+ return img
120
+
44
121
  def GetHeader(self, unchanged=False):
45
122
  buffer = bytearray(self.ROMFILE)
46
123
  data = {}
@@ -50,15 +127,23 @@ class RomFileAGB:
50
127
  nocart_hashes.append(bytearray([ 0xA5, 0x03, 0xA1, 0xB5, 0xF5, 0xDD, 0xBE, 0xFC, 0x87, 0xC7, 0x9B, 0x13, 0x59, 0xF7, 0xE1, 0xA5, 0xCF, 0xE0, 0xAC, 0x9F ])) # Method 1
51
128
  nocart_hashes.append(bytearray([ 0x46, 0x86, 0xE3, 0x81, 0xB2, 0x4A, 0x2D, 0xB0, 0x7D, 0xE8, 0x3D, 0x45, 0x2F, 0xA3, 0x1E, 0x8A, 0x04, 0x4B, 0x3A, 0x50 ])) # Method 2
52
129
  data["empty_nocart"] = hash in nocart_hashes
130
+ if not data["empty_nocart"]:
131
+ nocart_hashes.append(bytearray([ 0x2B, 0xDC, 0x7D, 0xEF, 0x6C, 0x48, 0x1F, 0xBF, 0xEE, 0xB8, 0x80, 0xB1, 0xD0, 0xFD, 0xF6, 0x57, 0x5D, 0x6A, 0x39, 0xBE ]))
132
+ nocart_hashes.append(bytearray([ 0x09, 0xB9, 0x0E, 0x53, 0x5E, 0x85, 0x50, 0xF8, 0x90, 0xA4, 0xF4, 0x77, 0x13, 0x7E, 0x45, 0x59, 0xA5, 0xC0, 0xA4, 0x45 ]))
133
+ data["empty_nocart"] = hashlib.sha1(buffer[0x10:0x50]).digest() in nocart_hashes
134
+
53
135
  data["empty"] = (buffer[0x04:0xA0] == bytearray([buffer[0x04]] * 0x9C)) or data["empty_nocart"]
54
136
  if data["empty_nocart"]: buffer = bytearray([0x00] * len(buffer))
55
137
  data["logo_correct"] = hashlib.sha1(buffer[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ])
138
+ temp = self.LogoToImage(buffer[0x04:0xA0], data["logo_correct"])
139
+ if temp is not False and not data["empty"]: data["logo"] = temp
140
+
56
141
  data["game_title_raw"] = bytearray(buffer[0xA0:0xAC]).decode("ascii", "replace")
57
142
  game_title = data["game_title_raw"]
58
143
  game_title = re.sub(r"(\x00+)$", "", game_title)
59
144
  game_title = re.sub(r"((_)_+|(\x00)\x00+|(\s)\s+)", "\\2\\3\\4", game_title).replace("\x00", "_")
60
145
  game_title = ''.join(filter(lambda x: x in set(string.printable), game_title))
61
- data["game_title"] = game_title
146
+ data["game_title"] = game_title.replace("\n", "")
62
147
  data["game_code_raw"] = bytearray(buffer[0xAC:0xB0]).decode("ascii", "replace")
63
148
  game_code = data["game_code_raw"]
64
149
  game_code = re.sub(r"(\x00+)$", "", game_code)
@@ -85,6 +170,11 @@ class RomFileAGB:
85
170
  data["save_type"] = None
86
171
  data["save_size"] = 0
87
172
 
173
+ # Vast Fame (unlicensed protected carts)
174
+ data["vast_fame"] = False
175
+ if buffer[0x15C:0x16C] == bytearray([ 0xB4, 0x00, 0x9F, 0xE5, 0x99, 0x10, 0xA0, 0xE3, 0x00, 0x10, 0xC0, 0xE5, 0xAC, 0x00, 0x9F, 0xE5 ]): # Initialization code always present in Vast Fame carts
176
+ data["vast_fame"] = True
177
+
88
178
  # 8M FLASH DACS
89
179
  data["dacs_8m"] = False
90
180
  if (data["game_title"] == "NGC-HIKARU3" and data["game_code"] == "GHTJ" and data["header_checksum"] == 0xB3):
FlashGBX/RomFileDMG.py CHANGED
@@ -196,7 +196,7 @@ class RomFileDMG:
196
196
  data["mapper_raw"] = 0x0B
197
197
 
198
198
  # Unlicensed 256M Mapper
199
- elif (data["game_title"].upper() == "GB HICOL" and data["header_checksum"] in (0x4A, 0x49, 0xE9)) or \
199
+ elif (data["game_title"].upper() == "GB HICOL" and data["header_checksum"] in (0x4A, 0x49, 0xE8, 0xE9)) or \
200
200
  (data["game_title"] == "BennVenn" and data["header_checksum"] == 0x48):
201
201
  data["rom_size_raw"] = 0x0A
202
202
  data["ram_size_raw"] = 0x201
@@ -18,6 +18,8 @@ class UserInputDialog(QtWidgets.QDialog):
18
18
 
19
19
  self.lblIntro = QtWidgets.QLabel(args["intro"])
20
20
  self.lblIntro.setStyleSheet("margin-bottom: 4px")
21
+ self.lblIntro.setMaximumWidth(350)
22
+ self.lblIntro.setWordWrap(True)
21
23
  self.btnOK = QtWidgets.QPushButton("&OK")
22
24
  self.btnCancel = QtWidgets.QPushButton("&Cancel")
23
25
 
@@ -42,6 +44,24 @@ class UserInputDialog(QtWidgets.QDialog):
42
44
  grid_layout.addWidget(lbl, grid_rows, 0, 1, 1)
43
45
  grid_layout.addWidget(cmb, grid_rows, 1, 1, 1)
44
46
 
47
+ elif param[1] in ("spb"):
48
+ lbl = QtWidgets.QLabel(param[2])
49
+ spb = QtWidgets.QSpinBox()
50
+ spb.setRange(param[3][0], param[3][1])
51
+ spb.setValue(param[4])
52
+ self.paramWidgets[param[0]] = spb
53
+ grid_layout.addWidget(lbl, grid_rows, 0, 1, 1)
54
+ grid_layout.addWidget(spb, grid_rows, 1, 1, 1)
55
+
56
+ elif param[1] in ("chk"):
57
+ #lbl = QtWidgets.QLabel(param[2])
58
+ chk = QtWidgets.QCheckBox()
59
+ chk.setChecked(param[4])
60
+ chk.setText(param[2])
61
+ self.paramWidgets[param[0]] = chk
62
+ #grid_layout.addWidget(lbl, grid_rows, 0, 1, 1)
63
+ grid_layout.addWidget(chk, grid_rows, 0, 1, 2)
64
+
45
65
  else:
46
66
  continue
47
67
 
FlashGBX/Util.py CHANGED
@@ -2,21 +2,22 @@
2
2
  # FlashGBX
3
3
  # Author: Lesserkuma (github.com/lesserkuma)
4
4
 
5
- import math, time, datetime, copy, configparser, threading, statistics, os, platform, traceback, io, struct, re
5
+ import math, time, datetime, copy, configparser, threading, os, platform, traceback, io, struct, re, statistics, random
6
+ from io import StringIO
6
7
  from enum import Enum
7
8
 
8
9
  # Common constants
9
10
  APPNAME = "FlashGBX"
10
- VERSION_PEP440 = "3.37"
11
+ VERSION_PEP440 = "4.0.1"
11
12
  VERSION = "v{:s}".format(VERSION_PEP440)
12
- VERSION_TIMESTAMP = 1709318129
13
+ VERSION_TIMESTAMP = 1719742346
13
14
  DEBUG = False
14
15
  DEBUG_LOG = []
15
16
  APP_PATH = ""
16
17
  CONFIG_PATH = ""
17
18
 
18
- AGB_Header_ROM_Sizes = [ "64 KiB", "128 KiB", "256 KiB", "512 KiB", "1 MiB", "2 MiB", "4 MiB", "8 MiB", "16 MiB", "32 MiB", "64 MiB", "128 MiB", "256 MiB", "512 MiB" ]
19
- AGB_Header_ROM_Sizes_Map = [ 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000 ]
19
+ AGB_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", "256 MiB", "512 MiB" ]
20
+ AGB_Header_ROM_Sizes_Map = [ 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000 ]
20
21
  AGB_Header_Save_Types = [ "None", "4K EEPROM (512 Bytes)", "64K EEPROM (8 KiB)", "256K SRAM/FRAM (32 KiB)", "512K FLASH (64 KiB)", "1M FLASH (128 KiB)", "8M DACS (1 MiB)", "Unlicensed 512K SRAM (64 KiB)", "Unlicensed 1M SRAM (128 KiB)", "Unlicensed Batteryless SRAM" ]
21
22
  AGB_Header_Save_Sizes = [ 0, 512, 8192, 32768, 65536, 131072, 1048576, 65536, 131072, 0 ]
22
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" }
@@ -110,21 +111,42 @@ class IniSettings():
110
111
  if self.FILENAME is not False:
111
112
  with open(self.FILENAME, "w", encoding="UTF-8") as f:
112
113
  self.SETTINGS.write(f)
114
+
115
+ def GetString(self):
116
+ output = StringIO()
117
+ self.SETTINGS.write(output)
118
+ return(output.getvalue())
113
119
 
114
120
  class Progress():
115
121
  MUTEX = threading.Lock()
116
122
  PROGRESS = {}
117
123
  UPDATER = None
124
+ WAITER = None
118
125
 
119
- def __init__(self, updater):
126
+ def __init__(self, updater, waiter):
120
127
  self.UPDATER = updater
128
+ self.WAITER = waiter
121
129
 
130
+ def IsOutlier(self, speeds, new_number, threshold):
131
+ if not speeds: return False
132
+ mean = sum(speeds) / len(speeds)
133
+ std_deviation = (sum((x - mean) ** 2 for x in speeds) / len(speeds)) ** 0.5
134
+ lower_bound = mean - threshold * std_deviation
135
+ upper_bound = mean + threshold * std_deviation
136
+ if new_number < lower_bound or new_number > upper_bound:
137
+ return True
138
+ else:
139
+ return False
140
+
122
141
  def SetProgress(self, args):
123
142
  self.MUTEX.acquire(1)
124
143
  try:
125
144
  if not "method" in self.PROGRESS: self.PROGRESS = {}
126
145
  now = time.time()
127
- if args["action"] == "INITIALIZE":
146
+ if args["action"] == "USER_ACTION":
147
+ self.WAITER(args)
148
+
149
+ elif args["action"] == "INITIALIZE":
128
150
  self.PROGRESS["action"] = args["action"]
129
151
  self.PROGRESS["method"] = args["method"]
130
152
  if "flash_offset" in args:
@@ -139,6 +161,10 @@ class Progress():
139
161
  self.PROGRESS["pos"] = args["pos"] - self.PROGRESS["flash_offset"]
140
162
  else:
141
163
  self.PROGRESS["pos"] = 0
164
+ if "sector_count" in args:
165
+ self.PROGRESS["sector_count"] = args["sector_count"]
166
+ else:
167
+ self.PROGRESS["sector_count"] = 1
142
168
  if "time_start" in args:
143
169
  self.PROGRESS["time_start"] = args["time_start"]
144
170
  else:
@@ -149,6 +175,7 @@ class Progress():
149
175
  self.PROGRESS["speed"] = 0
150
176
  self.PROGRESS["speeds"] = []
151
177
  self.PROGRESS["bytes_last_update_speed"] = 0
178
+ self.PROGRESS["sector_erase_time"] = 0
152
179
  self.UPDATER(self.PROGRESS)
153
180
 
154
181
  if args["action"] == "ABORT":
@@ -166,41 +193,51 @@ class Progress():
166
193
 
167
194
  elif self.PROGRESS == {}:
168
195
  return
169
-
170
- elif args["action"] == "UPDATE_POS":
171
- self.PROGRESS["pos"] = args["pos"] - self.PROGRESS["flash_offset"]
172
- self.PROGRESS["action"] = "PROGRESS"
173
- if "time_start" in self.PROGRESS:
174
- self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"]
175
-
176
- try:
177
- total_speed = statistics.mean(self.PROGRESS["speeds"])
178
- self.PROGRESS["time_left"] = (self.PROGRESS["size"] - self.PROGRESS["pos"]) / 1024 / total_speed
179
- except:
180
- pass
181
- if "abortable" in args: self.PROGRESS["abortable"] = args["abortable"]
182
- self.UPDATER(self.PROGRESS)
183
-
184
- elif args["action"] in ("READ", "WRITE"):
196
+
197
+ elif args["action"] in ("READ", "WRITE", "UPDATE_POS"):
185
198
  if "method" not in self.PROGRESS: return
186
- elif args["action"] in ("READ") and self.PROGRESS["method"] in ("SAVE_WRITE", "ROM_WRITE"): return
187
- elif args["action"] in ("WRITE") and self.PROGRESS["method"] in ("SAVE_READ", "ROM_READ", "ROM_WRITE_VERIFY"): return
188
- if self.PROGRESS["pos"] >= self.PROGRESS["size"]: return
189
-
199
+ elif args["action"] == "READ" and self.PROGRESS["method"] in ("SAVE_WRITE", "ROM_WRITE"): return
200
+ elif args["action"] == "WRITE" and self.PROGRESS["method"] in ("SAVE_READ", "ROM_READ", "ROM_WRITE_VERIFY"): return
201
+ if self.PROGRESS["pos"] > self.PROGRESS["size"]: return
202
+ skip_speed = False
190
203
  self.PROGRESS["action"] = "PROGRESS"
191
- self.PROGRESS["pos"] += args["bytes_added"]
192
- if (now - self.PROGRESS["time_last_emit"]) > 0.05:
204
+ if args["action"] in ("READ", "WRITE"):
205
+ self.PROGRESS["pos"] += args["bytes_added"]
206
+ elif args["action"] == "UPDATE_POS":
207
+ # print(self.PROGRESS["pos"], args["pos"] - self.PROGRESS["flash_offset"])
208
+ if self.PROGRESS["pos"] == args["pos"] - self.PROGRESS["flash_offset"]:
209
+ skip_speed = True
210
+ self.PROGRESS["pos"] = args["pos"] - self.PROGRESS["flash_offset"]
211
+ if "sector_erase_time" in args:
212
+ if "sector_erase_time" in self.PROGRESS:
213
+ self.PROGRESS["sector_erase_time"] = (self.PROGRESS["sector_erase_time"] + args["sector_erase_time"]) / 2
214
+ else:
215
+ self.PROGRESS["sector_erase_time"] = args["sector_erase_time"]
216
+ if "sector_pos" in args:
217
+ if "sector_erase_time" not in args: self.PROGRESS["sector_erase_time"] = 0
218
+ self.PROGRESS["sector_pos"] = args["sector_pos"]
219
+ if "abortable" in args:
220
+ self.PROGRESS["abortable"] = args["abortable"]
221
+
222
+ if ((now - self.PROGRESS["time_last_emit"]) > 0.06) or "force_update" in args and args["force_update"] is True:
193
223
  self.PROGRESS["time_elapsed"] = now - self.PROGRESS["time_start"]
194
- if (now - self.PROGRESS["time_last_update_speed"]) > 0.25:
195
- time_delta = now - self.PROGRESS["time_last_update_speed"]
196
- pos_delta = self.PROGRESS["pos"] - self.PROGRESS["bytes_last_update_speed"]
197
- if time_delta > 0:
198
- speed = (pos_delta / time_delta) / 1024
199
- self.PROGRESS["speeds"].append(speed)
200
- if len(self.PROGRESS["speeds"]) > 256: self.PROGRESS["speeds"].pop(0)
201
- self.PROGRESS["speed"] = statistics.median(self.PROGRESS["speeds"])
202
- self.PROGRESS["time_last_update_speed"] = now
203
- self.PROGRESS["bytes_last_update_speed"] = self.PROGRESS["pos"]
224
+ time_delta = now - self.PROGRESS["time_last_update_speed"]
225
+ pos_delta = self.PROGRESS["pos"] - self.PROGRESS["bytes_last_update_speed"]
226
+ if time_delta > 0 and (time.time() - self.PROGRESS["time_start"] > 2) and "sector_erase_time" not in args:
227
+ speed = (pos_delta / time_delta) / 1024
228
+ if speed > 0 and not skip_speed:
229
+ if len(self.PROGRESS["speeds"]) < 40 or not self.IsOutlier(speeds=self.PROGRESS["speeds"], new_number=speed, threshold=25):
230
+ self.PROGRESS["speeds"].append(speed)
231
+ if len(self.PROGRESS["speeds"]) > 50: self.PROGRESS["speeds"].pop(0)
232
+ if len(self.PROGRESS["speeds"]) > 1 and random.randint(0, 10) == 0: self.PROGRESS["speeds"].pop(0)
233
+ # print(len(self.PROGRESS["speeds"]), pos_delta, end=" \r", flush=True)
234
+ if len(self.PROGRESS["speeds"]) > 0:
235
+ # print(self.PROGRESS["speeds"])
236
+ self.PROGRESS["speed"] = statistics.mean(self.PROGRESS["speeds"])
237
+ else:
238
+ self.PROGRESS["speed"] = 0
239
+ self.PROGRESS["time_last_update_speed"] = now
240
+ self.PROGRESS["bytes_last_update_speed"] = self.PROGRESS["pos"]
204
241
 
205
242
  if "skipping" in args and args["skipping"] is True:
206
243
  self.PROGRESS["speed"] = 0
@@ -208,9 +245,11 @@ class Progress():
208
245
  else:
209
246
  self.PROGRESS["skipping"] = False
210
247
 
211
- if self.PROGRESS["speed"] > 0:
212
- total_speed = statistics.mean(self.PROGRESS["speeds"])
248
+ if self.PROGRESS["speed"] > 0 and len(self.PROGRESS["speeds"]) > 0:
249
+ total_speed = self.PROGRESS["speed"] #statistics.mean(self.PROGRESS["speeds"])
213
250
  self.PROGRESS["time_left"] = (self.PROGRESS["size"] - self.PROGRESS["pos"]) / 1024 / total_speed
251
+ if self.PROGRESS["sector_erase_time"] > 0:
252
+ self.PROGRESS["time_left"] += self.PROGRESS["sector_erase_time"] * (self.PROGRESS["sector_count"] - self.PROGRESS["sector_pos"])
214
253
 
215
254
  self.UPDATER(self.PROGRESS)
216
255
  self.PROGRESS["time_last_emit"] = now
@@ -235,9 +274,10 @@ class Progress():
235
274
 
236
275
  self.UPDATER(self.PROGRESS)
237
276
  del(self.PROGRESS["method"])
238
-
277
+
239
278
  finally:
240
279
  self.MUTEX.release()
280
+
241
281
 
242
282
  class TAMA5_CMD(Enum):
243
283
  RAM_WRITE = 0x0
@@ -332,7 +372,7 @@ def formatProgressTime(seconds, asFloat=False):
332
372
  s += ", "
333
373
  if sec >= 1 or seconds < 60:
334
374
  s += "{:d} second".format(int(sec))
335
- if sec != 1: s += "s"
375
+ if int(sec) != 1: s += "s"
336
376
  s += ", "
337
377
  return s[:-2]
338
378
 
@@ -535,14 +575,16 @@ def GetDumpReport(di, device):
535
575
  "* ROM Size: {rom_size:s}\n" \
536
576
  "* Mapper Type: {mapper_type:s}\n" \
537
577
  "* Cartridge Type: {cart_type:s}\n" \
538
- .format(system=di["system"], rom_size=di["rom_size"], mapper_type=di["mapper_type"], cart_type=di["cart_type"])
578
+ "* Read Method: {read_method:s}\n" \
579
+ .format(system=di["system"], rom_size=di["rom_size"], mapper_type=di["mapper_type"], cart_type=di["cart_type"], read_method=di["dmg_read_method"])
539
580
  elif mode == "AGB":
540
581
  s += "" \
541
582
  "\n== Dumping Settings ==\n" \
542
583
  "* Mode: {system:s}\n" \
543
584
  "* ROM Size: {rom_size:s}\n" \
544
585
  "* Cartridge Type: {cart_type:s}\n" \
545
- .format(system=di["system"], rom_size=di["rom_size"], cart_type=di["cart_type"])
586
+ "* Read Method: {read_method:s}\n" \
587
+ .format(system=di["system"], rom_size=di["rom_size"], cart_type=di["cart_type"], read_method=di["agb_read_method"])
546
588
 
547
589
  di["hdr_logo"] = "OK" if header["logo_correct"] else "Invalid"
548
590
  header["game_title_raw"] = header["game_title_raw"].replace("\0", "␀")
@@ -734,6 +776,11 @@ def GetDumpReport(di, device):
734
776
  if "eeprom_data" in di:
735
777
  s += "* EEPROM area: {:s}…\n".format(''.join(format(x, '02X') for x in di["eeprom_data"]))
736
778
 
779
+ if di["cart_type"] == "Vast Fame":
780
+ s += "\n== Vast Fame Protection Information ==\n"
781
+ s += "* Address Reordering: {:s}\n".format(str(di["vf_addr_reorder"]))
782
+ s += "* Value Reordering: {:s}\n".format(str(di["vf_value_reorder"]))
783
+
737
784
  if header["db"] is not None and header["db"]["rc"] == di["hash_crc32"]:
738
785
  db = header["db"]
739
786
  s += "\n== Database Match ==\n"
@@ -787,6 +834,7 @@ def GenerateFileName(mode, header, settings=None):
787
834
  path = path.replace("%TITLE%", path_title.strip())
788
835
  path = path.replace("%CODE%", path_code.strip())
789
836
  path = path.replace("%REVISION%", path_revision)
837
+ path = path.replace("%MAPPER%", get_mbc_name(header["mapper_raw"]))
790
838
  path = re.sub(r"[<>:\"/\\|\?\*]", "_", path)
791
839
  if get_mbc_name(header["mapper_raw"]) == "G-MMC1":
792
840
  if "gbmem_parsed" in header:
@@ -864,12 +912,12 @@ def find_size(data, max_size, min_size=0x20):
864
912
  return offset
865
913
 
866
914
  def dprint(*args, **kwargs):
867
- global DEBUG_LOG
868
915
  stack = traceback.extract_stack()
869
916
  stack = stack[len(stack)-2]
917
+
870
918
  msg = "[{:s}] [{:s}:{:d}] {:s}(): {:s}".format(datetime.datetime.now().astimezone().replace(microsecond=0).isoformat(), os.path.split(stack.filename)[1], stack.lineno, stack.name, " ".join(map(str, args)), **kwargs)
871
919
  DEBUG_LOG.append(msg)
872
- DEBUG_LOG = DEBUG_LOG[-32768:]
920
+ if len(DEBUG_LOG) > 64*1024: DEBUG_LOG.pop(0)
873
921
  if DEBUG:
874
922
  msg = "{:s}{:s}".format(ANSI.CLEAR_LINE, msg)
875
923
  print(msg)