all repos — mgba @ e2807b39158b867d1226b211dc592d5d9a24dbba

mGBA Game Boy Advance Emulator

src/gba/savedata.c (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "savedata.h"
  7
  8#include "gba/gba.h"
  9#include "gba/serialize.h"
 10
 11#include "util/memory.h"
 12#include "util/vfs.h"
 13
 14#include <errno.h>
 15#include <fcntl.h>
 16
 17// Some testing was done here...
 18// Erase cycles can vary greatly.
 19// Some games may vary anywhere between about 2000 cycles to up to 30000 cycles. (Observed on a Macronix (09C2) chip).
 20// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip).
 21// An average estimation is as follows.
 22#define FLASH_ERASE_CYCLES 30000
 23#define FLASH_PROGRAM_CYCLES 18000
 24// This needs real testing, and is only an estimation currently
 25#define EEPROM_SETTLE_CYCLES 1450
 26#define CLEANUP_THRESHOLD 15
 27
 28mLOG_DEFINE_CATEGORY(GBA_SAVE, "GBA Savedata");
 29
 30static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
 31static void _flashErase(struct GBASavedata* savedata);
 32static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
 33
 34void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
 35	savedata->type = SAVEDATA_AUTODETECT;
 36	savedata->data = 0;
 37	savedata->command = EEPROM_COMMAND_NULL;
 38	savedata->flashState = FLASH_STATE_RAW;
 39	savedata->vf = vf;
 40	savedata->realVf = vf;
 41	savedata->mapMode = MAP_WRITE;
 42	savedata->dirty = 0;
 43	savedata->dirtAge = 0;
 44}
 45
 46void GBASavedataDeinit(struct GBASavedata* savedata) {
 47	if (savedata->vf) {
 48		size_t size = GBASavedataSize(savedata);
 49		savedata->vf->unmap(savedata->vf, savedata->data, size);
 50		savedata->vf = NULL;
 51	} else {
 52		switch (savedata->type) {
 53		case SAVEDATA_SRAM:
 54			mappedMemoryFree(savedata->data, SIZE_CART_SRAM);
 55			break;
 56		case SAVEDATA_FLASH512:
 57			mappedMemoryFree(savedata->data, SIZE_CART_FLASH512);
 58			break;
 59		case SAVEDATA_FLASH1M:
 60			mappedMemoryFree(savedata->data, SIZE_CART_FLASH1M);
 61			break;
 62		case SAVEDATA_EEPROM:
 63			mappedMemoryFree(savedata->data, SIZE_CART_EEPROM);
 64			break;
 65		case SAVEDATA_FORCE_NONE:
 66		case SAVEDATA_AUTODETECT:
 67			break;
 68		}
 69	}
 70	savedata->data = 0;
 71	savedata->type = SAVEDATA_AUTODETECT;
 72}
 73
 74void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf, bool writeback) {
 75	enum SavedataType type = savedata->type;
 76	GBASavedataDeinit(savedata);
 77	savedata->vf = vf;
 78	savedata->mapMode = MAP_READ;
 79	savedata->maskWriteback = writeback;
 80	GBASavedataForceType(savedata, type, savedata->realisticTiming);
 81}
 82
 83void GBASavedataUnmask(struct GBASavedata* savedata) {
 84	if (savedata->vf == savedata->realVf) {
 85		return;
 86	}
 87	enum SavedataType type = savedata->type;
 88	struct VFile* vf = savedata->vf;
 89	GBASavedataDeinit(savedata);
 90	savedata->vf = savedata->realVf;
 91	savedata->mapMode = MAP_WRITE;
 92	GBASavedataForceType(savedata, type, savedata->realisticTiming);
 93	if (savedata->maskWriteback) {
 94		GBASavedataLoad(savedata, vf);
 95		savedata->maskWriteback = false;
 96	}
 97	vf->close(vf);
 98}
 99
100bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
101	if (savedata->data) {
102		switch (savedata->type) {
103		case SAVEDATA_SRAM:
104			return out->write(out, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
105		case SAVEDATA_FLASH512:
106			return out->write(out, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
107		case SAVEDATA_FLASH1M:
108			return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
109		case SAVEDATA_EEPROM:
110			return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
111		case SAVEDATA_AUTODETECT:
112		case SAVEDATA_FORCE_NONE:
113			return true;
114		}
115	} else if (savedata->vf) {
116		off_t read = 0;
117		uint8_t buffer[2048];
118		do {
119			read = savedata->vf->read(savedata->vf, buffer, sizeof(buffer));
120			out->write(out, buffer, read);
121		} while (read == sizeof(buffer));
122		return read >= 0;
123	}
124	return true;
125}
126
127size_t GBASavedataSize(struct GBASavedata* savedata) {
128	switch (savedata->type) {
129	case SAVEDATA_SRAM:
130		return SIZE_CART_SRAM;
131	case SAVEDATA_FLASH512:
132		return SIZE_CART_FLASH512;
133	case SAVEDATA_FLASH1M:
134		return SIZE_CART_FLASH1M;
135	case SAVEDATA_EEPROM:
136		return SIZE_CART_EEPROM;
137	case SAVEDATA_FORCE_NONE:
138		return 0;
139	case SAVEDATA_AUTODETECT:
140	default:
141		if (savedata->vf) {
142			return savedata->vf->size(savedata->vf);
143		}
144		return 0;
145	}
146}
147
148bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
149	if (savedata->vf) {
150		off_t read = 0;
151		uint8_t buffer[2048];
152		memset(buffer, 0xFF, sizeof(buffer));
153		savedata->vf->seek(savedata->vf, 0, SEEK_SET);
154		while (savedata->vf->seek(savedata->vf, 0, SEEK_CUR) < savedata->vf->size(savedata->vf)) {
155			savedata->vf->write(savedata->vf, buffer, sizeof(buffer));
156		}
157		savedata->vf->seek(savedata->vf, 0, SEEK_SET);
158		if (in) {
159			do {
160				read = in->read(in, buffer, sizeof(buffer));
161				read = savedata->vf->write(savedata->vf, buffer, read);
162			} while (read == sizeof(buffer));
163		}
164		return read >= 0;
165	} else if (savedata->data) {
166		if (!in && savedata->type != SAVEDATA_FORCE_NONE) {
167			return false;
168		}
169		switch (savedata->type) {
170		case SAVEDATA_SRAM:
171			return in->read(in, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
172		case SAVEDATA_FLASH512:
173			return in->read(in, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
174		case SAVEDATA_FLASH1M:
175			return in->read(in, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
176		case SAVEDATA_EEPROM:
177			return in->read(in, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
178		case SAVEDATA_AUTODETECT:
179		case SAVEDATA_FORCE_NONE:
180			return true;
181		}
182	}
183	return true;
184}
185
186void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
187	if (savedata->type != SAVEDATA_AUTODETECT) {
188		struct VFile* vf = savedata->vf;
189		GBASavedataDeinit(savedata);
190		GBASavedataInit(savedata, vf);
191	}
192	switch (type) {
193	case SAVEDATA_FLASH512:
194	case SAVEDATA_FLASH1M:
195		savedata->type = type;
196		GBASavedataInitFlash(savedata, realisticTiming);
197		break;
198	case SAVEDATA_EEPROM:
199		GBASavedataInitEEPROM(savedata, realisticTiming);
200		break;
201	case SAVEDATA_SRAM:
202		GBASavedataInitSRAM(savedata);
203		break;
204	case SAVEDATA_FORCE_NONE:
205		savedata->type = SAVEDATA_FORCE_NONE;
206		break;
207	case SAVEDATA_AUTODETECT:
208		break;
209	}
210}
211
212void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) {
213	if (savedata->type == SAVEDATA_AUTODETECT) {
214		savedata->type = SAVEDATA_FLASH512;
215	}
216	if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) {
217		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
218		return;
219	}
220	int32_t flashSize = SIZE_CART_FLASH512;
221	if (savedata->type == SAVEDATA_FLASH1M) {
222		flashSize = SIZE_CART_FLASH1M;
223	}
224	off_t end;
225	if (!savedata->vf) {
226		end = 0;
227		savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M);
228	} else {
229		end = savedata->vf->size(savedata->vf);
230		if (end < flashSize) {
231			savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
232			flashSize = SIZE_CART_FLASH1M;
233		}
234		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
235	}
236
237	savedata->currentBank = savedata->data;
238	savedata->dust = 0;
239	savedata->realisticTiming = realisticTiming;
240	if (end < SIZE_CART_FLASH512) {
241		memset(&savedata->data[end], 0xFF, flashSize - end);
242	}
243}
244
245void GBASavedataInitEEPROM(struct GBASavedata* savedata, bool realisticTiming) {
246	if (savedata->type == SAVEDATA_AUTODETECT) {
247		savedata->type = SAVEDATA_EEPROM;
248	} else {
249		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
250		return;
251	}
252	off_t end;
253	if (!savedata->vf) {
254		end = 0;
255		savedata->data = anonymousMemoryMap(SIZE_CART_EEPROM);
256	} else {
257		end = savedata->vf->size(savedata->vf);
258		if (end < SIZE_CART_EEPROM) {
259			savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM);
260		}
261		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, savedata->mapMode);
262	}
263	savedata->dust = 0;
264	savedata->realisticTiming = realisticTiming;
265	if (end < SIZE_CART_EEPROM) {
266		memset(&savedata->data[end], 0xFF, SIZE_CART_EEPROM - end);
267	}
268}
269
270void GBASavedataInitSRAM(struct GBASavedata* savedata) {
271	if (savedata->type == SAVEDATA_AUTODETECT) {
272		savedata->type = SAVEDATA_SRAM;
273	} else {
274		mLOG(GBA_SAVE, WARN, "Can't re-initialize savedata");
275		return;
276	}
277	off_t end;
278	if (!savedata->vf) {
279		end = 0;
280		savedata->data = anonymousMemoryMap(SIZE_CART_SRAM);
281	} else {
282		end = savedata->vf->size(savedata->vf);
283		if (end < SIZE_CART_SRAM) {
284			savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM);
285		}
286		savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, savedata->mapMode);
287	}
288
289	if (end < SIZE_CART_SRAM) {
290		memset(&savedata->data[end], 0xFF, SIZE_CART_SRAM - end);
291	}
292}
293
294uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
295	if (savedata->command == FLASH_COMMAND_ID) {
296		if (savedata->type == SAVEDATA_FLASH512) {
297			if (address < 2) {
298				return FLASH_MFG_PANASONIC >> (address * 8);
299			}
300		} else if (savedata->type == SAVEDATA_FLASH1M) {
301			if (address < 2) {
302				return FLASH_MFG_SANYO >> (address * 8);
303			}
304		}
305	}
306	if (savedata->dust > 0 && (address >> 12) == savedata->settling) {
307		// Give some overhead for waitstates and the comparison
308		// This estimation can probably be improved
309		savedata->dust -= 5000;
310		return 0x5F;
311	}
312	return savedata->currentBank[address];
313}
314
315void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8_t value) {
316	switch (savedata->flashState) {
317	case FLASH_STATE_RAW:
318		switch (savedata->command) {
319		case FLASH_COMMAND_PROGRAM:
320			savedata->dirty |= SAVEDATA_DIRT_NEW;
321			savedata->currentBank[address] = value;
322			savedata->command = FLASH_COMMAND_NONE;
323			if (savedata->realisticTiming) {
324				savedata->dust = FLASH_PROGRAM_CYCLES;
325			}
326			break;
327		case FLASH_COMMAND_SWITCH_BANK:
328			if (address == 0 && value < 2) {
329				_flashSwitchBank(savedata, value);
330			} else {
331				mLOG(GBA_SAVE, GAME_ERROR, "Bad flash bank switch");
332				savedata->command = FLASH_COMMAND_NONE;
333			}
334			savedata->command = FLASH_COMMAND_NONE;
335			break;
336		default:
337			if (address == FLASH_BASE_HI && value == FLASH_COMMAND_START) {
338				savedata->flashState = FLASH_STATE_START;
339			} else {
340				mLOG(GBA_SAVE, GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
341			}
342			break;
343		}
344		break;
345	case FLASH_STATE_START:
346		if (address == FLASH_BASE_LO && value == FLASH_COMMAND_CONTINUE) {
347			savedata->flashState = FLASH_STATE_CONTINUE;
348		} else {
349			mLOG(GBA_SAVE, GAME_ERROR, "Bad flash write: %#04x = %#02x", address, value);
350			savedata->flashState = FLASH_STATE_RAW;
351		}
352		break;
353	case FLASH_STATE_CONTINUE:
354		savedata->flashState = FLASH_STATE_RAW;
355		if (address == FLASH_BASE_HI) {
356			switch (savedata->command) {
357			case FLASH_COMMAND_NONE:
358				switch (value) {
359				case FLASH_COMMAND_ERASE:
360				case FLASH_COMMAND_ID:
361				case FLASH_COMMAND_PROGRAM:
362				case FLASH_COMMAND_SWITCH_BANK:
363					savedata->command = value;
364					break;
365				default:
366					mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash operation: %#02x", value);
367					break;
368				}
369				break;
370			case FLASH_COMMAND_ERASE:
371				switch (value) {
372				case FLASH_COMMAND_ERASE_CHIP:
373					_flashErase(savedata);
374					break;
375				default:
376					mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
377					break;
378				}
379				savedata->command = FLASH_COMMAND_NONE;
380				break;
381			case FLASH_COMMAND_ID:
382				if (value == FLASH_COMMAND_TERMINATE) {
383					savedata->command = FLASH_COMMAND_NONE;
384				}
385				break;
386			default:
387				mLOG(GBA_SAVE, ERROR, "Flash entered bad state: %#02x", savedata->command);
388				savedata->command = FLASH_COMMAND_NONE;
389				break;
390			}
391		} else if (savedata->command == FLASH_COMMAND_ERASE) {
392			if (value == FLASH_COMMAND_ERASE_SECTOR) {
393				_flashEraseSector(savedata, address);
394				savedata->command = FLASH_COMMAND_NONE;
395			} else {
396				mLOG(GBA_SAVE, GAME_ERROR, "Unsupported flash erase operation: %#02x", value);
397			}
398		}
399		break;
400	}
401}
402
403void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize) {
404	switch (savedata->command) {
405	// Read header
406	case EEPROM_COMMAND_NULL:
407	default:
408		savedata->command = value & 0x1;
409		break;
410	case EEPROM_COMMAND_PENDING:
411		savedata->command <<= 1;
412		savedata->command |= value & 0x1;
413		if (savedata->command == EEPROM_COMMAND_WRITE) {
414			savedata->writeAddress = 0;
415		} else {
416			savedata->readAddress = 0;
417		}
418		break;
419	// Do commands
420	case EEPROM_COMMAND_WRITE:
421		// Write
422		if (writeSize > 65) {
423			savedata->writeAddress <<= 1;
424			savedata->writeAddress |= (value & 0x1) << 6;
425		} else if (writeSize == 1) {
426			savedata->command = EEPROM_COMMAND_NULL;
427		} else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) {
428			uint8_t current = savedata->data[savedata->writeAddress >> 3];
429			current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
430			current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
431			savedata->dirty |= SAVEDATA_DIRT_NEW;
432			savedata->data[savedata->writeAddress >> 3] = current;
433			if (savedata->realisticTiming) {
434				savedata->dust = EEPROM_SETTLE_CYCLES;
435			}
436			++savedata->writeAddress;
437		} else {
438			mLOG(GBA_SAVE, GAME_ERROR, "Writing beyond end of EEPROM: %08X", (savedata->writeAddress >> 3));
439		}
440		break;
441	case EEPROM_COMMAND_READ_PENDING:
442		// Read
443		if (writeSize > 1) {
444			savedata->readAddress <<= 1;
445			if (value & 0x1) {
446				savedata->readAddress |= 0x40;
447			}
448		} else {
449			savedata->readBitsRemaining = 68;
450			savedata->command = EEPROM_COMMAND_READ;
451		}
452		break;
453	}
454}
455
456uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
457	if (savedata->command != EEPROM_COMMAND_READ) {
458		if (!savedata->realisticTiming || savedata->dust <= 0) {
459			return 1;
460		} else {
461			// Give some overhead for waitstates and the comparison
462			// This estimation can probably be improved
463			--savedata->dust;
464			return 0;
465		}
466	}
467	--savedata->readBitsRemaining;
468	if (savedata->readBitsRemaining < 64) {
469		int step = 63 - savedata->readBitsRemaining;
470		uint32_t address = (savedata->readAddress + step) >> 3;
471		if (address >= SIZE_CART_EEPROM) {
472			mLOG(GBA_SAVE, GAME_ERROR, "Reading beyond end of EEPROM: %08X", address);
473			return 0xFF;
474		}
475		uint8_t data = savedata->data[address] >> (0x7 - (step & 0x7));
476		if (!savedata->readBitsRemaining) {
477			savedata->command = EEPROM_COMMAND_NULL;
478		}
479		return data & 0x1;
480	}
481	return 0;
482}
483
484void GBASavedataClean(struct GBASavedata* savedata, uint32_t frameCount) {
485	if (!savedata->vf) {
486		return;
487	}
488	if (savedata->dirty & SAVEDATA_DIRT_NEW) {
489		savedata->dirtAge = frameCount;
490		savedata->dirty &= ~SAVEDATA_DIRT_NEW;
491		if (!(savedata->dirty & SAVEDATA_DIRT_SEEN)) {
492			savedata->dirty |= SAVEDATA_DIRT_SEEN;
493		}
494	} else if ((savedata->dirty & SAVEDATA_DIRT_SEEN) && frameCount - savedata->dirtAge > CLEANUP_THRESHOLD) {
495		if (savedata->maskWriteback) {
496			GBASavedataUnmask(savedata);
497		}
498		size_t size = GBASavedataSize(savedata);
499		savedata->dirty = 0;
500		if (savedata->data && savedata->vf->sync(savedata->vf, savedata->data, size)) {
501			mLOG(GBA_SAVE, INFO, "Savedata synced");
502		} else {
503			mLOG(GBA_SAVE, INFO, "Savedata failed to sync!");
504		}
505	}
506}
507
508void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
509	state->savedata.type = savedata->type;
510	state->savedata.command = savedata->command;
511	GBASerializedSavedataFlags flags = 0;
512	flags = GBASerializedSavedataFlagsSetFlashState(flags, savedata->flashState);
513	flags = GBASerializedSavedataFlagsTestFillFlashBank(flags, savedata->currentBank == &savedata->data[0x10000]);
514	state->savedata.flags = flags;
515	STORE_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
516	STORE_32(savedata->readAddress, 0, &state->savedata.readAddress);
517	STORE_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
518	STORE_16(savedata->settling, 0, &state->savedata.settlingSector);
519	STORE_16(savedata->dust, 0, &state->savedata.settlingDust);
520}
521
522void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state) {
523	if (savedata->type != state->savedata.type) {
524		mLOG(GBA_SAVE, DEBUG, "Switching save types");
525		GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming);
526	}
527	savedata->command = state->savedata.command;
528	GBASerializedSavedataFlags flags = state->savedata.flags;
529	savedata->flashState = GBASerializedSavedataFlagsGetFlashState(flags);
530	LOAD_32(savedata->readBitsRemaining, 0, &state->savedata.readBitsRemaining);
531	LOAD_32(savedata->readAddress, 0, &state->savedata.readAddress);
532	LOAD_32(savedata->writeAddress, 0, &state->savedata.writeAddress);
533	LOAD_16(savedata->settling, 0, &state->savedata.settlingSector);
534	LOAD_16(savedata->dust, 0, &state->savedata.settlingDust);
535
536	if (savedata->type == SAVEDATA_FLASH1M) {
537		_flashSwitchBank(savedata, GBASerializedSavedataFlagsGetFlashBank(flags));
538	}
539}
540
541void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
542	mLOG(GBA_SAVE, DEBUG, "Performing flash bank switch to bank %i", bank);
543	savedata->currentBank = &savedata->data[bank << 16];
544	if (bank > 0 && savedata->type == SAVEDATA_FLASH512) {
545		savedata->type = SAVEDATA_FLASH1M;
546		if (savedata->vf) {
547			savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
548			memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512);
549		}
550	}
551}
552
553void _flashErase(struct GBASavedata* savedata) {
554	mLOG(GBA_SAVE, DEBUG, "Performing flash chip erase");
555	savedata->dirty |= SAVEDATA_DIRT_NEW;
556	size_t size = SIZE_CART_FLASH512;
557	if (savedata->type == SAVEDATA_FLASH1M) {
558		size = SIZE_CART_FLASH1M;
559	}
560	memset(savedata->data, 0xFF, size);
561}
562
563void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
564	mLOG(GBA_SAVE, DEBUG, "Performing flash sector erase at 0x%04x", sectorStart);
565	savedata->dirty |= SAVEDATA_DIRT_NEW;
566	size_t size = 0x1000;
567	if (savedata->type == SAVEDATA_FLASH1M) {
568		mLOG(GBA_SAVE, DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
569	}
570	savedata->settling = sectorStart >> 12;
571	if (savedata->realisticTiming) {
572		savedata->dust = FLASH_ERASE_CYCLES;
573	}
574	memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
575}