FlashGBX 3.37__py3-none-any.whl → 4.0__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 +4526 -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.dist-info}/METADATA +33 -14
- FlashGBX-4.0.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.dist-info}/LICENSE +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.dist-info}/WHEEL +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.dist-info}/entry_points.txt +0 -0
- {FlashGBX-3.37.dist-info → FlashGBX-4.0.dist-info}/top_level.txt +0 -0
FlashGBX/LK_Device.py
ADDED
|
@@ -0,0 +1,4526 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# FlashGBX
|
|
3
|
+
# Author: Lesserkuma (github.com/lesserkuma)
|
|
4
|
+
|
|
5
|
+
import time, math, struct, traceback, zlib, copy, hashlib, os, datetime, platform, json, base64
|
|
6
|
+
import serial, serial.tools.list_ports
|
|
7
|
+
from serial import SerialException
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from .RomFileDMG import RomFileDMG
|
|
10
|
+
from .RomFileAGB import RomFileAGB
|
|
11
|
+
from .Mapper import DMG_MBC, AGB_GPIO
|
|
12
|
+
from .Flashcart import Flashcart, Flashcart_DMG_MMSA, Flashcart_AGB_GBAMP, Flashcart_DMG_BUNG_16M
|
|
13
|
+
from .Util import ANSI, dprint, bitswap, ParseCFI
|
|
14
|
+
from .GBMemory import GBMemoryMap
|
|
15
|
+
from . import Util
|
|
16
|
+
|
|
17
|
+
class LK_Device(ABC):
|
|
18
|
+
DEVICE_NAME = ""
|
|
19
|
+
DEVICE_MIN_FW = 0
|
|
20
|
+
DEVICE_MAX_FW = 0
|
|
21
|
+
DEVICE_LATEST_FW_TS = {}
|
|
22
|
+
PCB_VERSIONS = {}
|
|
23
|
+
BAUDRATE = 1000000
|
|
24
|
+
MAX_BUFFER_READ = 0x2000
|
|
25
|
+
MAX_BUFFER_WRITE = 0x400
|
|
26
|
+
|
|
27
|
+
DEVICE_CMD = {
|
|
28
|
+
"NULL":0x30,
|
|
29
|
+
"OFW_RESET_AVR":0x2A,
|
|
30
|
+
"OFW_CART_MODE":0x43,
|
|
31
|
+
"OFW_FW_VER":0x56,
|
|
32
|
+
"OFW_PCB_VER":0x68,
|
|
33
|
+
"OFW_USART_1_0M_SPEED":0x3C,
|
|
34
|
+
"OFW_USART_1_7M_SPEED":0x3E,
|
|
35
|
+
"OFW_CART_PWR_ON":0x2F,
|
|
36
|
+
"OFW_CART_PWR_OFF":0x2E,
|
|
37
|
+
"OFW_QUERY_CART_PWR":0x5D,
|
|
38
|
+
"OFW_DONE_LED_ON":0x3D,
|
|
39
|
+
"OFW_ERROR_LED_ON":0x3F,
|
|
40
|
+
"OFW_GB_CART_MODE":0x47,
|
|
41
|
+
"OFW_GB_FLASH_BANK_1_COMMAND_WRITES":0x4E,
|
|
42
|
+
"OFW_LNL_QUERY":0x25,
|
|
43
|
+
"DEBUG":0xA0,
|
|
44
|
+
"QUERY_FW_INFO":0xA1,
|
|
45
|
+
"SET_MODE_AGB":0xA2,
|
|
46
|
+
"SET_MODE_DMG":0xA3,
|
|
47
|
+
"SET_VOLTAGE_3_3V":0xA4,
|
|
48
|
+
"SET_VOLTAGE_5V":0xA5,
|
|
49
|
+
"SET_VARIABLE":0xA6,
|
|
50
|
+
"SET_FLASH_CMD":0xA7,
|
|
51
|
+
"SET_ADDR_AS_INPUTS":0xA8,
|
|
52
|
+
"CLK_TOGGLE":0xA9,
|
|
53
|
+
"ENABLE_PULLUPS":0xAB,
|
|
54
|
+
"DISABLE_PULLUPS":0xAC,
|
|
55
|
+
"GET_VARIABLE":0xAD,
|
|
56
|
+
"GET_VAR_STATE":0xAE,
|
|
57
|
+
"SET_VAR_STATE":0xAF,
|
|
58
|
+
"DMG_CART_READ":0xB1,
|
|
59
|
+
"DMG_CART_WRITE":0xB2,
|
|
60
|
+
"DMG_CART_WRITE_SRAM":0xB3,
|
|
61
|
+
"DMG_MBC_RESET":0xB4,
|
|
62
|
+
"DMG_MBC7_READ_EEPROM":0xB5,
|
|
63
|
+
"DMG_MBC7_WRITE_EEPROM":0xB6,
|
|
64
|
+
"DMG_MBC6_MMSA_WRITE_FLASH":0xB7,
|
|
65
|
+
"DMG_SET_BANK_CHANGE_CMD":0xB8,
|
|
66
|
+
"DMG_EEPROM_WRITE":0xB9,
|
|
67
|
+
"DMG_CART_READ_MEASURE":0xBA,
|
|
68
|
+
"AGB_CART_READ":0xC1,
|
|
69
|
+
"AGB_CART_WRITE":0xC2,
|
|
70
|
+
"AGB_CART_READ_SRAM":0xC3,
|
|
71
|
+
"AGB_CART_WRITE_SRAM":0xC4,
|
|
72
|
+
"AGB_CART_READ_EEPROM":0xC5,
|
|
73
|
+
"AGB_CART_WRITE_EEPROM":0xC6,
|
|
74
|
+
"AGB_CART_WRITE_FLASH_DATA":0xC7,
|
|
75
|
+
"AGB_CART_READ_3D_MEMORY":0xC8,
|
|
76
|
+
"AGB_BOOTUP_SEQUENCE":0xC9,
|
|
77
|
+
"AGB_READ_GPIO_RTC":0xCA,
|
|
78
|
+
"DMG_FLASH_WRITE_BYTE":0xD1,
|
|
79
|
+
"AGB_FLASH_WRITE_SHORT":0xD2,
|
|
80
|
+
"FLASH_PROGRAM":0xD3,
|
|
81
|
+
"CART_WRITE_FLASH_CMD":0xD4,
|
|
82
|
+
"CALC_CRC32":0xD5,
|
|
83
|
+
"BOOTLOADER_RESET":0xF1,
|
|
84
|
+
"CART_PWR_ON":0xF2,
|
|
85
|
+
"CART_PWR_OFF":0xF3,
|
|
86
|
+
"QUERY_CART_PWR":0xF4,
|
|
87
|
+
"SET_PIN":0xF5,
|
|
88
|
+
}
|
|
89
|
+
# \#define VAR(\d+)_([^\t]+)\t+(.+)
|
|
90
|
+
DEVICE_VAR = {
|
|
91
|
+
"ADDRESS":[32, 0x00],
|
|
92
|
+
"AUTO_POWEROFF_TIME":[32, 0x01],
|
|
93
|
+
"TRANSFER_SIZE":[16, 0x00],
|
|
94
|
+
"BUFFER_SIZE":[16, 0x01],
|
|
95
|
+
"DMG_ROM_BANK":[16, 0x02],
|
|
96
|
+
"STATUS_REGISTER":[16, 0x03],
|
|
97
|
+
"LAST_BANK_ACCESSED":[16, 0x04],
|
|
98
|
+
"STATUS_REGISTER_MASK":[16, 0x05],
|
|
99
|
+
"STATUS_REGISTER_VALUE":[16, 0x06],
|
|
100
|
+
"CART_MODE":[8, 0x00],
|
|
101
|
+
"DMG_ACCESS_MODE":[8, 0x01],
|
|
102
|
+
"FLASH_COMMAND_SET":[8, 0x02],
|
|
103
|
+
"FLASH_METHOD":[8, 0x03],
|
|
104
|
+
"FLASH_WE_PIN":[8, 0x04],
|
|
105
|
+
"FLASH_PULSE_RESET":[8, 0x05],
|
|
106
|
+
"FLASH_COMMANDS_BANK_1":[8, 0x06],
|
|
107
|
+
"FLASH_SHARP_VERIFY_SR":[8, 0x07],
|
|
108
|
+
"DMG_READ_CS_PULSE":[8, 0x08],
|
|
109
|
+
"DMG_WRITE_CS_PULSE":[8, 0x09],
|
|
110
|
+
"FLASH_DOUBLE_DIE":[8, 0x0A],
|
|
111
|
+
"DMG_READ_METHOD":[8, 0x0B],
|
|
112
|
+
"AGB_READ_METHOD":[8, 0x0C],
|
|
113
|
+
"CART_POWERED":[8, 0x0D],
|
|
114
|
+
"PULLUPS_ENABLED":[8, 0x0E],
|
|
115
|
+
"AUTO_POWEROFF_ENABLED":[8, 0x0F],
|
|
116
|
+
"AGB_IRQ_ENABLED":[8, 0x10],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ACTIONS = {"ROM_READ":1, "SAVE_READ":2, "SAVE_WRITE":3, "ROM_WRITE":4, "ROM_WRITE_VERIFY":4, "SAVE_WRITE_VERIFY":3, "RTC_WRITE":5}
|
|
120
|
+
SUPPORTED_CARTS = {}
|
|
121
|
+
|
|
122
|
+
FW = {}
|
|
123
|
+
FW_UPDATE_REQ = False
|
|
124
|
+
FW_VAR = {}
|
|
125
|
+
MODE = None
|
|
126
|
+
PORT = ''
|
|
127
|
+
DEVICE = None
|
|
128
|
+
WORKER = None
|
|
129
|
+
INFO = { "action":None, "last_action":None, "dump_info":{} }
|
|
130
|
+
ERROR = False
|
|
131
|
+
ERROR_ARGS = {}
|
|
132
|
+
CANCEL = False
|
|
133
|
+
CANCEL_ARGS = {}
|
|
134
|
+
SIGNAL = None
|
|
135
|
+
POS = 0
|
|
136
|
+
NO_PROG_UPDATE = False
|
|
137
|
+
FAST_READ = False
|
|
138
|
+
SKIPPING = False
|
|
139
|
+
DEVICE_TIMEOUT = 1
|
|
140
|
+
WRITE_DELAY = False
|
|
141
|
+
READ_ERRORS = 0
|
|
142
|
+
WRITE_ERRORS = 0
|
|
143
|
+
DMG_READ_METHOD = 1
|
|
144
|
+
DMG_READ_METHODS = ["RD", "A15", "SlowA15"]
|
|
145
|
+
AGB_READ_METHOD = 0
|
|
146
|
+
AGB_READ_METHODS = ["Single", "MemCpy", "Stream"]
|
|
147
|
+
LAST_CHECK_ACTIVE = 0
|
|
148
|
+
USER_ANSWER = None
|
|
149
|
+
|
|
150
|
+
def __init__(self):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
@abstractmethod
|
|
154
|
+
def Initialize(self, flashcarts, port=None, max_baud=2000000):
|
|
155
|
+
raise NotImplementedError
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def CheckActive(self):
|
|
159
|
+
raise NotImplementedError
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def LoadFirmwareVersion(self):
|
|
163
|
+
raise NotImplementedError
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def GetFirmwareVersion(self, more=False):
|
|
167
|
+
raise NotImplementedError
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
def ChangeBaudRate(self, baudrate):
|
|
171
|
+
raise NotImplementedError
|
|
172
|
+
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def CanSetVoltageManually(self):
|
|
175
|
+
raise NotImplementedError
|
|
176
|
+
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def CanSetVoltageAutomatically(self):
|
|
179
|
+
raise NotImplementedError
|
|
180
|
+
|
|
181
|
+
@abstractmethod
|
|
182
|
+
def CanPowerCycleCart(self):
|
|
183
|
+
raise NotImplementedError
|
|
184
|
+
|
|
185
|
+
@abstractmethod
|
|
186
|
+
def GetSupprtedModes(self):
|
|
187
|
+
raise NotImplementedError
|
|
188
|
+
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def IsSupported3dMemory(self):
|
|
191
|
+
raise NotImplementedError
|
|
192
|
+
|
|
193
|
+
@abstractmethod
|
|
194
|
+
def IsClkConnected(self):
|
|
195
|
+
raise NotImplementedError
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def GetFullNameExtended(self, more=False):
|
|
199
|
+
raise NotImplementedError
|
|
200
|
+
|
|
201
|
+
@abstractmethod
|
|
202
|
+
def SupportsFirmwareUpdates(self):
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
|
|
205
|
+
@abstractmethod
|
|
206
|
+
def FirmwareUpdateAvailable(self):
|
|
207
|
+
raise NotImplementedError
|
|
208
|
+
|
|
209
|
+
@abstractmethod
|
|
210
|
+
def GetFirmwareUpdaterClass(self):
|
|
211
|
+
raise NotImplementedError
|
|
212
|
+
|
|
213
|
+
@abstractmethod
|
|
214
|
+
def ResetLEDs(self):
|
|
215
|
+
raise NotImplementedError
|
|
216
|
+
|
|
217
|
+
@abstractmethod
|
|
218
|
+
def SupportsBootloaderReset(self):
|
|
219
|
+
raise NotImplementedError
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def BootloaderReset(self):
|
|
223
|
+
raise NotImplementedError
|
|
224
|
+
|
|
225
|
+
@abstractmethod
|
|
226
|
+
def SupportsAudioAsWe(self):
|
|
227
|
+
raise NotImplementedError
|
|
228
|
+
|
|
229
|
+
#################################################################
|
|
230
|
+
|
|
231
|
+
def IsSupportedMbc(self, mbc):
|
|
232
|
+
return mbc in ( 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x09, 0x0B, 0x0D, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0xFC, 0xFD, 0xFE, 0xFF, 0x101, 0x103, 0x104, 0x105, 0x110, 0x201, 0x202, 0x203, 0x204, 0x205 )
|
|
233
|
+
|
|
234
|
+
def TryConnect(self, port, baudrate):
|
|
235
|
+
dprint("Trying to connect to {:s} at baud rate {:d} ({:s})".format(port, baudrate, type(self).__module__))
|
|
236
|
+
try:
|
|
237
|
+
dev = serial.Serial(port, baudrate, timeout=0.1)
|
|
238
|
+
except SerialException as e:
|
|
239
|
+
dprint(f"Couldn’t connect to port {port:s} at baudrate {baudrate:d}:", e)
|
|
240
|
+
return False
|
|
241
|
+
self.DEVICE = dev
|
|
242
|
+
check = self.LoadFirmwareVersion()
|
|
243
|
+
self.DEVICE = None
|
|
244
|
+
dev.close()
|
|
245
|
+
return check
|
|
246
|
+
|
|
247
|
+
def GetBaudRate(self):
|
|
248
|
+
return self.BAUDRATE
|
|
249
|
+
|
|
250
|
+
def SetDMGReadMethod(self, method):
|
|
251
|
+
if self.FW["fw_ver"] < 12: return
|
|
252
|
+
if 0 < method >= len(self.DMG_READ_METHODS): method = 0
|
|
253
|
+
dprint("Setting DMG Read Method to", method)
|
|
254
|
+
self.DMG_READ_METHOD = method
|
|
255
|
+
self._set_fw_variable("DMG_READ_METHOD", self.DMG_READ_METHOD)
|
|
256
|
+
|
|
257
|
+
def SetAGBReadMethod(self, method):
|
|
258
|
+
if self.FW["fw_ver"] < 12: return
|
|
259
|
+
if 0 < method >= len(self.AGB_READ_METHODS): method = 0
|
|
260
|
+
dprint("Setting AGB Read Method to", method)
|
|
261
|
+
self.AGB_READ_METHOD = method
|
|
262
|
+
self._set_fw_variable("AGB_READ_METHOD", self.AGB_READ_METHOD)
|
|
263
|
+
|
|
264
|
+
def SetPin(self, pins: list, set_high):
|
|
265
|
+
if self.FW["fw_ver"] < 12: return
|
|
266
|
+
pin_names = [ "CART_POWER", "PIN_CLK", "PIN_WR", "PIN_RD", "PIN_CS" ]
|
|
267
|
+
for i in range(0, 24):
|
|
268
|
+
pin_names.append(f"PIN_A{i}")
|
|
269
|
+
pin_names += [ "PIN_CS2", "PIN_AUDIO" ]
|
|
270
|
+
value = 0
|
|
271
|
+
p = 0
|
|
272
|
+
for p in pins:
|
|
273
|
+
if isinstance(p, int):
|
|
274
|
+
if p > 30:
|
|
275
|
+
print("Invalid pin index specified:", p)
|
|
276
|
+
continue
|
|
277
|
+
elif isinstance(p, str):
|
|
278
|
+
if p not in pin_names:
|
|
279
|
+
print("Invalid pin index specified:", p)
|
|
280
|
+
continue
|
|
281
|
+
p = pin_names.index(p)
|
|
282
|
+
value |= (1 << p)
|
|
283
|
+
|
|
284
|
+
for i in range(0, 31):
|
|
285
|
+
if (value >> i & 1) == 1:
|
|
286
|
+
dprint(f"Setting pin {pin_names[p]}: {set_high}")
|
|
287
|
+
|
|
288
|
+
dprint(f"Value: {value:031b}")
|
|
289
|
+
buffer = bytearray([self.DEVICE_CMD["SET_PIN"]])
|
|
290
|
+
buffer.extend(struct.pack(">I", value))
|
|
291
|
+
buffer.extend(struct.pack("B", 1 if set_high else 0))
|
|
292
|
+
return self._write(buffer, wait=True)
|
|
293
|
+
|
|
294
|
+
def UpdateFlashCarts(self, flashcarts):
|
|
295
|
+
self.SUPPORTED_CARTS = {
|
|
296
|
+
"DMG":{ "Generic ROM Cartridge":"RETAIL" },
|
|
297
|
+
"AGB":{ "Generic ROM Cartridge":"RETAIL" }
|
|
298
|
+
}
|
|
299
|
+
for mode in flashcarts.keys():
|
|
300
|
+
for key in sorted(flashcarts[mode].keys(), key=str.casefold):
|
|
301
|
+
self.SUPPORTED_CARTS[mode][key] = flashcarts[mode][key]
|
|
302
|
+
|
|
303
|
+
def IsConnected(self):
|
|
304
|
+
if self.DEVICE is None: return False
|
|
305
|
+
if not self.DEVICE.isOpen(): return False
|
|
306
|
+
try:
|
|
307
|
+
while self.DEVICE.in_waiting > 0:
|
|
308
|
+
dprint("Clearing input buffer... ({:d})".format(self.DEVICE.in_waiting), self.DEVICE.read(self.DEVICE.in_waiting))
|
|
309
|
+
self.DEVICE.reset_input_buffer()
|
|
310
|
+
time.sleep(0.05)
|
|
311
|
+
self.DEVICE.reset_output_buffer()
|
|
312
|
+
return self.CheckActive()
|
|
313
|
+
except SerialException as e:
|
|
314
|
+
print("Connection lost!")
|
|
315
|
+
try:
|
|
316
|
+
if e.args[0].startswith("ClearCommError failed"):
|
|
317
|
+
self.DEVICE.close()
|
|
318
|
+
return False
|
|
319
|
+
except:
|
|
320
|
+
pass
|
|
321
|
+
print(str(e))
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def Close(self, cartPowerOff=False):
|
|
325
|
+
if self.IsConnected():
|
|
326
|
+
dprint("Disconnecting from the device")
|
|
327
|
+
try:
|
|
328
|
+
if cartPowerOff and self.CanPowerCycleCart():
|
|
329
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", 0)
|
|
330
|
+
if self.FW["fw_ver"] >= 12:
|
|
331
|
+
self._write(self.DEVICE_CMD["CART_PWR_OFF"], wait=True)
|
|
332
|
+
else:
|
|
333
|
+
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"], wait=False)
|
|
334
|
+
else:
|
|
335
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
336
|
+
self.DEVICE.close()
|
|
337
|
+
except:
|
|
338
|
+
self.DEVICE = None
|
|
339
|
+
self.MODE = None
|
|
340
|
+
|
|
341
|
+
def GetName(self):
|
|
342
|
+
return self.DEVICE_NAME
|
|
343
|
+
|
|
344
|
+
def GetFullNameLabel(self):
|
|
345
|
+
return "{:s} – Firmware {:s}".format(self.GetFullName(), self.GetFirmwareVersion())
|
|
346
|
+
|
|
347
|
+
def GetPCBVersion(self):
|
|
348
|
+
if self.FW["pcb_ver"] in self.PCB_VERSIONS:
|
|
349
|
+
return self.PCB_VERSIONS[self.FW["pcb_ver"]]
|
|
350
|
+
else:
|
|
351
|
+
return "(unknown revision)"
|
|
352
|
+
|
|
353
|
+
def GetFullName(self):
|
|
354
|
+
if len(self.GetPCBVersion()) > 0:
|
|
355
|
+
return "{:s} {:s}".format(self.GetName(), self.GetPCBVersion())
|
|
356
|
+
else:
|
|
357
|
+
return self.GetName()
|
|
358
|
+
|
|
359
|
+
def GetPort(self):
|
|
360
|
+
return self.PORT
|
|
361
|
+
|
|
362
|
+
def GetFWBuildDate(self):
|
|
363
|
+
return self.FW["fw_dt"]
|
|
364
|
+
|
|
365
|
+
def SetWriteDelay(self, enable=True):
|
|
366
|
+
if self.WRITE_DELAY != enable:
|
|
367
|
+
dprint("Setting Write Delay to", enable)
|
|
368
|
+
self.WRITE_DELAY = enable
|
|
369
|
+
|
|
370
|
+
def SetTimeout(self, seconds=1):
|
|
371
|
+
if seconds < 0.1: seconds = 0.1
|
|
372
|
+
self.DEVICE_TIMEOUT = seconds
|
|
373
|
+
self.DEVICE.timeout = self.DEVICE_TIMEOUT
|
|
374
|
+
|
|
375
|
+
def AbortOperation(self, from_user=True):
|
|
376
|
+
self.CANCEL_ARGS["from_user"] = from_user
|
|
377
|
+
self.CANCEL = True
|
|
378
|
+
self.ERROR = False
|
|
379
|
+
|
|
380
|
+
def wait_for_ack(self, values=None):
|
|
381
|
+
if values is None: values = [0x01, 0x03]
|
|
382
|
+
buffer = self._read(1)
|
|
383
|
+
if buffer not in values:
|
|
384
|
+
tb_stack = traceback.extract_stack()
|
|
385
|
+
stack = tb_stack[len(tb_stack)-2] # caller only
|
|
386
|
+
if stack.name == "_write": stack = tb_stack[len(tb_stack)-3]
|
|
387
|
+
dprint("CANCEL_ARGS:", self.CANCEL_ARGS)
|
|
388
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
|
389
|
+
return False
|
|
390
|
+
elif buffer is False:
|
|
391
|
+
dprint("Timeout error ({:s}(), line {:d}): {:f}".format(stack.name, stack.lineno, self.DEVICE.timeout))
|
|
392
|
+
dprint("Traceback:\n", ''.join(traceback.format_stack()[:-1]))
|
|
393
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A timeout error has occured at {:s}() in line {:d}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(stack.name, stack.lineno)})
|
|
394
|
+
elif buffer == 2:
|
|
395
|
+
dprint("Error reported ({:s}(), line {:d}): {:s}".format(stack.name, stack.lineno, str(buffer)))
|
|
396
|
+
dprint("Traceback:\n", ''.join(traceback.format_stack()[:-1]))
|
|
397
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"The device reported an error while running {:s}() in line {:d}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(stack.name, stack.lineno)})
|
|
398
|
+
else:
|
|
399
|
+
dprint("Communication error ({:s}(), line {:d}): {:s}".format(stack.name, stack.lineno, str(buffer)))
|
|
400
|
+
dprint("Traceback:\n", ''.join(traceback.format_stack()[:-1]))
|
|
401
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A communication error has occured at {:s}() in line {:d}. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.".format(stack.name, stack.lineno)})
|
|
402
|
+
self.ERROR = True
|
|
403
|
+
self.CANCEL = True
|
|
404
|
+
self.WRITE_ERRORS += 1
|
|
405
|
+
if self.WRITE_ERRORS > 3:
|
|
406
|
+
self.SetWriteDelay(enable=True)
|
|
407
|
+
return False
|
|
408
|
+
return buffer
|
|
409
|
+
|
|
410
|
+
def _try_write(self, data, retries=5):
|
|
411
|
+
while retries > 0:
|
|
412
|
+
ack = self._write(data, wait=True)
|
|
413
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
|
414
|
+
return False
|
|
415
|
+
if ack is not False:
|
|
416
|
+
self.ERROR = False
|
|
417
|
+
self.CANCEL = False
|
|
418
|
+
self.CANCEL_ARGS = {}
|
|
419
|
+
return ack
|
|
420
|
+
retries -= 1
|
|
421
|
+
dprint("Retries left:", retries)
|
|
422
|
+
|
|
423
|
+
hp = 10
|
|
424
|
+
temp = 0
|
|
425
|
+
while temp not in (1, 2) and hp > 0:
|
|
426
|
+
self.DEVICE.write(b'\x00')
|
|
427
|
+
temp = self._read(1)
|
|
428
|
+
dprint("Current response:", temp)
|
|
429
|
+
hp -= 1
|
|
430
|
+
if hp == 0: break
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
def _write(self, data, wait=False):
|
|
434
|
+
if not isinstance(data, bytearray):
|
|
435
|
+
data = bytearray([data])
|
|
436
|
+
|
|
437
|
+
if Util.DEBUG:
|
|
438
|
+
dstr = ' '.join(format(x, '02X') for x in data)
|
|
439
|
+
cmd = ""
|
|
440
|
+
if len(data) < 32:
|
|
441
|
+
try:
|
|
442
|
+
cmd = "[{:s}] ".format((list(self.DEVICE_CMD.keys())[list(self.DEVICE_CMD.values()).index(data[0])]))
|
|
443
|
+
except:
|
|
444
|
+
pass
|
|
445
|
+
dprint("[{:02X}] {:s}{:s}".format(int(len(dstr)/3) + 1, cmd, dstr[:96]))
|
|
446
|
+
|
|
447
|
+
self.DEVICE.write(data)
|
|
448
|
+
self.DEVICE.flush()
|
|
449
|
+
|
|
450
|
+
# On MacOS it’s possible not all bytes are transmitted successfully,
|
|
451
|
+
# even though we’re using flush() which is the tcdrain function.
|
|
452
|
+
# Still looking for a better solution than delaying here.
|
|
453
|
+
if self.WRITE_DELAY is True or (platform.system() == "Darwin" and ("pcb_name" not in self.FW or self.FW["pcb_name"] == "GBxCart RW")):
|
|
454
|
+
time.sleep(0.0014)
|
|
455
|
+
|
|
456
|
+
if wait: return self.wait_for_ack()
|
|
457
|
+
|
|
458
|
+
def _read(self, count):
|
|
459
|
+
if self.DEVICE.in_waiting > 1000: dprint("Warning: in_waiting={:d} bytes".format(self.DEVICE.in_waiting))
|
|
460
|
+
buffer = self.DEVICE.read(count)
|
|
461
|
+
|
|
462
|
+
if len(buffer) != count:
|
|
463
|
+
hp = 50
|
|
464
|
+
while (self.DEVICE.in_waiting != (count - len(buffer))) and hp > 0:
|
|
465
|
+
time.sleep(0.01)
|
|
466
|
+
hp -= 1
|
|
467
|
+
if hp > 0:
|
|
468
|
+
# dprint(f"Recovery: {self.DEVICE.in_waiting} byte(s) in queue! (HP: {hp}/50)")
|
|
469
|
+
buffer += self.DEVICE.read(count)
|
|
470
|
+
|
|
471
|
+
if len(buffer) != count:
|
|
472
|
+
tb_stack = traceback.extract_stack()
|
|
473
|
+
stack = tb_stack[len(tb_stack)-2] # caller only
|
|
474
|
+
if stack.name == "_read": stack = tb_stack[len(tb_stack)-3]
|
|
475
|
+
dprint("Error: Received only {:d} of {:d} byte(s) ({:s}(), line {:d})".format(len(buffer), count, stack.name, stack.lineno))
|
|
476
|
+
dprint("Timeout value:", self.DEVICE.timeout)
|
|
477
|
+
dprint("Traceback:\n", ''.join(traceback.format_stack()[:-1]))
|
|
478
|
+
self.READ_ERRORS += 1
|
|
479
|
+
while self.DEVICE.in_waiting > 0:
|
|
480
|
+
self.DEVICE.reset_input_buffer()
|
|
481
|
+
time.sleep(0.5)
|
|
482
|
+
self.DEVICE.reset_output_buffer()
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
if count == 1:
|
|
486
|
+
return buffer[0]
|
|
487
|
+
else:
|
|
488
|
+
return bytearray(buffer)
|
|
489
|
+
|
|
490
|
+
def _get_fw_variable(self, key):
|
|
491
|
+
if self.FW["fw_ver"] < 10: return 0
|
|
492
|
+
dprint("Getting firmware variable {:s}".format(key))
|
|
493
|
+
|
|
494
|
+
size = 0
|
|
495
|
+
for (k, v) in self.DEVICE_VAR.items():
|
|
496
|
+
if key in k:
|
|
497
|
+
if v[0] == 8: size = 1
|
|
498
|
+
elif v[0] == 16: size = 2
|
|
499
|
+
elif v[0] == 32: size = 4
|
|
500
|
+
key = v[1]
|
|
501
|
+
break
|
|
502
|
+
if size == 0:
|
|
503
|
+
raise KeyError("Unknown variable name specified.")
|
|
504
|
+
|
|
505
|
+
buffer = bytearray([self.DEVICE_CMD["GET_VARIABLE"], size])
|
|
506
|
+
buffer.extend(struct.pack(">I", key))
|
|
507
|
+
self._write(buffer)
|
|
508
|
+
temp = self._read(4)
|
|
509
|
+
try:
|
|
510
|
+
return struct.unpack(">I", temp)[0]
|
|
511
|
+
except:
|
|
512
|
+
dprint("Communication error:", temp)
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
def _set_fw_variable(self, key, value):
|
|
516
|
+
# if key == "FLASH_WE_PIN" and not self.SupportsAudioAsWe(): return
|
|
517
|
+
dprint("Setting firmware variable {:s} to 0x{:X}".format(key, value))
|
|
518
|
+
self.FW_VAR[key] = value
|
|
519
|
+
|
|
520
|
+
size = 0
|
|
521
|
+
for (k, v) in self.DEVICE_VAR.items():
|
|
522
|
+
if key in k:
|
|
523
|
+
if v[0] == 8: size = 1
|
|
524
|
+
elif v[0] == 16: size = 2
|
|
525
|
+
elif v[0] == 32: size = 4
|
|
526
|
+
key = v[1]
|
|
527
|
+
break
|
|
528
|
+
if size == 0:
|
|
529
|
+
raise KeyError("Unknown variable name specified.")
|
|
530
|
+
|
|
531
|
+
buffer = bytearray([self.DEVICE_CMD["SET_VARIABLE"], size])
|
|
532
|
+
buffer.extend(struct.pack(">I", key))
|
|
533
|
+
buffer.extend(struct.pack(">I", value))
|
|
534
|
+
|
|
535
|
+
self._try_write(buffer)
|
|
536
|
+
|
|
537
|
+
def _cart_read(self, address, length=0, agb_save_flash=False):
|
|
538
|
+
if self.MODE == "DMG":
|
|
539
|
+
if length == 0:
|
|
540
|
+
length = 1
|
|
541
|
+
if address < 0xA000:
|
|
542
|
+
return struct.unpack("B", self.ReadROM(address, 1))[0]
|
|
543
|
+
else:
|
|
544
|
+
return struct.unpack("B", self.ReadRAM(address - 0xA000, 1))[0]
|
|
545
|
+
else:
|
|
546
|
+
if address < 0xA000:
|
|
547
|
+
return self.ReadROM(address, length)
|
|
548
|
+
else:
|
|
549
|
+
return self.ReadRAM(address - 0xA000, length)
|
|
550
|
+
elif self.MODE == "AGB":
|
|
551
|
+
if length == 0:
|
|
552
|
+
if agb_save_flash:
|
|
553
|
+
length = 1
|
|
554
|
+
return struct.unpack("B", self.ReadRAM(address, length, command=self.DEVICE_CMD["AGB_CART_READ_SRAM"]))[0]
|
|
555
|
+
else:
|
|
556
|
+
length = 2
|
|
557
|
+
return struct.unpack(">H", self.ReadROM(address >> 1, length))[0]
|
|
558
|
+
else:
|
|
559
|
+
if agb_save_flash:
|
|
560
|
+
return self.ReadRAM(address, length, command=self.DEVICE_CMD["AGB_CART_READ_SRAM"])
|
|
561
|
+
else:
|
|
562
|
+
return self.ReadROM(address, length)
|
|
563
|
+
|
|
564
|
+
def _cart_write(self, address, value, flashcart=False, sram=False):
|
|
565
|
+
dprint("Writing to cartridge: 0x{:X} = 0x{:X} (args: {:s}, {:s})".format(address, value & 0xFF, str(flashcart), str(sram)))
|
|
566
|
+
if self.MODE == "DMG":
|
|
567
|
+
if flashcart:
|
|
568
|
+
buffer = bytearray([self.DEVICE_CMD["DMG_FLASH_WRITE_BYTE"]])
|
|
569
|
+
buffer.extend(struct.pack(">I", address))
|
|
570
|
+
buffer.extend(struct.pack("B", value & 0xFF))
|
|
571
|
+
else:
|
|
572
|
+
if sram:
|
|
573
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
|
|
574
|
+
self._set_fw_variable("ADDRESS", address)
|
|
575
|
+
self._set_fw_variable("TRANSFER_SIZE", 1)
|
|
576
|
+
self._write(self.DEVICE_CMD["DMG_CART_WRITE_SRAM"])
|
|
577
|
+
self._write(value, wait=True)
|
|
578
|
+
return
|
|
579
|
+
else:
|
|
580
|
+
buffer = bytearray([self.DEVICE_CMD["DMG_CART_WRITE"]])
|
|
581
|
+
buffer.extend(struct.pack(">I", address))
|
|
582
|
+
buffer.extend(struct.pack("B", value & 0xFF))
|
|
583
|
+
elif self.MODE == "AGB":
|
|
584
|
+
if sram:
|
|
585
|
+
self._set_fw_variable("TRANSFER_SIZE", 1)
|
|
586
|
+
self._set_fw_variable("ADDRESS", address)
|
|
587
|
+
self._write(self.DEVICE_CMD["AGB_CART_WRITE_SRAM"])
|
|
588
|
+
self._write(value, wait=True)
|
|
589
|
+
return
|
|
590
|
+
elif flashcart:
|
|
591
|
+
buffer = bytearray([self.DEVICE_CMD["AGB_FLASH_WRITE_SHORT"]])
|
|
592
|
+
else:
|
|
593
|
+
buffer = bytearray([self.DEVICE_CMD["AGB_CART_WRITE"]])
|
|
594
|
+
|
|
595
|
+
buffer.extend(struct.pack(">I", address >> 1))
|
|
596
|
+
buffer.extend(struct.pack(">H", value & 0xFFFF))
|
|
597
|
+
|
|
598
|
+
self._try_write(buffer)
|
|
599
|
+
|
|
600
|
+
if self.MODE == "DMG" and sram: self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
|
|
601
|
+
|
|
602
|
+
def _cart_write_flash(self, commands, flashcart=False):
|
|
603
|
+
if self.FW["fw_ver"] < 6 and not (self.MODE == "AGB" and not flashcart):
|
|
604
|
+
for command in commands:
|
|
605
|
+
if self._cart_write(command[0], command[1], flashcart=flashcart) is False:
|
|
606
|
+
print("ERROR")
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
num = len(commands)
|
|
610
|
+
buffer = bytearray([self.DEVICE_CMD["CART_WRITE_FLASH_CMD"]])
|
|
611
|
+
if self.FW["fw_ver"] >= 6:
|
|
612
|
+
buffer.extend(struct.pack("B", 1 if flashcart else 0))
|
|
613
|
+
buffer.extend(struct.pack("B", num))
|
|
614
|
+
for i in range(0, num):
|
|
615
|
+
dprint("Writing to cartridge: 0x{:X} = 0x{:X} ({:d} of {:d})".format(commands[i][0], commands[i][1], i+1, num))
|
|
616
|
+
if self.MODE == "AGB" and flashcart:
|
|
617
|
+
buffer.extend(struct.pack(">I", commands[i][0] >> 1))
|
|
618
|
+
else:
|
|
619
|
+
buffer.extend(struct.pack(">I", commands[i][0]))
|
|
620
|
+
|
|
621
|
+
if self.FW["fw_ver"] < 6:
|
|
622
|
+
buffer.extend(struct.pack("B", commands[i][1]))
|
|
623
|
+
else:
|
|
624
|
+
buffer.extend(struct.pack(">H", commands[i][1]))
|
|
625
|
+
|
|
626
|
+
self._write(buffer)
|
|
627
|
+
ret = self._read(1)
|
|
628
|
+
if ret != 0x01:
|
|
629
|
+
if ret is False:
|
|
630
|
+
msg = "Error: No response while trying to write to the device."
|
|
631
|
+
time.sleep(0.5)
|
|
632
|
+
self.DEVICE.reset_input_buffer()
|
|
633
|
+
else:
|
|
634
|
+
msg = "Error: Bad response “{:s}” while trying to write to the device.".format(str(ret))
|
|
635
|
+
print(f"{ANSI.RED}{msg}{ANSI.RESET}")
|
|
636
|
+
dprint(msg)
|
|
637
|
+
#self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"A critical communication error occured during a write. Please avoid passive USB hubs, try different USB ports/cables and re-connect the device."})
|
|
638
|
+
#self.CANCEL = True
|
|
639
|
+
#self.ERROR = True
|
|
640
|
+
return False
|
|
641
|
+
return True
|
|
642
|
+
|
|
643
|
+
def _clk_toggle(self, num=1):
|
|
644
|
+
if self.FW["fw_ver"] >= 12:
|
|
645
|
+
buffer = bytearray()
|
|
646
|
+
buffer.extend(struct.pack("B", self.DEVICE_CMD["CLK_TOGGLE"]))
|
|
647
|
+
buffer.extend(struct.pack(">I", num))
|
|
648
|
+
return self._write(buffer, wait=True)
|
|
649
|
+
else:
|
|
650
|
+
if not self.CanPowerCycleCart(): return False
|
|
651
|
+
for _ in range(0, num):
|
|
652
|
+
self._write(0xA9) # CLK_HIGH
|
|
653
|
+
self._write(0xAA) # CLK_LOW
|
|
654
|
+
return True
|
|
655
|
+
|
|
656
|
+
def _set_we_pin_wr(self):
|
|
657
|
+
if self.MODE == "DMG":
|
|
658
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x01) # FLASH_WE_PIN_WR
|
|
659
|
+
def _set_we_pin_audio(self):
|
|
660
|
+
if self.MODE == "DMG":
|
|
661
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x02) # FLASH_WE_PIN_AUDIO
|
|
662
|
+
|
|
663
|
+
def CartPowerCycleOrAskReconnect(self, delay=0.1):
|
|
664
|
+
if self.CanPowerCycleCart():
|
|
665
|
+
self.CartPowerCycle(delay=delay)
|
|
666
|
+
return
|
|
667
|
+
|
|
668
|
+
mode = self.GetMode()
|
|
669
|
+
var_state = self.GetVarState()
|
|
670
|
+
|
|
671
|
+
title = "Power cycle required"
|
|
672
|
+
msg = f"To continue, please re-connect the USB cable of your {self.GetName()} device on port {self.PORT}, preferably on the computer side."
|
|
673
|
+
while True:
|
|
674
|
+
self.SetProgress({"action":"USER_ACTION", "user_action":"REINSERT_CART", "msg":msg, "title":title})
|
|
675
|
+
while self.USER_ANSWER is None:
|
|
676
|
+
dprint("Waiting for the user to confirm the re-connecting of the device.")
|
|
677
|
+
time.sleep(1)
|
|
678
|
+
|
|
679
|
+
if self.USER_ANSWER is False:
|
|
680
|
+
self.CANCEL = True
|
|
681
|
+
self.USER_ANSWER = None
|
|
682
|
+
return False
|
|
683
|
+
|
|
684
|
+
self.USER_ANSWER = None
|
|
685
|
+
if self.CheckActive(): continue
|
|
686
|
+
|
|
687
|
+
dev = None
|
|
688
|
+
try:
|
|
689
|
+
dev = serial.Serial(self.PORT, self.BAUDRATE, timeout=0.1)
|
|
690
|
+
self.DEVICE = dev
|
|
691
|
+
self.LoadFirmwareVersion()
|
|
692
|
+
self.SetMode(mode)
|
|
693
|
+
self.SetVarState(var_state)
|
|
694
|
+
time.sleep(0.5)
|
|
695
|
+
break
|
|
696
|
+
|
|
697
|
+
except SerialException as e:
|
|
698
|
+
del(dev)
|
|
699
|
+
dprint("An error occured while re-connecting to the device:\n", e)
|
|
700
|
+
continue
|
|
701
|
+
|
|
702
|
+
def CartPowerCycle(self, delay=0.1):
|
|
703
|
+
if self.CanPowerCycleCart():
|
|
704
|
+
dprint("Power cycling cartridge with a delay of {:.1f} seconds".format(delay))
|
|
705
|
+
self.CartPowerOff(delay=delay)
|
|
706
|
+
self.CartPowerOn(delay=delay)
|
|
707
|
+
|
|
708
|
+
def CartPowerOff(self, delay=0.1):
|
|
709
|
+
dprint("Turning off the cartridge power")
|
|
710
|
+
if self.CanPowerCycleCart():
|
|
711
|
+
if self.FW["fw_ver"] >= 12:
|
|
712
|
+
self._write(self.DEVICE_CMD["CART_PWR_OFF"], wait=self.FW["fw_ver"] >= 12)
|
|
713
|
+
else:
|
|
714
|
+
self._write(self.DEVICE_CMD["OFW_CART_PWR_OFF"])
|
|
715
|
+
time.sleep(delay)
|
|
716
|
+
else:
|
|
717
|
+
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"], wait=self.FW["fw_ver"] >= 12)
|
|
718
|
+
|
|
719
|
+
def CartPowerOn(self, delay=0.1):
|
|
720
|
+
if self.CanPowerCycleCart():
|
|
721
|
+
if self.FW["fw_ver"] >= 12:
|
|
722
|
+
self._write(self.DEVICE_CMD["QUERY_CART_PWR"])
|
|
723
|
+
if self._read(1) == 0:
|
|
724
|
+
dprint("Turning on the cartridge power")
|
|
725
|
+
if self.MODE == "DMG":
|
|
726
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
727
|
+
elif self.MODE == "AGB":
|
|
728
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
729
|
+
self._write(self.DEVICE_CMD["CART_PWR_ON"])
|
|
730
|
+
time.sleep(0.2)
|
|
731
|
+
hp = 10
|
|
732
|
+
while hp > 0: # Workaround for GBxCart RW, it sometimes glitches after cart power on?
|
|
733
|
+
if self.DEVICE.in_waiting == 0:
|
|
734
|
+
dprint("Waiting for ACK...")
|
|
735
|
+
hp -= 1
|
|
736
|
+
time.sleep(0.05)
|
|
737
|
+
continue
|
|
738
|
+
temp = self.DEVICE.read(self.DEVICE.in_waiting)
|
|
739
|
+
if len(temp) >= 1: temp = temp[len(temp) - 1]
|
|
740
|
+
if temp == 1: break
|
|
741
|
+
self.DEVICE.timeout = 0.1
|
|
742
|
+
dprint("Unexpected ACK value:", temp)
|
|
743
|
+
self._write(self.DEVICE_CMD["QUERY_CART_PWR"])
|
|
744
|
+
time.sleep(0.05)
|
|
745
|
+
hp -= 1
|
|
746
|
+
self.DEVICE.timeout = self.DEVICE_TIMEOUT
|
|
747
|
+
|
|
748
|
+
self._write(self.DEVICE_CMD["QUERY_CART_PWR"])
|
|
749
|
+
if self._read(1) != 1:
|
|
750
|
+
dprint("Warning: No response from firmware on QUERY_CART_PWR.")
|
|
751
|
+
|
|
752
|
+
if self.MODE == "DMG":
|
|
753
|
+
dprint("Resetting Memory Bank Controller")
|
|
754
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True) # Sachen (and Xploder GB?) may need this
|
|
755
|
+
elif self.MODE == "AGB":
|
|
756
|
+
dprint("Executing AGB Bootup Sequence")
|
|
757
|
+
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=self.FW["fw_ver"] >= 12)
|
|
758
|
+
|
|
759
|
+
if self.FW["pcb_name"] == "GBxCart RW":
|
|
760
|
+
self._cart_write(0, 0xFF) # workaround for strange bootlegs
|
|
761
|
+
|
|
762
|
+
else:
|
|
763
|
+
self._write(self.DEVICE_CMD["OFW_QUERY_CART_PWR"])
|
|
764
|
+
if self._read(1) == 0:
|
|
765
|
+
dprint("Turning on the cartridge power.")
|
|
766
|
+
self._write(self.DEVICE_CMD["OFW_CART_PWR_ON"])
|
|
767
|
+
time.sleep(delay)
|
|
768
|
+
self.DEVICE.reset_input_buffer() # bug workaround
|
|
769
|
+
|
|
770
|
+
else:
|
|
771
|
+
if self.MODE == "AGB":
|
|
772
|
+
dprint("Executing AGB Bootup Sequence")
|
|
773
|
+
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=self.FW["fw_ver"] >= 12)
|
|
774
|
+
|
|
775
|
+
return True
|
|
776
|
+
|
|
777
|
+
def GetVarState(self):
|
|
778
|
+
self._write(self.DEVICE_CMD["GET_VAR_STATE"])
|
|
779
|
+
time.sleep(0.2)
|
|
780
|
+
var_state = bytearray(self.DEVICE.read(self.DEVICE.in_waiting))
|
|
781
|
+
dprint("Got the state of variables ({:d} bytes)".format(len(var_state)))
|
|
782
|
+
return var_state
|
|
783
|
+
|
|
784
|
+
def SetVarState(self, var_state):
|
|
785
|
+
dprint("Sending the state of variables ({:d} bytes)".format(len(var_state)))
|
|
786
|
+
self._write(self.DEVICE_CMD["SET_VAR_STATE"])
|
|
787
|
+
time.sleep(0.2)
|
|
788
|
+
self.DEVICE.write(var_state)
|
|
789
|
+
|
|
790
|
+
def GetMode(self):
|
|
791
|
+
if time.time() < self.LAST_CHECK_ACTIVE + 1: return self.MODE
|
|
792
|
+
if self.CheckActive() is False: return None
|
|
793
|
+
if self.MODE is None: return None
|
|
794
|
+
if self.FW["fw_ver"] < 12: return self.MODE
|
|
795
|
+
mode = self._get_fw_variable("CART_MODE")
|
|
796
|
+
if mode is False:
|
|
797
|
+
print("{:s}Error: Couldn’t get mode variable from firmware. Please re-connect the device.{:s}".format(ANSI.RED, ANSI.RESET))
|
|
798
|
+
self.DEVICE.close()
|
|
799
|
+
self.DEVICE = None
|
|
800
|
+
self.ERROR = True
|
|
801
|
+
self.CANCEL = True
|
|
802
|
+
return self.MODE
|
|
803
|
+
if mode == 0:
|
|
804
|
+
return None
|
|
805
|
+
modes = self.GetSupprtedModes()
|
|
806
|
+
if mode > len(modes):
|
|
807
|
+
print("{:s}Error: Invalid mode: {:s}{:s}".format(ANSI.RED, str(mode - 1), ANSI.RESET))
|
|
808
|
+
return self.MODE
|
|
809
|
+
self.MODE = modes[mode - 1]
|
|
810
|
+
return self.MODE
|
|
811
|
+
|
|
812
|
+
def SetMode(self, mode, delay=0.1):
|
|
813
|
+
if mode == "DMG":
|
|
814
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
815
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
816
|
+
self._set_fw_variable("DMG_READ_METHOD", self.DMG_READ_METHOD)
|
|
817
|
+
self._set_fw_variable("CART_MODE", 1)
|
|
818
|
+
self.MODE = "DMG"
|
|
819
|
+
elif mode == "AGB":
|
|
820
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
821
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
822
|
+
self._set_fw_variable("AGB_READ_METHOD", self.AGB_READ_METHOD)
|
|
823
|
+
self._set_fw_variable("CART_MODE", 2)
|
|
824
|
+
if self.FW["fw_ver"] >= 12: self._set_fw_variable("AGB_IRQ_ENABLED", 0)
|
|
825
|
+
self.MODE = "AGB"
|
|
826
|
+
self._set_fw_variable(key="ADDRESS", value=0)
|
|
827
|
+
|
|
828
|
+
if self.CanPowerCycleCart():
|
|
829
|
+
self.CartPowerOn()
|
|
830
|
+
else:
|
|
831
|
+
if mode == "DMG":
|
|
832
|
+
self.SetPin(["PIN_AUDIO"], True)
|
|
833
|
+
elif mode == "AGB":
|
|
834
|
+
self.SetPin(["PIN_AUDIO"], False)
|
|
835
|
+
|
|
836
|
+
def SetAutoPowerOff(self, value):
|
|
837
|
+
if not self.CanPowerCycleCart(): return
|
|
838
|
+
value &= 0xFFFFFFFF
|
|
839
|
+
dprint(f"Setting automatic power off time value to {value}")
|
|
840
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", value)
|
|
841
|
+
self._set_fw_variable("AUTO_POWEROFF_ENABLED", 1 if value != 0 else 0)
|
|
842
|
+
|
|
843
|
+
def GetSupportedCartridgesDMG(self):
|
|
844
|
+
return (list(self.SUPPORTED_CARTS['DMG'].keys()), list(self.SUPPORTED_CARTS['DMG'].values()))
|
|
845
|
+
|
|
846
|
+
def GetSupportedCartridgesAGB(self):
|
|
847
|
+
return (list(self.SUPPORTED_CARTS['AGB'].keys()), list(self.SUPPORTED_CARTS['AGB'].values()))
|
|
848
|
+
|
|
849
|
+
def SetProgress(self, args):
|
|
850
|
+
if self.CANCEL and args["action"] not in ("ABORT", "FINISHED", "ERROR"): return
|
|
851
|
+
if "pos" in args: self.POS = args["pos"]
|
|
852
|
+
if args["action"] == "UPDATE_POS": self.INFO["transferred"] = args["pos"]
|
|
853
|
+
try:
|
|
854
|
+
self.SIGNAL.emit(args)
|
|
855
|
+
except AttributeError:
|
|
856
|
+
if self.SIGNAL is not None:
|
|
857
|
+
self.SIGNAL(args)
|
|
858
|
+
|
|
859
|
+
if args["action"] == "INITIALIZE":
|
|
860
|
+
if self.CanPowerCycleCart(): self.CartPowerOn() # Ensure cart is powered
|
|
861
|
+
self.POS = 0
|
|
862
|
+
elif args["action"] == "FINISHED":
|
|
863
|
+
self.POS = 0
|
|
864
|
+
self.SIGNAL = None
|
|
865
|
+
|
|
866
|
+
def Debug(self):
|
|
867
|
+
# self.SetPin(["PIN_AUDIO"], True)
|
|
868
|
+
return
|
|
869
|
+
|
|
870
|
+
def ReadInfo(self, setPinsAsInputs=False, checkRtc=True):
|
|
871
|
+
self.Debug()
|
|
872
|
+
|
|
873
|
+
if not self.IsConnected(): raise ConnectionError("Couldn’t access the the device.")
|
|
874
|
+
data = {}
|
|
875
|
+
self.SIGNAL = None
|
|
876
|
+
|
|
877
|
+
if self.CanPowerCycleCart():
|
|
878
|
+
self.ResetLEDs()
|
|
879
|
+
self.CartPowerOn()
|
|
880
|
+
|
|
881
|
+
if self.FW["fw_ver"] >= 8: self._write(self.DEVICE_CMD["DISABLE_PULLUPS"], wait=True)
|
|
882
|
+
if self.MODE == "DMG":
|
|
883
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
884
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
885
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
886
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
|
|
887
|
+
elif self.MODE == "AGB":
|
|
888
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
889
|
+
if not self.CanPowerCycleCart():
|
|
890
|
+
dprint("Executing AGB Bootup Sequence")
|
|
891
|
+
self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=self.FW["fw_ver"] >= 12)
|
|
892
|
+
else:
|
|
893
|
+
print("{:s}Error: No mode was set.{:s}".format(ANSI.RED, ANSI.RESET))
|
|
894
|
+
return False
|
|
895
|
+
|
|
896
|
+
header = self.ReadROM(0, 0x180)
|
|
897
|
+
|
|
898
|
+
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
|
899
|
+
with open("debug_header.bin", "wb") as f: f.write(header)
|
|
900
|
+
|
|
901
|
+
# Parse ROM header
|
|
902
|
+
if self.MODE == "DMG":
|
|
903
|
+
data = RomFileDMG(header).GetHeader()
|
|
904
|
+
if "game_title" in data and data["game_title"] == "TETRIS" and hashlib.sha1(header).digest() != bytearray([0x1D, 0x69, 0x2A, 0x4B, 0x31, 0x7A, 0xA5, 0xE9, 0x67, 0xEE, 0xC2, 0x2F, 0xCC, 0x32, 0x43, 0x8C, 0xCB, 0xC5, 0x78, 0x0B]): # Sachen
|
|
905
|
+
header = self.ReadROM(0, 0x280)
|
|
906
|
+
data = RomFileDMG(header).GetHeader()
|
|
907
|
+
if "logo_correct" in data and data["logo_correct"] is False and not b"Future Console Design" in header:
|
|
908
|
+
self._cart_write(0, 0xFF) # workaround for strange bootlegs
|
|
909
|
+
time.sleep(0.1)
|
|
910
|
+
header = self.ReadROM(0, 0x280)
|
|
911
|
+
data = RomFileDMG(header).GetHeader()
|
|
912
|
+
if "mapper_raw" in data and data["mapper_raw"] == 0x203 or b"Future Console Design" in header: # Xploder GB version number
|
|
913
|
+
self._cart_write(0x0006, 0)
|
|
914
|
+
header[0:0x10] = self.ReadROM(0x4000, 0x10)
|
|
915
|
+
header[0xD0:0xE0] = self.ReadROM(0x40D0, 0x10)
|
|
916
|
+
data = RomFileDMG(header).GetHeader()
|
|
917
|
+
if data == {}: return False
|
|
918
|
+
|
|
919
|
+
data["has_rtc"] = False
|
|
920
|
+
data["rtc_dict"] = {}
|
|
921
|
+
data["rtc_string"] = "Not available"
|
|
922
|
+
if data["logo_correct"] is True:
|
|
923
|
+
_mbc = DMG_MBC().GetInstance(args={"mbc":data["mapper_raw"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
924
|
+
if checkRtc:
|
|
925
|
+
data["has_rtc"] = _mbc.HasRTC() is True
|
|
926
|
+
if data["has_rtc"] is True:
|
|
927
|
+
if _mbc.GetName() == "TAMA5": _mbc.EnableMapper()
|
|
928
|
+
_mbc.LatchRTC()
|
|
929
|
+
data["rtc_buffer"] = _mbc.ReadRTC()
|
|
930
|
+
if _mbc.GetName() == "TAMA5": self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
931
|
+
try:
|
|
932
|
+
data["rtc_dict"] = _mbc.GetRTCDict()
|
|
933
|
+
data["rtc_string"] = _mbc.GetRTCString()
|
|
934
|
+
except:
|
|
935
|
+
data["rtc_string"] = "Invalid data"
|
|
936
|
+
if _mbc.GetName() == "G-MMC1":
|
|
937
|
+
try:
|
|
938
|
+
temp = bytearray([0] * 0x100000)
|
|
939
|
+
temp[0:0x180] = header
|
|
940
|
+
_mbc.SelectBankROM(7)
|
|
941
|
+
if data["game_title"] == "NP M-MENU MENU":
|
|
942
|
+
gbmem_menudata = self.ReadROM(0x4000, 0x1000)
|
|
943
|
+
temp[0x1C000:0x1C000+0x1000] = gbmem_menudata
|
|
944
|
+
elif data["game_title"] == "DMG MULTI MENU ":
|
|
945
|
+
gbmem_menudata = self.ReadROM(0x4000, 0x4000)
|
|
946
|
+
temp[0x1C000:0x1C000+0x4000] = gbmem_menudata
|
|
947
|
+
_mbc.SelectBankROM(0)
|
|
948
|
+
data["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=_mbc.ReadHiddenSector(), buffer_rom=temp)
|
|
949
|
+
except:
|
|
950
|
+
print(traceback.format_exc())
|
|
951
|
+
print("{:s}An error occured while trying to read the hidden sector data of the NP GB-Memory cartridge.{:s}".format(ANSI.RED, ANSI.RESET))
|
|
952
|
+
|
|
953
|
+
elif self.MODE == "AGB":
|
|
954
|
+
# Unlock DACS carts on older firmware
|
|
955
|
+
if not self.CanPowerCycleCart() or self.FW["fw_ver"] == 1:
|
|
956
|
+
if header[0x04:0x04+0x9C] in (bytearray([0x00] * 0x9C), bytearray([0xFF] * 0x9C)):
|
|
957
|
+
self.ReadROM(0x1FFFFE0, 20)
|
|
958
|
+
header = self.ReadROM(0, 0x180)
|
|
959
|
+
|
|
960
|
+
data = RomFileAGB(header).GetHeader()
|
|
961
|
+
if data["logo_correct"] is False: # workaround for strange bootlegs
|
|
962
|
+
self._cart_write(0, 0xFF)
|
|
963
|
+
header = self.ReadROM(0, 0x180)
|
|
964
|
+
data = RomFileAGB(header).GetHeader()
|
|
965
|
+
|
|
966
|
+
if data["vast_fame"]: # Unlock full address space for Vast Fame protected carts
|
|
967
|
+
self._cart_write(0xFFF8, 0x99, sram=True)
|
|
968
|
+
self._cart_write(0xFFF9, 0x02, sram=True)
|
|
969
|
+
self._cart_write(0xFFFA, 0x05, sram=True)
|
|
970
|
+
self._cart_write(0xFFFB, 0x02, sram=True)
|
|
971
|
+
self._cart_write(0xFFFC, 0x03, sram=True)
|
|
972
|
+
|
|
973
|
+
self._cart_write(0xFFFD, 0x00, sram=True)
|
|
974
|
+
|
|
975
|
+
self._cart_write(0xFFF8, 0x99, sram=True)
|
|
976
|
+
self._cart_write(0xFFF9, 0x03, sram=True)
|
|
977
|
+
self._cart_write(0xFFFA, 0x62, sram=True)
|
|
978
|
+
self._cart_write(0xFFFB, 0x02, sram=True)
|
|
979
|
+
self._cart_write(0xFFFC, 0x56, sram=True)
|
|
980
|
+
|
|
981
|
+
if data["empty"] or data["empty_nocart"]:
|
|
982
|
+
data["rom_size"] = 0x2000000
|
|
983
|
+
else:
|
|
984
|
+
# Check where the ROM data repeats (for unlicensed carts)
|
|
985
|
+
size_check = header[0xA0:0xA0+16]
|
|
986
|
+
currAddr = 0x10000
|
|
987
|
+
while currAddr < 0x2000000:
|
|
988
|
+
buffer = self.ReadROM(currAddr + 0xA0, 64)[:16]
|
|
989
|
+
if buffer == size_check: break
|
|
990
|
+
currAddr *= 2
|
|
991
|
+
|
|
992
|
+
if data["vast_fame"]:
|
|
993
|
+
if currAddr < 0x2000000:
|
|
994
|
+
currAddr >>= 1 # Vast Fame carts are blank for the 1st mirror, so divide by 2
|
|
995
|
+
else: # Some Vast Fame carts have no mirror, check using VF pattern behaviour instead
|
|
996
|
+
currAddr = 0x200000
|
|
997
|
+
while currAddr < 0x2000000:
|
|
998
|
+
sentinel = self.ReadROM(currAddr + 0x2AAAA, 2)
|
|
999
|
+
if int.from_bytes(sentinel) == 0xAAAA: break
|
|
1000
|
+
currAddr *= 2
|
|
1001
|
+
|
|
1002
|
+
data["rom_size"] = currAddr
|
|
1003
|
+
|
|
1004
|
+
if (self.ReadROM(0x1FFE000, 0x0C) == b"AGBFLASHDACS"):
|
|
1005
|
+
data["dacs_8m"] = True
|
|
1006
|
+
if self.FW["pcb_name"] == "GBFlash" and self.FW["pcb_ver"] < 13:
|
|
1007
|
+
print("{:s}Note: This cartridge may not be fully compatible with your GBFlash hardware revision. Upgrade to v1.3 or newer for better compatibility.{:s}".format(ANSI.YELLOW, ANSI.RESET))
|
|
1008
|
+
|
|
1009
|
+
data["rtc_dict"] = {}
|
|
1010
|
+
data["rtc_string"] = "Not available"
|
|
1011
|
+
if checkRtc and data["logo_correct"] is True and header[0xC5] == 0 and header[0xC7] == 0 and header[0xC9] == 0:
|
|
1012
|
+
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
1013
|
+
if self.FW["fw_ver"] >= 12:
|
|
1014
|
+
self._write(self.DEVICE_CMD["AGB_READ_GPIO_RTC"])
|
|
1015
|
+
temp = self._read(8)
|
|
1016
|
+
data["has_rtc"] = _agb_gpio.HasRTC(temp) is True
|
|
1017
|
+
if data["has_rtc"] is True:
|
|
1018
|
+
data["rtc_buffer"] = temp[1:8]
|
|
1019
|
+
else:
|
|
1020
|
+
data["has_rtc"] = _agb_gpio.HasRTC() is True
|
|
1021
|
+
if data["has_rtc"] is True:
|
|
1022
|
+
data["rtc_buffer"] = _agb_gpio.ReadRTC()
|
|
1023
|
+
try:
|
|
1024
|
+
data["rtc_dict"] = _agb_gpio.GetRTCDict(has_rtc=data["has_rtc"])
|
|
1025
|
+
data["rtc_string"] = _agb_gpio.GetRTCString(has_rtc=data["has_rtc"])
|
|
1026
|
+
except Exception as e:
|
|
1027
|
+
dprint("RTC exception:", str(e.args[0]))
|
|
1028
|
+
data["has_rtc"] = False
|
|
1029
|
+
else:
|
|
1030
|
+
data["has_rtc"] = False
|
|
1031
|
+
data["no_rtc_reason"] = None
|
|
1032
|
+
|
|
1033
|
+
if data["ereader"] is True:
|
|
1034
|
+
bank = 0
|
|
1035
|
+
dprint("Switching to FLASH bank {:d}".format(bank))
|
|
1036
|
+
cmds = [
|
|
1037
|
+
[ 0x5555, 0xAA ],
|
|
1038
|
+
[ 0x2AAA, 0x55 ],
|
|
1039
|
+
[ 0x5555, 0xB0 ],
|
|
1040
|
+
[ 0, bank ]
|
|
1041
|
+
]
|
|
1042
|
+
self._cart_write_flash(cmds)
|
|
1043
|
+
temp = self.ReadRAM(address=0xD000, length=0x2000, command=self.DEVICE_CMD["AGB_CART_READ_SRAM"])
|
|
1044
|
+
if temp[0:0x14] == b'Card-E Reader 2001\0\0':
|
|
1045
|
+
data["ereader_calibration"] = temp
|
|
1046
|
+
else:
|
|
1047
|
+
data["ereader_calibration"] = None
|
|
1048
|
+
del(data["ereader_calibration"])
|
|
1049
|
+
|
|
1050
|
+
dprint("Header data:", data)
|
|
1051
|
+
data["raw"] = header
|
|
1052
|
+
self.INFO = {**self.INFO, **data}
|
|
1053
|
+
if "batteryless_sram" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["batteryless_sram"])
|
|
1054
|
+
self.INFO["dump_info"]["header"] = data
|
|
1055
|
+
self.INFO["flash_type"] = 0
|
|
1056
|
+
self.INFO["last_action"] = 0
|
|
1057
|
+
|
|
1058
|
+
if self.MODE == "DMG": #and setPinsAsInputs:
|
|
1059
|
+
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"], wait=self.FW["fw_ver"] >= 12)
|
|
1060
|
+
|
|
1061
|
+
return data
|
|
1062
|
+
|
|
1063
|
+
def DetectCartridge(self, mbc=None, limitVoltage=False, checkSaveType=True):
|
|
1064
|
+
self.SIGNAL = None
|
|
1065
|
+
self.CANCEL = False
|
|
1066
|
+
self.ERROR = False
|
|
1067
|
+
cart_type_id = 0
|
|
1068
|
+
save_type = None
|
|
1069
|
+
save_chip = None
|
|
1070
|
+
sram_unstable = None
|
|
1071
|
+
save_size = None
|
|
1072
|
+
checkBatterylessSRAM = False
|
|
1073
|
+
|
|
1074
|
+
# Header
|
|
1075
|
+
info = self.ReadInfo(checkRtc=True)
|
|
1076
|
+
if self.MODE == "DMG" and mbc is None:
|
|
1077
|
+
mbc = info["mapper_raw"]
|
|
1078
|
+
if mbc > 0x200: checkSaveType = False
|
|
1079
|
+
|
|
1080
|
+
# Disable Auto Power Off
|
|
1081
|
+
_apoe = False
|
|
1082
|
+
if self.CanPowerCycleCart():
|
|
1083
|
+
self.CartPowerCycle()
|
|
1084
|
+
if self.FW["fw_ver"] >= 12:
|
|
1085
|
+
_apoe = self._get_fw_variable("AUTO_POWEROFF_ENABLED") == 1
|
|
1086
|
+
if _apoe is True:
|
|
1087
|
+
_apot = self._get_fw_variable("AUTO_POWEROFF_TIME")
|
|
1088
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", 5000)
|
|
1089
|
+
|
|
1090
|
+
# Detect Flash Cart
|
|
1091
|
+
ret = self.DetectFlash(limitVoltage=limitVoltage)
|
|
1092
|
+
if ret is False: return False
|
|
1093
|
+
(cart_types, cart_type_id, flash_id, cfi_s, cfi, detected_size) = ret
|
|
1094
|
+
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
|
|
1095
|
+
cart_type = supported_carts[cart_type_id]
|
|
1096
|
+
|
|
1097
|
+
# Preparations
|
|
1098
|
+
if "command_set" in cart_type and cart_type["command_set"] in ("DMG-MBC5-32M-FLASH", "GBAMP"):
|
|
1099
|
+
checkSaveType = False
|
|
1100
|
+
elif self.MODE == "AGB" and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] == 1:
|
|
1101
|
+
save_size = 65536
|
|
1102
|
+
save_type = 7
|
|
1103
|
+
checkSaveType = False
|
|
1104
|
+
elif self.MODE == "AGB" and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 1:
|
|
1105
|
+
checkSaveType = False
|
|
1106
|
+
elif self.MODE == "DMG" and "mbc" in cart_type and cart_type["mbc"] == 0x105: # G-MMC1
|
|
1107
|
+
header = self.ReadROM(0, 0x180)
|
|
1108
|
+
data = RomFileDMG(header).GetHeader()
|
|
1109
|
+
_mbc = DMG_MBC().GetInstance(args={"mbc":cart_type["mbc"]}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
1110
|
+
temp = bytearray([0] * 0x100000)
|
|
1111
|
+
temp[0:0x180] = header
|
|
1112
|
+
_mbc.SelectBankROM(7)
|
|
1113
|
+
if data["game_title"] == "NP M-MENU MENU":
|
|
1114
|
+
gbmem_menudata = self.ReadROM(0x4000, 0x1000)
|
|
1115
|
+
temp[0x1C000:0x1C000+0x1000] = gbmem_menudata
|
|
1116
|
+
elif data["game_title"] == "DMG MULTI MENU ":
|
|
1117
|
+
gbmem_menudata = self.ReadROM(0x4000, 0x4000)
|
|
1118
|
+
temp[0x1C000:0x1C000+0x4000] = gbmem_menudata
|
|
1119
|
+
_mbc.SelectBankROM(0)
|
|
1120
|
+
info["gbmem"] = _mbc.ReadHiddenSector()
|
|
1121
|
+
info["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=info["gbmem"], buffer_rom=temp)
|
|
1122
|
+
|
|
1123
|
+
# Save Type and Size
|
|
1124
|
+
if checkSaveType:
|
|
1125
|
+
if self.MODE == "DMG":
|
|
1126
|
+
save_size = 131072
|
|
1127
|
+
save_type = 0x04
|
|
1128
|
+
if mbc == 0x20: # MBC6
|
|
1129
|
+
save_size = 1081344
|
|
1130
|
+
save_type = 0x104
|
|
1131
|
+
return (info, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, cfi, flash_id, detected_size)
|
|
1132
|
+
elif mbc == 0x22: # MBC7
|
|
1133
|
+
save_type = 0x102
|
|
1134
|
+
save_size = 512
|
|
1135
|
+
elif mbc == 0xFD: # TAMA5
|
|
1136
|
+
save_size = 32
|
|
1137
|
+
save_type = 0x103
|
|
1138
|
+
return (info, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, cfi, flash_id, detected_size)
|
|
1139
|
+
args = { 'mode':2, 'path':None, 'mbc':mbc, 'save_type':save_type, 'rtc':False, 'detect':True }
|
|
1140
|
+
elif self.MODE == "AGB":
|
|
1141
|
+
args = { 'mode':2, 'path':None, 'mbc':mbc, 'save_type':8, 'rtc':False, 'detect':True }
|
|
1142
|
+
|
|
1143
|
+
ret = self._BackupRestoreRAM(args=args)
|
|
1144
|
+
|
|
1145
|
+
if ret is not False and "data" in self.INFO:
|
|
1146
|
+
save_size = Util.find_size(self.INFO["data"], len(self.INFO["data"]))
|
|
1147
|
+
else:
|
|
1148
|
+
save_size = 0
|
|
1149
|
+
|
|
1150
|
+
if self.MODE == "DMG":
|
|
1151
|
+
try:
|
|
1152
|
+
save_type = Util.DMG_Header_RAM_Sizes_Map[Util.DMG_Header_RAM_Sizes_Flasher_Map.index(save_size)]
|
|
1153
|
+
except:
|
|
1154
|
+
save_size = 0
|
|
1155
|
+
save_type = 0
|
|
1156
|
+
|
|
1157
|
+
if save_size > 0x20:
|
|
1158
|
+
if mbc == 0x22: # MBC7
|
|
1159
|
+
if save_size == 256:
|
|
1160
|
+
save_type = 0x101
|
|
1161
|
+
elif save_size == 512:
|
|
1162
|
+
save_type = 0x102
|
|
1163
|
+
elif save_size > 0x10000: # MBC30+RTC?
|
|
1164
|
+
check = True
|
|
1165
|
+
for i in range(0x8000, 0x10000, 0x40):
|
|
1166
|
+
if self.INFO["data"][i:i+3] != bytearray([self.INFO["data"][i]] * 3):
|
|
1167
|
+
check = False
|
|
1168
|
+
break
|
|
1169
|
+
if check:
|
|
1170
|
+
save_size = 32768
|
|
1171
|
+
save_type = 0x03
|
|
1172
|
+
else:
|
|
1173
|
+
check = True
|
|
1174
|
+
for i in range(0x1A000, 0x20000, 0x40):
|
|
1175
|
+
if self.INFO["data"][i:i+3] != bytearray([self.INFO["data"][i]] * 3):
|
|
1176
|
+
check = False
|
|
1177
|
+
break
|
|
1178
|
+
if check:
|
|
1179
|
+
save_size = 65536
|
|
1180
|
+
save_type = 0x05
|
|
1181
|
+
|
|
1182
|
+
elif self.MODE == "AGB":
|
|
1183
|
+
if info["3d_memory"] is True:
|
|
1184
|
+
save_type = None
|
|
1185
|
+
save_size = 0
|
|
1186
|
+
else:
|
|
1187
|
+
# Check for FLASH
|
|
1188
|
+
ret = self.ReadFlashSaveID()
|
|
1189
|
+
if ret is not False:
|
|
1190
|
+
(flash_save_id, _) = ret
|
|
1191
|
+
try:
|
|
1192
|
+
if flash_save_id != 0 and flash_save_id in Util.AGB_Flash_Save_Chips:
|
|
1193
|
+
save_size = Util.AGB_Flash_Save_Chips_Sizes[list(Util.AGB_Flash_Save_Chips).index(flash_save_id)]
|
|
1194
|
+
save_chip = Util.AGB_Flash_Save_Chips[flash_save_id]
|
|
1195
|
+
if save_size == 131072:
|
|
1196
|
+
save_type = 5
|
|
1197
|
+
elif save_size == 65536:
|
|
1198
|
+
save_type = 4
|
|
1199
|
+
except:
|
|
1200
|
+
pass
|
|
1201
|
+
|
|
1202
|
+
if save_type is None:
|
|
1203
|
+
checkBatterylessSRAM = True
|
|
1204
|
+
if info["dacs_8m"] is True:
|
|
1205
|
+
save_size = 1048576
|
|
1206
|
+
save_type = 6
|
|
1207
|
+
elif save_size > 256: # SRAM
|
|
1208
|
+
if save_size == 131072:
|
|
1209
|
+
save_type = 8
|
|
1210
|
+
checkBatterylessSRAM = True
|
|
1211
|
+
elif save_size == 65536:
|
|
1212
|
+
save_type = 7
|
|
1213
|
+
checkBatterylessSRAM = True
|
|
1214
|
+
elif save_size == 32768:
|
|
1215
|
+
save_type = 3
|
|
1216
|
+
elif save_size in Util.AGB_Header_Save_Sizes:
|
|
1217
|
+
save_type = Util.AGB_Header_Save_Sizes.index(save_size)
|
|
1218
|
+
else:
|
|
1219
|
+
save_type = None
|
|
1220
|
+
save_size = 0
|
|
1221
|
+
else:
|
|
1222
|
+
dprint("Testing EEPROM")
|
|
1223
|
+
# Check for 4K EEPROM
|
|
1224
|
+
self._BackupRestoreRAM(args={ 'mode':2, 'path':None, 'mbc':mbc, 'save_type':1, 'rtc':False, 'detect':True })
|
|
1225
|
+
save_size = Util.find_size(self.INFO["data"], len(self.INFO["data"]))
|
|
1226
|
+
eeprom_4k = self.INFO["data"]
|
|
1227
|
+
# Check for 64K EEPROM
|
|
1228
|
+
self._BackupRestoreRAM(args={ 'mode':2, 'path':None, 'mbc':mbc, 'save_type':2, 'rtc':False, 'detect':True })
|
|
1229
|
+
save_size = Util.find_size(self.INFO["data"], len(self.INFO["data"]))
|
|
1230
|
+
eeprom_64k = self.INFO["data"]
|
|
1231
|
+
if eeprom_64k in (bytearray([0xFF] * len(eeprom_64k)), bytearray([0] * len(eeprom_64k))):
|
|
1232
|
+
save_type = None
|
|
1233
|
+
save_size = 0
|
|
1234
|
+
elif (eeprom_4k == eeprom_64k[:len(eeprom_4k)]):
|
|
1235
|
+
save_type = 2
|
|
1236
|
+
save_size = 8192
|
|
1237
|
+
checkBatterylessSRAM = False
|
|
1238
|
+
else:
|
|
1239
|
+
save_type = 1
|
|
1240
|
+
save_size = 512
|
|
1241
|
+
checkBatterylessSRAM = False
|
|
1242
|
+
|
|
1243
|
+
if checkBatterylessSRAM:
|
|
1244
|
+
batteryless = self.CheckBatterylessSRAM()
|
|
1245
|
+
if batteryless is not False:
|
|
1246
|
+
save_type = 9
|
|
1247
|
+
info["batteryless_sram"] = batteryless
|
|
1248
|
+
self.INFO["dump_info"]["batteryless_sram"] = batteryless
|
|
1249
|
+
|
|
1250
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
1251
|
+
self.INFO["last_action"] = 0
|
|
1252
|
+
self.INFO["action"] = None
|
|
1253
|
+
|
|
1254
|
+
if self.CanPowerCycleCart() and _apoe is True:
|
|
1255
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", _apot)
|
|
1256
|
+
|
|
1257
|
+
return (info, save_size, save_type, save_chip, sram_unstable, cart_types, cart_type_id, cfi_s, cfi, flash_id, detected_size)
|
|
1258
|
+
|
|
1259
|
+
def CheckBatterylessSRAM(self):
|
|
1260
|
+
bl_size = None
|
|
1261
|
+
bl_offset = None
|
|
1262
|
+
if self.MODE == "AGB":
|
|
1263
|
+
buffer = self.ReadROM(0, 0x180)
|
|
1264
|
+
header = RomFileAGB(buffer).GetHeader()
|
|
1265
|
+
if header["game_code"] in ("GMBC", "PNES"):
|
|
1266
|
+
bl_size = 0x10000
|
|
1267
|
+
state_id1 = 0x57a731d7
|
|
1268
|
+
state_id2 = 0x57a731d8
|
|
1269
|
+
state_id3 = 0x57a731d9
|
|
1270
|
+
if struct.unpack("<I", self.ReadROM(0x400000-0x40000, 4))[0] in (state_id1, state_id2, state_id3):
|
|
1271
|
+
bl_offset = 0x400000-0x40000
|
|
1272
|
+
elif struct.unpack("<I", self.ReadROM(0x800000-0x40000, 4))[0] in (state_id1, state_id2, state_id3):
|
|
1273
|
+
bl_offset = 0x800000-0x40000
|
|
1274
|
+
elif struct.unpack("<I", self.ReadROM(0x1000000-0x40000, 4))[0] in (state_id1, state_id2, state_id3):
|
|
1275
|
+
bl_offset = 0x1000000-0x40000
|
|
1276
|
+
elif struct.unpack("<I", self.ReadROM(0x2000000-0x40000, 4))[0] in (state_id1, state_id2, state_id3):
|
|
1277
|
+
bl_offset = 0x2000000-0x40000
|
|
1278
|
+
dprint("Detected Goomba Color or PocketNES Batteryless ROM by Lesserkuma")
|
|
1279
|
+
else:
|
|
1280
|
+
boot_vector = (struct.unpack("<I", buffer[0:3] + bytearray([0]))[0] + 2) << 2
|
|
1281
|
+
batteryless_loader = self.ReadROM(boot_vector, 0x2000)
|
|
1282
|
+
try:
|
|
1283
|
+
if bytearray(b'<3 from Maniac') in batteryless_loader:
|
|
1284
|
+
payload_size = struct.unpack("<H", batteryless_loader[batteryless_loader.index(bytearray(b'<3 from Maniac')):][0x0E:0x10])[0]
|
|
1285
|
+
if payload_size == 0:
|
|
1286
|
+
payload_size = 0x414
|
|
1287
|
+
bl_offset = batteryless_loader.index(bytearray(b'<3 from Maniac')) + boot_vector + 0x10
|
|
1288
|
+
payload = self.ReadROM(bl_offset - payload_size, payload_size)
|
|
1289
|
+
bl_size = struct.unpack("<I", payload[0x8:0xC])[0]
|
|
1290
|
+
dprint("Detected Batteryless SRAM ROM made with the Automatic batteryless saving patcher for GBA by metroid-maniac")
|
|
1291
|
+
if bl_size not in (0x2000, 0x8000, 0x10000, 0x20000):
|
|
1292
|
+
print("{:s}Warning: Unsupported Batteryless SRAM size value detected: 0x{:X}{:s}".format(ANSI.YELLOW, bl_size, ANSI.RESET))
|
|
1293
|
+
elif (bytearray([0x02, 0x13, 0xA0, 0xE3]) in batteryless_loader):
|
|
1294
|
+
if (
|
|
1295
|
+
bytearray([0x09, 0x04, 0xA0, 0xE3]) in batteryless_loader or
|
|
1296
|
+
bytearray([0x09, 0x14, 0xA0, 0xE3]) in batteryless_loader or
|
|
1297
|
+
bytearray([0x09, 0x24, 0xA0, 0xE3]) in batteryless_loader or
|
|
1298
|
+
bytearray([0x09, 0x34, 0xA0, 0xE3]) in batteryless_loader
|
|
1299
|
+
):
|
|
1300
|
+
bl_size = 0x20000
|
|
1301
|
+
else:
|
|
1302
|
+
bl_size = 0x10000
|
|
1303
|
+
base_addr = batteryless_loader.index(bytearray([0x02, 0x13, 0xA0, 0xE3]))
|
|
1304
|
+
addr_value = batteryless_loader[base_addr - 8]
|
|
1305
|
+
addr_rotate_right = batteryless_loader[base_addr - 7] * 2
|
|
1306
|
+
addr_shift = batteryless_loader[base_addr - 3] << 1
|
|
1307
|
+
address = (addr_value >> addr_rotate_right) | (addr_value << (32 - addr_rotate_right)) & 0xFFFFFFFF
|
|
1308
|
+
address = (address << addr_shift)
|
|
1309
|
+
if address < 32*1024*1024 and address > 0x1000:
|
|
1310
|
+
bl_offset = address
|
|
1311
|
+
dprint("Detected Chinese bootleg Batteryless SRAM ROM")
|
|
1312
|
+
else:
|
|
1313
|
+
dprint("Bad offset with Chinese bootleg Batteryless SRAM ROM:", hex(address))
|
|
1314
|
+
except Exception as e:
|
|
1315
|
+
dprint("An error occured while trying to determine the Batteryless SRAM method.\n", e)
|
|
1316
|
+
bl_offset = None
|
|
1317
|
+
bl_size = None
|
|
1318
|
+
if bl_offset is None or bl_size is None:
|
|
1319
|
+
dprint("No Batteryless SRAM routine detected")
|
|
1320
|
+
return False
|
|
1321
|
+
dprint("bl_offset=0x{:X}, bl_size=0x{:X}".format(bl_offset, bl_size))
|
|
1322
|
+
return {"bl_offset":bl_offset, "bl_size":bl_size}
|
|
1323
|
+
|
|
1324
|
+
def ReadFlashSaveID(self):
|
|
1325
|
+
# Check if actually SRAM/FRAM
|
|
1326
|
+
test1 = self._cart_read(0, 0x10, agb_save_flash=True)[4]
|
|
1327
|
+
self._cart_write_flash([[ 0x0004, test1 ^ 0xFF ]])
|
|
1328
|
+
test2 = self._cart_read(0, 0x10, agb_save_flash=True)[4]
|
|
1329
|
+
self._cart_write_flash([[ 0x0004, test1 ]])
|
|
1330
|
+
if test1 != test2:
|
|
1331
|
+
dprint(f"Seems to be SRAM/FRAM, not FLASH (value 0x{test1:02X} was changed to 0x{test2:02X} by SRAM access at address 0x4)", test1, test2)
|
|
1332
|
+
return False
|
|
1333
|
+
|
|
1334
|
+
# Read Chip ID
|
|
1335
|
+
temp5555 = self._cart_read(0x5555, agb_save_flash=True) >> 8
|
|
1336
|
+
temp2AAA = self._cart_read(0x2AAA, agb_save_flash=True) >> 8
|
|
1337
|
+
temp0000 = self._cart_read(0x0000, agb_save_flash=True) >> 8
|
|
1338
|
+
|
|
1339
|
+
cmds = [
|
|
1340
|
+
[ 0x5555, 0xAA ],
|
|
1341
|
+
[ 0x2AAA, 0x55 ],
|
|
1342
|
+
[ 0x5555, 0x90 ]
|
|
1343
|
+
]
|
|
1344
|
+
self._cart_write_flash(cmds)
|
|
1345
|
+
agb_flash_chip = struct.unpack(">H", self._cart_read(0, 2, agb_save_flash=True))[0]
|
|
1346
|
+
cmds = [
|
|
1347
|
+
[ 0x5555, 0xAA ],
|
|
1348
|
+
[ 0x2AAA, 0x55 ],
|
|
1349
|
+
[ 0x5555, 0xF0 ]
|
|
1350
|
+
]
|
|
1351
|
+
self._cart_write_flash(cmds)
|
|
1352
|
+
time.sleep(0.01)
|
|
1353
|
+
self._cart_write_flash([ [ 0, 0xF0 ] ])
|
|
1354
|
+
time.sleep(0.01)
|
|
1355
|
+
|
|
1356
|
+
if agb_flash_chip not in Util.AGB_Flash_Save_Chips:
|
|
1357
|
+
# Restore SRAM values
|
|
1358
|
+
cmds = [
|
|
1359
|
+
[ 0x5555, temp5555 ],
|
|
1360
|
+
[ 0x2AAA, temp2AAA ],
|
|
1361
|
+
[ 0x0000, temp0000 ]
|
|
1362
|
+
]
|
|
1363
|
+
self._cart_write_flash(cmds)
|
|
1364
|
+
agb_flash_chip_name = "Unknown flash chip ID (0x{:04X})".format(agb_flash_chip)
|
|
1365
|
+
else:
|
|
1366
|
+
agb_flash_chip_name = Util.AGB_Flash_Save_Chips[agb_flash_chip]
|
|
1367
|
+
|
|
1368
|
+
dprint(agb_flash_chip_name)
|
|
1369
|
+
return (agb_flash_chip, agb_flash_chip_name)
|
|
1370
|
+
|
|
1371
|
+
def ReadROM(self, address, length, skip_init=False, max_length=64):
|
|
1372
|
+
num = math.ceil(length / max_length)
|
|
1373
|
+
dprint("Reading 0x{:X} bytes from cartridge ROM at 0x{:X} in {:d} iteration(s)".format(length, address, num))
|
|
1374
|
+
if length > max_length: length = max_length
|
|
1375
|
+
|
|
1376
|
+
buffer = bytearray()
|
|
1377
|
+
if not skip_init:
|
|
1378
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1379
|
+
if self.MODE == "DMG":
|
|
1380
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1381
|
+
self._set_fw_variable("DMG_ACCESS_MODE", 1) # MODE_ROM_READ
|
|
1382
|
+
elif self.MODE == "AGB":
|
|
1383
|
+
self._set_fw_variable("ADDRESS", address >> 1)
|
|
1384
|
+
|
|
1385
|
+
if self.MODE == "DMG":
|
|
1386
|
+
command = "DMG_CART_READ"
|
|
1387
|
+
elif self.MODE == "AGB":
|
|
1388
|
+
command = "AGB_CART_READ"
|
|
1389
|
+
|
|
1390
|
+
for n in range(0, num):
|
|
1391
|
+
self._write(self.DEVICE_CMD[command])
|
|
1392
|
+
temp = self._read(length)
|
|
1393
|
+
if temp is not False and isinstance(temp, int): temp = bytearray([temp])
|
|
1394
|
+
if temp is False or len(temp) != length:
|
|
1395
|
+
dprint("Error while trying to read 0x{:X} bytes from cartridge ROM at 0x{:X} in iteration {:d} of {:d} (response: {:s})".format(length, address, n, num, str(temp)))
|
|
1396
|
+
return bytearray()
|
|
1397
|
+
buffer += temp
|
|
1398
|
+
if self.INFO["action"] in (self.ACTIONS["ROM_READ"], self.ACTIONS["SAVE_READ"], self.ACTIONS["ROM_WRITE_VERIFY"]) and not self.NO_PROG_UPDATE:
|
|
1399
|
+
self.SetProgress({"action":"READ", "bytes_added":len(temp)})
|
|
1400
|
+
|
|
1401
|
+
return buffer
|
|
1402
|
+
|
|
1403
|
+
def ReadROM_3DMemory(self, address, length, max_length=64):
|
|
1404
|
+
buffer_size = 0x1000
|
|
1405
|
+
num = math.ceil(length / max_length)
|
|
1406
|
+
dprint("Reading 0x{:X} bytes from cartridge ROM in {:d} iteration(s)".format(length, num))
|
|
1407
|
+
if length > max_length: length = max_length
|
|
1408
|
+
|
|
1409
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1410
|
+
self._set_fw_variable("BUFFER_SIZE", buffer_size)
|
|
1411
|
+
self._set_fw_variable("ADDRESS", address >> 1)
|
|
1412
|
+
|
|
1413
|
+
buffer = bytearray()
|
|
1414
|
+
error = False
|
|
1415
|
+
for _ in range(0, int(num / (buffer_size / length))): #32
|
|
1416
|
+
for _ in range(0, int(buffer_size / length)): # 0x1000/0x200=8
|
|
1417
|
+
self._write(self.DEVICE_CMD["AGB_CART_READ_3D_MEMORY"])
|
|
1418
|
+
temp = self._read(length)
|
|
1419
|
+
if isinstance(temp, int): temp = bytearray([temp])
|
|
1420
|
+
if temp is False or len(temp) != length:
|
|
1421
|
+
error = True
|
|
1422
|
+
buffer += temp
|
|
1423
|
+
if self.INFO["action"] == self.ACTIONS["ROM_READ"] and not self.NO_PROG_UPDATE:
|
|
1424
|
+
self.SetProgress({"action":"READ", "bytes_added":length})
|
|
1425
|
+
self._write(0)
|
|
1426
|
+
|
|
1427
|
+
if error: return bytearray()
|
|
1428
|
+
return buffer
|
|
1429
|
+
|
|
1430
|
+
def ReadROM_GBAMP(self, address, length, max_length=64):
|
|
1431
|
+
dprint("GBAMP ROM Read Mode", hex(address), hex(length), hex(max_length))
|
|
1432
|
+
addr = (address >> 13 << 16) | (address & 0x1FFF)
|
|
1433
|
+
dprint(f"0x{address:07X} → 0x{addr:07X}")
|
|
1434
|
+
return self.ReadROM(address=addr, length=length, max_length=max_length)
|
|
1435
|
+
|
|
1436
|
+
def ReadRAM(self, address, length, command=None, max_length=64):
|
|
1437
|
+
num = math.ceil(length / max_length)
|
|
1438
|
+
dprint("Reading 0x{:X} bytes from cartridge RAM in {:d} iteration(s)".format(length, num))
|
|
1439
|
+
if length > max_length: length = max_length
|
|
1440
|
+
buffer = bytearray()
|
|
1441
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1442
|
+
|
|
1443
|
+
if self.MODE == "DMG":
|
|
1444
|
+
self._set_fw_variable("ADDRESS", 0xA000 + address)
|
|
1445
|
+
self._set_fw_variable("DMG_ACCESS_MODE", 3) # MODE_RAM_READ
|
|
1446
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 1)
|
|
1447
|
+
if command is None: command = self.DEVICE_CMD["DMG_CART_READ"]
|
|
1448
|
+
elif self.MODE == "AGB":
|
|
1449
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1450
|
+
if command is None: command = self.DEVICE_CMD["AGB_CART_READ_SRAM"]
|
|
1451
|
+
|
|
1452
|
+
for _ in range(0, num):
|
|
1453
|
+
self._write(command)
|
|
1454
|
+
temp = self._read(length)
|
|
1455
|
+
if isinstance(temp, int): temp = bytearray([temp])
|
|
1456
|
+
if temp is False or len(temp) != length: return bytearray()
|
|
1457
|
+
buffer += temp
|
|
1458
|
+
if not self.NO_PROG_UPDATE:
|
|
1459
|
+
self.SetProgress({"action":"READ", "bytes_added":len(temp)})
|
|
1460
|
+
|
|
1461
|
+
if self.MODE == "DMG":
|
|
1462
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
1463
|
+
|
|
1464
|
+
return buffer
|
|
1465
|
+
|
|
1466
|
+
def ReadRAM_MBC7(self, address, length):
|
|
1467
|
+
max_length = 32
|
|
1468
|
+
num = math.ceil(length / max_length)
|
|
1469
|
+
dprint("Reading 0x{:X} bytes from cartridge EEPROM in {:d} iteration(s)".format(length, num))
|
|
1470
|
+
if length > max_length: length = max_length
|
|
1471
|
+
buffer = bytearray()
|
|
1472
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1473
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1474
|
+
for _ in range(0, num):
|
|
1475
|
+
self._write(self.DEVICE_CMD["DMG_MBC7_READ_EEPROM"])
|
|
1476
|
+
temp = self._read(length)
|
|
1477
|
+
if isinstance(temp, int): temp = bytearray([temp])
|
|
1478
|
+
buffer += temp
|
|
1479
|
+
if not self.NO_PROG_UPDATE:
|
|
1480
|
+
self.SetProgress({"action":"READ", "bytes_added":len(temp)})
|
|
1481
|
+
|
|
1482
|
+
return buffer
|
|
1483
|
+
|
|
1484
|
+
def ReadRAM_TAMA5(self):
|
|
1485
|
+
dprint("Reading 0x20 bytes from cartridge RAM")
|
|
1486
|
+
buffer = bytearray()
|
|
1487
|
+
npu = self.NO_PROG_UPDATE
|
|
1488
|
+
self.NO_PROG_UPDATE = True
|
|
1489
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 1)
|
|
1490
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
|
|
1491
|
+
|
|
1492
|
+
# Read save state
|
|
1493
|
+
for i in range(0, 0x20):
|
|
1494
|
+
self._cart_write(0xA001, 0x06, sram=True) # register select and address (high)
|
|
1495
|
+
self._cart_write(0xA000, i >> 4 | 0x01 << 1, sram=True) # bit 0 = higher ram address, rest = command
|
|
1496
|
+
self._cart_write(0xA001, 0x07, sram=True) # address (low)
|
|
1497
|
+
self._cart_write(0xA000, i & 0x0F, sram=True) # bits 0-3 = lower ram address
|
|
1498
|
+
self._cart_write(0xA001, 0x0D, sram=True) # data out (high)
|
|
1499
|
+
value1, value2 = None, None
|
|
1500
|
+
while value1 is None or value1 != value2:
|
|
1501
|
+
value2 = value1
|
|
1502
|
+
value1 = self._cart_read(0xA000)
|
|
1503
|
+
data_h = value1
|
|
1504
|
+
self._cart_write(0xA001, 0x0C, sram=True) # data out (low)
|
|
1505
|
+
|
|
1506
|
+
value1, value2 = None, None
|
|
1507
|
+
while value1 is None or value1 != value2:
|
|
1508
|
+
value2 = value1
|
|
1509
|
+
value1 = self._cart_read(0xA000)
|
|
1510
|
+
data_l = value1
|
|
1511
|
+
|
|
1512
|
+
data = ((data_h & 0xF) << 4) | (data_l & 0xF)
|
|
1513
|
+
buffer.append(data)
|
|
1514
|
+
self.SetProgress({"action":"UPDATE_POS", "abortable":False, "pos":i+1})
|
|
1515
|
+
|
|
1516
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
1517
|
+
|
|
1518
|
+
self.NO_PROG_UPDATE = npu
|
|
1519
|
+
return buffer
|
|
1520
|
+
|
|
1521
|
+
def WriteRAM(self, address, buffer, command=None, max_length=256):
|
|
1522
|
+
length = len(buffer)
|
|
1523
|
+
num = math.ceil(length / max_length)
|
|
1524
|
+
dprint("Writing 0x{:X} bytes to cartridge RAM in {:d} iteration(s)".format(length, num))
|
|
1525
|
+
if length > max_length: length = max_length
|
|
1526
|
+
|
|
1527
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1528
|
+
if self.MODE == "DMG":
|
|
1529
|
+
self._set_fw_variable("ADDRESS", 0xA000 + address)
|
|
1530
|
+
self._set_fw_variable("DMG_ACCESS_MODE", 4) # MODE_RAM_WRITE
|
|
1531
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
|
|
1532
|
+
if command is None: command = self.DEVICE_CMD["DMG_CART_WRITE_SRAM"]
|
|
1533
|
+
elif self.MODE == "AGB":
|
|
1534
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1535
|
+
if command is None: command = self.DEVICE_CMD["AGB_CART_WRITE_SRAM"]
|
|
1536
|
+
|
|
1537
|
+
for i in range(0, num):
|
|
1538
|
+
self._write(command)
|
|
1539
|
+
self._write(buffer[i*length:i*length+length], wait=True)
|
|
1540
|
+
#self._read(1)
|
|
1541
|
+
if self.INFO["action"] == self.ACTIONS["SAVE_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1542
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1543
|
+
|
|
1544
|
+
if self.MODE == "DMG":
|
|
1545
|
+
self._set_fw_variable("ADDRESS", 0)
|
|
1546
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
|
|
1547
|
+
|
|
1548
|
+
return True
|
|
1549
|
+
|
|
1550
|
+
def WriteFlash_MBC6(self, address, buffer, mapper):
|
|
1551
|
+
length = len(buffer)
|
|
1552
|
+
max_length = 128
|
|
1553
|
+
num = math.ceil(length / max_length)
|
|
1554
|
+
if length > max_length: length = max_length
|
|
1555
|
+
dprint("Write 0x{:X} bytes to cartridge FLASH in {:d} iteration(s)".format(length, num))
|
|
1556
|
+
|
|
1557
|
+
skip_write = False
|
|
1558
|
+
for i in range(0, num):
|
|
1559
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1560
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1561
|
+
dprint("Now in iteration {:d}".format(i))
|
|
1562
|
+
|
|
1563
|
+
if buffer[i*length:i*length+length] == bytearray([0xFF] * length):
|
|
1564
|
+
skip_write = True
|
|
1565
|
+
address += length
|
|
1566
|
+
continue
|
|
1567
|
+
|
|
1568
|
+
cmds = [
|
|
1569
|
+
[ 0x2000, 0x01 ],
|
|
1570
|
+
[ 0x3000, 0x02 ],
|
|
1571
|
+
[ 0x7555, 0xAA ],
|
|
1572
|
+
[ 0x4AAA, 0x55 ],
|
|
1573
|
+
[ 0x7555, 0xA0 ],
|
|
1574
|
+
]
|
|
1575
|
+
self._cart_write_flash(cmds)
|
|
1576
|
+
mapper.SelectBankFlash(mapper.GetROMBank())
|
|
1577
|
+
self._write(self.DEVICE_CMD["DMG_MBC6_MMSA_WRITE_FLASH"])
|
|
1578
|
+
self._write(buffer[i*length:i*length+length])
|
|
1579
|
+
ret = self._read(1)
|
|
1580
|
+
if ret not in (0x01, 0x03):
|
|
1581
|
+
dprint("Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length))
|
|
1582
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]: return False
|
|
1583
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)})
|
|
1584
|
+
self.CANCEL = True
|
|
1585
|
+
self.ERROR = True
|
|
1586
|
+
return False
|
|
1587
|
+
self._cart_write(address + length - 1, 0x00)
|
|
1588
|
+
hp = 100
|
|
1589
|
+
while hp > 0:
|
|
1590
|
+
sr = self._cart_read(address + length - 1)
|
|
1591
|
+
if sr == 0x80: break
|
|
1592
|
+
time.sleep(0.001)
|
|
1593
|
+
hp -= 1
|
|
1594
|
+
|
|
1595
|
+
if hp == 0:
|
|
1596
|
+
dprint("Flash write timeout (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length))
|
|
1597
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]: return False
|
|
1598
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Flash write timeout (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)})
|
|
1599
|
+
self.CANCEL = True
|
|
1600
|
+
self.ERROR = True
|
|
1601
|
+
return False
|
|
1602
|
+
|
|
1603
|
+
address += length
|
|
1604
|
+
if self.INFO["action"] == self.ACTIONS["SAVE_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1605
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1606
|
+
self._cart_write(address - 1, 0xF0)
|
|
1607
|
+
self.SKIPPING = skip_write
|
|
1608
|
+
|
|
1609
|
+
def WriteEEPROM_MBC7(self, address, buffer):
|
|
1610
|
+
length = len(buffer)
|
|
1611
|
+
max_length = 32
|
|
1612
|
+
num = math.ceil(length / max_length)
|
|
1613
|
+
if length > max_length: length = max_length
|
|
1614
|
+
dprint("Write 0x{:X} bytes to cartridge EEPROM in {:d} iteration(s)".format(length, num))
|
|
1615
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1616
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1617
|
+
for i in range(0, num):
|
|
1618
|
+
self._write(self.DEVICE_CMD["DMG_MBC7_WRITE_EEPROM"])
|
|
1619
|
+
self._write(buffer[i*length:i*length+length])
|
|
1620
|
+
response = self._read(1)
|
|
1621
|
+
dprint("Response:", response)
|
|
1622
|
+
if self.INFO["action"] == self.ACTIONS["SAVE_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1623
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1624
|
+
|
|
1625
|
+
def WriteRAM_TAMA5(self, buffer):
|
|
1626
|
+
npu = self.NO_PROG_UPDATE
|
|
1627
|
+
self.NO_PROG_UPDATE = True
|
|
1628
|
+
|
|
1629
|
+
for i in range(0, 0x20):
|
|
1630
|
+
self._cart_write(0xA001, 0x05, sram=True) # data in (high)
|
|
1631
|
+
self._cart_write(0xA000, buffer[i] >> 4, sram=True)
|
|
1632
|
+
self._cart_write(0xA001, 0x04, sram=True) # data in (low)
|
|
1633
|
+
self._cart_write(0xA000, buffer[i] & 0xF, sram=True)
|
|
1634
|
+
self._cart_write(0xA001, 0x06, sram=True) # register select and address (high)
|
|
1635
|
+
self._cart_write(0xA000, i >> 4 | 0x00 << 1, sram=True) # bit 0 = higher ram address, rest = command
|
|
1636
|
+
self._cart_write(0xA001, 0x07, sram=True) # address (low)
|
|
1637
|
+
self._cart_write(0xA000, i & 0x0F, sram=True) # bits 0-3 = lower ram address
|
|
1638
|
+
value1, value2 = None, None
|
|
1639
|
+
while value1 is None or value1 != value2:
|
|
1640
|
+
value2 = value1
|
|
1641
|
+
value1 = self._cart_read(0xA000)
|
|
1642
|
+
self.SetProgress({"action":"UPDATE_POS", "abortable":False, "pos":i+1})
|
|
1643
|
+
|
|
1644
|
+
self.NO_PROG_UPDATE = npu
|
|
1645
|
+
|
|
1646
|
+
def WriteROM(self, address, buffer, flash_buffer_size=False, skip_init=False, rumble_stop=False, max_length=MAX_BUFFER_WRITE):
|
|
1647
|
+
length = len(buffer)
|
|
1648
|
+
num = math.ceil(length / max_length)
|
|
1649
|
+
dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s)".format(length, num))
|
|
1650
|
+
if length == 0:
|
|
1651
|
+
dprint("Length is zero?")
|
|
1652
|
+
return False
|
|
1653
|
+
elif length > max_length:
|
|
1654
|
+
length = max_length
|
|
1655
|
+
|
|
1656
|
+
skip_write = False
|
|
1657
|
+
ret = 0
|
|
1658
|
+
num_of_chunks = math.ceil(flash_buffer_size / length)
|
|
1659
|
+
pos = 0
|
|
1660
|
+
|
|
1661
|
+
if not skip_init:
|
|
1662
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1663
|
+
if flash_buffer_size is not False:
|
|
1664
|
+
self._set_fw_variable("BUFFER_SIZE", flash_buffer_size)
|
|
1665
|
+
|
|
1666
|
+
for i in range(0, num):
|
|
1667
|
+
data = bytearray(buffer[i*length:i*length+length])
|
|
1668
|
+
if (num_of_chunks == 1 or flash_buffer_size == 0) and (data == bytearray([0xFF] * len(data))):
|
|
1669
|
+
skip_init = False
|
|
1670
|
+
skip_write = True
|
|
1671
|
+
else:
|
|
1672
|
+
if skip_write:
|
|
1673
|
+
skip_write = False
|
|
1674
|
+
|
|
1675
|
+
if not skip_write:
|
|
1676
|
+
if not skip_init:
|
|
1677
|
+
if self.MODE == "DMG":
|
|
1678
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1679
|
+
elif self.MODE == "AGB":
|
|
1680
|
+
self._set_fw_variable("ADDRESS", address >> 1)
|
|
1681
|
+
# print("==>", hex(self._get_fw_variable("ADDRESS")), hex(self._get_fw_variable("TRANSFER_SIZE")), hex(self._get_fw_variable("BUFFER_SIZE")))
|
|
1682
|
+
|
|
1683
|
+
skip_init = True
|
|
1684
|
+
|
|
1685
|
+
if ret != 0x03: self._write(self.DEVICE_CMD["FLASH_PROGRAM"])
|
|
1686
|
+
ret = self._write(data, wait=True)
|
|
1687
|
+
|
|
1688
|
+
if ret not in (0x01, 0x03):
|
|
1689
|
+
dprint("Flash error at 0x{:X} in iteration {:d} of {:d} while trying to write a total of 0x{:X} bytes (response = {:s})".format(address, i, num, len(buffer), str(ret)))
|
|
1690
|
+
self.ERROR_ARGS = { "iteration":i }
|
|
1691
|
+
self.SKIPPING = False
|
|
1692
|
+
return False
|
|
1693
|
+
pos += len(data)
|
|
1694
|
+
|
|
1695
|
+
if rumble_stop and flash_buffer_size > 0 and pos % flash_buffer_size == 0:
|
|
1696
|
+
self._cart_write(address=0xC4, value=0x00, flashcart=True)
|
|
1697
|
+
self._cart_write(address=0xC6, value=0x00, flashcart=True)
|
|
1698
|
+
rumble_stop = False
|
|
1699
|
+
|
|
1700
|
+
address += length
|
|
1701
|
+
if ((pos % length) * 10 == 0) and (self.INFO["action"] in (self.ACTIONS["ROM_WRITE"], self.ACTIONS["SAVE_WRITE"]) and not self.NO_PROG_UPDATE):
|
|
1702
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length, "skipping":skip_write})
|
|
1703
|
+
|
|
1704
|
+
self.SKIPPING = skip_write
|
|
1705
|
+
|
|
1706
|
+
def WriteROM_GBMEMORY(self, address, buffer, bank):
|
|
1707
|
+
length = len(buffer)
|
|
1708
|
+
max_length = 128
|
|
1709
|
+
num = math.ceil(length / max_length)
|
|
1710
|
+
if length > max_length: length = max_length
|
|
1711
|
+
dprint("Writing 0x{:X} bytes to GB-Memory Flash ROM in {:d} iteration(s)".format(length, num))
|
|
1712
|
+
|
|
1713
|
+
skip_write = False
|
|
1714
|
+
for i in range(0, num):
|
|
1715
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1716
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1717
|
+
dprint("Now in iteration {:d}".format(i))
|
|
1718
|
+
|
|
1719
|
+
if buffer[i*length:i*length+length] == bytearray([0xFF] * length):
|
|
1720
|
+
skip_write = True
|
|
1721
|
+
address += length
|
|
1722
|
+
continue
|
|
1723
|
+
|
|
1724
|
+
# Enable flash chip access
|
|
1725
|
+
self._cart_write_flash([
|
|
1726
|
+
[ 0x120, 0x09 ],
|
|
1727
|
+
[ 0x121, 0xAA ],
|
|
1728
|
+
[ 0x122, 0x55 ],
|
|
1729
|
+
[ 0x13F, 0xA5 ],
|
|
1730
|
+
])
|
|
1731
|
+
# Re-Enable writes to MBC registers
|
|
1732
|
+
self._cart_write_flash([
|
|
1733
|
+
[ 0x120, 0x11 ],
|
|
1734
|
+
[ 0x13F, 0xA5 ],
|
|
1735
|
+
])
|
|
1736
|
+
# Bank 1 for commands
|
|
1737
|
+
self._cart_write_flash([
|
|
1738
|
+
[ 0x2100, 0x01 ],
|
|
1739
|
+
])
|
|
1740
|
+
|
|
1741
|
+
# Write setup
|
|
1742
|
+
self._cart_write_flash([
|
|
1743
|
+
[ 0x120, 0x0F ],
|
|
1744
|
+
[ 0x125, 0x55 ],
|
|
1745
|
+
[ 0x126, 0x55 ],
|
|
1746
|
+
[ 0x127, 0xAA ],
|
|
1747
|
+
[ 0x13F, 0xA5 ],
|
|
1748
|
+
])
|
|
1749
|
+
self._cart_write_flash([
|
|
1750
|
+
[ 0x120, 0x0F ],
|
|
1751
|
+
[ 0x125, 0x2A ],
|
|
1752
|
+
[ 0x126, 0xAA ],
|
|
1753
|
+
[ 0x127, 0x55 ],
|
|
1754
|
+
[ 0x13F, 0xA5 ],
|
|
1755
|
+
])
|
|
1756
|
+
self._cart_write_flash([
|
|
1757
|
+
[ 0x120, 0x0F ],
|
|
1758
|
+
[ 0x125, 0x55 ],
|
|
1759
|
+
[ 0x126, 0x55 ],
|
|
1760
|
+
[ 0x127, 0xA0 ],
|
|
1761
|
+
[ 0x13F, 0xA5 ],
|
|
1762
|
+
])
|
|
1763
|
+
|
|
1764
|
+
# Set bank back
|
|
1765
|
+
self._cart_write_flash([
|
|
1766
|
+
[ 0x2100, bank ],
|
|
1767
|
+
])
|
|
1768
|
+
|
|
1769
|
+
# Disable writes to MBC registers
|
|
1770
|
+
self._cart_write_flash([
|
|
1771
|
+
[ 0x120, 0x10 ],
|
|
1772
|
+
[ 0x13F, 0xA5 ],
|
|
1773
|
+
])
|
|
1774
|
+
|
|
1775
|
+
# Undo Wakeup
|
|
1776
|
+
self._cart_write_flash([
|
|
1777
|
+
[ 0x120, 0x08 ],
|
|
1778
|
+
[ 0x13F, 0xA5 ],
|
|
1779
|
+
])
|
|
1780
|
+
|
|
1781
|
+
self._write(self.DEVICE_CMD["DMG_MBC6_MMSA_WRITE_FLASH"])
|
|
1782
|
+
self._write(buffer[i*length:i*length+length])
|
|
1783
|
+
ret = self._read(1)
|
|
1784
|
+
if ret not in (0x01, 0x03):
|
|
1785
|
+
#self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)})
|
|
1786
|
+
#self.CANCEL = True
|
|
1787
|
+
#self.ERROR = True
|
|
1788
|
+
return False
|
|
1789
|
+
|
|
1790
|
+
self._cart_write(address + length - 1, 0xFF)
|
|
1791
|
+
while True:
|
|
1792
|
+
sr = self._cart_read(address + length - 1)
|
|
1793
|
+
if sr & 0x80 == 0x80: break
|
|
1794
|
+
time.sleep(0.001)
|
|
1795
|
+
|
|
1796
|
+
address += length
|
|
1797
|
+
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1798
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1799
|
+
|
|
1800
|
+
self._cart_write(address - 1, 0xF0)
|
|
1801
|
+
self.SKIPPING = skip_write
|
|
1802
|
+
|
|
1803
|
+
def WriteROM_DMG_MBC5_32M_FLASH(self, address, buffer, bank):
|
|
1804
|
+
length = len(buffer)
|
|
1805
|
+
max_length = 128
|
|
1806
|
+
num = math.ceil(length / max_length)
|
|
1807
|
+
if length > max_length: length = max_length
|
|
1808
|
+
dprint("Writing 0x{:X} bytes to Flash ROM in {:d} iteration(s)".format(length, num))
|
|
1809
|
+
|
|
1810
|
+
skip_write = False
|
|
1811
|
+
for i in range(0, num):
|
|
1812
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1813
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1814
|
+
dprint("Now in iteration {:d}".format(i))
|
|
1815
|
+
|
|
1816
|
+
if buffer[i*length:i*length+length] == bytearray([0xFF] * length):
|
|
1817
|
+
skip_write = True
|
|
1818
|
+
address += length
|
|
1819
|
+
continue
|
|
1820
|
+
|
|
1821
|
+
self._cart_write(0x4000, 0xFF)
|
|
1822
|
+
self._cart_write_flash([
|
|
1823
|
+
[ address, 0xE0 ],
|
|
1824
|
+
[ address, length - 1 ],
|
|
1825
|
+
[ address, 0x00 ],
|
|
1826
|
+
])
|
|
1827
|
+
self._write(self.DEVICE_CMD["DMG_MBC6_MMSA_WRITE_FLASH"])
|
|
1828
|
+
self._write(buffer[i*length:i*length+length])
|
|
1829
|
+
ret = self._read(1)
|
|
1830
|
+
if ret not in (0x01, 0x03):
|
|
1831
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)})
|
|
1832
|
+
self.CANCEL = True
|
|
1833
|
+
self.ERROR = True
|
|
1834
|
+
return False
|
|
1835
|
+
|
|
1836
|
+
self._cart_write_flash([
|
|
1837
|
+
[ address, 0x0C ],
|
|
1838
|
+
[ address, length - 1 ],
|
|
1839
|
+
[ address, 0x00 ],
|
|
1840
|
+
])
|
|
1841
|
+
lives = 100
|
|
1842
|
+
while lives > 0:
|
|
1843
|
+
self._cart_write(0x4000, 0x70)
|
|
1844
|
+
try:
|
|
1845
|
+
sr = self._cart_read(address + length - 1)
|
|
1846
|
+
dprint("sr=0x{:X}".format(sr))
|
|
1847
|
+
except:
|
|
1848
|
+
sr = 0
|
|
1849
|
+
dprint("sr=Error")
|
|
1850
|
+
if sr & 0x80 == 0x80: break
|
|
1851
|
+
lives -= 1
|
|
1852
|
+
self._cart_write(0x4000, 0xFF)
|
|
1853
|
+
|
|
1854
|
+
if lives == 0:
|
|
1855
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Flash write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(sr), i, length)})
|
|
1856
|
+
self.CANCEL = True
|
|
1857
|
+
self.ERROR = True
|
|
1858
|
+
return False
|
|
1859
|
+
|
|
1860
|
+
address += length
|
|
1861
|
+
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1862
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1863
|
+
|
|
1864
|
+
self._cart_write(address - 1, 0xFF)
|
|
1865
|
+
self.SKIPPING = skip_write
|
|
1866
|
+
|
|
1867
|
+
def WriteROM_DMG_DatelOrbitV2(self, address, buffer, bank):
|
|
1868
|
+
length = len(buffer)
|
|
1869
|
+
dprint("Writing 0x{:X} bytes to Datel Orbit V2 cartridge".format(length))
|
|
1870
|
+
for i in range(0, length):
|
|
1871
|
+
self._cart_write(0x7FE1, 2)
|
|
1872
|
+
self._cart_write(0x5555, 0xAA)
|
|
1873
|
+
self._cart_write(0x2AAA, 0x55)
|
|
1874
|
+
self._cart_write(0x5555, 0xA0)
|
|
1875
|
+
self._cart_write(0x7FE1, bank)
|
|
1876
|
+
self._cart_write(address + i, buffer[i])
|
|
1877
|
+
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1878
|
+
self.SetProgress({"action":"WRITE", "bytes_added":1})
|
|
1879
|
+
return True
|
|
1880
|
+
|
|
1881
|
+
def WriteROM_DMG_EEPROM(self, address, buffer, bank, eeprom_buffer_size=0x80):
|
|
1882
|
+
length = len(buffer)
|
|
1883
|
+
if not self.CanPowerCycleCart() or self.BAUDRATE == 1000000:
|
|
1884
|
+
max_length = 256
|
|
1885
|
+
else:
|
|
1886
|
+
max_length = 1024
|
|
1887
|
+
num = math.ceil(length / max_length)
|
|
1888
|
+
if length > max_length: length = max_length
|
|
1889
|
+
dprint("Writing 0x{:X} bytes to EEPROM in {:d} iteration(s)".format(length, num))
|
|
1890
|
+
|
|
1891
|
+
for i in range(0, num):
|
|
1892
|
+
self._set_fw_variable("BUFFER_SIZE", eeprom_buffer_size)
|
|
1893
|
+
self._set_fw_variable("TRANSFER_SIZE", length)
|
|
1894
|
+
self._set_fw_variable("ADDRESS", address)
|
|
1895
|
+
dprint("Now in iteration {:d}".format(i))
|
|
1896
|
+
|
|
1897
|
+
self._write(self.DEVICE_CMD["DMG_EEPROM_WRITE"])
|
|
1898
|
+
self._write(buffer[i*length:i*length+length])
|
|
1899
|
+
ret = self._read(1)
|
|
1900
|
+
if ret not in (0x01, 0x03):
|
|
1901
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"EEPROM write error (response = {:s}) in iteration {:d} while trying to write 0x{:X} bytes".format(str(ret), i, length)})
|
|
1902
|
+
self.CANCEL = True
|
|
1903
|
+
self.ERROR = True
|
|
1904
|
+
return False
|
|
1905
|
+
|
|
1906
|
+
address += length
|
|
1907
|
+
if self.INFO["action"] == self.ACTIONS["ROM_WRITE"] and not self.NO_PROG_UPDATE:
|
|
1908
|
+
self.SetProgress({"action":"WRITE", "bytes_added":length})
|
|
1909
|
+
|
|
1910
|
+
commands = [
|
|
1911
|
+
[0x0006, 0x01],
|
|
1912
|
+
[0x5555, 0xAA],
|
|
1913
|
+
[0x2AAA, 0x55],
|
|
1914
|
+
[0x5555, 0xF0],
|
|
1915
|
+
[0x0006, bank]
|
|
1916
|
+
]
|
|
1917
|
+
for command in commands:
|
|
1918
|
+
self._cart_write(command[0], command[1])
|
|
1919
|
+
|
|
1920
|
+
def CheckROMStable(self):
|
|
1921
|
+
if not self.IsConnected(): raise ConnectionError("Couldn’t access the the device.")
|
|
1922
|
+
if self.CanPowerCycleCart():
|
|
1923
|
+
self.CartPowerOn()
|
|
1924
|
+
|
|
1925
|
+
buffer1 = self.ReadROM(0x80, 0x40)
|
|
1926
|
+
time.sleep(0.1)
|
|
1927
|
+
buffer2 = self.ReadROM(0x80, 0x40)
|
|
1928
|
+
return buffer1 == buffer2
|
|
1929
|
+
|
|
1930
|
+
def CompareCRC32(self, buffer, offset, length, address, flashcart=None, max_length=0x20000, reset=False):
|
|
1931
|
+
left = length
|
|
1932
|
+
chunk_pos = 0
|
|
1933
|
+
verified = False
|
|
1934
|
+
while left > 0:
|
|
1935
|
+
chunk_len = max_length if left > max_length else left
|
|
1936
|
+
chunk_from = offset + chunk_pos
|
|
1937
|
+
chunk_to = offset + chunk_pos + chunk_len
|
|
1938
|
+
dprint(f"Running CRC32 verification, comparing between source 0x{chunk_from:x}~0x{chunk_to:x} and target 0x{address+chunk_pos:x}~0x{address+chunk_pos+chunk_len:x}")
|
|
1939
|
+
crc32_expected = zlib.crc32(buffer[chunk_from:chunk_to])
|
|
1940
|
+
|
|
1941
|
+
for i in range(0, 2 if (reset is True and flashcart is not None) else 1): # for retrying with reset
|
|
1942
|
+
if self.MODE == "DMG":
|
|
1943
|
+
self._set_fw_variable("ADDRESS", address + chunk_pos)
|
|
1944
|
+
elif self.MODE == "AGB":
|
|
1945
|
+
self._set_fw_variable("ADDRESS", (address + chunk_pos) >> 1)
|
|
1946
|
+
self._write(self.DEVICE_CMD["CALC_CRC32"])
|
|
1947
|
+
self._write(bytearray(struct.pack(">I", chunk_len)))
|
|
1948
|
+
temp = self._read(4)
|
|
1949
|
+
if temp is False:
|
|
1950
|
+
crc32_calculated = False
|
|
1951
|
+
else:
|
|
1952
|
+
crc32_calculated = struct.unpack(">I", temp)[0]
|
|
1953
|
+
if crc32_expected != crc32_calculated:
|
|
1954
|
+
if i == 0 and flashcart is not None:
|
|
1955
|
+
flashcart.Reset(full_reset=True)
|
|
1956
|
+
verified = (crc32_expected, crc32_calculated)
|
|
1957
|
+
continue
|
|
1958
|
+
else:
|
|
1959
|
+
return (crc32_expected, crc32_calculated)
|
|
1960
|
+
else:
|
|
1961
|
+
verified = True
|
|
1962
|
+
break
|
|
1963
|
+
|
|
1964
|
+
left -= chunk_len
|
|
1965
|
+
chunk_pos += chunk_len
|
|
1966
|
+
|
|
1967
|
+
return verified
|
|
1968
|
+
|
|
1969
|
+
def DetectFlash(self, limitVoltage=False):
|
|
1970
|
+
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
|
|
1971
|
+
fc_fncptr = {
|
|
1972
|
+
"cart_write_fncptr":self._cart_write,
|
|
1973
|
+
"cart_write_fast_fncptr":self._cart_write_flash,
|
|
1974
|
+
"cart_read_fncptr":self.ReadROM,
|
|
1975
|
+
"cart_powercycle_fncptr":self.CartPowerCycleOrAskReconnect,
|
|
1976
|
+
"progress_fncptr":self.SetProgress,
|
|
1977
|
+
"set_we_pin_wr":self._set_we_pin_wr,
|
|
1978
|
+
"set_we_pin_audio":self._set_we_pin_audio,
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
detected_size = 0
|
|
1982
|
+
cfi = False
|
|
1983
|
+
cfi_buffer = bytearray()
|
|
1984
|
+
cfi_s = ""
|
|
1985
|
+
flash_type_id = 0
|
|
1986
|
+
flash_id_s = ""
|
|
1987
|
+
flash_types = []
|
|
1988
|
+
flash_id_methods = []
|
|
1989
|
+
|
|
1990
|
+
if self.MODE == "DMG":
|
|
1991
|
+
if limitVoltage:
|
|
1992
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
1993
|
+
else:
|
|
1994
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
1995
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
1996
|
+
|
|
1997
|
+
elif self.MODE == "AGB":
|
|
1998
|
+
read_method = self.AGB_READ_METHOD
|
|
1999
|
+
self.SetAGBReadMethod(0)
|
|
2000
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
2001
|
+
|
|
2002
|
+
rom = self._cart_read(0, 8)
|
|
2003
|
+
|
|
2004
|
+
dprint("Resetting and unlocking all cart types")
|
|
2005
|
+
cmds = []
|
|
2006
|
+
cmds_reset = []
|
|
2007
|
+
cmds_unlock_read = []
|
|
2008
|
+
for f in range(1, len(supported_carts)):
|
|
2009
|
+
cart_type = supported_carts[f]
|
|
2010
|
+
if "command_set" not in cart_type: continue
|
|
2011
|
+
if "manual_select" in cart_type and cart_type["manual_select"] is True: continue
|
|
2012
|
+
if self.MODE == "DMG" and cart_type["command_set"] == "BLAZE_XPLODER":
|
|
2013
|
+
self._cart_read(0x102, 1)
|
|
2014
|
+
self._cart_write(6, 1)
|
|
2015
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2016
|
+
self._cart_write(6, 0)
|
|
2017
|
+
rom1 = self._cart_read(0x4000, 8)
|
|
2018
|
+
self._cart_write(6, 1)
|
|
2019
|
+
self._cart_write_flash(cart_type["commands"]["read_identifier"])
|
|
2020
|
+
rom2 = self._cart_read(0x4000, 8)
|
|
2021
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2022
|
+
found = True
|
|
2023
|
+
flash_types.append(f)
|
|
2024
|
+
dprint("Found a BLAZE Xploder GB")
|
|
2025
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2026
|
+
break
|
|
2027
|
+
continue
|
|
2028
|
+
elif self.MODE == "DMG" and cart_type["command_set"] == "DATEL_ORBITV2":
|
|
2029
|
+
rom1 = self._cart_read(cart_type["read_identifier_at"], 10)
|
|
2030
|
+
for cmd in cart_type["commands"]["unlock_read"]: self._cart_read(cmd[0], 1)
|
|
2031
|
+
self._cart_write_flash(cart_type["commands"]["unlock"])
|
|
2032
|
+
self._cart_write_flash(cart_type["commands"]["read_identifier"])
|
|
2033
|
+
rom2 = self._cart_read(cart_type["read_identifier_at"], 10)
|
|
2034
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2035
|
+
found = True
|
|
2036
|
+
flash_types.append(f)
|
|
2037
|
+
dprint("Found a GameShark or Action Replay")
|
|
2038
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2039
|
+
break
|
|
2040
|
+
continue
|
|
2041
|
+
elif self.MODE == "DMG" and cart_type["command_set"] == "GBMEMORY":
|
|
2042
|
+
rom1 = self._cart_read(0, 8)
|
|
2043
|
+
self._cart_write_flash(cart_type["commands"]["unlock"])
|
|
2044
|
+
self._cart_write_flash(cart_type["commands"]["read_identifier"])
|
|
2045
|
+
rom2 = self._cart_read(0, 8)
|
|
2046
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2047
|
+
found = True
|
|
2048
|
+
flash_types.append(f)
|
|
2049
|
+
dprint("Found a GB-Memory Cartridge")
|
|
2050
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2051
|
+
break
|
|
2052
|
+
continue
|
|
2053
|
+
elif self.MODE == "DMG" and cart_type["command_set"] == "DMG-MBC5-32M-FLASH":
|
|
2054
|
+
self._set_we_pin_audio()
|
|
2055
|
+
self._cart_write_flash(cart_type["commands"]["unlock"], flashcart=False)
|
|
2056
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2057
|
+
rom1 = self._cart_read(0, 8)
|
|
2058
|
+
self._cart_write_flash(cart_type["commands"]["read_identifier"])
|
|
2059
|
+
rom2 = self._cart_read(0, 8)
|
|
2060
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2061
|
+
found = True
|
|
2062
|
+
flash_types.append(f)
|
|
2063
|
+
dprint("Found a DMG-MBC5-32M-FLASH Development Cartridge")
|
|
2064
|
+
self._cart_write_flash(cart_type["commands"]["reset"])
|
|
2065
|
+
break
|
|
2066
|
+
self._set_we_pin_wr()
|
|
2067
|
+
continue
|
|
2068
|
+
elif self.MODE == "AGB" and cart_type["command_set"] == "GBAMP":
|
|
2069
|
+
rom1 = self._cart_read(0x1E8F << 1, 2) + self._cart_read(0x168F << 1, 2)
|
|
2070
|
+
for cmd in cart_type["commands"]["unlock_read"]: self._cart_read(cmd[0] << 1)
|
|
2071
|
+
self._cart_write_flash(cart_type["commands"]["read_identifier"], flashcart=True)
|
|
2072
|
+
rom2 = self._cart_read(0x1E8F << 1, 2) + self._cart_read(0x168F << 1, 2)
|
|
2073
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2074
|
+
found = True
|
|
2075
|
+
flash_types.append(f)
|
|
2076
|
+
dprint("Found a GBA Movie Player v2")
|
|
2077
|
+
self._cart_write_flash(cart_type["commands"]["reset"], flashcart=True)
|
|
2078
|
+
break
|
|
2079
|
+
continue
|
|
2080
|
+
elif self.MODE == "DMG" and cart_type["command_set"] == "BUNG_16M":
|
|
2081
|
+
self._set_we_pin_audio()
|
|
2082
|
+
rom1 = self._cart_read(0, 4)
|
|
2083
|
+
self._cart_write(0x2000, 0x02, flashcart=False)
|
|
2084
|
+
self._cart_write(0x6AAA, 0xAA, flashcart=True)
|
|
2085
|
+
self._cart_write(0x2000, 0x01, flashcart=False)
|
|
2086
|
+
self._cart_write(0x5554, 0x55, flashcart=True)
|
|
2087
|
+
self._cart_write(0x2000, 0x02, flashcart=False)
|
|
2088
|
+
self._cart_write(0x6AAA, 0x90, flashcart=True)
|
|
2089
|
+
rom2 = self._cart_read(0, 4)
|
|
2090
|
+
if rom1 != rom2 and list(rom2[:len(cart_type["flash_ids"][0])]) == cart_type["flash_ids"][0]:
|
|
2091
|
+
found = True
|
|
2092
|
+
flash_types.append(f)
|
|
2093
|
+
dprint("Found a BUNG Doctor GB Card 16M")
|
|
2094
|
+
self._cart_write(0x2000, 0x02, flashcart=False)
|
|
2095
|
+
self._cart_write(0x6AAA, 0xAA, flashcart=True)
|
|
2096
|
+
self._cart_write(0x2000, 0x01, flashcart=False)
|
|
2097
|
+
self._cart_write(0x5554, 0x55, flashcart=True)
|
|
2098
|
+
self._cart_write(0x2000, 0x02, flashcart=False)
|
|
2099
|
+
self._cart_write(0x6AAA, 0xF0, flashcart=True)
|
|
2100
|
+
self._cart_write(0x2000, 0x00, flashcart=False)
|
|
2101
|
+
break
|
|
2102
|
+
self._set_we_pin_wr()
|
|
2103
|
+
continue
|
|
2104
|
+
|
|
2105
|
+
if "commands" in cart_type:
|
|
2106
|
+
c = {
|
|
2107
|
+
"reset":[],
|
|
2108
|
+
"read_identifier":[],
|
|
2109
|
+
"read_cfi":[],
|
|
2110
|
+
}
|
|
2111
|
+
if "reset" in cart_type["commands"]:
|
|
2112
|
+
c["reset"] = cart_type["commands"]["reset"]
|
|
2113
|
+
if cart_type["commands"]["reset"] not in cmds_reset:
|
|
2114
|
+
cmds_reset.append(cart_type["commands"]["reset"])
|
|
2115
|
+
if "unlock_read" in cart_type["commands"]:
|
|
2116
|
+
if cart_type["commands"]["unlock_read"] not in cmds_unlock_read:
|
|
2117
|
+
cmds_unlock_read.append(cart_type["commands"]["unlock_read"])
|
|
2118
|
+
if "unlock" in cart_type["commands"]:
|
|
2119
|
+
if cart_type["commands"]["unlock"] not in cmds_reset:
|
|
2120
|
+
cmds_reset.append(cart_type["commands"]["unlock"])
|
|
2121
|
+
if cart_type["commands"]["reset"] not in cmds_reset:
|
|
2122
|
+
cmds_reset.append(cart_type["commands"]["reset"])
|
|
2123
|
+
if "read_identifier" in cart_type["commands"]:
|
|
2124
|
+
c["read_identifier"] = cart_type["commands"]["read_identifier"]
|
|
2125
|
+
if "read_cfi" in cart_type["commands"]:
|
|
2126
|
+
c["read_cfi"] = cart_type["commands"]["read_cfi"]
|
|
2127
|
+
if len(c["read_identifier"]) > 0:
|
|
2128
|
+
if self.MODE == "DMG" and cart_type["commands"]["read_identifier"][0][0] > 0x7000: continue
|
|
2129
|
+
if c not in cmds:
|
|
2130
|
+
found = False
|
|
2131
|
+
for t in cmds:
|
|
2132
|
+
if c["read_identifier"] == t["read_identifier"]:
|
|
2133
|
+
found = True
|
|
2134
|
+
if not found:
|
|
2135
|
+
cmds.append(c)
|
|
2136
|
+
|
|
2137
|
+
for c in cmds_reset:
|
|
2138
|
+
for cmd in c:
|
|
2139
|
+
dprint(f"Resetting by writing to 0x{cmd[0]:X}=0x{cmd[1]:X}")
|
|
2140
|
+
self._cart_write(cmd[0], cmd[1], flashcart=True)
|
|
2141
|
+
|
|
2142
|
+
flash_id_cmds = sorted(cmds, key=lambda x: x['read_identifier'][0][0])
|
|
2143
|
+
|
|
2144
|
+
rom_s = " ".join(format(x, '02X') for x in rom)
|
|
2145
|
+
if self.MODE == "DMG":
|
|
2146
|
+
flash_id_s = "[ ROM ] " + rom_s + "\n"
|
|
2147
|
+
we_pins = [ "WR", "AUDIO" ]
|
|
2148
|
+
else:
|
|
2149
|
+
flash_id_s = "[ ROM ] " + rom_s + "\n"
|
|
2150
|
+
we_pins = [ None ]
|
|
2151
|
+
|
|
2152
|
+
if len(flash_types) == 0:
|
|
2153
|
+
dprint("Trying to find the Flash ID")
|
|
2154
|
+
if self.SupportsAudioAsWe():
|
|
2155
|
+
wes = {"DMG":[1, 2], "AGB":[0]}
|
|
2156
|
+
else:
|
|
2157
|
+
wes = {"DMG":[1], "AGB":[0]}
|
|
2158
|
+
#rom = self._cart_read(0, 8)
|
|
2159
|
+
for we in wes[self.MODE]:
|
|
2160
|
+
if self.MODE == "DMG": self._set_fw_variable("FLASH_WE_PIN", we)
|
|
2161
|
+
for i in range(0, len(flash_id_cmds)):
|
|
2162
|
+
self._cart_write_flash(flash_id_cmds[i]["reset"], flashcart=True)
|
|
2163
|
+
self._cart_write_flash(flash_id_cmds[i]["read_identifier"], flashcart=True)
|
|
2164
|
+
|
|
2165
|
+
cmp = self._cart_read(0, 8)
|
|
2166
|
+
self._cart_write_flash(flash_id_cmds[i]["reset"], flashcart=True)
|
|
2167
|
+
if rom != cmp: # ROM data changed
|
|
2168
|
+
if "read_cfi" not in flash_id_cmds[i]:
|
|
2169
|
+
flash_id_cmds[i]["read_cfi"] = [[ flash_id_cmds[i]["read_identifier"][0][0], 0x98 ]]
|
|
2170
|
+
self._cart_write_flash(flash_id_cmds[i]["read_cfi"], flashcart=True)
|
|
2171
|
+
cfi_buffer = self._cart_read(0, 0x400)
|
|
2172
|
+
self._cart_write_flash(flash_id_cmds[i]["reset"], flashcart=True)
|
|
2173
|
+
flash_id_methods.append([we - 1, i, list(cmp), cfi_buffer, flash_id_cmds[i]["read_identifier"]])
|
|
2174
|
+
|
|
2175
|
+
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
|
2176
|
+
with open("debug_cfi.bin", "wb") as f: f.write(cfi_buffer)
|
|
2177
|
+
magic = "{:s}{:s}{:s}".format(chr(cfi_buffer[0x20]), chr(cfi_buffer[0x22]), chr(cfi_buffer[0x24]))
|
|
2178
|
+
d_swap = (0, 0)
|
|
2179
|
+
if magic == "QRY": # D0D1 not swapped
|
|
2180
|
+
pass
|
|
2181
|
+
elif magic == "RQZ": # D0D1 swapped
|
|
2182
|
+
d_swap = [(0, 1)]
|
|
2183
|
+
for j2 in range(0, len(d_swap)):
|
|
2184
|
+
for j in range(0, len(cfi_buffer)):
|
|
2185
|
+
cfi_buffer[j] = bitswap(cfi_buffer[j], d_swap[j2])
|
|
2186
|
+
elif magic == "\x92\x91\x9A": # D0D1+D6D7 swapped
|
|
2187
|
+
d_swap = [( 0, 1 ), ( 6, 7 )]
|
|
2188
|
+
for j2 in range(0, len(d_swap)):
|
|
2189
|
+
for j in range(0, len(cfi_buffer)):
|
|
2190
|
+
cfi_buffer[j] = bitswap(cfi_buffer[j], d_swap[j2])
|
|
2191
|
+
with open("debug_cfi_2.bin", "wb") as f: f.write(cfi_buffer)
|
|
2192
|
+
else:
|
|
2193
|
+
cfi_buffer = None
|
|
2194
|
+
|
|
2195
|
+
if self.MODE == "DMG":
|
|
2196
|
+
flash_id_s += "[{:s}/{:4X}/{:2X}] {:s}\n".format(we_pins[we-1].ljust(5), flash_id_cmds[i]["read_identifier"][0][0], flash_id_cmds[i]["read_identifier"][0][1], ' '.join(format(x, '02X') for x in cmp))
|
|
2197
|
+
else:
|
|
2198
|
+
flash_id_s += "[{:6X}/{:4X}] {:s}\n".format(flash_id_cmds[i]["read_identifier"][0][0], flash_id_cmds[i]["read_identifier"][0][1], ' '.join(format(x, '02X') for x in cmp))
|
|
2199
|
+
|
|
2200
|
+
dprint(f"Found {len(flash_id_methods):d} result(s)")
|
|
2201
|
+
self._cart_write_flash([[0, 0xFF]], True)
|
|
2202
|
+
self._cart_write_flash([[0, 0xF0]], True)
|
|
2203
|
+
|
|
2204
|
+
for f in range(1, len(supported_carts)):
|
|
2205
|
+
cart_type = supported_carts[f]
|
|
2206
|
+
flashcart = Flashcart(config=cart_type, fncptr=fc_fncptr)
|
|
2207
|
+
if "flash_ids" not in cart_type or len(cart_type["flash_ids"]) == 0: continue
|
|
2208
|
+
if "commands" not in cart_type or len(cart_type["commands"]) == 0: continue
|
|
2209
|
+
found = False
|
|
2210
|
+
if flash_id_methods not in (None, []) and len(flash_id_methods) > 0:
|
|
2211
|
+
for we, type, flash_id, _, cmd_rfi in flash_id_methods:
|
|
2212
|
+
if (cmd_rfi != cart_type["commands"]["read_identifier"]): continue
|
|
2213
|
+
if self.MODE == "DMG" and "write_pin" in cart_type and cart_type["write_pin"] != we_pins[we]: continue
|
|
2214
|
+
fcm_flash_ids = list(map(list, {tuple(sublist) for sublist in cart_type["flash_ids"]}))
|
|
2215
|
+
for fcm_flash_id in fcm_flash_ids:
|
|
2216
|
+
if fcm_flash_id == flash_id[:len(fcm_flash_id)]:
|
|
2217
|
+
if self.MODE == "DMG":
|
|
2218
|
+
dprint("“{:s}” matches with Flash ID “{:s}” ({:s}/{:X}/{:X})".format(cart_type["names"][0], " ".join(format(x, '02X') for x in fcm_flash_id), we_pins[we], flash_id_cmds[type]["read_identifier"][0][0], flash_id_cmds[type]["read_identifier"][0][1]))
|
|
2219
|
+
elif self.MODE == "AGB":
|
|
2220
|
+
dprint("“{:s}” matches with Flash ID “{:s}” ({:X})/{:X}".format(cart_type["names"][0], " ".join(format(x, '02X') for x in fcm_flash_id), flash_id_cmds[type]["read_identifier"][0][0], flash_id_cmds[type]["read_identifier"][0][1]))
|
|
2221
|
+
found = True
|
|
2222
|
+
flash_type = f
|
|
2223
|
+
flash_types.append(flash_type)
|
|
2224
|
+
if "commands" in cart_type and "reset" in cart_type["commands"]:
|
|
2225
|
+
self._cart_write_flash(cart_type["commands"]["reset"], flashcart=True)
|
|
2226
|
+
break
|
|
2227
|
+
if found is True: break
|
|
2228
|
+
if found is False: continue
|
|
2229
|
+
|
|
2230
|
+
dprint("Compatible flash types:", [(index, supported_carts[index]["names"][0]) for index in flash_types])
|
|
2231
|
+
|
|
2232
|
+
if cfi_buffer is None or len(cfi_buffer) < 0x400:
|
|
2233
|
+
cfi = False
|
|
2234
|
+
else:
|
|
2235
|
+
cfi = ParseCFI(cfi_buffer)
|
|
2236
|
+
if cfi is not False:
|
|
2237
|
+
cfi["raw"] = cfi_buffer
|
|
2238
|
+
s = ""
|
|
2239
|
+
if d_swap is not None and d_swap != ( 0, 0 ): s += "Swapped pins: {:s}\n".format(str(d_swap).replace("[", "").replace("]", ""))
|
|
2240
|
+
s += "Device size: 0x{:07X} ({:.2f} MiB)\n".format(cfi["device_size"], cfi["device_size"] / 1024 / 1024)
|
|
2241
|
+
s += "Voltage: {:.1f}–{:.1f} V\n".format(cfi["vdd_min"], cfi["vdd_max"])
|
|
2242
|
+
s += "Single write: {:s}\n".format(str(cfi["single_write"]))
|
|
2243
|
+
if "buffer_size" in cfi:
|
|
2244
|
+
s += "Buffered write: {:s} ({:d} Bytes)\n".format(str(cfi["buffer_write"]), cfi["buffer_size"])
|
|
2245
|
+
else:
|
|
2246
|
+
s += "Buffered write: {:s}\n".format(str(cfi["buffer_write"]))
|
|
2247
|
+
if cfi["chip_erase"]: s += "Chip erase: {:d}–{:d} ms\n".format(cfi["chip_erase_time_avg"], cfi["chip_erase_time_max"])
|
|
2248
|
+
if cfi["sector_erase"]: s += "Sector erase: {:d}–{:d} ms\n".format(cfi["sector_erase_time_avg"], cfi["sector_erase_time_max"])
|
|
2249
|
+
if cfi["tb_boot_sector"] is not False: s += "Sector flags: {:s}\n".format(str(cfi["tb_boot_sector"]))
|
|
2250
|
+
pos = 0
|
|
2251
|
+
oversize = False
|
|
2252
|
+
s = s[:-1]
|
|
2253
|
+
for i in range(0, cfi['erase_sector_regions']):
|
|
2254
|
+
esb = cfi['erase_sector_blocks'][i]
|
|
2255
|
+
s += "\nRegion {:d}: 0x{:07X}–0x{:07X} @ 0x{:X} Bytes × {:d}".format(i+1, pos, pos+esb[2]-1, esb[0], esb[1])
|
|
2256
|
+
if oversize: s += " (alt)"
|
|
2257
|
+
pos += esb[2]
|
|
2258
|
+
if pos >= cfi['device_size']:
|
|
2259
|
+
pos = 0
|
|
2260
|
+
oversize = True
|
|
2261
|
+
cfi_s = s
|
|
2262
|
+
|
|
2263
|
+
# Check flash size
|
|
2264
|
+
if len(flash_types) > 0:
|
|
2265
|
+
flash_type_id = flash_types[0]
|
|
2266
|
+
flashcart = Flashcart(config=supported_carts[flash_type_id], fncptr=fc_fncptr)
|
|
2267
|
+
if self.MODE == "DMG":
|
|
2268
|
+
supp_flash_types = self.GetSupportedCartridgesDMG()
|
|
2269
|
+
elif self.MODE == "AGB":
|
|
2270
|
+
supp_flash_types = self.GetSupportedCartridgesAGB()
|
|
2271
|
+
|
|
2272
|
+
if "flash_size" in supp_flash_types[1][flash_types[0]]:
|
|
2273
|
+
size = supp_flash_types[1][flash_types[0]]["flash_size"]
|
|
2274
|
+
size_undetected = False
|
|
2275
|
+
for i in range(0, len(flash_types)):
|
|
2276
|
+
if "flash_size" in supp_flash_types[1][flash_types[i]]:
|
|
2277
|
+
if size != supp_flash_types[1][flash_types[i]]["flash_size"]:
|
|
2278
|
+
size_undetected = True
|
|
2279
|
+
|
|
2280
|
+
if size_undetected:
|
|
2281
|
+
if "flash_bank_select_type" in supp_flash_types[1][flash_types[0]] and supp_flash_types[1][flash_types[0]]["flash_bank_select_type"] == 1:
|
|
2282
|
+
# Check where the ROM data repeats (by bank switching)
|
|
2283
|
+
flashcart.SelectBankROM(0)
|
|
2284
|
+
size_check = self.ReadROM(0, 0x1000) + self.ReadROM(0x1FFF000, 0x1000)
|
|
2285
|
+
num_banks = 1
|
|
2286
|
+
while num_banks < (flashcart.GetFlashSize() // 0x2000000) + 1:
|
|
2287
|
+
dprint("Checking bank {:d}".format(num_banks))
|
|
2288
|
+
flashcart.SelectBankROM(num_banks)
|
|
2289
|
+
buffer = self.ReadROM(0, 0x1000) + self.ReadROM(0x1FFF000, 0x1000)
|
|
2290
|
+
if buffer == size_check: break
|
|
2291
|
+
num_banks <<= 1
|
|
2292
|
+
detected_size = 0x2000000 * num_banks
|
|
2293
|
+
for i in range(0, len(flash_types)):
|
|
2294
|
+
if detected_size == supp_flash_types[1][flash_types[i]]["flash_size"]:
|
|
2295
|
+
dprint("Detected {:d} flash banks".format(num_banks))
|
|
2296
|
+
flash_type_id = flash_types[i]
|
|
2297
|
+
size_undetected = False
|
|
2298
|
+
break
|
|
2299
|
+
flashcart.SelectBankROM(0)
|
|
2300
|
+
|
|
2301
|
+
elif isinstance(cfi, dict) and "device_size" in cfi:
|
|
2302
|
+
for i in range(0, len(flash_types)):
|
|
2303
|
+
if "flash_size" in supp_flash_types[1][flash_types[i]] and cfi['device_size'] == supp_flash_types[1][flash_types[i]]["flash_size"]:
|
|
2304
|
+
flash_type_id = flash_types[i]
|
|
2305
|
+
size_undetected = False
|
|
2306
|
+
break
|
|
2307
|
+
else:
|
|
2308
|
+
if self.MODE == "AGB":
|
|
2309
|
+
# Check where the ROM data repeats (for unlicensed carts)
|
|
2310
|
+
header = self.ReadROM(0, 0x180)
|
|
2311
|
+
size_check = header[0xA0:0xA0+16]
|
|
2312
|
+
currAddr = 0x10000
|
|
2313
|
+
while currAddr < 0x2000000:
|
|
2314
|
+
buffer = self.ReadROM(currAddr + 0xA0, 64)[:16]
|
|
2315
|
+
if buffer == size_check: break
|
|
2316
|
+
currAddr *= 2
|
|
2317
|
+
rom_size = currAddr
|
|
2318
|
+
|
|
2319
|
+
for i in range(0, len(flash_types)):
|
|
2320
|
+
if "flash_size" in supp_flash_types[1][flash_types[i]] and rom_size == supp_flash_types[1][flash_types[i]]["flash_size"]:
|
|
2321
|
+
flash_type_id = flash_types[i]
|
|
2322
|
+
size_undetected = False
|
|
2323
|
+
break
|
|
2324
|
+
|
|
2325
|
+
if self.MODE == "DMG":
|
|
2326
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
2327
|
+
self._set_fw_variable("FLASH_WE_PIN", 1) # back to WE=WR
|
|
2328
|
+
time.sleep(0.1)
|
|
2329
|
+
elif self.MODE == "AGB":
|
|
2330
|
+
self.SetAGBReadMethod(read_method)
|
|
2331
|
+
|
|
2332
|
+
return (flash_types, flash_type_id, flash_id_s, cfi_s, cfi, detected_size)
|
|
2333
|
+
|
|
2334
|
+
def GetDumpReport(self):
|
|
2335
|
+
return Util.GetDumpReport(self.INFO["dump_info"], self)
|
|
2336
|
+
|
|
2337
|
+
def GetReadErrors(self):
|
|
2338
|
+
return self.READ_ERRORS
|
|
2339
|
+
|
|
2340
|
+
#################################################################
|
|
2341
|
+
|
|
2342
|
+
def DoTransfer(self, mode, fncSetProgress, args):
|
|
2343
|
+
from . import DataTransfer
|
|
2344
|
+
args['mode'] = mode
|
|
2345
|
+
args['port'] = self
|
|
2346
|
+
if self.WORKER is None:
|
|
2347
|
+
self.WORKER = DataTransfer.DataTransfer(args)
|
|
2348
|
+
if fncSetProgress not in (False, None):
|
|
2349
|
+
self.WORKER.updateProgress.connect(fncSetProgress)
|
|
2350
|
+
else:
|
|
2351
|
+
self.WORKER.setConfig(args)
|
|
2352
|
+
self.WORKER.start()
|
|
2353
|
+
|
|
2354
|
+
def BackupROM(self, fncSetProgress=None, args=None):
|
|
2355
|
+
self.DoTransfer(1, fncSetProgress, args)
|
|
2356
|
+
|
|
2357
|
+
def BackupRAM(self, fncSetProgress=None, args=None):
|
|
2358
|
+
if fncSetProgress is False:
|
|
2359
|
+
args['mode'] = 2
|
|
2360
|
+
args['port'] = self
|
|
2361
|
+
self._BackupRestoreRAM(args=args)
|
|
2362
|
+
else:
|
|
2363
|
+
self.DoTransfer(2, fncSetProgress, args)
|
|
2364
|
+
|
|
2365
|
+
def RestoreRAM(self, fncSetProgress=None, args=None):
|
|
2366
|
+
self.DoTransfer(3, fncSetProgress, args)
|
|
2367
|
+
|
|
2368
|
+
def FlashROM(self, fncSetProgress=None, args=None):
|
|
2369
|
+
self.DoTransfer(4, fncSetProgress, args)
|
|
2370
|
+
|
|
2371
|
+
#################################################################
|
|
2372
|
+
|
|
2373
|
+
def _BackupROM(self, args):
|
|
2374
|
+
file = None
|
|
2375
|
+
if len(args["path"]) > 0:
|
|
2376
|
+
file = open(args["path"], "wb")
|
|
2377
|
+
|
|
2378
|
+
self.FAST_READ = True
|
|
2379
|
+
agb_read_method = self.AGB_READ_METHOD
|
|
2380
|
+
dmg_read_method = self.DMG_READ_METHOD
|
|
2381
|
+
flashcart = False
|
|
2382
|
+
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
|
|
2383
|
+
cart_type = copy.deepcopy(supported_carts[args["cart_type"]])
|
|
2384
|
+
if not isinstance(cart_type, str):
|
|
2385
|
+
cart_type["_index"] = 0
|
|
2386
|
+
for i in range(0, len(list(self.SUPPORTED_CARTS[self.MODE].keys()))):
|
|
2387
|
+
if i == args["cart_type"]:
|
|
2388
|
+
try:
|
|
2389
|
+
cart_type["_index"] = cart_type["names"].index(list(self.SUPPORTED_CARTS[self.MODE].keys())[i])
|
|
2390
|
+
|
|
2391
|
+
fc_fncptr = {
|
|
2392
|
+
"cart_write_fncptr":self._cart_write,
|
|
2393
|
+
"cart_write_fast_fncptr":self._cart_write_flash,
|
|
2394
|
+
"cart_read_fncptr":self.ReadROM,
|
|
2395
|
+
"cart_powercycle_fncptr":self.CartPowerCycleOrAskReconnect,
|
|
2396
|
+
"progress_fncptr":self.SetProgress,
|
|
2397
|
+
"set_we_pin_wr":self._set_we_pin_wr,
|
|
2398
|
+
"set_we_pin_audio":self._set_we_pin_audio,
|
|
2399
|
+
}
|
|
2400
|
+
flashcart = Flashcart(config=cart_type, fncptr=fc_fncptr)
|
|
2401
|
+
except:
|
|
2402
|
+
pass
|
|
2403
|
+
|
|
2404
|
+
# Firmware check L8
|
|
2405
|
+
if self.FW["fw_ver"] < 8 and flashcart and "enable_pullups" in cart_type:
|
|
2406
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
|
2407
|
+
return False
|
|
2408
|
+
# Firmware check L8
|
|
2409
|
+
|
|
2410
|
+
buffer_len = 0x4000
|
|
2411
|
+
|
|
2412
|
+
self.INFO["dump_info"]["timestamp"] = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()
|
|
2413
|
+
self.INFO["dump_info"]["file_name"] = args["path"]
|
|
2414
|
+
self.INFO["dump_info"]["file_size"] = args["rom_size"]
|
|
2415
|
+
self.INFO["dump_info"]["cart_type"] = args["cart_type"]
|
|
2416
|
+
self.INFO["dump_info"]["system"] = self.MODE
|
|
2417
|
+
|
|
2418
|
+
is_3dmemory = (self.MODE == "AGB" and "command_set" in cart_type and cart_type["command_set"] == "3DMEMORY")
|
|
2419
|
+
|
|
2420
|
+
if self.MODE == "DMG":
|
|
2421
|
+
self.INFO["dump_info"]["rom_size"] = args["rom_size"]
|
|
2422
|
+
self.INFO["dump_info"]["mapper_type"] = args["mbc"]
|
|
2423
|
+
self.INFO["dump_info"]["dmg_read_method"] = self.DMG_READ_METHODS[self.DMG_READ_METHOD]
|
|
2424
|
+
|
|
2425
|
+
self.INFO["mapper_raw"] = args["mbc"]
|
|
2426
|
+
if not self.IsSupportedMbc(args["mbc"]):
|
|
2427
|
+
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device.".format(Util.APPNAME, self.GetFullName())
|
|
2428
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
|
|
2429
|
+
return False
|
|
2430
|
+
|
|
2431
|
+
if "verify_mbc" in args and args["verify_mbc"] is not None:
|
|
2432
|
+
_mbc = args["verify_mbc"]
|
|
2433
|
+
else:
|
|
2434
|
+
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
2435
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
2436
|
+
|
|
2437
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
|
|
2438
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
2439
|
+
if _mbc.GetName() == "TAMA5":
|
|
2440
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
|
|
2441
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 1)
|
|
2442
|
+
_mbc.EnableMapper()
|
|
2443
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
2444
|
+
elif _mbc.GetName() == "Sachen":
|
|
2445
|
+
start_bank = int(args["rom_size"] / 0x4000)
|
|
2446
|
+
_mbc.SetStartBank(start_bank)
|
|
2447
|
+
else:
|
|
2448
|
+
_mbc.EnableMapper()
|
|
2449
|
+
|
|
2450
|
+
rom_size = args["rom_size"]
|
|
2451
|
+
rom_banks = _mbc.GetROMBanks(rom_size)
|
|
2452
|
+
rom_bank_size = _mbc.GetROMBankSize()
|
|
2453
|
+
size = _mbc.GetROMSize()
|
|
2454
|
+
|
|
2455
|
+
elif self.MODE == "AGB":
|
|
2456
|
+
self.INFO["dump_info"]["mapper_type"] = None
|
|
2457
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
2458
|
+
|
|
2459
|
+
buffer_len = 0x10000
|
|
2460
|
+
size = 32 * 1024 * 1024
|
|
2461
|
+
if "agb_rom_size" in args: size = args["agb_rom_size"]
|
|
2462
|
+
self.INFO["dump_info"]["rom_size"] = size
|
|
2463
|
+
if is_3dmemory:
|
|
2464
|
+
self.INFO["dump_info"]["agb_read_method"] = "3D Memory"
|
|
2465
|
+
|
|
2466
|
+
elif flashcart and "command_set" in cart_type and cart_type["command_set"] == "GBAMP":
|
|
2467
|
+
# if not self.CanPowerCycleCart():
|
|
2468
|
+
# self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is not compatible with your " + self.DEVICE_NAME + " hardware revision due to missing cartridge power cycling support.", "abortable":False})
|
|
2469
|
+
# return False
|
|
2470
|
+
self.INFO["dump_info"]["agb_read_method"] = "GBA Movie Player"
|
|
2471
|
+
if not "verify_write" in args: self.CartPowerCycleOrAskReconnect()
|
|
2472
|
+
flashcart.Unlock()
|
|
2473
|
+
buffer_len = 0x4000
|
|
2474
|
+
|
|
2475
|
+
# Initialize Vast Fame ROM (if it was not autodetected) and determine SRAM address/value bit reordering (necessary for emulation)
|
|
2476
|
+
elif flashcart and "command_set" in cart_type and cart_type["command_set"] == "VASTFAME":
|
|
2477
|
+
addr_reorder = [[-1 for i in range(16)] for j in range(4)]
|
|
2478
|
+
value_reorder = [[-1 for i in range(8)] for j in range(4)]
|
|
2479
|
+
for mode in range(0, 16):
|
|
2480
|
+
# Set SRAM mode
|
|
2481
|
+
self._cart_write(0xFFF8, 0x99, sram=True)
|
|
2482
|
+
self._cart_write(0xFFF9, 0x02, sram=True)
|
|
2483
|
+
self._cart_write(0xFFFA, 0x05, sram=True)
|
|
2484
|
+
self._cart_write(0xFFFB, 0x02, sram=True)
|
|
2485
|
+
self._cart_write(0xFFFC, 0x03, sram=True)
|
|
2486
|
+
|
|
2487
|
+
self._cart_write(0xFFFD, 0x00, sram=True)
|
|
2488
|
+
self._cart_write(0xFFFE, mode, sram=True)
|
|
2489
|
+
|
|
2490
|
+
self._cart_write(0xFFF8, 0x99, sram=True)
|
|
2491
|
+
self._cart_write(0xFFF9, 0x03, sram=True)
|
|
2492
|
+
self._cart_write(0xFFFA, 0x62, sram=True)
|
|
2493
|
+
self._cart_write(0xFFFB, 0x02, sram=True)
|
|
2494
|
+
self._cart_write(0xFFFC, 0x56, sram=True)
|
|
2495
|
+
|
|
2496
|
+
# Blank SRAM
|
|
2497
|
+
for i in range(0, 16):
|
|
2498
|
+
self._cart_write(1 << i, 0, sram=True)
|
|
2499
|
+
self._cart_write(0x8000, 0, sram=True)
|
|
2500
|
+
|
|
2501
|
+
# Get address reordering for SRAM writes (repeats every 4 modes so only check first 4)
|
|
2502
|
+
if mode < 4:
|
|
2503
|
+
for i in range(0, 16):
|
|
2504
|
+
self._cart_write(1 << i, 0xAA, sram=True)
|
|
2505
|
+
for j in range(0, 16):
|
|
2506
|
+
value = self._cart_read(1 << j, 1, True)[0]
|
|
2507
|
+
if value != 0:
|
|
2508
|
+
addr_reorder[mode][j] = i
|
|
2509
|
+
break
|
|
2510
|
+
self._cart_write(1 << i, 0, sram=True) # reblank SRAM
|
|
2511
|
+
addr_reorder[mode].reverse()
|
|
2512
|
+
|
|
2513
|
+
# Get value reordering for SRAM writes/reads (assumes upper bit of address is never reordered)
|
|
2514
|
+
if mode % 4 == 0:
|
|
2515
|
+
for i in range(0, 8):
|
|
2516
|
+
self._cart_write(0x8000, 1 << i, sram=True)
|
|
2517
|
+
value = self._cart_read(0x8000, 1, True)[0]
|
|
2518
|
+
for j in range(0, 8):
|
|
2519
|
+
if (1 << j) == value:
|
|
2520
|
+
value_reorder[mode // 4][j] = i
|
|
2521
|
+
break
|
|
2522
|
+
value_reorder[mode // 4].reverse()
|
|
2523
|
+
|
|
2524
|
+
self.INFO["dump_info"]["vf_addr_reorder"] = addr_reorder
|
|
2525
|
+
self.INFO["dump_info"]["vf_value_reorder"] = value_reorder
|
|
2526
|
+
|
|
2527
|
+
else:
|
|
2528
|
+
self.INFO["dump_info"]["agb_read_method"] = self.AGB_READ_METHODS[self.AGB_READ_METHOD]
|
|
2529
|
+
|
|
2530
|
+
if flashcart and "flash_bank_size" in cart_type:
|
|
2531
|
+
if "verify_write" in args:
|
|
2532
|
+
rom_banks = math.ceil(len(args["verify_write"]) / cart_type["flash_bank_size"])
|
|
2533
|
+
else:
|
|
2534
|
+
rom_banks = math.ceil(size / cart_type["flash_bank_size"])
|
|
2535
|
+
rom_bank_size = cart_type["flash_bank_size"]
|
|
2536
|
+
else:
|
|
2537
|
+
rom_banks = 1
|
|
2538
|
+
rom_bank_size = 0x2000000
|
|
2539
|
+
|
|
2540
|
+
#if "header" in self.INFO["dump_info"] and "dacs_8m" in self.INFO["dump_info"]["header"] and self.INFO["dump_info"]["header"]["dacs_8m"] is True:
|
|
2541
|
+
# self.SetAGBReadMethod(0)
|
|
2542
|
+
|
|
2543
|
+
if self.ERROR: return
|
|
2544
|
+
|
|
2545
|
+
if "verify_write" in args:
|
|
2546
|
+
size = len(args["verify_write"])
|
|
2547
|
+
buffer_len = min(buffer_len, size)
|
|
2548
|
+
else:
|
|
2549
|
+
if "bl_offset" in args:
|
|
2550
|
+
method = "SAVE_READ"
|
|
2551
|
+
else:
|
|
2552
|
+
method = "ROM_READ"
|
|
2553
|
+
pos = 0
|
|
2554
|
+
self.SetProgress({"action":"INITIALIZE", "method":method, "size":size})
|
|
2555
|
+
self.INFO["action"] = self.ACTIONS[method]
|
|
2556
|
+
if self.FW["fw_ver"] >= 8:
|
|
2557
|
+
if flashcart and "enable_pullups" in cart_type:
|
|
2558
|
+
self._write(self.DEVICE_CMD["ENABLE_PULLUPS"], wait=True)
|
|
2559
|
+
self.SetAGBReadMethod(0)
|
|
2560
|
+
dprint("Pullups enabled")
|
|
2561
|
+
if (self.FW["pcb_name"] == "GBFlash" and self.FW["pcb_ver"] < 13) or (self.FW["pcb_name"] == "Joey Jr"):
|
|
2562
|
+
print("{:s}Note: This cartridge may not be fully compatible with your {:s} device.{:s}".format(ANSI.YELLOW, self.FW["pcb_name"], ANSI.RESET))
|
|
2563
|
+
else:
|
|
2564
|
+
self._write(self.DEVICE_CMD["DISABLE_PULLUPS"], wait=True)
|
|
2565
|
+
dprint("Pullups disabled")
|
|
2566
|
+
if self.FW["fw_ver"] >= 12:
|
|
2567
|
+
if "enable_pullup_wr" in cart_type: # Joey Jr bug workaround
|
|
2568
|
+
self._set_fw_variable("PULLUPS_ENABLED", 2 if cart_type["enable_pullup_wr"] is True else 0)
|
|
2569
|
+
|
|
2570
|
+
buffer = bytearray(size)
|
|
2571
|
+
max_length = self.MAX_BUFFER_READ
|
|
2572
|
+
dprint("Max buffer size: 0x{:X}".format(max_length))
|
|
2573
|
+
if is_3dmemory:
|
|
2574
|
+
max_length = min(max_length, 0x1000)
|
|
2575
|
+
else:
|
|
2576
|
+
max_length = min(max_length, 0x2000)
|
|
2577
|
+
self.INFO["dump_info"]["transfer_size"] = max_length
|
|
2578
|
+
pos_total = 0
|
|
2579
|
+
start_address = 0
|
|
2580
|
+
end_address = size
|
|
2581
|
+
# dprint("ROM banks:", rom_banks)
|
|
2582
|
+
|
|
2583
|
+
start_bank = 0
|
|
2584
|
+
if "verify_write" in args:
|
|
2585
|
+
buffer_pos = args["verify_from"]
|
|
2586
|
+
start_address = buffer_pos
|
|
2587
|
+
end_address = args["verify_from"] + args["verify_len"]
|
|
2588
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
2589
|
+
end_bank = math.ceil((buffer_pos + args["verify_len"]) / rom_bank_size)
|
|
2590
|
+
rom_banks = end_bank
|
|
2591
|
+
elif "bl_offset" in args:
|
|
2592
|
+
buffer_pos = args["bl_offset"]
|
|
2593
|
+
start_address = buffer_pos
|
|
2594
|
+
end_address = args["bl_offset"] + args["bl_size"]
|
|
2595
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
2596
|
+
end_bank = math.ceil((buffer_pos + args["bl_size"]) / rom_bank_size)
|
|
2597
|
+
rom_banks = end_bank
|
|
2598
|
+
|
|
2599
|
+
dprint("start_address=0x{:X}, end_address=0x{:X}, start_bank=0x{:X}, rom_banks=0x{:X}, buffer_len=0x{:X}, max_length=0x{:X}".format(start_address, end_address, start_bank, rom_banks, buffer_len, max_length))
|
|
2600
|
+
bank = start_bank
|
|
2601
|
+
while bank < rom_banks:
|
|
2602
|
+
# ↓↓↓ Switch ROM bank
|
|
2603
|
+
if self.MODE == "DMG":
|
|
2604
|
+
if _mbc.ResetBeforeBankChange(bank) is True:
|
|
2605
|
+
dprint("Resetting the MBC")
|
|
2606
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
2607
|
+
(start_address, bank_size) = _mbc.SelectBankROM(bank)
|
|
2608
|
+
end_address = start_address + bank_size
|
|
2609
|
+
buffer_len = min(buffer_len, _mbc.GetROMBankSize())
|
|
2610
|
+
if "verify_write" in args:
|
|
2611
|
+
buffer_len = min(buffer_len, bank_size, len(args["verify_write"]))
|
|
2612
|
+
end_address = start_address + bank_size
|
|
2613
|
+
start_address += (buffer_pos % rom_bank_size)
|
|
2614
|
+
if end_address > start_address + args["verify_len"]:
|
|
2615
|
+
end_address = start_address + args["verify_len"]
|
|
2616
|
+
elif self.MODE == "AGB":
|
|
2617
|
+
if "verify_write" in args:
|
|
2618
|
+
buffer_len = min(buffer_len, len(args["verify_write"]))
|
|
2619
|
+
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
|
2620
|
+
flashcart.SelectBankROM(bank)
|
|
2621
|
+
temp = end_address - start_address
|
|
2622
|
+
start_address %= cart_type["flash_bank_size"]
|
|
2623
|
+
end_address = min(cart_type["flash_bank_size"], start_address + temp)
|
|
2624
|
+
# ↑↑↑ Switch ROM bank
|
|
2625
|
+
|
|
2626
|
+
skip_init = False
|
|
2627
|
+
pos = start_address
|
|
2628
|
+
lives = 20
|
|
2629
|
+
|
|
2630
|
+
while pos < end_address:
|
|
2631
|
+
temp = bytearray()
|
|
2632
|
+
if self.CANCEL:
|
|
2633
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
2634
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
2635
|
+
self.CANCEL_ARGS = {}
|
|
2636
|
+
self.ERROR_ARGS = {}
|
|
2637
|
+
self.SetProgress(cancel_args)
|
|
2638
|
+
try:
|
|
2639
|
+
if file is not None: file.close()
|
|
2640
|
+
except:
|
|
2641
|
+
pass
|
|
2642
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
2643
|
+
return
|
|
2644
|
+
|
|
2645
|
+
if is_3dmemory:
|
|
2646
|
+
temp = self.ReadROM_3DMemory(address=pos, length=buffer_len, max_length=max_length)
|
|
2647
|
+
elif flashcart and cart_type["command_set"] == "GBAMP":
|
|
2648
|
+
temp = self.ReadROM_GBAMP(address=pos, length=buffer_len, max_length=max_length)
|
|
2649
|
+
else:
|
|
2650
|
+
if self.FW["fw_ver"] >= 10 and "verify_write" in args:
|
|
2651
|
+
if self.MODE == "AGB": self.SetAGBReadMethod(0)
|
|
2652
|
+
if self.MODE == "DMG": self.SetDMGReadMethod(2)
|
|
2653
|
+
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=skip_init, max_length=max_length)
|
|
2654
|
+
if self.MODE == "AGB": self.SetAGBReadMethod(agb_read_method)
|
|
2655
|
+
if self.MODE == "DMG": self.SetDMGReadMethod(dmg_read_method)
|
|
2656
|
+
else:
|
|
2657
|
+
# Normal read
|
|
2658
|
+
temp = self.ReadROM(address=pos, length=buffer_len, skip_init=skip_init, max_length=max_length)
|
|
2659
|
+
skip_init = True
|
|
2660
|
+
|
|
2661
|
+
if len(temp) != buffer_len:
|
|
2662
|
+
pos_temp = pos_total
|
|
2663
|
+
if "verify_write" in args:
|
|
2664
|
+
pos_temp += args["verify_base_pos"]
|
|
2665
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total})
|
|
2666
|
+
else:
|
|
2667
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":pos_total})
|
|
2668
|
+
|
|
2669
|
+
err_text = "{:s}Note: Incomplete transfer detected. Resuming from 0x{:X}...{:s}".format(ANSI.YELLOW, pos_temp, ANSI.RESET)
|
|
2670
|
+
if (max_length >> 1) < 64:
|
|
2671
|
+
dprint("Failed to receive 0x{:X} bytes from the device at position 0x{:X}.".format(buffer_len, pos_temp))
|
|
2672
|
+
max_length = 64
|
|
2673
|
+
elif lives >= 20 and pos_temp != 0:
|
|
2674
|
+
dprint("Failed to receive 0x{:X} bytes from the device at position 0x{:X}.".format(buffer_len, pos_temp))
|
|
2675
|
+
else:
|
|
2676
|
+
dprint("Failed to receive 0x{:X} bytes from the device at position 0x{:X}. Decreasing maximum transfer buffer size to 0x{:X}.".format(buffer_len, pos_temp, max_length >> 1))
|
|
2677
|
+
max_length >>= 1
|
|
2678
|
+
self.MAX_BUFFER_READ = max_length
|
|
2679
|
+
err_text += "\nBuffer size adjusted to {:d} bytes.".format(max_length)
|
|
2680
|
+
if ".dev" in Util.VERSION_PEP440 and not Util.DEBUG: print(err_text)
|
|
2681
|
+
|
|
2682
|
+
self.INFO["dump_info"]["transfer_size"] = max_length
|
|
2683
|
+
skip_init = False
|
|
2684
|
+
self.DEVICE.reset_input_buffer()
|
|
2685
|
+
self.DEVICE.reset_output_buffer()
|
|
2686
|
+
lives -= 1
|
|
2687
|
+
if lives == 0:
|
|
2688
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"An error occured while reading from the cartridge. When connecting the device, avoid passive USB hubs and try different USB ports/cables."})
|
|
2689
|
+
self.CANCEL = True
|
|
2690
|
+
self.ERROR = True
|
|
2691
|
+
if "verify_write" in args: return False
|
|
2692
|
+
continue
|
|
2693
|
+
elif lives < 20:
|
|
2694
|
+
lives = 20
|
|
2695
|
+
|
|
2696
|
+
if file is not None: file.write(temp)
|
|
2697
|
+
buffer[pos_total:pos_total+len(temp)] = temp
|
|
2698
|
+
pos_total += len(temp)
|
|
2699
|
+
|
|
2700
|
+
if "verify_write" in args:
|
|
2701
|
+
#if pos_total >= len(args["verify_write"]): break
|
|
2702
|
+
check = args["verify_write"][pos_total-len(temp):pos_total]
|
|
2703
|
+
if temp[:len(check)] != check:
|
|
2704
|
+
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
|
2705
|
+
dprint("Writing 0x{:X} bytes to debug_verify_0x{:X}.bin".format(len(temp), pos_total-len(temp)))
|
|
2706
|
+
with open("debug_verify_0x{:X}.bin".format(pos_total-len(temp)), "ab") as f: f.write(temp)
|
|
2707
|
+
|
|
2708
|
+
for i in range(0, pos_total):
|
|
2709
|
+
if (i < len(args["verify_write"]) - 1) and (i < pos_total - 1) and args["verify_write"][i] != buffer[i]:
|
|
2710
|
+
if args["rtc_area"] is True and i in (0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9):
|
|
2711
|
+
dprint("Skipping RTC area at 0x{:X}".format(i))
|
|
2712
|
+
else:
|
|
2713
|
+
dprint("Mismatch during verification at 0x{:X}".format(i))
|
|
2714
|
+
return i
|
|
2715
|
+
else:
|
|
2716
|
+
dprint("Verification successful between 0x{:X} and 0x{:X}".format(pos_total-len(temp), pos_total))
|
|
2717
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":args["verify_from"]+pos_total})
|
|
2718
|
+
else:
|
|
2719
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":pos_total})
|
|
2720
|
+
|
|
2721
|
+
pos += buffer_len
|
|
2722
|
+
|
|
2723
|
+
bank += 1
|
|
2724
|
+
|
|
2725
|
+
if "verify_write" in args:
|
|
2726
|
+
return min(pos_total, len(args["verify_write"]))
|
|
2727
|
+
|
|
2728
|
+
if not "bl_offset" in args:
|
|
2729
|
+
# Hidden sector (GB-Memory)
|
|
2730
|
+
if self.MODE == "DMG" and len(args["path"]) > 0 and _mbc.HasHiddenSector():
|
|
2731
|
+
file = open(os.path.splitext(args["path"])[0] + ".map", "wb")
|
|
2732
|
+
temp = _mbc.ReadHiddenSector()
|
|
2733
|
+
self.INFO["hidden_sector"] = temp
|
|
2734
|
+
self.INFO["dump_info"]["gbmem"] = temp
|
|
2735
|
+
self.INFO["dump_info"]["gbmem_parsed"] = (GBMemoryMap()).ParseMapData(buffer_map=temp, buffer_rom=buffer)
|
|
2736
|
+
file.write(temp)
|
|
2737
|
+
file.close()
|
|
2738
|
+
gbmp = self.INFO["dump_info"]["gbmem_parsed"]
|
|
2739
|
+
if (isinstance(gbmp, list)) and len(args["path"]) > 2:
|
|
2740
|
+
for i in range(1, len(gbmp)):
|
|
2741
|
+
if gbmp[i]["header"] == {} or gbmp[i]["header"]["logo_correct"] is False: continue
|
|
2742
|
+
settings = None
|
|
2743
|
+
if "settings" in args: settings = args["settings"]
|
|
2744
|
+
gbmp_n = Util.GenerateFileName(mode="DMG", header=gbmp[i]["header"], settings=settings)
|
|
2745
|
+
gbmp_p = "{:s} - {:s}".format(os.path.splitext(args["path"])[0], gbmp_n)
|
|
2746
|
+
with open(gbmp_p, "wb") as f:
|
|
2747
|
+
f.write(buffer[gbmp[i]["rom_offset"]:gbmp[i]["rom_offset"]+gbmp[i]["rom_size"]])
|
|
2748
|
+
else:
|
|
2749
|
+
if "hidden_sector" in self.INFO: del(self.INFO["hidden_sector"])
|
|
2750
|
+
if "gbmem" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["gbmem"])
|
|
2751
|
+
if "gbmem_parsed" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["gbmem_parsed"])
|
|
2752
|
+
|
|
2753
|
+
# Calculate Global Checksum
|
|
2754
|
+
if self.MODE == "DMG":
|
|
2755
|
+
if _mbc.GetName() == "MMM01":
|
|
2756
|
+
self.INFO["dump_info"]["header"].update(RomFileDMG(buffer[-0x8000:-0x8000+0x180]).GetHeader(unchanged=True))
|
|
2757
|
+
elif _mbc.GetName() == "Sachen":
|
|
2758
|
+
pass
|
|
2759
|
+
#chk = _mbc.CalcChecksum(buffer)
|
|
2760
|
+
self.INFO["rom_checksum_calc"] = _mbc.CalcChecksum(buffer)
|
|
2761
|
+
elif self.MODE == "AGB":
|
|
2762
|
+
self.INFO["dump_info"]["header"].update(RomFileAGB(buffer[:0x180]).GetHeader())
|
|
2763
|
+
|
|
2764
|
+
temp_ver = "N/A"
|
|
2765
|
+
try:
|
|
2766
|
+
ids = [ b"SRAM_", b"EEPROM_V", b"FLASH_V", b"FLASH512_V", b"FLASH1M_V", b"AGB_8MDACS_DL_V" ]
|
|
2767
|
+
for id in ids:
|
|
2768
|
+
temp_pos = buffer.find(id)
|
|
2769
|
+
if temp_pos > 0:
|
|
2770
|
+
temp_ver = buffer[temp_pos:temp_pos+0x20]
|
|
2771
|
+
temp_ver = temp_ver[:temp_ver.index(0x00)].decode("ascii", "replace")
|
|
2772
|
+
break
|
|
2773
|
+
except ValueError:
|
|
2774
|
+
temp_ver = "N/A"
|
|
2775
|
+
self.INFO["dump_info"]["agb_savelib"] = temp_ver
|
|
2776
|
+
self.INFO["dump_info"]["agb_save_flash_id"] = None
|
|
2777
|
+
if "FLASH" in temp_ver:
|
|
2778
|
+
try:
|
|
2779
|
+
agb_save_flash_id = self.ReadFlashSaveID()
|
|
2780
|
+
if agb_save_flash_id is not False and len(agb_save_flash_id) == 2:
|
|
2781
|
+
self.INFO["dump_info"]["agb_save_flash_id"] = agb_save_flash_id
|
|
2782
|
+
except:
|
|
2783
|
+
print("Error querying the flash save chip.")
|
|
2784
|
+
self.DEVICE.reset_input_buffer()
|
|
2785
|
+
self.DEVICE.reset_output_buffer()
|
|
2786
|
+
|
|
2787
|
+
if "eeprom_data" in self.INFO["dump_info"]: del(self.INFO["dump_info"]["eeprom_data"])
|
|
2788
|
+
if "EEPROM" in temp_ver and len(buffer) == 0x2000000:
|
|
2789
|
+
padding_byte = buffer[0x1FFFEFF]
|
|
2790
|
+
dprint("Replacing unmapped ROM data of cartridge (32 MiB ROM + EEPROM save type) with the original padding byte of 0x{:02X}.".format(padding_byte))
|
|
2791
|
+
self.INFO["dump_info"]["eeprom_data"] = buffer[0x1FFFF00:0x1FFFF10]
|
|
2792
|
+
buffer[0x1FFFF00:0x2000000] = bytearray([padding_byte] * 0x100)
|
|
2793
|
+
file.seek(0x1FFFF00)
|
|
2794
|
+
file.write(buffer[0x1FFFF00:0x2000000])
|
|
2795
|
+
|
|
2796
|
+
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
|
|
2797
|
+
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest()
|
|
2798
|
+
self.INFO["file_sha256"] = hashlib.sha256(buffer).hexdigest()
|
|
2799
|
+
self.INFO["file_md5"] = hashlib.md5(buffer).hexdigest()
|
|
2800
|
+
self.INFO["dump_info"]["hash_crc32"] = self.INFO["file_crc32"]
|
|
2801
|
+
self.INFO["dump_info"]["hash_sha1"] = self.INFO["file_sha1"]
|
|
2802
|
+
self.INFO["dump_info"]["hash_sha256"] = self.INFO["file_sha256"]
|
|
2803
|
+
self.INFO["dump_info"]["hash_md5"] = self.INFO["file_md5"]
|
|
2804
|
+
|
|
2805
|
+
# Check for ROM loops
|
|
2806
|
+
self.INFO["loop_detected"] = False
|
|
2807
|
+
temp = min(0x2000000, len(buffer))
|
|
2808
|
+
while temp > 0x4000:
|
|
2809
|
+
temp = temp >> 1
|
|
2810
|
+
if (buffer[0:0x4000] == buffer[temp:temp+0x4000]):
|
|
2811
|
+
if buffer[0:temp] == buffer[temp:temp*2]:
|
|
2812
|
+
self.INFO["loop_detected"] = temp
|
|
2813
|
+
else:
|
|
2814
|
+
break
|
|
2815
|
+
|
|
2816
|
+
if file is not None: file.close()
|
|
2817
|
+
|
|
2818
|
+
# ↓↓↓ Switch to first ROM bank
|
|
2819
|
+
if self.MODE == "DMG":
|
|
2820
|
+
if _mbc.ResetBeforeBankChange(0) is True:
|
|
2821
|
+
dprint("Resetting the MBC")
|
|
2822
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
2823
|
+
_mbc.SelectBankROM(0)
|
|
2824
|
+
self.SetDMGReadMethod(dmg_read_method)
|
|
2825
|
+
elif self.MODE == "AGB":
|
|
2826
|
+
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
|
2827
|
+
flashcart.SelectBankROM(0)
|
|
2828
|
+
self.SetAGBReadMethod(agb_read_method)
|
|
2829
|
+
# ↑↑↑ Switch to first ROM bank
|
|
2830
|
+
|
|
2831
|
+
# Clean up
|
|
2832
|
+
self.INFO["last_action"] = self.INFO["action"]
|
|
2833
|
+
self.INFO["action"] = None
|
|
2834
|
+
self.INFO["last_path"] = args["path"]
|
|
2835
|
+
self.SetProgress({"action":"FINISHED"})
|
|
2836
|
+
return True
|
|
2837
|
+
|
|
2838
|
+
def WriteRTC(self, args):
|
|
2839
|
+
if self.CanPowerCycleCart():
|
|
2840
|
+
self.CartPowerOn()
|
|
2841
|
+
|
|
2842
|
+
if self.MODE == "DMG":
|
|
2843
|
+
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
2844
|
+
if not _mbc.HasRTC(): return False
|
|
2845
|
+
ret = _mbc.WriteRTCDict(args["rtc_dict"])
|
|
2846
|
+
elif self.MODE == "AGB":
|
|
2847
|
+
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
2848
|
+
if _agb_gpio.HasRTC() is not True: return False
|
|
2849
|
+
ret = _agb_gpio.WriteRTCDict(args["rtc_dict"])
|
|
2850
|
+
return ret
|
|
2851
|
+
|
|
2852
|
+
def _BackupRestoreRAM(self, args):
|
|
2853
|
+
self.FAST_READ = False
|
|
2854
|
+
if "rtc" not in args: args["rtc"] = False
|
|
2855
|
+
|
|
2856
|
+
# Prepare some stuff
|
|
2857
|
+
command = None
|
|
2858
|
+
empty_data_byte = 0x00
|
|
2859
|
+
extra_size = 0
|
|
2860
|
+
audio_low = False
|
|
2861
|
+
|
|
2862
|
+
cart_type = None
|
|
2863
|
+
if "cart_type" in args and args["cart_type"] >= 0:
|
|
2864
|
+
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
|
|
2865
|
+
cart_type = copy.deepcopy(supported_carts[args["cart_type"]])
|
|
2866
|
+
if self.FW["fw_ver"] >= 12:
|
|
2867
|
+
if "enable_pullup_wr" in cart_type: # Joey Jr bug workaround
|
|
2868
|
+
self._set_fw_variable("PULLUPS_ENABLED", 2 if cart_type["enable_pullup_wr"] is True else 0)
|
|
2869
|
+
|
|
2870
|
+
self._set_fw_variable("STATUS_REGISTER_MASK", 0x80)
|
|
2871
|
+
self._set_fw_variable("STATUS_REGISTER_VALUE", 0x80)
|
|
2872
|
+
|
|
2873
|
+
if self.MODE == "DMG":
|
|
2874
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True) # fixes Taobao FRAM cart save data
|
|
2875
|
+
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
2876
|
+
if not self.IsSupportedMbc(args["mbc"]):
|
|
2877
|
+
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device.".format(Util.APPNAME, self.GetFullName())
|
|
2878
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
|
|
2879
|
+
return False
|
|
2880
|
+
if "save_size" in args:
|
|
2881
|
+
save_size = args["save_size"]
|
|
2882
|
+
else:
|
|
2883
|
+
save_size = Util.DMG_Header_RAM_Sizes_Flasher_Map[Util.DMG_Header_RAM_Sizes_Map.index(args["save_type"])]
|
|
2884
|
+
ram_banks = _mbc.GetRAMBanks(save_size)
|
|
2885
|
+
buffer_len = min(0x200, _mbc.GetRAMBankSize())
|
|
2886
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
2887
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 0)
|
|
2888
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
2889
|
+
|
|
2890
|
+
# Enable mappers
|
|
2891
|
+
if _mbc.GetName() == "TAMA5":
|
|
2892
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1)
|
|
2893
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 1)
|
|
2894
|
+
_mbc.EnableMapper()
|
|
2895
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
2896
|
+
buffer_len = 0x20
|
|
2897
|
+
elif _mbc.GetName() == "MBC7":
|
|
2898
|
+
buffer_len = save_size
|
|
2899
|
+
elif _mbc.GetName() == "MBC6":
|
|
2900
|
+
empty_data_byte = 0xFF
|
|
2901
|
+
audio_low = True
|
|
2902
|
+
self._set_fw_variable("FLASH_METHOD", 0x04) # FLASH_METHOD_DMG_MBC6
|
|
2903
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x01) # WR
|
|
2904
|
+
_mbc.EnableFlash(enable=True, enable_write=True if (args["mode"] == 3) else False)
|
|
2905
|
+
elif _mbc.GetName() == "Xploder GB":
|
|
2906
|
+
empty_data_byte = 0xFF
|
|
2907
|
+
self._set_fw_variable("FLASH_PULSE_RESET", 0)
|
|
2908
|
+
self._set_fw_variable("FLASH_DOUBLE_DIE", 0)
|
|
2909
|
+
self._write(self.DEVICE_CMD["SET_FLASH_CMD"])
|
|
2910
|
+
self._write(0x00) # FLASH_COMMAND_SET_NONE
|
|
2911
|
+
self._write(0x01) # FLASH_METHOD_UNBUFFERED
|
|
2912
|
+
self._write(0x01) # FLASH_WE_PIN_WR
|
|
2913
|
+
commands = [
|
|
2914
|
+
[ 0x5555, 0xAA ],
|
|
2915
|
+
[ 0x2AAA, 0x55 ],
|
|
2916
|
+
[ 0x5555, 0xA0 ],
|
|
2917
|
+
]
|
|
2918
|
+
for i in range(0, 6):
|
|
2919
|
+
if i >= len(commands):
|
|
2920
|
+
self._write(bytearray(struct.pack(">I", 0)) + bytearray(struct.pack(">H", 0)))
|
|
2921
|
+
else:
|
|
2922
|
+
self._write(bytearray(struct.pack(">I", commands[i][0])) + bytearray(struct.pack(">H", commands[i][1])))
|
|
2923
|
+
if self.FW["fw_ver"] >= 12: self.wait_for_ack()
|
|
2924
|
+
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
|
|
2925
|
+
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
|
|
2926
|
+
self._write(1) # number of commands
|
|
2927
|
+
self._write(bytearray(struct.pack(">I", 0x0006))) # address/value
|
|
2928
|
+
self._write(0) # type = address
|
|
2929
|
+
ret = self._read(1)
|
|
2930
|
+
if ret != 0x01:
|
|
2931
|
+
print("Error in DMG_SET_BANK_CHANGE_CMD:", ret)
|
|
2932
|
+
else:
|
|
2933
|
+
_mbc.EnableMapper()
|
|
2934
|
+
|
|
2935
|
+
if args["rtc"] is True:
|
|
2936
|
+
extra_size = _mbc.GetRTCBufferSize()
|
|
2937
|
+
|
|
2938
|
+
# Check for DMG-MBC5-32M-FLASH
|
|
2939
|
+
self._cart_write(0x2000, 0x00)
|
|
2940
|
+
self._cart_write(0x4000, 0x90)
|
|
2941
|
+
flash_id = self._cart_read(0x4000, 2)
|
|
2942
|
+
if flash_id == bytearray([0xB0, 0x88]): audio_low = True
|
|
2943
|
+
self._cart_write(0x4000, 0xF0)
|
|
2944
|
+
self._cart_write(0x4000, 0xFF)
|
|
2945
|
+
self._cart_write(0x2000, 0x01)
|
|
2946
|
+
if audio_low:
|
|
2947
|
+
dprint("DMG-MBC5-32M-FLASH Development Cartridge detected")
|
|
2948
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x01)
|
|
2949
|
+
self.SetPin(["PIN_AUDIO"], False)
|
|
2950
|
+
self._cart_write(0x4000, 0x00)
|
|
2951
|
+
|
|
2952
|
+
_mbc.EnableRAM(enable=True)
|
|
2953
|
+
|
|
2954
|
+
elif self.MODE == "AGB":
|
|
2955
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
2956
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
2957
|
+
buffer_len = 0x2000
|
|
2958
|
+
if "save_size" in args:
|
|
2959
|
+
save_size = args["save_size"]
|
|
2960
|
+
else:
|
|
2961
|
+
save_size = Util.AGB_Header_Save_Sizes[args["save_type"]]
|
|
2962
|
+
ram_banks = math.ceil(save_size / 0x10000)
|
|
2963
|
+
agb_flash_chip = 0
|
|
2964
|
+
|
|
2965
|
+
if args["save_type"] in (1, 2): # EEPROM
|
|
2966
|
+
if args["mode"] == 3:
|
|
2967
|
+
buffer_len = 0x40
|
|
2968
|
+
else:
|
|
2969
|
+
buffer_len = 0x100
|
|
2970
|
+
elif args["save_type"] in (4, 5): # FLASH
|
|
2971
|
+
empty_data_byte = 0xFF
|
|
2972
|
+
ret = self.ReadFlashSaveID()
|
|
2973
|
+
if ret is False:
|
|
2974
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Couldn’t detect the save data flash chip type.", "abortable":False})
|
|
2975
|
+
return False
|
|
2976
|
+
buffer_len = 0x1000
|
|
2977
|
+
(agb_flash_chip, _) = ret
|
|
2978
|
+
if agb_flash_chip in (0xBF5B, 0xFFFF): # Bootlegs
|
|
2979
|
+
buffer_len = 0x800
|
|
2980
|
+
elif args["save_type"] == 6: # DACS
|
|
2981
|
+
# self._write(self.DEVICE_CMD["AGB_BOOTUP_SEQUENCE"], wait=self.FW["fw_ver"] >= 12)
|
|
2982
|
+
empty_data_byte = 0xFF
|
|
2983
|
+
# Read Chip ID
|
|
2984
|
+
ram_banks = 1
|
|
2985
|
+
self._cart_write(0, 0x90)
|
|
2986
|
+
flash_id = self._cart_read(0, 4)
|
|
2987
|
+
self._cart_write(0, 0x50)
|
|
2988
|
+
self._cart_write(0, 0xFF)
|
|
2989
|
+
if flash_id != bytearray([ 0xB0, 0x00, 0x9F, 0x00 ]):
|
|
2990
|
+
dprint("Warning: Unknown DACS flash chip ID ({:s})".format(' '.join(format(x, '02X') for x in flash_id)))
|
|
2991
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Couldn’t detect the DACS flash chip.\nUnknown Flash ID: {:s}".format(' '.join(format(x, '02X') for x in flash_id)), "abortable":False})
|
|
2992
|
+
return False
|
|
2993
|
+
buffer_len = 0x2000
|
|
2994
|
+
|
|
2995
|
+
# ↓↓↓ Load commands into firmware
|
|
2996
|
+
flash_cmds = [
|
|
2997
|
+
[ "PA", 0x70 ],
|
|
2998
|
+
[ "PA", 0x10 ],
|
|
2999
|
+
[ "PA", "PD" ]
|
|
3000
|
+
]
|
|
3001
|
+
self._set_fw_variable("FLASH_SHARP_VERIFY_SR", 1)
|
|
3002
|
+
self._write(self.DEVICE_CMD["SET_FLASH_CMD"])
|
|
3003
|
+
self._write(0x02) # FLASH_COMMAND_SET_INTEL
|
|
3004
|
+
self._write(0x01) # FLASH_METHOD_UNBUFFERED
|
|
3005
|
+
self._write(0x00) # unset
|
|
3006
|
+
for i in range(0, 6):
|
|
3007
|
+
if i > len(flash_cmds) - 1: # skip
|
|
3008
|
+
self._write(bytearray(struct.pack(">I", 0)) + bytearray(struct.pack(">H", 0)))
|
|
3009
|
+
else:
|
|
3010
|
+
address = flash_cmds[i][0]
|
|
3011
|
+
value = flash_cmds[i][1]
|
|
3012
|
+
if not isinstance(address, int): address = 0
|
|
3013
|
+
if not isinstance(value, int): value = 0
|
|
3014
|
+
address >>= 1
|
|
3015
|
+
dprint("Setting command #{:d} to 0x{:X}=0x{:X}".format(i, address, value))
|
|
3016
|
+
self._write(bytearray(struct.pack(">I", address)) + bytearray(struct.pack(">H", value)))
|
|
3017
|
+
if self.FW["fw_ver"] >= 12: self.wait_for_ack()
|
|
3018
|
+
# ↑↑↑ Load commands into firmware
|
|
3019
|
+
|
|
3020
|
+
# Bootleg mapper
|
|
3021
|
+
if cart_type is not None and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] == 1:
|
|
3022
|
+
sram_5 = struct.unpack("B", bytes(self._cart_read(address=5, length=1, agb_save_flash=True)))[0]
|
|
3023
|
+
self._cart_write(address=5, value=1, sram=True)
|
|
3024
|
+
|
|
3025
|
+
commands = [ # save type commands
|
|
3026
|
+
[ [None], [None] ], # No save
|
|
3027
|
+
[ bytearray([ self.DEVICE_CMD["AGB_CART_READ_EEPROM"], 1]), bytearray([ self.DEVICE_CMD["AGB_CART_WRITE_EEPROM"], 1]) ], # 4K EEPROM
|
|
3028
|
+
[ bytearray([ self.DEVICE_CMD["AGB_CART_READ_EEPROM"], 2]), bytearray([ self.DEVICE_CMD["AGB_CART_WRITE_EEPROM"], 2]) ], # 64K EEPROM
|
|
3029
|
+
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], self.DEVICE_CMD["AGB_CART_WRITE_SRAM"] ], # 256K SRAM
|
|
3030
|
+
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], bytearray([ self.DEVICE_CMD["AGB_CART_WRITE_FLASH_DATA"], 2 if agb_flash_chip == 0x1F3D else 1]) ], # 512K FLASH
|
|
3031
|
+
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], bytearray([ self.DEVICE_CMD["AGB_CART_WRITE_FLASH_DATA"], 1]) ], # 1M FLASH
|
|
3032
|
+
[ False, False ], # 8M DACS
|
|
3033
|
+
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], self.DEVICE_CMD["AGB_CART_WRITE_SRAM"] ], # 512K SRAM
|
|
3034
|
+
[ self.DEVICE_CMD["AGB_CART_READ_SRAM"], self.DEVICE_CMD["AGB_CART_WRITE_SRAM"] ], # 1M SRAM
|
|
3035
|
+
]
|
|
3036
|
+
command = commands[args["save_type"]][args["mode"] - 2]
|
|
3037
|
+
|
|
3038
|
+
if args["rtc"] is True:
|
|
3039
|
+
extra_size = 0x10
|
|
3040
|
+
|
|
3041
|
+
if args["mode"] == 2: # Backup
|
|
3042
|
+
action = "SAVE_READ"
|
|
3043
|
+
buffer = bytearray()
|
|
3044
|
+
elif args["mode"] == 3: # Restore
|
|
3045
|
+
action = "SAVE_WRITE"
|
|
3046
|
+
self.INFO["save_erase"] = args["erase"]
|
|
3047
|
+
if args["erase"] == True:
|
|
3048
|
+
buffer = bytearray([ empty_data_byte ] * save_size)
|
|
3049
|
+
if self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
|
|
3050
|
+
buffer[0] = 0x00
|
|
3051
|
+
else:
|
|
3052
|
+
if args["path"] is None:
|
|
3053
|
+
if "buffer" in args:
|
|
3054
|
+
buffer = args["buffer"]
|
|
3055
|
+
else:
|
|
3056
|
+
buffer = self.INFO["data"]
|
|
3057
|
+
else:
|
|
3058
|
+
with open(args["path"], "rb") as f:
|
|
3059
|
+
buffer = bytearray(f.read())
|
|
3060
|
+
|
|
3061
|
+
# Fill too small file
|
|
3062
|
+
if not (self.MODE == "AGB" and args["save_type"] == 6): # Not DACS
|
|
3063
|
+
if args["mode"] == 3:
|
|
3064
|
+
while len(buffer) < save_size:
|
|
3065
|
+
buffer += bytearray(buffer)
|
|
3066
|
+
|
|
3067
|
+
#if self.MODE == "AGB" and "ereader" in self.INFO and self.INFO["ereader"] is True: # e-Reader
|
|
3068
|
+
# buffer[0xFF80:0x10000] = bytearray([0] * 0x80)
|
|
3069
|
+
# buffer[0x1FF80:0x20000] = bytearray([0] * 0x80)
|
|
3070
|
+
|
|
3071
|
+
# Main loop
|
|
3072
|
+
if not (args["mode"] == 2 and "verify_write" in args and args["verify_write"]):
|
|
3073
|
+
self.INFO["action"] = self.ACTIONS[action]
|
|
3074
|
+
self.SetProgress({"action":"INITIALIZE", "method":action, "size":save_size+extra_size})
|
|
3075
|
+
|
|
3076
|
+
buffer_offset = 0
|
|
3077
|
+
for bank in range(0, ram_banks):
|
|
3078
|
+
if self.MODE == "DMG":
|
|
3079
|
+
if _mbc.GetName() == "MBC6" and bank > 7:
|
|
3080
|
+
self._set_fw_variable("DMG_ROM_BANK", bank - 8)
|
|
3081
|
+
(start_address, bank_size) = _mbc.SelectBankFlash(bank - 8)
|
|
3082
|
+
end_address = start_address + bank_size
|
|
3083
|
+
buffer_len = 0x2000
|
|
3084
|
+
if args["mode"] == 3: # Restore
|
|
3085
|
+
if ((buffer_offset - 0x8000) % 0x20000) == 0:
|
|
3086
|
+
dprint("Erasing flash sector at position 0x{:X}".format(buffer_offset))
|
|
3087
|
+
_mbc.EraseFlashSector()
|
|
3088
|
+
elif _mbc.GetName() == "Xploder GB":
|
|
3089
|
+
self._set_fw_variable("DMG_ROM_BANK", bank + 8)
|
|
3090
|
+
(start_address, bank_size) = _mbc.SelectBankRAM(bank)
|
|
3091
|
+
end_address = min(save_size, start_address + bank_size)
|
|
3092
|
+
else:
|
|
3093
|
+
self._set_fw_variable("DMG_WRITE_CS_PULSE", 1 if _mbc.WriteWithCSPulse() else 0)
|
|
3094
|
+
(start_address, bank_size) = _mbc.SelectBankRAM(bank)
|
|
3095
|
+
end_address = min(save_size, start_address + bank_size)
|
|
3096
|
+
elif self.MODE == "AGB":
|
|
3097
|
+
start_address = 0
|
|
3098
|
+
bank_size = 0x10000
|
|
3099
|
+
if args["save_type"] == 6: # DACS
|
|
3100
|
+
bank_size = min(save_size, 0x100000)
|
|
3101
|
+
buffer_len = 0x2000
|
|
3102
|
+
|
|
3103
|
+
end_address = min(save_size, bank_size)
|
|
3104
|
+
|
|
3105
|
+
if save_size > bank_size:
|
|
3106
|
+
if args["save_type"] == 8 or agb_flash_chip in (0xBF5B, 0xFFFF): # Bootleg 1M
|
|
3107
|
+
dprint("Switching to bootleg save bank {:d}".format(bank))
|
|
3108
|
+
self._cart_write(0x1000000, bank)
|
|
3109
|
+
elif args["save_type"] == 5: # FLASH 1M
|
|
3110
|
+
dprint("Switching to FLASH bank {:d}".format(bank))
|
|
3111
|
+
cmds = [
|
|
3112
|
+
[ 0x5555, 0xAA ],
|
|
3113
|
+
[ 0x2AAA, 0x55 ],
|
|
3114
|
+
[ 0x5555, 0xB0 ],
|
|
3115
|
+
[ 0, bank ]
|
|
3116
|
+
]
|
|
3117
|
+
self._cart_write_flash(cmds)
|
|
3118
|
+
else:
|
|
3119
|
+
dprint("Unknown bank switching method")
|
|
3120
|
+
time.sleep(0.05)
|
|
3121
|
+
|
|
3122
|
+
# if "detect" in args and args["detect"] is True:
|
|
3123
|
+
# start_address = end_address - 64
|
|
3124
|
+
# buffer_len = 64
|
|
3125
|
+
|
|
3126
|
+
max_length = 64
|
|
3127
|
+
dprint("start_address=0x{:X}, end_address=0x{:X}, buffer_len=0x{:X}, buffer_offset=0x{:X}".format(start_address, end_address, buffer_len, buffer_offset))
|
|
3128
|
+
pos = start_address
|
|
3129
|
+
while pos < end_address:
|
|
3130
|
+
if self.CANCEL:
|
|
3131
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
3132
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
3133
|
+
self.CANCEL_ARGS = {}
|
|
3134
|
+
self.ERROR_ARGS = {}
|
|
3135
|
+
self.SetProgress(cancel_args)
|
|
3136
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
3137
|
+
return
|
|
3138
|
+
|
|
3139
|
+
if args["mode"] == 2: # Backup
|
|
3140
|
+
in_temp = [None] * 2
|
|
3141
|
+
if "verify_read" in args and args["verify_read"]: # Read twice for detecting instabilities
|
|
3142
|
+
xe = 2
|
|
3143
|
+
else:
|
|
3144
|
+
xe = 1
|
|
3145
|
+
for x in range(0, xe):
|
|
3146
|
+
if x == 1:
|
|
3147
|
+
self.NO_PROG_UPDATE = True
|
|
3148
|
+
else:
|
|
3149
|
+
self.NO_PROG_UPDATE = False
|
|
3150
|
+
|
|
3151
|
+
if self.MODE == "DMG" and _mbc.GetName() == "MBC7":
|
|
3152
|
+
in_temp[x] = self.ReadRAM_MBC7(address=pos, length=buffer_len)
|
|
3153
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "MBC6" and bank > 7: # MBC6 flash save memory
|
|
3154
|
+
in_temp[x] = self.ReadROM(address=pos, length=buffer_len, skip_init=False, max_length=max_length)
|
|
3155
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5":
|
|
3156
|
+
in_temp[x] = self.ReadRAM_TAMA5()
|
|
3157
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
|
|
3158
|
+
in_temp[x] = self.ReadROM(address=0x20000+pos, length=buffer_len, skip_init=False, max_length=max_length)
|
|
3159
|
+
elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM
|
|
3160
|
+
in_temp[x] = self.ReadRAM(address=int(pos/8), length=buffer_len, command=command, max_length=max_length)
|
|
3161
|
+
elif self.MODE == "AGB" and args["save_type"] == 6: # DACS
|
|
3162
|
+
in_temp[x] = self.ReadROM(address=0x1F00000+pos, length=buffer_len, skip_init=False, max_length=max_length)
|
|
3163
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "MBC2":
|
|
3164
|
+
in_temp[x] = self.ReadRAM(address=pos, length=buffer_len, command=command, max_length=max_length)
|
|
3165
|
+
for i in range(0, len(in_temp[x])):
|
|
3166
|
+
in_temp[x][i] = in_temp[x][i] & 0x0F
|
|
3167
|
+
else:
|
|
3168
|
+
in_temp[x] = self.ReadRAM(address=pos, length=buffer_len, command=command, max_length=max_length)
|
|
3169
|
+
|
|
3170
|
+
if len(in_temp[x]) != buffer_len:
|
|
3171
|
+
if (max_length >> 1) < 64:
|
|
3172
|
+
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}!".format(len(in_temp[x]), buffer_len, len(buffer)))
|
|
3173
|
+
max_length = 64
|
|
3174
|
+
else:
|
|
3175
|
+
dprint("Received 0x{:X} bytes instead of 0x{:X} bytes from the device at position 0x{:X}! Decreasing maximum transfer buffer length to 0x{:X}.".format(len(in_temp[x]), buffer_len, len(buffer), max_length >> 1))
|
|
3176
|
+
max_length >>= 1
|
|
3177
|
+
self.DEVICE.reset_input_buffer()
|
|
3178
|
+
self.DEVICE.reset_output_buffer()
|
|
3179
|
+
continue
|
|
3180
|
+
|
|
3181
|
+
if xe == 2 and in_temp[0] != in_temp[1]:
|
|
3182
|
+
# print(in_temp[0], in_temp[1], sep="\n")
|
|
3183
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Failed to read save data consistently. Please ensure that the cartridge contacts are clean.", "abortable":False})
|
|
3184
|
+
return False
|
|
3185
|
+
|
|
3186
|
+
temp = in_temp[0]
|
|
3187
|
+
buffer += temp
|
|
3188
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":len(buffer)})
|
|
3189
|
+
|
|
3190
|
+
elif args["mode"] == 3: # Restore
|
|
3191
|
+
if self.MODE == "DMG" and _mbc.GetName() == "MBC7":
|
|
3192
|
+
self.WriteEEPROM_MBC7(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
|
3193
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "MBC6" and bank > 7: # MBC6 flash save memory
|
|
3194
|
+
if self.FW["fw_ver"] > 1:
|
|
3195
|
+
self.WriteROM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
|
3196
|
+
self._cart_write(pos + buffer_len - 1, 0xF0)
|
|
3197
|
+
else:
|
|
3198
|
+
self.WriteFlash_MBC6(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], mapper=_mbc)
|
|
3199
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "TAMA5":
|
|
3200
|
+
self.WriteRAM_TAMA5(buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
|
3201
|
+
elif self.MODE == "DMG" and _mbc.GetName() == "Xploder GB":
|
|
3202
|
+
self.WriteROM_DMG_EEPROM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], bank=bank+8)
|
|
3203
|
+
elif self.MODE == "AGB" and args["save_type"] in (1, 2): # EEPROM
|
|
3204
|
+
self.WriteRAM(address=int(pos/8), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
|
3205
|
+
elif self.MODE == "AGB" and args["save_type"] in (4, 5): # FLASH
|
|
3206
|
+
sector_address = pos % 0x10000
|
|
3207
|
+
if agb_flash_chip == 0x1F3D: # Atmel AT29LV512
|
|
3208
|
+
self.WriteRAM(address=int(pos/128), buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
|
3209
|
+
else:
|
|
3210
|
+
dprint("Erasing flash save sector; pos=0x{:X}, sector_address=0x{:X}".format(pos, sector_address))
|
|
3211
|
+
cmds = [
|
|
3212
|
+
[ 0x5555, 0xAA ],
|
|
3213
|
+
[ 0x2AAA, 0x55 ],
|
|
3214
|
+
[ 0x5555, 0x80 ],
|
|
3215
|
+
[ 0x5555, 0xAA ],
|
|
3216
|
+
[ 0x2AAA, 0x55 ],
|
|
3217
|
+
[ sector_address, 0x30 ]
|
|
3218
|
+
]
|
|
3219
|
+
self._cart_write_flash(cmds)
|
|
3220
|
+
sr = 0
|
|
3221
|
+
lives = 50
|
|
3222
|
+
while True:
|
|
3223
|
+
time.sleep(0.01)
|
|
3224
|
+
sr = self._cart_read(sector_address, 2, agb_save_flash=True)
|
|
3225
|
+
try:
|
|
3226
|
+
sr = struct.unpack(">H", sr)[0]
|
|
3227
|
+
except:
|
|
3228
|
+
sr = str(sr)
|
|
3229
|
+
dprint("Data Check: 0x{:X} == 0xFFFF? {:s}".format(sr, str(sr == 0xFFFF)))
|
|
3230
|
+
if sr == 0xFFFF: break
|
|
3231
|
+
lives -= 1
|
|
3232
|
+
if lives == 0:
|
|
3233
|
+
errmsg = "Error: Save data flash sector at 0x{:X} didn’t erase successfully (SR={:04X}).".format(bank*0x10000 + pos, sr)
|
|
3234
|
+
print("{:s}{:s}{:s}".format(ANSI.RED, errmsg, ANSI.RESET))
|
|
3235
|
+
dprint(errmsg)
|
|
3236
|
+
break
|
|
3237
|
+
if buffer[buffer_offset:buffer_offset+buffer_len] != bytearray([0xFF] * buffer_len):
|
|
3238
|
+
if ("ereader" in self.INFO and self.INFO["ereader"] is True and sector_address == 0xF000):
|
|
3239
|
+
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+0xF80], command=command, max_length=0x80)
|
|
3240
|
+
else:
|
|
3241
|
+
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
|
3242
|
+
elif self.MODE == "AGB" and args["save_type"] == 6: # DACS
|
|
3243
|
+
sector_address = pos + 0x1F00000
|
|
3244
|
+
if sector_address in (0x1F00000, 0x1F10000, 0x1F20000, 0x1F30000, 0x1F40000, 0x1F50000, 0x1F60000, 0x1F70000, 0x1F80000, 0x1F90000, 0x1FA0000, 0x1FB0000, 0x1FC0000, 0x1FD0000, 0x1FE0000, 0x1FF0000, 0x1FF2000, 0x1FF4000, 0x1FF6000, 0x1FF8000, 0x1FFA000, 0x1FFC000):
|
|
3245
|
+
dprint("DACS: Now at sector 0x{:X}".format(sector_address))
|
|
3246
|
+
cmds = [
|
|
3247
|
+
[ # Erase Sector
|
|
3248
|
+
[ 0, 0x50 ],
|
|
3249
|
+
[ sector_address, 0x20 ],
|
|
3250
|
+
[ sector_address, 0xD0 ],
|
|
3251
|
+
]
|
|
3252
|
+
]
|
|
3253
|
+
if sector_address == 0x1F00000: # First write
|
|
3254
|
+
temp = [
|
|
3255
|
+
[ # Unlock
|
|
3256
|
+
[ 0, 0x50 ],
|
|
3257
|
+
[ 0, 0x60 ],
|
|
3258
|
+
[ 0, 0xD0 ],
|
|
3259
|
+
]
|
|
3260
|
+
]
|
|
3261
|
+
temp.extend(cmds)
|
|
3262
|
+
cmds = temp
|
|
3263
|
+
elif sector_address == 0x1FFC000: # Boot sector
|
|
3264
|
+
temp = [
|
|
3265
|
+
[ # Unlock 1
|
|
3266
|
+
[ 0, 0x50 ],
|
|
3267
|
+
[ 0, 0x60 ],
|
|
3268
|
+
[ 0, 0xD0 ],
|
|
3269
|
+
],
|
|
3270
|
+
[ # Unlock 2
|
|
3271
|
+
[ 0, 0x50 ],
|
|
3272
|
+
[ sector_address, 0x60 ],
|
|
3273
|
+
[ sector_address, 0xDC ],
|
|
3274
|
+
]
|
|
3275
|
+
]
|
|
3276
|
+
temp.extend(cmds)
|
|
3277
|
+
cmds = temp
|
|
3278
|
+
|
|
3279
|
+
for cmd in cmds:
|
|
3280
|
+
dprint("Executing DACS commands:", cmd)
|
|
3281
|
+
self._cart_write_flash(commands=cmd, flashcart=True)
|
|
3282
|
+
sr = 0
|
|
3283
|
+
lives = 20
|
|
3284
|
+
while True:
|
|
3285
|
+
time.sleep(0.1)
|
|
3286
|
+
sr = struct.unpack("<H", self._cart_read(sector_address, 2))[0]
|
|
3287
|
+
dprint("DACS: Status Register Check: 0x{:X} == 0x80? {:s}".format(sr, str(sr & 0xE0 == 0x80)))
|
|
3288
|
+
if sr & 0xE0 == 0x80: break
|
|
3289
|
+
lives -= 1
|
|
3290
|
+
if lives == 0:
|
|
3291
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while writing to the DACS cartridge. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
|
|
3292
|
+
return False
|
|
3293
|
+
if sector_address < 0x1FFE000:
|
|
3294
|
+
dprint("DACS: Writing to area 0x{:X}–0x{:X}".format(0x1F00000+pos, 0x1F00000+pos+buffer_len-1))
|
|
3295
|
+
self.WriteROM(address=0x1F00000+pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len])
|
|
3296
|
+
else:
|
|
3297
|
+
dprint("DACS: Skipping read-only area 0x{:X}–0x{:X}".format(0x1F00000+pos, 0x1F00000+pos+buffer_len-1))
|
|
3298
|
+
else:
|
|
3299
|
+
self.WriteRAM(address=pos, buffer=buffer[buffer_offset:buffer_offset+buffer_len], command=command)
|
|
3300
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_offset+buffer_len})
|
|
3301
|
+
|
|
3302
|
+
pos += buffer_len
|
|
3303
|
+
buffer_offset += buffer_len
|
|
3304
|
+
|
|
3305
|
+
# if "detect" in args and args["detect"] is True:
|
|
3306
|
+
# buffer += bytearray(end_address - 64)
|
|
3307
|
+
# pos += end_address - 64
|
|
3308
|
+
# buffer_offset += end_address - 64
|
|
3309
|
+
|
|
3310
|
+
verified = False
|
|
3311
|
+
if args["mode"] == 2: # Backup
|
|
3312
|
+
self.INFO["transferred"] = len(buffer)
|
|
3313
|
+
rtc_buffer = None
|
|
3314
|
+
# Real Time Clock
|
|
3315
|
+
if args["rtc"] is True:
|
|
3316
|
+
self.NO_PROG_UPDATE = True
|
|
3317
|
+
if self.MODE == "DMG" and args["rtc"] is True:
|
|
3318
|
+
if _mbc.HasRTC():
|
|
3319
|
+
_mbc.LatchRTC()
|
|
3320
|
+
rtc_buffer = _mbc.ReadRTC()
|
|
3321
|
+
elif self.MODE == "AGB":
|
|
3322
|
+
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
3323
|
+
rtc_buffer = None
|
|
3324
|
+
if self.FW["fw_ver"] >= 12:
|
|
3325
|
+
self._write(self.DEVICE_CMD["AGB_READ_GPIO_RTC"])
|
|
3326
|
+
rtc_buffer = self._read(8)
|
|
3327
|
+
if len(rtc_buffer) == 8 and _agb_gpio.HasRTC(rtc_buffer) is True:
|
|
3328
|
+
rtc_buffer = rtc_buffer[1:]
|
|
3329
|
+
else:
|
|
3330
|
+
rtc_buffer = None
|
|
3331
|
+
else:
|
|
3332
|
+
if _agb_gpio.HasRTC() is True:
|
|
3333
|
+
rtc_buffer = _agb_gpio.ReadRTC(buffer=rtc_buffer)
|
|
3334
|
+
self.NO_PROG_UPDATE = False
|
|
3335
|
+
if rtc_buffer in (False, None): rtc_buffer = bytearray()
|
|
3336
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":len(buffer)+len(rtc_buffer)})
|
|
3337
|
+
|
|
3338
|
+
# Bootleg mapper
|
|
3339
|
+
if self.MODE == "AGB" and cart_type is not None and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] == 1:
|
|
3340
|
+
buffer[5] = sram_5
|
|
3341
|
+
|
|
3342
|
+
if args["path"] is not None:
|
|
3343
|
+
if self.MODE == "DMG" and _mbc.GetName() == "MBC2":
|
|
3344
|
+
for i in range(0, len(buffer)):
|
|
3345
|
+
buffer[i] = buffer[i] & 0x0F
|
|
3346
|
+
file = open(args["path"], "wb")
|
|
3347
|
+
file.write(buffer)
|
|
3348
|
+
if rtc_buffer is not None:
|
|
3349
|
+
file.write(rtc_buffer)
|
|
3350
|
+
file.close()
|
|
3351
|
+
else:
|
|
3352
|
+
self.INFO["data"] = buffer
|
|
3353
|
+
|
|
3354
|
+
self.INFO["file_crc32"] = zlib.crc32(buffer) & 0xFFFFFFFF
|
|
3355
|
+
self.INFO["file_sha1"] = hashlib.sha1(buffer).hexdigest()
|
|
3356
|
+
|
|
3357
|
+
if "verify_write" in args and args["verify_write"] not in (None, False):
|
|
3358
|
+
return True
|
|
3359
|
+
elif "verify_write" not in args or args["verify_write"] is False:
|
|
3360
|
+
verified = True
|
|
3361
|
+
|
|
3362
|
+
elif args["mode"] == 3: # Restore
|
|
3363
|
+
self.INFO["transferred"] = len(buffer)
|
|
3364
|
+
if args["rtc"] is True:
|
|
3365
|
+
advance = "rtc_advance" in args and args["rtc_advance"]
|
|
3366
|
+
self.SetProgress({"action":"UPDATE_RTC", "method":"write"})
|
|
3367
|
+
if self.MODE == "DMG" and args["rtc"] is True:
|
|
3368
|
+
_mbc.WriteRTC(buffer[-_mbc.GetRTCBufferSize():], advance=advance)
|
|
3369
|
+
elif self.MODE == "AGB":
|
|
3370
|
+
_agb_gpio = AGB_GPIO(args={"rtc":True}, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
3371
|
+
_agb_gpio.WriteRTC(buffer[-0x10:], advance=advance)
|
|
3372
|
+
|
|
3373
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":len(buffer), "force_update":True})
|
|
3374
|
+
|
|
3375
|
+
# ↓↓↓ Write verify
|
|
3376
|
+
if "verify_write" in args and args["verify_write"] is True and args["erase"] is not True:
|
|
3377
|
+
if self.MODE == "DMG": _mbc.SelectBankRAM(0)
|
|
3378
|
+
self.SetProgress({"action":"INITIALIZE", "method":"SAVE_WRITE_VERIFY", "size":buffer_offset})
|
|
3379
|
+
|
|
3380
|
+
verify_args = copy.copy(args)
|
|
3381
|
+
start_address = 0
|
|
3382
|
+
end_address = buffer_offset
|
|
3383
|
+
if self.MODE == "AGB" and args["save_type"] == 6: # DACS
|
|
3384
|
+
end_address = min(len(buffer), 0xFE000)
|
|
3385
|
+
|
|
3386
|
+
path = args["path"] # backup path
|
|
3387
|
+
verify_args.update({"mode":2, "verify_write":buffer, "path":None})
|
|
3388
|
+
self.ReadROM(0, 4) # dummy read
|
|
3389
|
+
self.INFO["data"] = None
|
|
3390
|
+
if self._BackupRestoreRAM(verify_args) is False: return
|
|
3391
|
+
|
|
3392
|
+
if args["mbc"] == 6: # MBC2
|
|
3393
|
+
for i in range(0, len(self.INFO["data"])):
|
|
3394
|
+
self.INFO["data"][i] &= 0x0F
|
|
3395
|
+
buffer[i] &= 0x0F
|
|
3396
|
+
|
|
3397
|
+
args["path"] = path # restore path
|
|
3398
|
+
if self.CANCEL is True:
|
|
3399
|
+
pass
|
|
3400
|
+
|
|
3401
|
+
if self.MODE == "AGB" and "ereader" in self.INFO and self.INFO["ereader"] is True: # e-Reader
|
|
3402
|
+
buffer[0xFF80:0x10000] = self.INFO["data"][0xFF80:0x10000]
|
|
3403
|
+
buffer[0x1FF80:0x20000] = self.INFO["data"][0xFF80:0x10000]
|
|
3404
|
+
|
|
3405
|
+
elif (self.INFO["data"][:end_address] != buffer[:end_address]):
|
|
3406
|
+
msg = ""
|
|
3407
|
+
count = 0
|
|
3408
|
+
time_start = time.time()
|
|
3409
|
+
for i in range(0, len(self.INFO["data"])):
|
|
3410
|
+
if i >= len(buffer): break
|
|
3411
|
+
if time.time() > time_start + 10:
|
|
3412
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The save data was written completely, but didn’t pass the verification check.", "abortable":False})
|
|
3413
|
+
return False
|
|
3414
|
+
data1 = self.INFO["data"][i]
|
|
3415
|
+
data2 = buffer[:end_address][i]
|
|
3416
|
+
if data1 != data2:
|
|
3417
|
+
count += 1
|
|
3418
|
+
if len(msg.split("\n")) <= 10:
|
|
3419
|
+
msg += "- 0x{:06X}: {:02X}≠{:02X}\n".format(i, data1, data2)
|
|
3420
|
+
elif len(msg.split("\n")) == 11:
|
|
3421
|
+
msg += "(more than 10 differences found)\n"
|
|
3422
|
+
else:
|
|
3423
|
+
pass
|
|
3424
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The save data was written completely, but {:d} byte(s) ({:.2f}%) didn’t pass the verification check.\n\n{:s}".format(count, (count / len(self.INFO["data"]) * 100), msg[:-1]), "abortable":False})
|
|
3425
|
+
return False
|
|
3426
|
+
else:
|
|
3427
|
+
verified = True
|
|
3428
|
+
else:
|
|
3429
|
+
verified = True
|
|
3430
|
+
# ↑↑↑ Write verify
|
|
3431
|
+
|
|
3432
|
+
if self.MODE == "DMG":
|
|
3433
|
+
_mbc.SelectBankRAM(0)
|
|
3434
|
+
_mbc.EnableRAM(enable=False)
|
|
3435
|
+
self._set_fw_variable("DMG_READ_CS_PULSE", 0)
|
|
3436
|
+
if audio_low:
|
|
3437
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x02)
|
|
3438
|
+
self.SetPin(["PIN_AUDIO"], True)
|
|
3439
|
+
self._write(self.DEVICE_CMD["SET_ADDR_AS_INPUTS"], wait=self.FW["fw_ver"] >= 12) # Prevent hotplugging corruptions on rare occasions
|
|
3440
|
+
elif self.MODE == "AGB":
|
|
3441
|
+
# Bootleg mapper
|
|
3442
|
+
if cart_type is not None and "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] == 1:
|
|
3443
|
+
self._cart_write(address=5, value=0, sram=True)
|
|
3444
|
+
self._cart_write(address=5, value=buffer[5], sram=True)
|
|
3445
|
+
|
|
3446
|
+
# Clean up
|
|
3447
|
+
self.INFO["last_action"] = self.INFO["action"]
|
|
3448
|
+
self.INFO["action"] = None
|
|
3449
|
+
self.INFO["last_path"] = args["path"]
|
|
3450
|
+
self.SetProgress({"action":"FINISHED", "verified":verified})
|
|
3451
|
+
return True
|
|
3452
|
+
|
|
3453
|
+
def _FlashROM(self, args):
|
|
3454
|
+
self.FAST_READ = True
|
|
3455
|
+
if "buffer" in args:
|
|
3456
|
+
data_import = args["buffer"]
|
|
3457
|
+
else:
|
|
3458
|
+
with open(args["path"], "rb") as file: data_import = bytearray(file.read())
|
|
3459
|
+
|
|
3460
|
+
flash_offset = 0 # Batteryless SRAM or Transfer Resume
|
|
3461
|
+
if "flash_offset" in args:
|
|
3462
|
+
flash_offset = args["flash_offset"]
|
|
3463
|
+
if "start_addr" in args and args["start_addr"] > 0:
|
|
3464
|
+
data_import = bytearray(b'\xFF' * args["start_addr"]) + data_import
|
|
3465
|
+
|
|
3466
|
+
# Pad data
|
|
3467
|
+
if len(data_import) > 0:
|
|
3468
|
+
if len(data_import) < 0x400:
|
|
3469
|
+
data_import += bytearray([0xFF] * (0x400 - len(data_import)))
|
|
3470
|
+
if len(data_import) % 0x4000 > 0:
|
|
3471
|
+
data_import += bytearray([0xFF] * (0x4000 - len(data_import) % 0x4000))
|
|
3472
|
+
|
|
3473
|
+
# Skip writing the last 256 bytes of 32 MiB ROMs with EEPROM save type
|
|
3474
|
+
if self.MODE == "AGB" and len(data_import) == 0x2000000:
|
|
3475
|
+
temp_ver = "N/A"
|
|
3476
|
+
try:
|
|
3477
|
+
ids = [ b"SRAM_", b"EEPROM_V", b"FLASH_V", b"FLASH512_V", b"FLASH1M_V", b"AGB_8MDACS_DL_V" ]
|
|
3478
|
+
for id in ids:
|
|
3479
|
+
temp_pos = data_import.find(id)
|
|
3480
|
+
if temp_pos > 0:
|
|
3481
|
+
temp_ver = data_import[temp_pos:temp_pos+0x20]
|
|
3482
|
+
temp_ver = temp_ver[:temp_ver.index(0x00)].decode("ascii", "replace")
|
|
3483
|
+
break
|
|
3484
|
+
except ValueError:
|
|
3485
|
+
temp_ver = "N/A"
|
|
3486
|
+
if "EEPROM" in temp_ver:
|
|
3487
|
+
print("{:s}Note: The last 256 bytes of this 32 MiB ROM will not be written as this area is reserved by the EEPROM save type.{:s}".format(ANSI.YELLOW, ANSI.RESET))
|
|
3488
|
+
data_import = data_import[:0x1FFFF00]
|
|
3489
|
+
|
|
3490
|
+
# Fix bootlogo and header
|
|
3491
|
+
if "fix_bootlogo" in args and isinstance(args["fix_bootlogo"], bytearray):
|
|
3492
|
+
dstr = ''.join(format(x, '02X') for x in args["fix_bootlogo"])
|
|
3493
|
+
dprint("Replacing bootlogo data with", dstr)
|
|
3494
|
+
if self.MODE == "DMG":
|
|
3495
|
+
data_import[0x104:0x134] = args["fix_bootlogo"]
|
|
3496
|
+
elif self.MODE == "AGB":
|
|
3497
|
+
data_import[0x04:0xA0] = args["fix_bootlogo"]
|
|
3498
|
+
if "fix_header" in args and args["fix_header"]:
|
|
3499
|
+
dprint("Fixing header checksums")
|
|
3500
|
+
if self.MODE == "DMG":
|
|
3501
|
+
temp = RomFileDMG(data_import[0:0x200]).FixHeader()
|
|
3502
|
+
elif self.MODE == "AGB":
|
|
3503
|
+
temp = RomFileAGB(data_import[0:0x200]).FixHeader()
|
|
3504
|
+
data_import[0:0x200] = temp
|
|
3505
|
+
|
|
3506
|
+
supported_carts = list(self.SUPPORTED_CARTS[self.MODE].values())
|
|
3507
|
+
cart_type = copy.deepcopy(supported_carts[args["cart_type"]])
|
|
3508
|
+
if cart_type == "RETAIL": return False # Generic ROM Cartridge is not flashable
|
|
3509
|
+
|
|
3510
|
+
# Special carts
|
|
3511
|
+
# if "power_cycle" in cart_type and cart_type["power_cycle"] is True and not self.CanPowerCycleCart():
|
|
3512
|
+
# self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is not flashable using FlashGBX and your " + self.DEVICE_NAME + " hardware revision due to missing cartridge power cycling support.", "abortable":False})
|
|
3513
|
+
# return False
|
|
3514
|
+
# Special carts
|
|
3515
|
+
# Firmware check L1
|
|
3516
|
+
if (cart_type["type"] == "DMG" and "write_pin" in cart_type and cart_type["write_pin"] == "WR+RESET" and self.FW["fw_ver"] < 2) or (self.FW["fw_ver"] < 2 and ("pulse_reset_after_write" in cart_type and cart_type["pulse_reset_after_write"] is True)):
|
|
3517
|
+
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is not supported by FlashGBX using your " + self.DEVICE_NAME + " hardware revision and/or firmware version.", "abortable":False})
|
|
3518
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L2.", "abortable":False})
|
|
3519
|
+
return False
|
|
3520
|
+
# Firmware check L1
|
|
3521
|
+
# Firmware check L2
|
|
3522
|
+
if (self.FW["fw_ver"] < 3 and ("command_set" in cart_type and cart_type["command_set"] == "SHARP") and ("buffer_write" in cart_type["commands"])):
|
|
3523
|
+
print("{:s}Note: Update your {:s} firmware to version L3 or higher for a better transfer rate with this cartridge.{:s}".format(ANSI.YELLOW, self.DEVICE_NAME, ANSI.RESET))
|
|
3524
|
+
del(cart_type["commands"]["buffer_write"])
|
|
3525
|
+
# Firmware check L2
|
|
3526
|
+
# Firmware check L5
|
|
3527
|
+
if (self.FW["fw_ver"] < 5 and ("flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True)):
|
|
3528
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L5.", "abortable":False})
|
|
3529
|
+
return False
|
|
3530
|
+
if (self.FW["fw_ver"] < 5 and ("double_die" in cart_type and cart_type["double_die"] is True)):
|
|
3531
|
+
print("{:s}Note: Update your {:s} firmware to version L5 or higher for a better transfer rate with this cartridge.{:s}".format(ANSI.YELLOW, self.DEVICE_NAME, ANSI.RESET))
|
|
3532
|
+
del(cart_type["commands"]["buffer_write"])
|
|
3533
|
+
# Firmware check L5
|
|
3534
|
+
# Firmware check L8
|
|
3535
|
+
if (self.FW["fw_ver"] < 8 and "enable_pullups" in cart_type and cart_type["enable_pullups"] is True):
|
|
3536
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L8.", "abortable":False})
|
|
3537
|
+
return False
|
|
3538
|
+
# Firmware check L8
|
|
3539
|
+
# Firmware check L12
|
|
3540
|
+
if (self.FW["fw_ver"] < 12 and "set_irq_high" in cart_type and cart_type["set_irq_high"] is True):
|
|
3541
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type requires at least firmware version L12.", "abortable":False})
|
|
3542
|
+
return False
|
|
3543
|
+
# Firmware check L12
|
|
3544
|
+
|
|
3545
|
+
# Ensure cart is powered
|
|
3546
|
+
if self.CanPowerCycleCart(): self.CartPowerOn()
|
|
3547
|
+
|
|
3548
|
+
cart_type["_index"] = 0
|
|
3549
|
+
for i in range(0, len(list(self.SUPPORTED_CARTS[self.MODE].keys()))):
|
|
3550
|
+
if i == args["cart_type"]:
|
|
3551
|
+
try:
|
|
3552
|
+
cart_type["_index"] = cart_type["names"].index(list(self.SUPPORTED_CARTS[self.MODE].keys())[i])
|
|
3553
|
+
except:
|
|
3554
|
+
pass
|
|
3555
|
+
|
|
3556
|
+
fc_fncptr = {
|
|
3557
|
+
"cart_write_fncptr":self._cart_write,
|
|
3558
|
+
"cart_write_fast_fncptr":self._cart_write_flash,
|
|
3559
|
+
"cart_read_fncptr":self.ReadROM,
|
|
3560
|
+
"cart_powercycle_fncptr":self.CartPowerCycleOrAskReconnect,
|
|
3561
|
+
"progress_fncptr":self.SetProgress,
|
|
3562
|
+
"set_we_pin_wr":self._set_we_pin_wr,
|
|
3563
|
+
"set_we_pin_audio":self._set_we_pin_audio,
|
|
3564
|
+
}
|
|
3565
|
+
if cart_type["command_set"] == "GBMEMORY":
|
|
3566
|
+
flashcart = Flashcart_DMG_MMSA(config=cart_type, fncptr=fc_fncptr)
|
|
3567
|
+
elif cart_type["command_set"] == "GBAMP":
|
|
3568
|
+
flashcart = Flashcart_AGB_GBAMP(config=cart_type, fncptr=fc_fncptr)
|
|
3569
|
+
elif cart_type["command_set"] == "BUNG_16M":
|
|
3570
|
+
flashcart = Flashcart_DMG_BUNG_16M(config=cart_type, fncptr=fc_fncptr)
|
|
3571
|
+
else:
|
|
3572
|
+
flashcart = Flashcart(config=cart_type, fncptr=fc_fncptr)
|
|
3573
|
+
|
|
3574
|
+
rumble = "rumble" in flashcart.CONFIG and flashcart.CONFIG["rumble"] is True
|
|
3575
|
+
|
|
3576
|
+
# ↓↓↓ Set Voltage
|
|
3577
|
+
if args["override_voltage"] is not False:
|
|
3578
|
+
if args["override_voltage"] == 5:
|
|
3579
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
3580
|
+
else:
|
|
3581
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
3582
|
+
elif flashcart.GetVoltage() == 3.3:
|
|
3583
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_3_3V"], wait=self.FW["fw_ver"] >= 12)
|
|
3584
|
+
elif flashcart.GetVoltage() == 5:
|
|
3585
|
+
self._write(self.DEVICE_CMD["SET_VOLTAGE_5V"], wait=self.FW["fw_ver"] >= 12)
|
|
3586
|
+
# ↑↑↑ Set Voltage
|
|
3587
|
+
|
|
3588
|
+
# ↓↓↓ Pad data for full chip erase on sector erase mode
|
|
3589
|
+
if not flashcart.SupportsChipErase() and flashcart.SupportsSectorErase() and args["prefer_chip_erase"]:
|
|
3590
|
+
print("{:s}Note: Chip erase mode is not supported for this flash cartridge type. Sector erase mode will be used.{:s}\n".format(ANSI.YELLOW, ANSI.RESET))
|
|
3591
|
+
flash_size = flashcart.GetFlashSize()
|
|
3592
|
+
if flash_size is not False and len(data_import) < flash_size:
|
|
3593
|
+
# Pad with FF till the end (with MemoryError fix)
|
|
3594
|
+
pad_len = flash_size - len(data_import)
|
|
3595
|
+
while pad_len > 0x2000000:
|
|
3596
|
+
data_import += bytearray([0xFF] * 0x2000000)
|
|
3597
|
+
pad_len -= 0x2000000
|
|
3598
|
+
data_import += bytearray([0xFF] * (flash_size - len(data_import)))
|
|
3599
|
+
# ↑↑↑ Pad data for full chip erase on sector erase mode
|
|
3600
|
+
|
|
3601
|
+
# ↓↓↓ Flashcart configuration
|
|
3602
|
+
if self.FW["fw_ver"] >= 8:
|
|
3603
|
+
if "enable_pullups" in cart_type:
|
|
3604
|
+
if cart_type["enable_pullups"] is True:
|
|
3605
|
+
self._write(self.DEVICE_CMD["ENABLE_PULLUPS"], wait=True)
|
|
3606
|
+
dprint("Pullups enabled")
|
|
3607
|
+
else:
|
|
3608
|
+
self._write(self.DEVICE_CMD["DISABLE_PULLUPS"], wait=True)
|
|
3609
|
+
dprint("Pullups disabled")
|
|
3610
|
+
if self.FW["fw_ver"] >= 12:
|
|
3611
|
+
if "enable_pullup_wr" in cart_type: # Joey Jr bug workaround
|
|
3612
|
+
self._set_fw_variable("PULLUPS_ENABLED", 2 if cart_type["enable_pullup_wr"] is True else 0)
|
|
3613
|
+
if self.MODE == "AGB":
|
|
3614
|
+
self._set_fw_variable("AGB_IRQ_ENABLED", 1 if "set_irq_high" in cart_type else 0)
|
|
3615
|
+
|
|
3616
|
+
_mbc = None
|
|
3617
|
+
errmsg_mbc_selection = ""
|
|
3618
|
+
if self.MODE == "DMG":
|
|
3619
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
3620
|
+
mbc = flashcart.GetMBC()
|
|
3621
|
+
if mbc is not False and isinstance(mbc, int):
|
|
3622
|
+
args["mbc"] = mbc
|
|
3623
|
+
dprint("Using forced mapper type 0x{:02X} for flashing".format(mbc))
|
|
3624
|
+
elif mbc == "manual":
|
|
3625
|
+
dprint("Using manually selected mapper type 0x{:02X} for flashing".format(args["mbc"]))
|
|
3626
|
+
else:
|
|
3627
|
+
args["mbc"] = 0
|
|
3628
|
+
dprint("Using default mapper type 0x{:02X} for flashing".format(args["mbc"]))
|
|
3629
|
+
if args["mbc"] == 0: args["mbc"] = 0x19 # MBC5 default
|
|
3630
|
+
|
|
3631
|
+
if not self.IsSupportedMbc(args["mbc"]):
|
|
3632
|
+
msg = "This cartridge uses a mapper that is not supported by {:s} using your {:s} device.".format(Util.APPNAME, self.GetFullName())
|
|
3633
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":msg, "abortable":False})
|
|
3634
|
+
return False
|
|
3635
|
+
|
|
3636
|
+
_mbc = DMG_MBC().GetInstance(args=args, cart_write_fncptr=self._cart_write, cart_read_fncptr=self._cart_read, cart_powercycle_fncptr=self.CartPowerCycleOrAskReconnect, clk_toggle_fncptr=self._clk_toggle)
|
|
3637
|
+
|
|
3638
|
+
self._set_fw_variable("FLASH_PULSE_RESET", 1 if flashcart.PulseResetAfterWrite() else 0)
|
|
3639
|
+
|
|
3640
|
+
end_bank = math.ceil(len(data_import) / _mbc.GetROMBankSize())
|
|
3641
|
+
rom_bank_size = _mbc.GetROMBankSize()
|
|
3642
|
+
|
|
3643
|
+
_mbc.EnableMapper()
|
|
3644
|
+
|
|
3645
|
+
if flashcart.GetMBC() == "manual":
|
|
3646
|
+
errmsg_mbc_selection += "\n- Check mapper type used: {:s} (manual selection)".format(_mbc.GetName())
|
|
3647
|
+
else:
|
|
3648
|
+
errmsg_mbc_selection += "\n- Check mapper type used: {:s} (forced by selected cartridge type)".format(_mbc.GetName())
|
|
3649
|
+
if len(data_import) > _mbc.GetMaxROMSize():
|
|
3650
|
+
errmsg_mbc_selection += "\n- Check mapper type ROM size limit: likely up to {:s}".format(Util.formatFileSize(size=_mbc.GetMaxROMSize()))
|
|
3651
|
+
|
|
3652
|
+
elif self.MODE == "AGB":
|
|
3653
|
+
self._write(self.DEVICE_CMD["SET_MODE_AGB"], wait=self.FW["fw_ver"] >= 12)
|
|
3654
|
+
if flashcart and "flash_bank_size" in cart_type:
|
|
3655
|
+
end_bank = math.ceil(len(data_import) / cart_type["flash_bank_size"])
|
|
3656
|
+
else:
|
|
3657
|
+
end_bank = 1
|
|
3658
|
+
if flashcart and "flash_bank_size" in cart_type:
|
|
3659
|
+
rom_bank_size = cart_type["flash_bank_size"]
|
|
3660
|
+
else:
|
|
3661
|
+
rom_bank_size = 0x2000000
|
|
3662
|
+
|
|
3663
|
+
flash_buffer_size = flashcart.GetBufferSize()
|
|
3664
|
+
# ↑↑↑ Flashcart configuration
|
|
3665
|
+
|
|
3666
|
+
# ↓↓↓ DMG-MMSA-JPN hidden sector
|
|
3667
|
+
if self.MODE == "DMG" and _mbc.GetName() == "G-MMC1":
|
|
3668
|
+
if "buffer_map" not in args:
|
|
3669
|
+
if os.path.exists(os.path.splitext(args["path"])[0] + ".map"):
|
|
3670
|
+
with open(os.path.splitext(args["path"])[0] + ".map", "rb") as file: args["buffer_map"] = file.read()
|
|
3671
|
+
else:
|
|
3672
|
+
temp = data_import
|
|
3673
|
+
if len(temp) == 0: temp = bytearray([0xFF] * 0x180)
|
|
3674
|
+
try:
|
|
3675
|
+
gbmem = GBMemoryMap(rom=temp, oldmap=_mbc.ReadHiddenSector())
|
|
3676
|
+
args["buffer_map"] = gbmem.GetMapData()
|
|
3677
|
+
except Exception:
|
|
3678
|
+
print(traceback.format_exc())
|
|
3679
|
+
print("{:s}An error occured while trying to generate the hidden sector data for the NP GB-Memory cartridge.{:s}".format(ANSI.RED, ANSI.RESET))
|
|
3680
|
+
args["buffer_map"] = False
|
|
3681
|
+
|
|
3682
|
+
if args["buffer_map"] is False:
|
|
3683
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"The NP GB-Memory Cartridge requires extra hidden sector data. As it couldn’t be auto-generated, please provide your own at the following path: {:s}".format(os.path.splitext(args["path"])[0] + ".map"), "abortable":False})
|
|
3684
|
+
return False
|
|
3685
|
+
else:
|
|
3686
|
+
dprint("Hidden sector data:", args["buffer_map"])
|
|
3687
|
+
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
|
3688
|
+
with open("debug_mmsa_map.bin", "wb") as f: f.write(args["buffer_map"])
|
|
3689
|
+
data_map_import = copy.copy(args["buffer_map"])
|
|
3690
|
+
data_map_import = bytearray(data_map_import)
|
|
3691
|
+
dprint("Hidden sector data loaded")
|
|
3692
|
+
# ↑↑↑ DMG-MMSA-JPN hidden sector
|
|
3693
|
+
|
|
3694
|
+
# ↓↓↓ Load commands into firmware
|
|
3695
|
+
flash_cmds = []
|
|
3696
|
+
command_set_type = flashcart.GetCommandSetType()
|
|
3697
|
+
temp = 0
|
|
3698
|
+
if command_set_type == "AMD":
|
|
3699
|
+
temp = 0x01
|
|
3700
|
+
#self._write(0x01) # FLASH_COMMAND_SET_AMD
|
|
3701
|
+
#self._set_fw_variable("FLASH_SHARP_VERIFY_SR", 0)
|
|
3702
|
+
dprint("Using AMD command set")
|
|
3703
|
+
elif command_set_type == "INTEL":
|
|
3704
|
+
temp = 0x02
|
|
3705
|
+
#self._write(0x02) # FLASH_COMMAND_SET_INTEL
|
|
3706
|
+
self._set_fw_variable("FLASH_SHARP_VERIFY_SR", 0)
|
|
3707
|
+
dprint("Using Intel command set")
|
|
3708
|
+
elif command_set_type == "SHARP":
|
|
3709
|
+
temp = 0x02
|
|
3710
|
+
#self._write(0x02) # FLASH_COMMAND_SET_INTEL
|
|
3711
|
+
self._set_fw_variable("FLASH_SHARP_VERIFY_SR", 1)
|
|
3712
|
+
dprint("Using Sharp/Intel command set")
|
|
3713
|
+
elif command_set_type in ("GBMEMORY", "DMG-MBC5-32M-FLASH"):
|
|
3714
|
+
temp = 0x00
|
|
3715
|
+
dprint("Using GB-Memory command set")
|
|
3716
|
+
elif command_set_type in ("BLAZE_XPLODER", "DATEL_ORBITV2", "EEPROM", "GBAMP", "BUNG_16M"):
|
|
3717
|
+
temp = 0x00
|
|
3718
|
+
else:
|
|
3719
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM flashing.", "abortable":False})
|
|
3720
|
+
return False
|
|
3721
|
+
|
|
3722
|
+
if flashcart.HasDoubleDie() and self.FW["fw_ver"] >= 5:
|
|
3723
|
+
self._set_fw_variable("FLASH_DOUBLE_DIE", 1)
|
|
3724
|
+
else:
|
|
3725
|
+
self._set_fw_variable("FLASH_DOUBLE_DIE", 0)
|
|
3726
|
+
|
|
3727
|
+
if command_set_type == "GBMEMORY" and self.FW["fw_ver"] < 2:
|
|
3728
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x01)
|
|
3729
|
+
dprint("Using legacy GB-Memory mode")
|
|
3730
|
+
elif command_set_type == "DMG-MBC5-32M-FLASH" and self.FW["fw_ver"] < 12:
|
|
3731
|
+
self._set_fw_variable("FLASH_WE_PIN", 0x02)
|
|
3732
|
+
else:
|
|
3733
|
+
self._write(self.DEVICE_CMD["SET_FLASH_CMD"])
|
|
3734
|
+
self._write(temp)
|
|
3735
|
+
|
|
3736
|
+
if flashcart.IsF2A():
|
|
3737
|
+
self._write(0x05) # FLASH_METHOD_AGB_FLASH2ADVANCE
|
|
3738
|
+
flash_cmds = [
|
|
3739
|
+
[ "SA", 0xE8 ],
|
|
3740
|
+
[ "SA", "BS" ],
|
|
3741
|
+
[ "PA", "PD" ],
|
|
3742
|
+
[ "SA", 0xD0 ],
|
|
3743
|
+
[ "SA", 0xFF ]
|
|
3744
|
+
]
|
|
3745
|
+
dprint("Using Flash2Advance mode with a buffer of {:d} bytes".format(flash_buffer_size))
|
|
3746
|
+
elif command_set_type == "GBMEMORY" and self.FW["fw_ver"] >= 2:
|
|
3747
|
+
self._write(0x03) # FLASH_METHOD_DMG_MMSA
|
|
3748
|
+
dprint("Using GB-Memory mode")
|
|
3749
|
+
elif command_set_type == "DATEL_ORBITV2" and self.FW["fw_ver"] >= 12:
|
|
3750
|
+
self._write(0x09) # FLASH_METHOD_DMG_DATEL_ORBITV2
|
|
3751
|
+
dprint("Using Datel Orbit V2 mode")
|
|
3752
|
+
elif command_set_type == "DMG-MBC5-32M-FLASH" and self.FW["fw_ver"] >= 12:
|
|
3753
|
+
self._write(0x0A) # FLASH_METHOD_DMG_E201264
|
|
3754
|
+
dprint("Using E201264 mode")
|
|
3755
|
+
elif command_set_type == "GBAMP" and self.FW["fw_ver"] >= 12:
|
|
3756
|
+
self._write(0x0B) # FLASH_METHOD_AGB_GBAMP
|
|
3757
|
+
dprint("Using GBAMP mode")
|
|
3758
|
+
elif command_set_type == "BUNG_16M" and self.FW["fw_ver"] >= 12:
|
|
3759
|
+
self._write(0x0C) # FLASH_METHOD_DMG_BUNG_16M
|
|
3760
|
+
dprint("Using BUNG Doctor GB Card 16M mode")
|
|
3761
|
+
elif flashcart.SupportsBufferWrite() and flash_buffer_size > 0:
|
|
3762
|
+
self._write(0x02) # FLASH_METHOD_BUFFERED
|
|
3763
|
+
flash_cmds = flashcart.GetCommands("buffer_write")
|
|
3764
|
+
dprint("Using buffered writing with a buffer of {:d} bytes".format(flash_buffer_size))
|
|
3765
|
+
elif flashcart.SupportsPageWrite() and flash_buffer_size > 0 and self.FW["fw_ver"] >= 12:
|
|
3766
|
+
self._write(0x08) # FLASH_METHOD_PAGED
|
|
3767
|
+
flash_cmds = flashcart.GetCommands("page_write")
|
|
3768
|
+
dprint("Using paged writing with a buffer of {:d} bytes".format(flash_buffer_size))
|
|
3769
|
+
elif flashcart.SupportsSingleWrite():
|
|
3770
|
+
self._write(0x01) # FLASH_METHOD_UNBUFFERED
|
|
3771
|
+
flash_cmds = flashcart.GetCommands("single_write")
|
|
3772
|
+
dprint("Using single writing")
|
|
3773
|
+
else:
|
|
3774
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"This cartridge type is currently not supported for ROM writing.", "abortable":False})
|
|
3775
|
+
return False
|
|
3776
|
+
|
|
3777
|
+
if flashcart.WEisWR():
|
|
3778
|
+
we = 0x01 # FLASH_WE_PIN_WR
|
|
3779
|
+
self._write(we)
|
|
3780
|
+
dprint("Using WR as WE")
|
|
3781
|
+
elif flashcart.WEisAUDIO():
|
|
3782
|
+
we = 0x02 # FLASH_WE_PIN_AUDIO
|
|
3783
|
+
self._write(we)
|
|
3784
|
+
dprint("Using AUDIO as WE")
|
|
3785
|
+
elif flashcart.WEisWR_RESET():
|
|
3786
|
+
we = 0x03 # FLASH_WE_PIN_WR_RESET
|
|
3787
|
+
self._write(we) # FLASH_WE_PIN_WR_RESET
|
|
3788
|
+
dprint("Using WR+RESET as WE")
|
|
3789
|
+
else:
|
|
3790
|
+
we = 0x00
|
|
3791
|
+
self._write(we) # unset
|
|
3792
|
+
|
|
3793
|
+
for i in range(0, 6):
|
|
3794
|
+
if i > len(flash_cmds) - 1: # skip
|
|
3795
|
+
self._write(bytearray(struct.pack(">I", 0)) + bytearray(struct.pack(">H", 0)))
|
|
3796
|
+
else:
|
|
3797
|
+
address = flash_cmds[i][0]
|
|
3798
|
+
value = flash_cmds[i][1]
|
|
3799
|
+
if not isinstance(address, int): address = 0
|
|
3800
|
+
if not isinstance(value, int): value = 0
|
|
3801
|
+
if self.MODE == "AGB": address >>= 1
|
|
3802
|
+
dprint("Setting command #{:d} to 0x{:X}=0x{:X}".format(i, address, value))
|
|
3803
|
+
self._write(bytearray(struct.pack(">I", address)) + bytearray(struct.pack(">H", value)))
|
|
3804
|
+
if self.FW["fw_ver"] >= 12: self.wait_for_ack()
|
|
3805
|
+
|
|
3806
|
+
if self.FW["fw_ver"] >= 6:
|
|
3807
|
+
if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
|
|
3808
|
+
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
|
|
3809
|
+
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
|
|
3810
|
+
if "bank_switch" in cart_type["commands"]:
|
|
3811
|
+
self._write(len(cart_type["commands"]["bank_switch"])) # number of commands
|
|
3812
|
+
for command in cart_type["commands"]["bank_switch"]:
|
|
3813
|
+
address = command[0]
|
|
3814
|
+
value = command[1]
|
|
3815
|
+
if value == "ID":
|
|
3816
|
+
self._write(bytearray(struct.pack(">I", address))) # address
|
|
3817
|
+
self._write(0) # type = address
|
|
3818
|
+
else:
|
|
3819
|
+
self._write(bytearray(struct.pack(">I", value))) # value
|
|
3820
|
+
self._write(1) # type = value
|
|
3821
|
+
ret = self._read(1)
|
|
3822
|
+
if ret != 0x01:
|
|
3823
|
+
print("Error in DMG_SET_BANK_CHANGE_CMD:", ret)
|
|
3824
|
+
else:
|
|
3825
|
+
self._write(0, wait=True)
|
|
3826
|
+
else:
|
|
3827
|
+
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 0)
|
|
3828
|
+
self._write(self.DEVICE_CMD["DMG_SET_BANK_CHANGE_CMD"])
|
|
3829
|
+
self._write(0, wait=True)
|
|
3830
|
+
else:
|
|
3831
|
+
if "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
|
|
3832
|
+
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 1)
|
|
3833
|
+
else:
|
|
3834
|
+
self._set_fw_variable("FLASH_COMMANDS_BANK_1", 0)
|
|
3835
|
+
|
|
3836
|
+
if self.FW["fw_ver"] >= 12:
|
|
3837
|
+
if "status_register_mask" in cart_type:
|
|
3838
|
+
self._set_fw_variable("STATUS_REGISTER_MASK", cart_type["status_register_mask"])
|
|
3839
|
+
self._set_fw_variable("STATUS_REGISTER_VALUE", cart_type["status_register_value"])
|
|
3840
|
+
else:
|
|
3841
|
+
self._set_fw_variable("STATUS_REGISTER_MASK", 0x80)
|
|
3842
|
+
self._set_fw_variable("STATUS_REGISTER_VALUE", 0x80)
|
|
3843
|
+
# ↑↑↑ Load commands into firmware
|
|
3844
|
+
|
|
3845
|
+
# ↓↓↓ Preparations
|
|
3846
|
+
if self.MODE == "DMG" and "flash_commands_on_bank_1" in cart_type and cart_type["flash_commands_on_bank_1"] is True:
|
|
3847
|
+
dprint("Setting ROM bank 1")
|
|
3848
|
+
_mbc.SelectBankROM(1)
|
|
3849
|
+
if we != 0x00:
|
|
3850
|
+
self._set_fw_variable("FLASH_WE_PIN", we)
|
|
3851
|
+
# ↑↑↑ Preparations
|
|
3852
|
+
|
|
3853
|
+
# ↓↓↓ Read Flash ID
|
|
3854
|
+
if "flash_ids" in cart_type:
|
|
3855
|
+
(verified, flash_id) = flashcart.VerifyFlashID()
|
|
3856
|
+
if not verified and not command_set_type == "BLAZE_XPLODER":
|
|
3857
|
+
print("{:s}Note: This cartridge’s Flash ID ({:s}) doesn’t match the cartridge type selection.{:s}".format(ANSI.YELLOW, ' '.join(format(x, '02X') for x in flash_id), ANSI.RESET))
|
|
3858
|
+
else:
|
|
3859
|
+
if flashcart.Unlock() is False: return False
|
|
3860
|
+
# ↑↑↑ Read Flash ID
|
|
3861
|
+
|
|
3862
|
+
# ↓↓↓ Read Sector Map
|
|
3863
|
+
sector_map = flashcart.GetSectorMap()
|
|
3864
|
+
smallest_sector_size = 0x2000
|
|
3865
|
+
sector_offsets = []
|
|
3866
|
+
write_sectors = []
|
|
3867
|
+
verify_sectors = []
|
|
3868
|
+
sector_pos = 0
|
|
3869
|
+
delta_state_new = None
|
|
3870
|
+
flash_capacity = len(data_import)
|
|
3871
|
+
if sector_map is not None and sector_map is not False:
|
|
3872
|
+
smallest_sector_size = flashcart.GetSmallestSectorSize()
|
|
3873
|
+
sector_offsets = flashcart.GetSectorOffsets(rom_size=flashcart.GetFlashSize(default=len(data_import)), rom_bank_size=rom_bank_size)
|
|
3874
|
+
if len(sector_offsets) > 0:
|
|
3875
|
+
flash_capacity = sector_offsets[-1][0] + sector_offsets[-1][1]
|
|
3876
|
+
if flash_capacity < len(data_import) and not (flashcart.SupportsChipErase() and args["prefer_chip_erase"]):
|
|
3877
|
+
#self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"There are not enough flash sectors available to write this ROM. The maximum capacity is {:s}.".format(Util.formatFileSize(size=flash_capacity, asInt=False)), "abortable":False})
|
|
3878
|
+
#return False
|
|
3879
|
+
sector_offsets = flashcart.GetSectorOffsets(rom_size=len(data_import), rom_bank_size=rom_bank_size)
|
|
3880
|
+
|
|
3881
|
+
sector_offsets_hash = base64.urlsafe_b64encode(hashlib.sha1(str(sector_offsets).encode("UTF-8")).digest()).decode("ASCII", "ignore")[:4]
|
|
3882
|
+
|
|
3883
|
+
# Delta flashing
|
|
3884
|
+
if len(sector_offsets) > 1:
|
|
3885
|
+
splitext = os.path.splitext(args["path"])
|
|
3886
|
+
if splitext[0].endswith(".delta") and os.path.exists(splitext[0][:-6] + splitext[1]):
|
|
3887
|
+
delta_state_new = []
|
|
3888
|
+
with open(splitext[0][:-6] + splitext[1], "rb") as f:
|
|
3889
|
+
for i in range(0, len(sector_offsets)):
|
|
3890
|
+
s_from = sector_offsets[i][0]
|
|
3891
|
+
s_size = sector_offsets[i][1]
|
|
3892
|
+
s_to = s_from + s_size
|
|
3893
|
+
if data_import[s_from:s_to] != f.read(s_to - s_from):
|
|
3894
|
+
x = [ s_from, s_size, zlib.crc32(data_import[s_from:s_to]) & 0xFFFFFFFF ]
|
|
3895
|
+
delta_state_new.append(x)
|
|
3896
|
+
dprint("Sector differs:", x)
|
|
3897
|
+
write_sectors = copy.copy(delta_state_new)
|
|
3898
|
+
json_file = "{:s}_{:s}.json".format(splitext[0], sector_offsets_hash)
|
|
3899
|
+
if os.path.exists(json_file):
|
|
3900
|
+
with open(json_file, "rb") as f:
|
|
3901
|
+
try:
|
|
3902
|
+
delta_state_old = json.loads(f.read().decode("UTF-8-SIG"))
|
|
3903
|
+
except:
|
|
3904
|
+
delta_state_old = []
|
|
3905
|
+
if len(delta_state_old) > 0:
|
|
3906
|
+
for x in delta_state_old:
|
|
3907
|
+
if x in write_sectors:
|
|
3908
|
+
del(write_sectors[write_sectors.index(x)])
|
|
3909
|
+
dprint("Skipping sector:", x)
|
|
3910
|
+
else:
|
|
3911
|
+
write_sectors2 = []
|
|
3912
|
+
for y in write_sectors: write_sectors2.append(y[:-1])
|
|
3913
|
+
if x[:-1] not in write_sectors2:
|
|
3914
|
+
write_sectors.append(x)
|
|
3915
|
+
dprint("Forcing sector:", x)
|
|
3916
|
+
|
|
3917
|
+
if len(write_sectors) == 0:
|
|
3918
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"No flash sectors were found that would need to be updated for delta flashing.", "abortable":False})
|
|
3919
|
+
return False
|
|
3920
|
+
|
|
3921
|
+
elif "flash_sectors" in args and len(args["flash_sectors"]) > 0:
|
|
3922
|
+
write_sectors = args["flash_sectors"]
|
|
3923
|
+
|
|
3924
|
+
elif flash_offset > 0: # Batteryless SRAM
|
|
3925
|
+
if "flash_size" not in args:
|
|
3926
|
+
flash_size = len(data_import) - flash_offset
|
|
3927
|
+
else:
|
|
3928
|
+
flash_size = args["flash_size"]
|
|
3929
|
+
data_import = data_import[:flash_size]
|
|
3930
|
+
bl_sectors = []
|
|
3931
|
+
for sector in sector_offsets:
|
|
3932
|
+
if flash_offset > sector[0]: continue
|
|
3933
|
+
#if len(bl_sectors) > 0 and (flash_offset + flash_size) < (sector[0] + sector[1]): break
|
|
3934
|
+
if (flash_offset + flash_size) < sector[0]: break
|
|
3935
|
+
bl_sectors.append(sector)
|
|
3936
|
+
write_sectors = bl_sectors
|
|
3937
|
+
if "bl_save" in args:
|
|
3938
|
+
data_import = bytearray([0] * bl_sectors[0][0]) + data_import
|
|
3939
|
+
else:
|
|
3940
|
+
write_sectors = []
|
|
3941
|
+
for item in sector_offsets:
|
|
3942
|
+
if item[0] < len(data_import):
|
|
3943
|
+
write_sectors.append(item)
|
|
3944
|
+
|
|
3945
|
+
dprint("Sectors to update:", write_sectors)
|
|
3946
|
+
# ↑↑↑ Read Sector Map
|
|
3947
|
+
|
|
3948
|
+
# ↓↓↓ Chip erase
|
|
3949
|
+
chip_erase = False
|
|
3950
|
+
if flashcart.SupportsChipErase() and not flash_offset > 0:
|
|
3951
|
+
if flashcart.SupportsSectorErase() and args["prefer_chip_erase"] is False and sector_map is not False:
|
|
3952
|
+
chip_erase = False
|
|
3953
|
+
else:
|
|
3954
|
+
chip_erase = True
|
|
3955
|
+
dprint("Erasing the entire flash chip")
|
|
3956
|
+
if flashcart.ChipErase() is False:
|
|
3957
|
+
return False
|
|
3958
|
+
elif flashcart.SupportsSectorErase() is False:
|
|
3959
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"No erase method available.", "abortable":False})
|
|
3960
|
+
return False
|
|
3961
|
+
# ↑↑↑ Chip erase
|
|
3962
|
+
|
|
3963
|
+
# ↓↓↓ Flash Write
|
|
3964
|
+
if chip_erase:
|
|
3965
|
+
write_sectors = [[ 0, len(data_import) ]]
|
|
3966
|
+
for i in range(0, len(data_import), 0x20000):
|
|
3967
|
+
verify_sectors.append([i, 0x20000])
|
|
3968
|
+
elif len(write_sectors) == 0:
|
|
3969
|
+
write_sectors = sector_offsets
|
|
3970
|
+
|
|
3971
|
+
self.SetProgress({"action":"INITIALIZE", "method":"ROM_WRITE", "size":len(data_import), "flash_offset":flash_offset, "sector_count":len(write_sectors)})
|
|
3972
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":flash_offset})
|
|
3973
|
+
self.INFO["action"] = self.ACTIONS["ROM_WRITE"]
|
|
3974
|
+
|
|
3975
|
+
if smallest_sector_size is not False:
|
|
3976
|
+
buffer_len = smallest_sector_size
|
|
3977
|
+
elif self.MODE == "DMG":
|
|
3978
|
+
buffer_len = _mbc.GetROMBankSize()
|
|
3979
|
+
if _mbc.HasFlashBanks(): _mbc.SelectBankFlash(0)
|
|
3980
|
+
else:
|
|
3981
|
+
buffer_len = 0x2000
|
|
3982
|
+
dprint("Transfer buffer length is 0x{:X}".format(buffer_len))
|
|
3983
|
+
|
|
3984
|
+
current_bank = 0
|
|
3985
|
+
start_bank = 0
|
|
3986
|
+
start_address = 0
|
|
3987
|
+
buffer_pos = 0
|
|
3988
|
+
retry_hp = 0
|
|
3989
|
+
end_address = len(data_import)
|
|
3990
|
+
dprint("ROM banks:", end_bank)
|
|
3991
|
+
|
|
3992
|
+
if len(write_sectors) == 0:
|
|
3993
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"Coulnd’t start writing ROM because the flash cart couldn’t be detected properly.", "abortable":False})
|
|
3994
|
+
return False
|
|
3995
|
+
|
|
3996
|
+
for sector in write_sectors:
|
|
3997
|
+
if chip_erase is False:
|
|
3998
|
+
if retry_hp == 0:
|
|
3999
|
+
retry_hp = 15 # First sector
|
|
4000
|
+
else:
|
|
4001
|
+
retry_hp = 100 # Other sectors
|
|
4002
|
+
|
|
4003
|
+
if self.MODE == "AGB":
|
|
4004
|
+
dprint("Writing sector:", hex(sector[0]), hex(sector[1]))
|
|
4005
|
+
buffer_pos = sector[0]
|
|
4006
|
+
start_address = buffer_pos
|
|
4007
|
+
end_address = sector[0] + sector[1]
|
|
4008
|
+
if sector[:2] not in sector_offsets:
|
|
4009
|
+
dprint("Sector not found for delta writing:", sector)
|
|
4010
|
+
continue
|
|
4011
|
+
sector_pos = sector_offsets.index(sector[:2])
|
|
4012
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
4013
|
+
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
|
4014
|
+
# print(hex(start_address), hex(end_address), start_bank, end_bank)
|
|
4015
|
+
# print(hex(sector_offsets[sector_pos][0]), sector_pos)
|
|
4016
|
+
# print("")
|
|
4017
|
+
elif self.MODE == "DMG":
|
|
4018
|
+
dprint("Writing sector:", hex(sector[0]), hex(sector[1]))
|
|
4019
|
+
buffer_pos = sector[0]
|
|
4020
|
+
#end_address = sector[0] + sector[1]
|
|
4021
|
+
if sector[:2] not in sector_offsets:
|
|
4022
|
+
print("Sector not found for delta writing:", sector)
|
|
4023
|
+
continue
|
|
4024
|
+
sector_pos = sector_offsets.index(sector[:2])
|
|
4025
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
4026
|
+
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
|
4027
|
+
# print(hex(start_address), hex(end_address), start_bank, end_bank)
|
|
4028
|
+
# print(hex(sector_offsets[sector_pos][0]), sector_pos)
|
|
4029
|
+
# print("")
|
|
4030
|
+
|
|
4031
|
+
#for bank in range(start_bank, end_bank):
|
|
4032
|
+
bank = start_bank
|
|
4033
|
+
while bank < end_bank:
|
|
4034
|
+
if self.CANCEL:
|
|
4035
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
4036
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
4037
|
+
self.CANCEL_ARGS = {}
|
|
4038
|
+
self.ERROR_ARGS = {}
|
|
4039
|
+
self.SetProgress(cancel_args)
|
|
4040
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
4041
|
+
return
|
|
4042
|
+
|
|
4043
|
+
status = None
|
|
4044
|
+
#print("Bank:", bank, "...")
|
|
4045
|
+
# ↓↓↓ Switch ROM bank
|
|
4046
|
+
if self.MODE == "DMG":
|
|
4047
|
+
if _mbc.ResetBeforeBankChange(bank) is True:
|
|
4048
|
+
dprint("Resetting the MBC")
|
|
4049
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
4050
|
+
(start_address, bank_size) = _mbc.SelectBankROM(bank)
|
|
4051
|
+
if flashcart.PulseResetAfterWrite():
|
|
4052
|
+
if bank == 0:
|
|
4053
|
+
if self.FW["fw_ver"] < 2:
|
|
4054
|
+
self._write(self.DEVICE_CMD["OFW_GB_CART_MODE"])
|
|
4055
|
+
else:
|
|
4056
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
4057
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
4058
|
+
# else:
|
|
4059
|
+
# self._write(self.DEVICE_CMD["OFW_GB_FLASH_BANK_1_COMMAND_WRITES"])
|
|
4060
|
+
self._set_fw_variable("DMG_ROM_BANK", bank)
|
|
4061
|
+
|
|
4062
|
+
buffer_len = min(buffer_len, bank_size)
|
|
4063
|
+
if "start_addr" in flashcart.CONFIG and bank == 0: start_address = flashcart.CONFIG["start_addr"]
|
|
4064
|
+
end_address = start_address + bank_size
|
|
4065
|
+
start_address += (buffer_pos % rom_bank_size)
|
|
4066
|
+
if end_address > start_address + sector[1]:
|
|
4067
|
+
end_address = start_address + sector[1]
|
|
4068
|
+
|
|
4069
|
+
elif self.MODE == "AGB":
|
|
4070
|
+
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
|
4071
|
+
if bank != current_bank:
|
|
4072
|
+
flashcart.Reset(full_reset=True)
|
|
4073
|
+
flashcart.SelectBankROM(bank)
|
|
4074
|
+
temp = end_address - start_address
|
|
4075
|
+
start_address %= cart_type["flash_bank_size"]
|
|
4076
|
+
end_address = min(cart_type["flash_bank_size"], start_address + temp)
|
|
4077
|
+
current_bank = bank
|
|
4078
|
+
# ↑↑↑ Switch ROM bank
|
|
4079
|
+
|
|
4080
|
+
skip_init = False
|
|
4081
|
+
pos = start_address
|
|
4082
|
+
dprint("buffer_pos=0x{:X}, start_address=0x{:X}, end_address=0x{:X}".format(buffer_pos, start_address, end_address))
|
|
4083
|
+
|
|
4084
|
+
while pos < end_address:
|
|
4085
|
+
if self.CANCEL:
|
|
4086
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
4087
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
4088
|
+
self.CANCEL_ARGS = {}
|
|
4089
|
+
self.ERROR_ARGS = {}
|
|
4090
|
+
self.SetProgress(cancel_args)
|
|
4091
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
4092
|
+
return
|
|
4093
|
+
|
|
4094
|
+
if buffer_pos >= len(data_import): break
|
|
4095
|
+
|
|
4096
|
+
# ↓↓↓ Sector erase
|
|
4097
|
+
se_ret = None
|
|
4098
|
+
if chip_erase is False:
|
|
4099
|
+
if sector_pos < len(sector_offsets) and buffer_pos == sector_offsets[sector_pos][0]:
|
|
4100
|
+
len_rest = end_address - pos
|
|
4101
|
+
skip_se = False
|
|
4102
|
+
ts_se_start = time.time()
|
|
4103
|
+
if "compare_sectors" in args and args["compare_sectors"] is True and sector[1] <= len_rest and not (flashcart and cart_type["command_set"] == "GBAMP"):
|
|
4104
|
+
verified = self.CompareCRC32(buffer=data_import, offset=sector[0], length=sector[1], address=start_address, flashcart=flashcart, reset=True)
|
|
4105
|
+
if verified is True:
|
|
4106
|
+
skip_se = True
|
|
4107
|
+
elif verified is not True and len(verified) == 2:
|
|
4108
|
+
dprint("Writing sector #{:d}, because the CRC32 didn’t match: 0x{:X} ≠ 0x{:X}".format(sector_pos, verified[0], verified[1]))
|
|
4109
|
+
verified = False
|
|
4110
|
+
skip_se = False
|
|
4111
|
+
|
|
4112
|
+
if self.CANCEL:
|
|
4113
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
4114
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
4115
|
+
self.CANCEL_ARGS = {}
|
|
4116
|
+
self.ERROR_ARGS = {}
|
|
4117
|
+
self.SetProgress(cancel_args)
|
|
4118
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
4119
|
+
return
|
|
4120
|
+
|
|
4121
|
+
if skip_se is True:
|
|
4122
|
+
dprint("Skipping sector #{:d}, because the CRC32 matched".format(sector_pos))
|
|
4123
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "sector_pos":sector_pos, "force_update":True})
|
|
4124
|
+
else:
|
|
4125
|
+
if sector not in verify_sectors:
|
|
4126
|
+
verify_sectors.append(sector)
|
|
4127
|
+
dprint("Erasing sector #{:d} at position 0x{:X} (0x{:X})".format(sector_pos, buffer_pos, pos))
|
|
4128
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "force_update":True})
|
|
4129
|
+
if self.CanPowerCycleCart(): self.CartPowerOn()
|
|
4130
|
+
|
|
4131
|
+
sector_pos += 1
|
|
4132
|
+
if flashcart.FlashCommandsOnBank1(): _mbc.SelectBankROM(bank)
|
|
4133
|
+
self.NO_PROG_UPDATE = True
|
|
4134
|
+
se_ret = flashcart.SectorErase(pos=pos, buffer_pos=buffer_pos, skip=skip_se)
|
|
4135
|
+
self.NO_PROG_UPDATE = False
|
|
4136
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
|
4137
|
+
continue
|
|
4138
|
+
|
|
4139
|
+
ts_se_elapsed = time.time() - ts_se_start
|
|
4140
|
+
if se_ret:
|
|
4141
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos, "sector_pos":sector_pos, "sector_erase_time":ts_se_elapsed, "force_update":True})
|
|
4142
|
+
sector_size = se_ret
|
|
4143
|
+
dprint("Next sector size: 0x{:X}".format(sector_size))
|
|
4144
|
+
skip_init = False
|
|
4145
|
+
|
|
4146
|
+
if skip_se:
|
|
4147
|
+
buffer_pos += sector_size
|
|
4148
|
+
pos += sector_size
|
|
4149
|
+
continue
|
|
4150
|
+
# ↑↑↑ Sector erase
|
|
4151
|
+
|
|
4152
|
+
if se_ret is not False:
|
|
4153
|
+
if command_set_type == "GBMEMORY" and self.FW["fw_ver"] < 2:
|
|
4154
|
+
status = self.WriteROM_GBMEMORY(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
|
4155
|
+
elif command_set_type == "GBMEMORY" and self.FW["fw_ver"] >= 2:
|
|
4156
|
+
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING))
|
|
4157
|
+
elif command_set_type == "DMG-MBC5-32M-FLASH" and self.FW["fw_ver"] < 12:
|
|
4158
|
+
status = self.WriteROM_DMG_MBC5_32M_FLASH(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
|
4159
|
+
#elif command_set_type == "DMG-MBC5-32M-FLASH" and self.FW["fw_ver"] >= 12:
|
|
4160
|
+
# status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING))
|
|
4161
|
+
elif command_set_type == "EEPROM":
|
|
4162
|
+
status = self.WriteROM_DMG_EEPROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank, eeprom_buffer_size=256)
|
|
4163
|
+
elif command_set_type == "BLAZE_XPLODER":
|
|
4164
|
+
status = self.WriteROM_DMG_EEPROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
|
4165
|
+
elif command_set_type == "DATEL_ORBITV2" and self.FW["fw_ver"] >= 12:
|
|
4166
|
+
status = self.WriteROM(address=(pos | (bank << 24)), buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING))
|
|
4167
|
+
elif command_set_type == "DATEL_ORBITV2" and self.FW["fw_ver"] < 12:
|
|
4168
|
+
status = self.WriteROM_DMG_DatelOrbitV2(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], bank=bank)
|
|
4169
|
+
else:
|
|
4170
|
+
max_buffer_write = self.MAX_BUFFER_WRITE
|
|
4171
|
+
if (len(data_import) == 0x1FFFF00) and (buffer_pos+buffer_len > len(data_import)):
|
|
4172
|
+
# 32 MiB ROM + EEPROM cart
|
|
4173
|
+
max_buffer_write = 256
|
|
4174
|
+
buffer_len = (buffer_pos+buffer_len - len(data_import))
|
|
4175
|
+
status = self.WriteROM(address=pos, buffer=data_import[buffer_pos:buffer_pos+buffer_len], flash_buffer_size=flash_buffer_size, skip_init=(skip_init and not self.SKIPPING), rumble_stop=rumble, max_length=max_buffer_write)
|
|
4176
|
+
|
|
4177
|
+
if status is False or se_ret is False:
|
|
4178
|
+
self.CANCEL = True
|
|
4179
|
+
self.ERROR = True
|
|
4180
|
+
sr = "Unknown"
|
|
4181
|
+
if self.FW["fw_ver"] >= 12:
|
|
4182
|
+
if se_ret is False:
|
|
4183
|
+
sr = flashcart.LAST_SR
|
|
4184
|
+
if isinstance(sr, int):
|
|
4185
|
+
if sr < 0x100:
|
|
4186
|
+
sr = f"0x{sr:02X} ({sr>>4:04b} {sr&0xF:04b})"
|
|
4187
|
+
else:
|
|
4188
|
+
sr = f"0x{sr:04X} ({sr>>8:08b} {sr&0xFF:08b})"
|
|
4189
|
+
else:
|
|
4190
|
+
lives = 3
|
|
4191
|
+
while lives > 0:
|
|
4192
|
+
dprint("Retrieving last status register value...")
|
|
4193
|
+
self.DEVICE.reset_input_buffer()
|
|
4194
|
+
self.DEVICE.reset_output_buffer()
|
|
4195
|
+
sr = self._get_fw_variable("STATUS_REGISTER")
|
|
4196
|
+
if sr not in (False, None):
|
|
4197
|
+
if sr < 0x100:
|
|
4198
|
+
sr = f"0x{sr:02X} ({sr>>4:04b} {sr&0xF:04b})"
|
|
4199
|
+
else:
|
|
4200
|
+
sr = f"0x{sr:04X} ({sr>>8:08b} {sr&0xFF:08b})"
|
|
4201
|
+
break
|
|
4202
|
+
dprint("Erroneous response:", sr)
|
|
4203
|
+
lives -= 1
|
|
4204
|
+
if lives == 0:
|
|
4205
|
+
sr = "Timeout"
|
|
4206
|
+
dprint("Last status register value:", sr)
|
|
4207
|
+
|
|
4208
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
|
4209
|
+
break
|
|
4210
|
+
elif not self.DEVICE.is_open or self.DEVICE is None:
|
|
4211
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"An error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.\n\nTroubleshooting advice:\n- Clean cartridge contacts\n- Avoid passive USB hubs and try different USB ports/cables\n- Check cartridge type selection{:s}\n\nStatus Register: {:s}".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), errmsg_mbc_selection, sr)})
|
|
4212
|
+
break
|
|
4213
|
+
else:
|
|
4214
|
+
if chip_erase: retry_hp = 0
|
|
4215
|
+
if "iteration" in self.ERROR_ARGS and self.ERROR_ARGS["iteration"] > 0:
|
|
4216
|
+
retry_hp -= 5
|
|
4217
|
+
if retry_hp <= 0:
|
|
4218
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"Unstable connection detected while writing 0x{:X} bytes in iteration {:d} at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.\n\nTroubleshooting advice:\n- Clean cartridge contacts\n- Avoid passive USB hubs and try different USB ports/cables\n\nStatus Register: {:s}".format(buffer_len, self.ERROR_ARGS["iteration"], buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), sr)})
|
|
4219
|
+
continue
|
|
4220
|
+
else:
|
|
4221
|
+
retry_hp -= 10
|
|
4222
|
+
if retry_hp <= 0:
|
|
4223
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_critical", "info_msg":"An error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.\n\nTroubleshooting advice:\n- Clean cartridge contacts\n- Avoid passive USB hubs and try different USB ports/cables\n- Check cartridge type selection\n- Check cartridge ROM storage size (at least {:s} is required){:s}\n- Your {:s} may also be incompatible with this cartridge\n\nStatus Register: {:s}".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False), Util.formatFileSize(size=len(data_import), asInt=False), errmsg_mbc_selection, self.DEVICE_NAME, sr), "abortable":False})
|
|
4224
|
+
continue
|
|
4225
|
+
|
|
4226
|
+
rev_buffer_pos = sector_offsets[sector_pos - 1][0]
|
|
4227
|
+
buffer_pos = rev_buffer_pos
|
|
4228
|
+
bank = start_bank
|
|
4229
|
+
sector_pos -= 1
|
|
4230
|
+
err_text = "Write error! Retrying from 0x{:X}...".format(rev_buffer_pos)
|
|
4231
|
+
print("{:s}{:s}{:s}".format(ANSI.RED, err_text, ANSI.RESET))
|
|
4232
|
+
dprint(err_text, "Bank {:d} | HP: {:d}/100".format(bank, retry_hp))
|
|
4233
|
+
pos = end_address
|
|
4234
|
+
status = False
|
|
4235
|
+
|
|
4236
|
+
self.SetProgress({"action":"ERROR", "abortable":True, "pos":buffer_pos, "text":err_text})
|
|
4237
|
+
delay = 0.5 # + (100-retry_hp)/100
|
|
4238
|
+
if self.CanPowerCycleCart():
|
|
4239
|
+
self.CartPowerOff()
|
|
4240
|
+
time.sleep(delay)
|
|
4241
|
+
self.CartPowerOn()
|
|
4242
|
+
if self.MODE == "DMG" and _mbc.HasFlashBanks(): _mbc.SelectBankFlash(bank)
|
|
4243
|
+
time.sleep(delay)
|
|
4244
|
+
if self.DEVICE is None:
|
|
4245
|
+
raise ConnectionAbortedError("A critical connection error occured while writing 0x{:X} bytes at position 0x{:X} ({:s}). Please re-connect the device and try again from the beginning.".format(buffer_len, buffer_pos, Util.formatFileSize(size=buffer_pos, asInt=False)))
|
|
4246
|
+
|
|
4247
|
+
self.ERROR = False
|
|
4248
|
+
if "from_user" in self.CANCEL_ARGS and self.CANCEL_ARGS["from_user"]:
|
|
4249
|
+
self.CANCEL_ARGS.update({"info_type":"msgbox_warning", "info_msg":"The erroneous process has been stopped.", "abortable":False})
|
|
4250
|
+
break
|
|
4251
|
+
self.CANCEL = False
|
|
4252
|
+
self.CANCEL_ARGS = {}
|
|
4253
|
+
self.DEVICE.reset_input_buffer()
|
|
4254
|
+
self.DEVICE.reset_output_buffer()
|
|
4255
|
+
self._cart_write(pos, 0xF0)
|
|
4256
|
+
self._cart_write(pos, 0xFF)
|
|
4257
|
+
if flashcart.Unlock() is False: return False
|
|
4258
|
+
continue
|
|
4259
|
+
|
|
4260
|
+
self.CANCEL = True
|
|
4261
|
+
self.ERROR = True
|
|
4262
|
+
continue
|
|
4263
|
+
|
|
4264
|
+
skip_init = True
|
|
4265
|
+
|
|
4266
|
+
buffer_pos += buffer_len
|
|
4267
|
+
pos += buffer_len
|
|
4268
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos})
|
|
4269
|
+
|
|
4270
|
+
if status is not False:
|
|
4271
|
+
bank += 1
|
|
4272
|
+
|
|
4273
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":len(data_import), "force_update":True})
|
|
4274
|
+
# ↑↑↑ Flash write
|
|
4275
|
+
|
|
4276
|
+
# ↓↓↓ GB-Memory Hidden Sector
|
|
4277
|
+
if command_set_type == "GBMEMORY":
|
|
4278
|
+
if flashcart.EraseHiddenSector(buffer=data_map_import) is False:
|
|
4279
|
+
return False
|
|
4280
|
+
status = self.WriteROM_GBMEMORY(address=0, buffer=data_map_import[0:128], bank=1)
|
|
4281
|
+
if status is False:
|
|
4282
|
+
self.SetProgress({"action":"ABORT", "info_type":"msgbox_critical", "info_msg":"An error occured while writing the hidden sector. Please make sure that the cartridge contacts are clean, re-connect the device and try again from the beginning.", "abortable":False})
|
|
4283
|
+
return False
|
|
4284
|
+
# ↑↑↑ GB-Memory Hidden Sector
|
|
4285
|
+
|
|
4286
|
+
# ↓↓↓ Reset flash
|
|
4287
|
+
flashcart.Reset(full_reset=True)
|
|
4288
|
+
# ↑↑↑ Reset flash
|
|
4289
|
+
|
|
4290
|
+
# ↓↓↓ Flash verify
|
|
4291
|
+
verified = False
|
|
4292
|
+
crc32_errors = 0
|
|
4293
|
+
if "broken_sectors" in self.INFO: del(self.INFO["broken_sectors"])
|
|
4294
|
+
if "verify_write" in args and args["verify_write"] is True:
|
|
4295
|
+
self.SetProgress({"action":"INITIALIZE", "method":"ROM_WRITE_VERIFY", "size":len(data_import), "flash_offset":flash_offset})
|
|
4296
|
+
if ".dev" in Util.VERSION_PEP440 or Util.DEBUG:
|
|
4297
|
+
with open("debug_verify.bin", "wb") as f: pass
|
|
4298
|
+
|
|
4299
|
+
current_bank = None
|
|
4300
|
+
broken_sectors = []
|
|
4301
|
+
|
|
4302
|
+
for sector in verify_sectors:
|
|
4303
|
+
if self.CANCEL:
|
|
4304
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
4305
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
4306
|
+
self.CANCEL_ARGS = {}
|
|
4307
|
+
self.ERROR_ARGS = {}
|
|
4308
|
+
self.SetProgress(cancel_args)
|
|
4309
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
4310
|
+
return
|
|
4311
|
+
|
|
4312
|
+
if sector[0] >= len(data_import): break
|
|
4313
|
+
|
|
4314
|
+
verified = False
|
|
4315
|
+
if self.FW["fw_ver"] >= 10 and not (flashcart and cart_type["command_set"] == "GBAMP"):
|
|
4316
|
+
if self.MODE == "AGB":
|
|
4317
|
+
dprint("Verifying sector:", hex(sector[0]), hex(sector[1]))
|
|
4318
|
+
buffer_pos = sector[0]
|
|
4319
|
+
start_address = buffer_pos
|
|
4320
|
+
end_address = sector[0] + sector[1]
|
|
4321
|
+
if not chip_erase:
|
|
4322
|
+
sector_pos = sector_offsets.index(sector[:2])
|
|
4323
|
+
else:
|
|
4324
|
+
sector_pos = 0
|
|
4325
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
4326
|
+
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
|
4327
|
+
elif self.MODE == "DMG":
|
|
4328
|
+
dprint("Verifying sector:", hex(sector[0]), hex(sector[1]))
|
|
4329
|
+
buffer_pos = sector[0]
|
|
4330
|
+
if not chip_erase:
|
|
4331
|
+
sector_pos = sector_offsets.index(sector[:2])
|
|
4332
|
+
else:
|
|
4333
|
+
sector_pos = 0
|
|
4334
|
+
start_bank = math.floor(buffer_pos / rom_bank_size)
|
|
4335
|
+
end_bank = math.ceil((buffer_pos + sector[1]) / rom_bank_size)
|
|
4336
|
+
|
|
4337
|
+
bank = start_bank
|
|
4338
|
+
while bank < end_bank:
|
|
4339
|
+
# ↓↓↓ Switch ROM bank
|
|
4340
|
+
if self.MODE == "DMG":
|
|
4341
|
+
if _mbc.ResetBeforeBankChange(bank) is True:
|
|
4342
|
+
dprint("Resetting the MBC")
|
|
4343
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
4344
|
+
(start_address, bank_size) = _mbc.SelectBankROM(bank)
|
|
4345
|
+
verify_len = bank_size
|
|
4346
|
+
if flashcart.PulseResetAfterWrite():
|
|
4347
|
+
if bank == 0:
|
|
4348
|
+
if self.FW["fw_ver"] < 2:
|
|
4349
|
+
self._write(self.DEVICE_CMD["OFW_GB_CART_MODE"])
|
|
4350
|
+
else:
|
|
4351
|
+
self._write(self.DEVICE_CMD["SET_MODE_DMG"], wait=self.FW["fw_ver"] >= 12)
|
|
4352
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
4353
|
+
# else:
|
|
4354
|
+
# self._write(self.DEVICE_CMD["OFW_GB_FLASH_BANK_1_COMMAND_WRITES"])
|
|
4355
|
+
self._set_fw_variable("DMG_ROM_BANK", bank)
|
|
4356
|
+
|
|
4357
|
+
buffer_len = min(buffer_len, bank_size)
|
|
4358
|
+
if "start_addr" in flashcart.CONFIG and bank == 0: start_address = flashcart.CONFIG["start_addr"]
|
|
4359
|
+
end_address = start_address + bank_size
|
|
4360
|
+
start_address += (buffer_pos % rom_bank_size)
|
|
4361
|
+
if end_address > start_address + sector[1]:
|
|
4362
|
+
end_address = start_address + sector[1]
|
|
4363
|
+
pos_from = (bank * start_address)
|
|
4364
|
+
# pos_to = (bank * start_address) + verify_len
|
|
4365
|
+
|
|
4366
|
+
elif self.MODE == "AGB":
|
|
4367
|
+
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
|
4368
|
+
if bank != current_bank:
|
|
4369
|
+
flashcart.Reset(full_reset=True)
|
|
4370
|
+
flashcart.SelectBankROM(bank)
|
|
4371
|
+
temp = end_address - start_address
|
|
4372
|
+
start_address %= cart_type["flash_bank_size"]
|
|
4373
|
+
end_address = min(cart_type["flash_bank_size"], start_address + temp)
|
|
4374
|
+
current_bank = bank
|
|
4375
|
+
verify_len = sector[1]
|
|
4376
|
+
pos_from = sector[0]
|
|
4377
|
+
# pos_to = pos_from + verify_len
|
|
4378
|
+
# ↑↑↑ Switch ROM bank
|
|
4379
|
+
|
|
4380
|
+
dprint(f"Verifying ROM bank #{bank} at 0x{pos_from:x} (physical 0x{start_address:X}, 0x{verify_len:X} bytes)")
|
|
4381
|
+
|
|
4382
|
+
verified = False
|
|
4383
|
+
if sector[1] >= verify_len and crc32_errors < 5:
|
|
4384
|
+
verified = self.CompareCRC32(buffer=data_import, offset=pos_from, length=verify_len, address=start_address, flashcart=flashcart, reset=False)
|
|
4385
|
+
if verified is True:
|
|
4386
|
+
dprint("CRC32 verification successful between 0x{:X} and 0x{:X}".format(pos_from, verify_len))
|
|
4387
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":pos_from + verify_len})
|
|
4388
|
+
elif verified is not True and len(verified) == 2:
|
|
4389
|
+
crc32_errors += 1
|
|
4390
|
+
dprint("Mismatch during CRC32 verification at 0x{:X}".format(pos_from), "Errors:", crc32_errors)
|
|
4391
|
+
verified = False
|
|
4392
|
+
break
|
|
4393
|
+
|
|
4394
|
+
if verified is False: break
|
|
4395
|
+
|
|
4396
|
+
else:
|
|
4397
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":buffer_pos})
|
|
4398
|
+
|
|
4399
|
+
bank += 1
|
|
4400
|
+
|
|
4401
|
+
if not verified:
|
|
4402
|
+
verify_args = copy.copy(args)
|
|
4403
|
+
verify_args.update({"verify_write":data_import[sector[0]:sector[0]+sector[1]], "rom_size":len(data_import), "verify_from":sector[0], "path":"", "rtc_area":flashcart.HasRTC(), "verify_mbc":_mbc})
|
|
4404
|
+
verify_args["verify_base_pos"] = sector[0]
|
|
4405
|
+
verify_args["verify_len"] = len(verify_args["verify_write"])
|
|
4406
|
+
verify_args["rom_size"] = len(verify_args["verify_write"])
|
|
4407
|
+
|
|
4408
|
+
self.NO_PROG_UPDATE = True
|
|
4409
|
+
self.ReadROM(0, 4) # dummy read
|
|
4410
|
+
self.NO_PROG_UPDATE = False
|
|
4411
|
+
start_address = 0
|
|
4412
|
+
end_address = buffer_pos
|
|
4413
|
+
|
|
4414
|
+
verified_size = self._BackupROM(verify_args)
|
|
4415
|
+
if isinstance(verified_size, int): dprint("args[\"verify_len\"]=0x{:X}, verified_size=0x{:X}".format(verify_args["verify_len"], verified_size))
|
|
4416
|
+
if self.CANCEL or self.ERROR:
|
|
4417
|
+
cancel_args = {"action":"ABORT", "abortable":False}
|
|
4418
|
+
cancel_args.update(self.CANCEL_ARGS)
|
|
4419
|
+
self.CANCEL_ARGS = {}
|
|
4420
|
+
self.ERROR_ARGS = {}
|
|
4421
|
+
self.SetProgress(cancel_args)
|
|
4422
|
+
if self.CanPowerCycleCart(): self.CartPowerCycle()
|
|
4423
|
+
verified = False
|
|
4424
|
+
return
|
|
4425
|
+
elif (verified_size is not True) and (verify_args["verify_len"] != verified_size):
|
|
4426
|
+
if verified_size is None:
|
|
4427
|
+
print("{:s}Verification failed! Sector: {:s}{:s}".format(ANSI.RED, str(sector), ANSI.RESET))
|
|
4428
|
+
else:
|
|
4429
|
+
print("{:s}Verification failed at 0x{:X}! Sector: {:s}{:s}".format(ANSI.RED, sector[0]+verified_size, str(sector), ANSI.RESET))
|
|
4430
|
+
if sector not in broken_sectors:
|
|
4431
|
+
broken_sectors.append(sector)
|
|
4432
|
+
continue
|
|
4433
|
+
else:
|
|
4434
|
+
dprint("Verification between 0x{:X} and 0x{:X} successful by normal reading.".format(sector[0], sector[0]+sector[1]))
|
|
4435
|
+
verified = True
|
|
4436
|
+
|
|
4437
|
+
self.SetProgress({"action":"UPDATE_POS", "pos":len(data_import), "force_update":True})
|
|
4438
|
+
if len(broken_sectors) > 0:
|
|
4439
|
+
self.INFO["broken_sectors"] = broken_sectors
|
|
4440
|
+
self.INFO["verify_error_params"] = {}
|
|
4441
|
+
self.INFO["verify_error_params"]["rom_size"] = len(data_import)
|
|
4442
|
+
if self.MODE == "DMG":
|
|
4443
|
+
self.INFO["verify_error_params"]["mapper_name"] = _mbc.GetName()
|
|
4444
|
+
if flashcart.GetMBC() == "manual":
|
|
4445
|
+
self.INFO["verify_error_params"]["mapper_selection_type"] = 1 # manual
|
|
4446
|
+
else:
|
|
4447
|
+
self.INFO["verify_error_params"]["mapper_selection_type"] = 2 # forced by cart type
|
|
4448
|
+
self.INFO["verify_error_params"]["mapper_max_size"] = _mbc.GetMaxROMSize()
|
|
4449
|
+
verified = False
|
|
4450
|
+
# ↑↑↑ Flash verify
|
|
4451
|
+
|
|
4452
|
+
if delta_state_new is not None and not chip_erase and not "broken_sectors" in self.INFO:
|
|
4453
|
+
try:
|
|
4454
|
+
with open(json_file, "wb") as f:
|
|
4455
|
+
f.write(json.dumps(delta_state_new).encode("UTF-8-SIG"))
|
|
4456
|
+
except PermissionError:
|
|
4457
|
+
print("Error: Couldn’t update write-protected file “{:s}”".format(json_file))
|
|
4458
|
+
|
|
4459
|
+
# ↓↓↓ Switch to first ROM bank
|
|
4460
|
+
if self.MODE == "DMG":
|
|
4461
|
+
if _mbc.ResetBeforeBankChange(0) is True:
|
|
4462
|
+
dprint("Resetting the MBC")
|
|
4463
|
+
self._write(self.DEVICE_CMD["DMG_MBC_RESET"], wait=True)
|
|
4464
|
+
_mbc.SelectBankROM(0)
|
|
4465
|
+
self._set_fw_variable("DMG_ROM_BANK", 0)
|
|
4466
|
+
elif self.MODE == "AGB":
|
|
4467
|
+
if "flash_bank_select_type" in cart_type and cart_type["flash_bank_select_type"] > 0:
|
|
4468
|
+
flashcart.SelectBankROM(0)
|
|
4469
|
+
# ↑↑↑ Switch to first ROM bank
|
|
4470
|
+
|
|
4471
|
+
self.SetMode(self.MODE)
|
|
4472
|
+
|
|
4473
|
+
self.INFO["last_action"] = self.INFO["action"]
|
|
4474
|
+
self.INFO["action"] = None
|
|
4475
|
+
self.SetProgress({"action":"FINISHED", "verified":verified})
|
|
4476
|
+
return True
|
|
4477
|
+
|
|
4478
|
+
#################################################################
|
|
4479
|
+
|
|
4480
|
+
def TransferData(self, args, signal):
|
|
4481
|
+
self.ERROR = False
|
|
4482
|
+
self.CANCEL = False
|
|
4483
|
+
self.CANCEL_ARGS = {}
|
|
4484
|
+
self.READ_ERRORS = 0
|
|
4485
|
+
self.WRITE_ERRORS = 0
|
|
4486
|
+
if self.IsConnected():
|
|
4487
|
+
_apoe = False
|
|
4488
|
+
if self.CanPowerCycleCart():
|
|
4489
|
+
self.CartPowerOn()
|
|
4490
|
+
if self.FW["fw_ver"] >= 12:
|
|
4491
|
+
_apoe = self._get_fw_variable("AUTO_POWEROFF_ENABLED") == 1
|
|
4492
|
+
if _apoe is True:
|
|
4493
|
+
_apot = self._get_fw_variable("AUTO_POWEROFF_TIME")
|
|
4494
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", 5000)
|
|
4495
|
+
|
|
4496
|
+
ret = False
|
|
4497
|
+
self.SIGNAL = signal
|
|
4498
|
+
try:
|
|
4499
|
+
temp = copy.copy(args)
|
|
4500
|
+
if "buffer" in temp: temp["buffer"] = "(data)"
|
|
4501
|
+
dprint("args:", temp)
|
|
4502
|
+
del(temp)
|
|
4503
|
+
self.NO_PROG_UPDATE = False
|
|
4504
|
+
if args['mode'] == 1: ret = self._BackupROM(args)
|
|
4505
|
+
elif args['mode'] == 2: ret = self._BackupRestoreRAM(args)
|
|
4506
|
+
elif args['mode'] == 3: ret = self._BackupRestoreRAM(args)
|
|
4507
|
+
elif args['mode'] == 4: ret = self._FlashROM(args)
|
|
4508
|
+
elif args['mode'] == 0xFF: self.Debug()
|
|
4509
|
+
if self.FW is None: return False
|
|
4510
|
+
if self.FW["fw_ver"] >= 2 and self.FW["pcb_name"] == "GBxCart RW":
|
|
4511
|
+
if ret is True:
|
|
4512
|
+
self._write(self.DEVICE_CMD["OFW_DONE_LED_ON"])
|
|
4513
|
+
elif self.ERROR is True:
|
|
4514
|
+
self._write(self.DEVICE_CMD["OFW_ERROR_LED_ON"])
|
|
4515
|
+
|
|
4516
|
+
except serial.serialutil.SerialTimeoutException as _:
|
|
4517
|
+
print("Connection timed out. Please reconnect the device.")
|
|
4518
|
+
return False
|
|
4519
|
+
except serial.serialutil.PortNotOpenError as _:
|
|
4520
|
+
print("Connection closed.")
|
|
4521
|
+
return False
|
|
4522
|
+
|
|
4523
|
+
if self.CanPowerCycleCart() and _apoe is True:
|
|
4524
|
+
self._set_fw_variable("AUTO_POWEROFF_TIME", _apot)
|
|
4525
|
+
|
|
4526
|
+
return True
|