all repos — mgba @ 46782f16a06d03de5fc85a15193f2238b9dca661

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