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}