all repos — mgba @ 243c2b330f87388a9645801de5828bed00761165

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