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/FlashGBX.py +14 -7
- FlashGBX/FlashGBX_CLI.py +201 -24
- FlashGBX/FlashGBX_GUI.py +636 -204
- FlashGBX/Flashcart.py +184 -83
- FlashGBX/GBMemory.py +4 -5
- FlashGBX/LK_Device.py +4533 -0
- FlashGBX/Mapper.py +534 -356
- FlashGBX/RomFileAGB.py +92 -2
- FlashGBX/RomFileDMG.py +1 -1
- FlashGBX/UserInputDialog.py +20 -0
- FlashGBX/Util.py +95 -47
- FlashGBX/fw_GBFlash.py +426 -0
- FlashGBX/fw_GBxCartRW_v1_3.py +1 -1
- FlashGBX/fw_JoeyJr.py +472 -0
- FlashGBX/hw_GBFlash.py +244 -0
- FlashGBX/hw_GBxCartRW.py +200 -3777
- FlashGBX/hw_JoeyJr.py +309 -0
- FlashGBX/res/Third Party Notices.md +342 -0
- FlashGBX/res/config.zip +0 -0
- FlashGBX/res/fw_GBFlash.zip +0 -0
- FlashGBX/res/fw_GBxCart_RW_v1_3.zip +0 -0
- FlashGBX/res/fw_GBxCart_RW_v1_4.zip +0 -0
- FlashGBX/res/fw_GBxCart_RW_v1_4a.zip +0 -0
- FlashGBX/res/fw_JoeyJr.zip +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.1.dist-info}/METADATA +36 -16
- FlashGBX-4.0.1.dist-info/RECORD +43 -0
- FlashGBX/hw_GBxCartRW_ofw.py +0 -2599
- FlashGBX-3.37.dist-info/RECORD +0 -36
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.1.dist-info}/LICENSE +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.1.dist-info}/WHEEL +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.1.dist-info}/entry_points.txt +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.1.dist-info}/top_level.txt +0 -0
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
|
FlashGBX/UserInputDialog.py
CHANGED
|
@@ -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,
|
|
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 = "
|
|
11
|
+
VERSION_PEP440 = "4.0.1"
|
|
11
12
|
VERSION = "v{:s}".format(VERSION_PEP440)
|
|
12
|
-
VERSION_TIMESTAMP =
|
|
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"] == "
|
|
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"]
|
|
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"]
|
|
187
|
-
elif args["action"]
|
|
188
|
-
if self.PROGRESS["pos"]
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
self.PROGRESS["speeds"].
|
|
200
|
-
|
|
201
|
-
self.PROGRESS["
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|