all repos — mgba @ 7de5b33554a9c0b41c963d220dd85ceda1bf3022

mGBA Game Boy Advance Emulator

src/gb/mbc.c (view raw)

  1/* Copyright (c) 2013-2016 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/gb/mbc.h>
  7
  8#include <mgba/core/interface.h>
  9#include <mgba/internal/lr35902/lr35902.h>
 10#include <mgba/internal/gb/gb.h>
 11#include <mgba/internal/gb/memory.h>
 12#include <mgba-util/vfs.h>
 13
 14mLOG_DEFINE_CATEGORY(GB_MBC, "GB MBC", "gb.mbc");
 15
 16static void _GBMBCNone(struct GB* gb, uint16_t address, uint8_t value) {
 17	UNUSED(gb);
 18	UNUSED(address);
 19	UNUSED(value);
 20
 21	mLOG(GB_MBC, GAME_ERROR, "Wrote to invalid MBC");
 22}
 23
 24static void _GBMBC1(struct GB*, uint16_t address, uint8_t value);
 25static void _GBMBC2(struct GB*, uint16_t address, uint8_t value);
 26static void _GBMBC3(struct GB*, uint16_t address, uint8_t value);
 27static void _GBMBC5(struct GB*, uint16_t address, uint8_t value);
 28static void _GBMBC6(struct GB*, uint16_t address, uint8_t value);
 29static void _GBMBC7(struct GB*, uint16_t address, uint8_t value);
 30static void _GBHuC3(struct GB*, uint16_t address, uint8_t value);
 31
 32void GBMBCSwitchBank(struct GB* gb, int bank) {
 33	size_t bankStart = bank * GB_SIZE_CART_BANK0;
 34	if (bankStart + GB_SIZE_CART_BANK0 > gb->memory.romSize) {
 35		mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank);
 36		bankStart &= (gb->memory.romSize - 1);
 37		bank = bankStart / GB_SIZE_CART_BANK0;
 38		if (!bank) {
 39			++bank;
 40		}
 41	}
 42	gb->memory.romBank = &gb->memory.rom[bankStart];
 43	gb->memory.currentBank = bank;
 44	if (gb->cpu->pc < GB_BASE_VRAM) {
 45		gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
 46	}
 47}
 48
 49void GBMBCSwitchSramBank(struct GB* gb, int bank) {
 50	size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM;
 51	GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM);
 52	gb->memory.sramBank = &gb->memory.sram[bankStart];
 53	gb->memory.sramCurrentBank = bank;
 54}
 55
 56void GBMBCInit(struct GB* gb) {
 57	const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
 58	if (gb->memory.rom) {
 59		switch (cart->ramSize) {
 60		case 0:
 61			gb->sramSize = 0;
 62			break;
 63		case 1:
 64			gb->sramSize = 0x800;
 65			break;
 66		default:
 67		case 2:
 68			gb->sramSize = 0x2000;
 69			break;
 70		case 3:
 71			gb->sramSize = 0x8000;
 72			break;
 73		}
 74
 75		if (gb->memory.mbcType == GB_MBC_AUTODETECT) {
 76			switch (cart->type) {
 77			case 0:
 78			case 8:
 79			case 9:
 80				gb->memory.mbcType = GB_MBC_NONE;
 81				break;
 82			case 1:
 83			case 2:
 84			case 3:
 85				gb->memory.mbcType = GB_MBC1;
 86				break;
 87			case 5:
 88			case 6:
 89				gb->memory.mbcType = GB_MBC2;
 90				break;
 91			case 0x0F:
 92			case 0x10:
 93				gb->memory.mbcType = GB_MBC3_RTC;
 94				break;
 95			case 0x11:
 96			case 0x12:
 97			case 0x13:
 98				gb->memory.mbcType = GB_MBC3;
 99				break;
100			default:
101				mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type);
102				// Fall through
103			case 0x19:
104			case 0x1A:
105			case 0x1B:
106				gb->memory.mbcType = GB_MBC5;
107				break;
108			case 0x1C:
109			case 0x1D:
110			case 0x1E:
111				gb->memory.mbcType = GB_MBC5_RUMBLE;
112				break;
113			case 0x20:
114				gb->memory.mbcType = GB_MBC6;
115				break;
116			case 0x22:
117				gb->memory.mbcType = GB_MBC7;
118				break;
119			case 0xFE:
120				gb->memory.mbcType = GB_HuC3;
121				break;
122			}
123		}
124	} else {
125		gb->memory.mbcType = GB_MBC_NONE;
126	}
127	switch (gb->memory.mbcType) {
128	case GB_MBC_NONE:
129		gb->memory.mbc = _GBMBCNone;
130		break;
131	case GB_MBC1:
132		gb->memory.mbc = _GBMBC1;
133		break;
134	case GB_MBC2:
135		gb->memory.mbc = _GBMBC2;
136		gb->sramSize = 0x200;
137		break;
138	case GB_MBC3:
139		gb->memory.mbc = _GBMBC3;
140		break;
141	default:
142		mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type);
143		// Fall through
144	case GB_MBC5:
145		gb->memory.mbc = _GBMBC5;
146		break;
147	case GB_MBC6:
148		mLOG(GB_MBC, WARN, "unimplemented MBC: MBC6");
149		gb->memory.mbc = _GBMBC6;
150		break;
151	case GB_MBC7:
152		gb->memory.mbc = _GBMBC7;
153		gb->sramSize = GB_SIZE_EXTERNAL_RAM;
154		break;
155	case GB_MMM01:
156		mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01");
157		gb->memory.mbc = _GBMBC1;
158		break;
159	case GB_HuC1:
160		mLOG(GB_MBC, WARN, "unimplemented MBC: HuC-1");
161		gb->memory.mbc = _GBMBC1;
162		break;
163	case GB_HuC3:
164		gb->memory.mbc = _GBHuC3;
165		break;
166	case GB_MBC3_RTC:
167		memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
168		gb->memory.mbc = _GBMBC3;
169		break;
170	case GB_MBC5_RUMBLE:
171		gb->memory.mbc = _GBMBC5;
172		break;
173	}
174
175	GBResizeSram(gb, gb->sramSize);
176
177	if (gb->memory.mbcType == GB_MBC3_RTC) {
178		GBMBCRTCRead(gb);
179	}
180}
181
182static void _latchRtc(struct mRTCSource* rtc, uint8_t* rtcRegs, time_t* rtcLastLatch) {
183	time_t t;
184	if (rtc) {
185		if (rtc->sample) {
186			rtc->sample(rtc);
187		}
188		t = rtc->unixTime(rtc);
189	} else {
190		t = time(0);
191	}
192	time_t currentLatch = t;
193	t -= *rtcLastLatch;
194	*rtcLastLatch = currentLatch;
195
196	int64_t diff;
197	diff = rtcRegs[0] + t % 60;
198	if (diff < 0) {
199		diff += 60;
200		t -= 60;
201	}
202	rtcRegs[0] = diff % 60;
203	t /= 60;
204	t += diff / 60;
205
206	diff = rtcRegs[1] + t % 60;
207	if (diff < 0) {
208		diff += 60;
209		t -= 60;
210	}
211	rtcRegs[1] = diff % 60;
212	t /= 60;
213	t += diff / 60;
214
215	diff = rtcRegs[2] + t % 24;
216	if (diff < 0) {
217		diff += 24;
218		t -= 24;
219	}
220	rtcRegs[2] = diff % 24;
221	t /= 24;
222	t += diff / 24;
223
224	diff = rtcRegs[3] + ((rtcRegs[4] & 1) << 8) + (t & 0x1FF);
225	rtcRegs[3] = diff;
226	rtcRegs[4] &= 0xFE;
227	rtcRegs[4] |= (diff >> 8) & 1;
228	if (diff & 0x200) {
229		rtcRegs[4] |= 0x80;
230	}
231}
232
233void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) {
234	struct GBMemory* memory = &gb->memory;
235	int bank = value & 0x1F;
236	switch (address >> 13) {
237	case 0x0:
238		switch (value) {
239		case 0:
240			memory->sramAccess = false;
241			break;
242		case 0xA:
243			memory->sramAccess = true;
244			GBMBCSwitchSramBank(gb, memory->sramCurrentBank);
245			break;
246		default:
247			// TODO
248			mLOG(GB_MBC, STUB, "MBC1 unknown value %02X", value);
249			break;
250		}
251		break;
252	case 0x1:
253		if (!bank) {
254			++bank;
255		}
256		GBMBCSwitchBank(gb, bank | (memory->currentBank & 0x60));
257		break;
258	case 0x2:
259		bank &= 3;
260		if (!memory->mbcState.mbc1.mode) {
261			GBMBCSwitchBank(gb, (bank << 5) | (memory->currentBank & 0x1F));
262		} else {
263			GBMBCSwitchSramBank(gb, bank);
264		}
265		break;
266	case 0x3:
267		memory->mbcState.mbc1.mode = value & 1;
268		if (memory->mbcState.mbc1.mode) {
269			GBMBCSwitchBank(gb, memory->currentBank & 0x1F);
270		} else {
271			GBMBCSwitchSramBank(gb, 0);
272		}
273		break;
274	default:
275		// TODO
276		mLOG(GB_MBC, STUB, "MBC1 unknown address: %04X:%02X", address, value);
277		break;
278	}
279}
280
281void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) {
282	struct GBMemory* memory = &gb->memory;
283	int bank = value & 0xF;
284	switch (address >> 13) {
285	case 0x0:
286		switch (value) {
287		case 0:
288			memory->sramAccess = false;
289			break;
290		case 0xA:
291			memory->sramAccess = true;
292			GBMBCSwitchSramBank(gb, memory->sramCurrentBank);
293			break;
294		default:
295			// TODO
296			mLOG(GB_MBC, STUB, "MBC1 unknown value %02X", value);
297			break;
298		}
299		break;
300	case 0x1:
301		if (!bank) {
302			++bank;
303		}
304		GBMBCSwitchBank(gb, bank);
305		break;
306	default:
307		// TODO
308		mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);
309		break;
310	}}
311
312void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) {
313	struct GBMemory* memory = &gb->memory;
314	int bank = value & 0x7F;
315	switch (address >> 13) {
316	case 0x0:
317		switch (value) {
318		case 0:
319			memory->sramAccess = false;
320			break;
321		case 0xA:
322			memory->sramAccess = true;
323			GBMBCSwitchSramBank(gb, memory->sramCurrentBank);
324			break;
325		default:
326			// TODO
327			mLOG(GB_MBC, STUB, "MBC3 unknown value %02X", value);
328			break;
329		}
330		break;
331	case 0x1:
332		if (!bank) {
333			++bank;
334		}
335		GBMBCSwitchBank(gb, bank);
336		break;
337	case 0x2:
338		if (value < 4) {
339			GBMBCSwitchSramBank(gb, value);
340			memory->rtcAccess = false;
341		} else if (value >= 8 && value <= 0xC) {
342			memory->activeRtcReg = value - 8;
343			memory->rtcAccess = true;
344		}
345		break;
346	case 0x3:
347		if (memory->rtcLatched && value == 0) {
348			memory->rtcLatched = false;
349		} else if (!memory->rtcLatched && value == 1) {
350			_latchRtc(gb->memory.rtc, gb->memory.rtcRegs, &gb->memory.rtcLastLatch);
351			memory->rtcLatched = true;
352		}
353		break;
354	}
355}
356
357void _GBMBC5(struct GB* gb, uint16_t address, uint8_t value) {
358	struct GBMemory* memory = &gb->memory;
359	int bank;
360	switch (address >> 12) {
361	case 0x0:
362	case 0x1:
363		switch (value) {
364		case 0:
365			memory->sramAccess = false;
366			break;
367		case 0xA:
368			memory->sramAccess = true;
369			GBMBCSwitchSramBank(gb, memory->sramCurrentBank);
370			break;
371		default:
372			// TODO
373			mLOG(GB_MBC, STUB, "MBC5 unknown value %02X", value);
374			break;
375		}
376		break;
377	case 0x2:
378		bank = (memory->currentBank & 0x100) | value;
379		GBMBCSwitchBank(gb, bank);
380		break;
381	case 0x3:
382		bank = (memory->currentBank & 0xFF) | ((value & 1) << 8);
383		GBMBCSwitchBank(gb, bank);
384		break;
385	case 0x4:
386	case 0x5:
387		if (memory->mbcType == GB_MBC5_RUMBLE && memory->rumble) {
388			memory->rumble->setRumble(memory->rumble, (value >> 3) & 1);
389			value &= ~8;
390		}
391		GBMBCSwitchSramBank(gb, value & 0xF);
392		break;
393	default:
394		// TODO
395		mLOG(GB_MBC, STUB, "MBC5 unknown address: %04X:%02X", address, value);
396		break;
397	}
398}
399
400void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) {
401	// TODO
402	mLOG(GB_MBC, STUB, "MBC6 unimplemented");
403	UNUSED(gb);
404	UNUSED(address);
405	UNUSED(value);
406}
407
408void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
409	int bank = value & 0x7F;
410	switch (address >> 13) {
411	case 0x1:
412		GBMBCSwitchBank(gb, bank);
413		break;
414	case 0x2:
415		if (value < 0x10) {
416			GBMBCSwitchSramBank(gb, value);
417		}
418		break;
419	default:
420		// TODO
421		mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);
422		break;
423	}
424}
425
426uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) {
427	struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
428	switch (address & 0xF0) {
429	case 0x00:
430	case 0x10:
431	case 0x60:
432	case 0x70:
433		return 0;
434	case 0x20:
435		if (memory->rotation && memory->rotation->readTiltX) {
436			int32_t x = -memory->rotation->readTiltX(memory->rotation);
437			x >>= 21;
438			x += 2047;
439			return x;
440		}
441		return 0xFF;
442	case 0x30:
443		if (memory->rotation && memory->rotation->readTiltX) {
444			int32_t x = -memory->rotation->readTiltX(memory->rotation);
445			x >>= 21;
446			x += 2047;
447			return x >> 8;
448		}
449		return 7;
450	case 0x40:
451		if (memory->rotation && memory->rotation->readTiltY) {
452			int32_t y = -memory->rotation->readTiltY(memory->rotation);
453			y >>= 21;
454			y += 2047;
455			return y;
456		}
457		return 0xFF;
458	case 0x50:
459		if (memory->rotation && memory->rotation->readTiltY) {
460			int32_t y = -memory->rotation->readTiltY(memory->rotation);
461			y >>= 21;
462			y += 2047;
463			return y >> 8;
464		}
465		return 7;
466	case 0x80:
467		return (mbc7->sr >> 16) & 1;
468	default:
469		return 0xFF;
470	}
471}
472
473void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) {
474	if ((address & 0xF0) != 0x80) {
475		return;
476	}
477	struct GBMBC7State* mbc7 = &memory->mbcState.mbc7;
478	GBMBC7Field old = memory->mbcState.mbc7.field;
479	mbc7->field = GBMBC7FieldClearIO(value);
480	if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
481		if (mbc7->state == GBMBC7_STATE_WRITE) {
482			if (mbc7->writable) {
483				memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8;
484				memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr;
485			}
486			mbc7->sr = 0x1FFFF;
487			mbc7->state = GBMBC7_STATE_NULL;
488		} else {
489			mbc7->state = GBMBC7_STATE_IDLE;
490		}
491	}
492	if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) {
493		if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) {
494			mbc7->sr <<= 1;
495			mbc7->sr |= GBMBC7FieldGetIO(value);
496			++mbc7->srBits;
497		}
498		switch (mbc7->state) {
499		case GBMBC7_STATE_IDLE:
500			if (GBMBC7FieldIsIO(value)) {
501				mbc7->state = GBMBC7_STATE_READ_COMMAND;
502				mbc7->srBits = 0;
503				mbc7->sr = 0;
504			}
505			break;
506		case GBMBC7_STATE_READ_COMMAND:
507			if (mbc7->srBits == 2) {
508				mbc7->state = GBMBC7_STATE_READ_ADDRESS;
509				mbc7->srBits = 0;
510				mbc7->command = mbc7->sr;
511			}
512			break;
513		case GBMBC7_STATE_READ_ADDRESS:
514			if (mbc7->srBits == 8) {
515				mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command;
516				mbc7->srBits = 0;
517				mbc7->address = mbc7->sr;
518				if (mbc7->state == GBMBC7_STATE_COMMAND_0) {
519					switch (mbc7->address >> 6) {
520					case 0:
521						mbc7->writable = false;
522						mbc7->state = GBMBC7_STATE_NULL;
523						break;
524					case 3:
525						mbc7->writable = true;
526						mbc7->state = GBMBC7_STATE_NULL;
527						break;
528					}
529				}
530			}
531			break;
532		case GBMBC7_STATE_COMMAND_0:
533			if (mbc7->srBits == 16) {
534				switch (mbc7->address >> 6) {
535				case 0:
536					mbc7->writable = false;
537					mbc7->state = GBMBC7_STATE_NULL;
538					break;
539				case 1:
540					mbc7->state = GBMBC7_STATE_WRITE;
541					if (mbc7->writable) {
542						int i;
543						for (i = 0; i < 256; ++i) {
544							memory->sramBank[i * 2] = mbc7->sr >> 8;
545							memory->sramBank[i * 2 + 1] = mbc7->sr;
546						}
547					}
548					break;
549				case 2:
550					mbc7->state = GBMBC7_STATE_WRITE;
551					if (mbc7->writable) {
552						int i;
553						for (i = 0; i < 256; ++i) {
554							memory->sramBank[i * 2] = 0xFF;
555							memory->sramBank[i * 2 + 1] = 0xFF;
556						}
557					}
558					break;
559				case 3:
560					mbc7->writable = true;
561					mbc7->state = GBMBC7_STATE_NULL;
562					break;
563				}
564			}
565			break;
566		case GBMBC7_STATE_COMMAND_SR_WRITE:
567			if (mbc7->srBits == 16) {
568				mbc7->srBits = 0;
569				mbc7->state = GBMBC7_STATE_WRITE;
570			}
571			break;
572		case GBMBC7_STATE_COMMAND_SR_READ:
573			if (mbc7->srBits == 1) {
574				mbc7->sr = memory->sramBank[mbc7->address * 2] << 8;
575				mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1];
576				mbc7->srBits = 0;
577				mbc7->state = GBMBC7_STATE_READ;
578			}
579			break;
580		case GBMBC7_STATE_COMMAND_SR_FILL:
581			if (mbc7->srBits == 16) {
582				mbc7->sr = 0xFFFF;
583				mbc7->srBits = 0;
584				mbc7->state = GBMBC7_STATE_WRITE;
585			}
586			break;
587		default:
588			break;
589		}
590	} else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) {
591		if (mbc7->state == GBMBC7_STATE_READ) {
592			mbc7->sr <<= 1;
593			++mbc7->srBits;
594			if (mbc7->srBits == 16) {
595				mbc7->srBits = 0;
596				mbc7->state = GBMBC7_STATE_NULL;
597			}
598		}
599	}
600}
601
602void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) {
603	struct GBMemory* memory = &gb->memory;
604	int bank = value & 0x3F;
605	if (address & 0x1FFF) {
606		mLOG(GB_MBC, STUB, "HuC-3 unknown value %04X:%02X", address, value);
607	}
608
609	switch (address >> 13) {
610	case 0x0:
611		switch (value) {
612		case 0xA:
613			memory->sramAccess = true;
614			GBMBCSwitchSramBank(gb, memory->sramCurrentBank);
615			break;
616		default:
617			memory->sramAccess = false;
618			break;
619		}
620		break;
621	case 0x1:
622		GBMBCSwitchBank(gb, bank);
623		break;
624	case 0x2:
625		GBMBCSwitchSramBank(gb, bank);
626		break;
627	default:
628		// TODO
629		mLOG(GB_MBC, STUB, "HuC-3 unknown address: %04X:%02X", address, value);
630		break;
631	}
632}
633
634void GBMBCRTCRead(struct GB* gb) {
635	struct GBMBCRTCSaveBuffer rtcBuffer;
636	struct VFile* vf = gb->sramVf;
637	if (!vf) {
638		return;
639	}
640	ssize_t end = vf->seek(vf, -sizeof(rtcBuffer), SEEK_END);
641	switch (end & 0x1FFF) {
642	case 0:
643		break;
644	case 0x1FFC:
645		vf->seek(vf, -sizeof(rtcBuffer) - 4, SEEK_END);
646		break;
647	default:
648		return;
649	}
650	vf->read(vf, &rtcBuffer, sizeof(rtcBuffer));
651
652	LOAD_32LE(gb->memory.rtcRegs[0], 0, &rtcBuffer.latchedSec);
653	LOAD_32LE(gb->memory.rtcRegs[1], 0, &rtcBuffer.latchedMin);
654	LOAD_32LE(gb->memory.rtcRegs[2], 0, &rtcBuffer.latchedHour);
655	LOAD_32LE(gb->memory.rtcRegs[3], 0, &rtcBuffer.latchedDays);
656	LOAD_32LE(gb->memory.rtcRegs[4], 0, &rtcBuffer.latchedDaysHi);
657	LOAD_64LE(gb->memory.rtcLastLatch, 0, &rtcBuffer.unixTime);
658}
659
660void GBMBCRTCWrite(struct GB* gb) {
661	struct VFile* vf = gb->sramVf;
662	if (!vf) {
663		return;
664	}
665
666	uint8_t rtcRegs[5];
667	memcpy(rtcRegs, gb->memory.rtcRegs, sizeof(rtcRegs));
668	time_t rtcLastLatch = gb->memory.rtcLastLatch;
669	_latchRtc(gb->memory.rtc, rtcRegs, &rtcLastLatch);
670
671	struct GBMBCRTCSaveBuffer rtcBuffer;
672	STORE_32LE(rtcRegs[0], 0, &rtcBuffer.sec);
673	STORE_32LE(rtcRegs[1], 0, &rtcBuffer.min);
674	STORE_32LE(rtcRegs[2], 0, &rtcBuffer.hour);
675	STORE_32LE(rtcRegs[3], 0, &rtcBuffer.days);
676	STORE_32LE(rtcRegs[4], 0, &rtcBuffer.daysHi);
677	STORE_32LE(gb->memory.rtcRegs[0], 0, &rtcBuffer.latchedSec);
678	STORE_32LE(gb->memory.rtcRegs[1], 0, &rtcBuffer.latchedMin);
679	STORE_32LE(gb->memory.rtcRegs[2], 0, &rtcBuffer.latchedHour);
680	STORE_32LE(gb->memory.rtcRegs[3], 0, &rtcBuffer.latchedDays);
681	STORE_32LE(gb->memory.rtcRegs[4], 0, &rtcBuffer.latchedDaysHi);
682	STORE_64LE(rtcLastLatch, 0, &rtcBuffer.unixTime);
683
684	if (vf->size(vf) == gb->sramSize) {
685		// Writing past the end of the file can invalidate the file mapping
686		vf->unmap(vf, gb->memory.sram, gb->sramSize);
687		gb->memory.sram = NULL;
688	}
689	vf->seek(vf, gb->sramSize, SEEK_SET);
690	vf->write(vf, &rtcBuffer, sizeof(rtcBuffer));
691	if (!gb->memory.sram) {
692		gb->memory.sram = vf->map(vf, gb->sramSize, MAP_WRITE);
693		GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank);
694	}
695}