all repos — mgba @ 3a9c99370ee715e1bb0e603f8740a7900033a041

mGBA Game Boy Advance Emulator

src/gb/gb.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 "gb.h"
  7
  8#include "gb/io.h"
  9#include "gb/mbc.h"
 10
 11#include "core/core.h"
 12#include "core/cheats.h"
 13#include "util/crc32.h"
 14#include "util/memory.h"
 15#include "util/math.h"
 16#include "util/patch.h"
 17#include "util/vfs.h"
 18
 19#define CLEANUP_THRESHOLD 15
 20
 21const uint32_t CGB_LR35902_FREQUENCY = 0x800000;
 22const uint32_t SGB_LR35902_FREQUENCY = 0x418B1E;
 23
 24const uint32_t GB_COMPONENT_MAGIC = 0x400000;
 25
 26#define DMG_2_BIOS_CHECKSUM 0x59C8598E
 27#define CGB_BIOS_CHECKSUM 0x41884E46
 28
 29mLOG_DEFINE_CATEGORY(GB, "GB");
 30
 31static void GBInit(void* cpu, struct mCPUComponent* component);
 32static void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh);
 33static void GBProcessEvents(struct LR35902Core* cpu);
 34static void GBSetInterrupts(struct LR35902Core* cpu, bool enable);
 35static void GBIllegal(struct LR35902Core* cpu);
 36static void GBStop(struct LR35902Core* cpu);
 37
 38#ifdef _3DS
 39extern uint32_t* romBuffer;
 40extern size_t romBufferSize;
 41#endif
 42
 43void GBCreate(struct GB* gb) {
 44	gb->d.id = GB_COMPONENT_MAGIC;
 45	gb->d.init = GBInit;
 46	gb->d.deinit = 0;
 47}
 48
 49static void GBInit(void* cpu, struct mCPUComponent* component) {
 50	struct GB* gb = (struct GB*) component;
 51	gb->cpu = cpu;
 52	gb->sync = NULL;
 53
 54	GBInterruptHandlerInit(&gb->cpu->irqh);
 55	GBMemoryInit(gb);
 56
 57	gb->video.p = gb;
 58	GBVideoInit(&gb->video);
 59
 60	gb->audio.p = gb;
 61	GBAudioInit(&gb->audio, 2048, &gb->memory.io[REG_NR52], GB_AUDIO_DMG); // TODO: Remove magic constant
 62
 63	gb->sio.p = gb;
 64	GBSIOInit(&gb->sio);
 65
 66	gb->timer.p = gb;
 67
 68	gb->model = GB_MODEL_AUTODETECT;
 69
 70	gb->biosVf = 0;
 71	gb->romVf = 0;
 72	gb->sramVf = 0;
 73
 74	gb->pristineRom = 0;
 75	gb->pristineRomSize = 0;
 76	gb->yankedRomSize = 0;
 77
 78	gb->stream = NULL;
 79}
 80
 81bool GBLoadROM(struct GB* gb, struct VFile* vf) {
 82	GBUnloadROM(gb);
 83	gb->romVf = vf;
 84	gb->pristineRomSize = vf->size(vf);
 85	vf->seek(vf, 0, SEEK_SET);
 86#ifdef _3DS
 87	gb->pristineRom = 0;
 88	if (gb->pristineRomSize <= romBufferSize) {
 89		gb->pristineRom = romBuffer;
 90		vf->read(vf, romBuffer, gb->pristineRomSize);
 91	}
 92#else
 93	gb->pristineRom = vf->map(vf, gb->pristineRomSize, MAP_READ);
 94#endif
 95	if (!gb->pristineRom) {
 96		return false;
 97	}
 98	gb->yankedRomSize = 0;
 99	gb->memory.rom = gb->pristineRom;
100	gb->memory.romBase = gb->memory.rom;
101	gb->memory.romSize = gb->pristineRomSize;
102	gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
103
104	// TODO: error check
105	return true;
106}
107
108bool GBLoadSave(struct GB* gb, struct VFile* vf) {
109	gb->sramVf = vf;
110	gb->sramRealVf = vf;
111	return vf;
112}
113
114static void GBSramDeinit(struct GB* gb) {
115	if (gb->sramVf) {
116		gb->sramVf->unmap(gb->sramVf, gb->memory.sram, gb->sramSize);
117		if (gb->memory.mbcType == GB_MBC3_RTC) {
118			GBMBCRTCWrite(gb);
119		}
120		gb->sramVf->close(gb->sramVf);
121		gb->sramVf = 0;
122	} else if (gb->memory.sram) {
123		mappedMemoryFree(gb->memory.sram, gb->sramSize);
124	}
125	gb->memory.sram = 0;
126}
127
128void GBResizeSram(struct GB* gb, size_t size) {
129	if (gb->memory.sram && size <= gb->sramSize) {
130		return;
131	}
132	mLOG(GB, INFO, "Resizing SRAM to %"PRIz"u bytes", size);
133	struct VFile* vf = gb->sramVf;
134	if (vf) {
135		if (vf == gb->sramRealVf) {
136			ssize_t vfSize = vf->size(vf);
137			if (vfSize >= 0 && (size_t) vfSize < size) {
138				uint8_t extdataBuffer[0x100];
139				if (vfSize & 0xFF) {
140					vf->seek(vf, -(vfSize & 0xFF), SEEK_END);
141					vf->read(vf, extdataBuffer, vfSize & 0xFF);
142				}
143				if (gb->memory.sram) {
144					vf->unmap(vf, gb->memory.sram, gb->sramSize);
145				}
146				vf->truncate(vf, size + (vfSize & 0xFF));
147				if (vfSize & 0xFF) {
148					vf->seek(vf, size, SEEK_SET);
149					vf->write(vf, extdataBuffer, vfSize & 0xFF);
150				}
151				gb->memory.sram = vf->map(vf, size, MAP_WRITE);
152				memset(&gb->memory.sram[gb->sramSize], 0xFF, size - gb->sramSize);
153			} else if (size > gb->sramSize || !gb->memory.sram) {
154				if (gb->memory.sram) {
155					vf->unmap(vf, gb->memory.sram, gb->sramSize);
156				}
157				gb->memory.sram = vf->map(vf, size, MAP_WRITE);
158			}
159		} else {
160			if (gb->memory.sram) {
161				vf->unmap(vf, gb->memory.sram, gb->sramSize);
162			}
163			gb->memory.sram = vf->map(vf, size, MAP_READ);
164		}
165		if (gb->memory.sram == (void*) -1) {
166			gb->memory.sram = NULL;
167		}
168	} else {
169		uint8_t* newSram = anonymousMemoryMap(size);
170		if (gb->memory.sram) {
171			if (size > gb->sramSize) {
172				memcpy(newSram, gb->memory.sram, gb->sramSize);
173				memset(&newSram[gb->sramSize], 0xFF, size - gb->sramSize);
174			} else {
175				memcpy(newSram, gb->memory.sram, size);
176			}
177			mappedMemoryFree(gb->memory.sram, gb->sramSize);
178		} else {
179			memset(newSram, 0xFF, size);
180		}
181		gb->memory.sram = newSram;
182	}
183	if (gb->sramSize < size) {
184		gb->sramSize = size;
185	}
186}
187
188void GBSramClean(struct GB* gb, uint32_t frameCount) {
189	// TODO: Share with GBASavedataClean
190	if (!gb->sramVf) {
191		return;
192	}
193	if (gb->sramDirty & GB_SRAM_DIRT_NEW) {
194		gb->sramDirtAge = frameCount;
195		gb->sramDirty &= ~GB_SRAM_DIRT_NEW;
196		if (!(gb->sramDirty & GB_SRAM_DIRT_SEEN)) {
197			gb->sramDirty |= GB_SRAM_DIRT_SEEN;
198		}
199	} else if ((gb->sramDirty & GB_SRAM_DIRT_SEEN) && frameCount - gb->sramDirtAge > CLEANUP_THRESHOLD) {
200		if (gb->memory.mbcType == GB_MBC3_RTC) {
201			GBMBCRTCWrite(gb);
202		}
203		gb->sramDirty = 0;
204		if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {
205			mLOG(GB_MEM, INFO, "Savedata synced");
206		} else {
207			mLOG(GB_MEM, INFO, "Savedata failed to sync!");
208		}
209	}
210}
211
212void GBSavedataMask(struct GB* gb, struct VFile* vf) {
213	GBSramDeinit(gb);
214	gb->sramVf = vf;
215	gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ);
216}
217
218void GBSavedataUnmask(struct GB* gb) {
219	if (gb->sramVf == gb->sramRealVf) {
220		return;
221	}
222	GBSramDeinit(gb);
223	gb->sramVf = gb->sramRealVf;
224	gb->memory.sram = gb->sramVf->map(gb->sramVf, gb->sramSize, MAP_WRITE);
225}
226
227void GBUnloadROM(struct GB* gb) {
228	// TODO: Share with GBAUnloadROM
229	if (gb->memory.rom && gb->memory.romBase != gb->memory.rom) {
230		free(gb->memory.romBase);
231	}
232	if (gb->memory.rom && gb->pristineRom != gb->memory.rom) {
233		if (gb->yankedRomSize) {
234			gb->yankedRomSize = 0;
235		}
236		mappedMemoryFree(gb->memory.rom, GB_SIZE_CART_MAX);
237	}
238	gb->memory.rom = 0;
239
240	if (gb->romVf) {
241#ifndef _3DS
242		gb->romVf->unmap(gb->romVf, gb->pristineRom, gb->pristineRomSize);
243#endif
244		gb->romVf->close(gb->romVf);
245		gb->pristineRom = 0;
246		gb->romVf = 0;
247	}
248
249	GBSramDeinit(gb);
250}
251
252void GBLoadBIOS(struct GB* gb, struct VFile* vf) {
253	gb->biosVf = vf;
254}
255
256void GBApplyPatch(struct GB* gb, struct Patch* patch) {
257	size_t patchedSize = patch->outputSize(patch, gb->memory.romSize);
258	if (!patchedSize) {
259		return;
260	}
261	if (patchedSize > GB_SIZE_CART_MAX) {
262		patchedSize = GB_SIZE_CART_MAX;
263	}
264	gb->memory.rom = anonymousMemoryMap(GB_SIZE_CART_MAX);
265	if (!patch->applyPatch(patch, gb->pristineRom, gb->pristineRomSize, gb->memory.rom, patchedSize)) {
266		mappedMemoryFree(gb->memory.rom, patchedSize);
267		gb->memory.rom = gb->pristineRom;
268		return;
269	}
270	gb->memory.romSize = patchedSize;
271	gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
272}
273
274void GBDestroy(struct GB* gb) {
275	GBUnloadROM(gb);
276
277	if (gb->biosVf) {
278		gb->biosVf->close(gb->biosVf);
279		gb->biosVf = 0;
280	}
281
282	GBMemoryDeinit(gb);
283	GBVideoDeinit(&gb->video);
284	GBSIODeinit(&gb->sio);
285}
286
287void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh) {
288	irqh->reset = GBReset;
289	irqh->processEvents = GBProcessEvents;
290	irqh->setInterrupts = GBSetInterrupts;
291	irqh->hitIllegal = GBIllegal;
292	irqh->stop = GBStop;
293	irqh->halt = GBHalt;
294}
295
296static uint32_t _GBBiosCRC32(struct VFile* vf) {
297	ssize_t size = vf->size(vf);
298	if (size <= 0 || size > GB_SIZE_CART_BANK0) {
299		return 0;
300	}
301	void* bios = vf->map(vf, size, MAP_READ);
302	uint32_t biosCrc = doCrc32(bios, size);
303	vf->unmap(vf, bios, size);
304	return biosCrc;
305}
306
307bool GBIsBIOS(struct VFile* vf) {
308	switch (_GBBiosCRC32(vf)) {
309	case DMG_2_BIOS_CHECKSUM:
310	case CGB_BIOS_CHECKSUM:
311		return true;
312	default:
313		return false;
314	}
315}
316
317void GBReset(struct LR35902Core* cpu) {
318	struct GB* gb = (struct GB*) cpu->master;
319	GBDetectModel(gb);
320	if (gb->biosVf) {
321		if (!GBIsBIOS(gb->biosVf)) {
322			gb->biosVf->close(gb->biosVf);
323			gb->biosVf = NULL;
324		} else {
325			gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
326			gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
327			ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
328			memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
329			if (size > 0x100) {
330				memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
331			}
332
333			cpu->a = 0;
334			cpu->f.packed = 0;
335			cpu->c = 0;
336			cpu->e = 0;
337			cpu->h = 0;
338			cpu->l = 0;
339			cpu->sp = 0;
340			cpu->pc = 0;
341		}
342	}
343
344	cpu->b = 0;
345	cpu->d = 0;
346
347	if (!gb->biosVf) {
348		switch (gb->model) {
349		case GB_MODEL_DMG:
350			// TODO: SGB
351		case GB_MODEL_SGB:
352		case GB_MODEL_AUTODETECT: // Silence warnings
353			gb->model = GB_MODEL_DMG;
354			cpu->a = 1;
355			cpu->f.packed = 0xB0;
356			cpu->c = 0x13;
357			cpu->e = 0xD8;
358			cpu->h = 1;
359			cpu->l = 0x4D;
360			break;
361		case GB_MODEL_AGB:
362			cpu->b = 1;
363			// Fall through
364		case GB_MODEL_CGB:
365			cpu->a = 0x11;
366			cpu->f.packed = 0x80;
367			cpu->c = 0;
368			cpu->e = 0x08;
369			cpu->h = 0;
370			cpu->l = 0x7C;
371			break;
372		}
373
374		cpu->sp = 0xFFFE;
375		cpu->pc = 0x100;
376	}
377
378	gb->eiPending = INT_MAX;
379	gb->doubleSpeed = 0;
380
381	cpu->memory.setActiveRegion(cpu, cpu->pc);
382
383	if (gb->yankedRomSize) {
384		gb->memory.romSize = gb->yankedRomSize;
385		gb->yankedRomSize = 0;
386	}
387	GBMemoryReset(gb);
388	GBVideoReset(&gb->video);
389	GBTimerReset(&gb->timer);
390	GBIOReset(gb);
391	GBAudioReset(&gb->audio);
392	GBSIOReset(&gb->sio);
393
394	GBSavedataUnmask(gb);
395}
396
397void GBDetectModel(struct GB* gb) {
398	if (gb->model != GB_MODEL_AUTODETECT) {
399		return;
400	}
401	if (gb->biosVf) {
402		switch (_GBBiosCRC32(gb->biosVf)) {
403		case DMG_2_BIOS_CHECKSUM:
404			gb->model = GB_MODEL_DMG;
405			break;
406		case CGB_BIOS_CHECKSUM:
407			gb->model = GB_MODEL_CGB;
408			break;
409		default:
410			gb->biosVf->close(gb->biosVf);
411			gb->biosVf = NULL;
412		}
413	}
414	if (gb->model == GB_MODEL_AUTODETECT && gb->memory.rom) {
415		const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
416		if (cart->cgb & 0x80) {
417			gb->model = GB_MODEL_CGB;
418		} else {
419			gb->model = GB_MODEL_DMG;
420		}
421	}
422
423	switch (gb->model) {
424	case GB_MODEL_DMG:
425	case GB_MODEL_SGB:
426	case GB_MODEL_AUTODETECT: //Silence warnings
427		gb->audio.style = GB_AUDIO_DMG;
428		break;
429	case GB_MODEL_AGB:
430	case GB_MODEL_CGB:
431		gb->audio.style = GB_AUDIO_CGB;
432		break;
433	}
434}
435
436void GBUpdateIRQs(struct GB* gb) {
437	int irqs = gb->memory.ie & gb->memory.io[REG_IF];
438	if (!irqs) {
439		return;
440	}
441	gb->cpu->halted = false;
442
443	if (!gb->memory.ime || gb->cpu->irqPending) {
444		return;
445	}
446
447	if (irqs & (1 << GB_IRQ_VBLANK)) {
448		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_VBLANK);
449		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_VBLANK);
450		return;
451	}
452	if (irqs & (1 << GB_IRQ_LCDSTAT)) {
453		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_LCDSTAT);
454		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_LCDSTAT);
455		return;
456	}
457	if (irqs & (1 << GB_IRQ_TIMER)) {
458		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_TIMER);
459		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_TIMER);
460		return;
461	}
462	if (irqs & (1 << GB_IRQ_SIO)) {
463		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_SIO);
464		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_SIO);
465		return;
466	}
467	if (irqs & (1 << GB_IRQ_KEYPAD)) {
468		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_KEYPAD);
469		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_KEYPAD);
470	}
471}
472
473void GBProcessEvents(struct LR35902Core* cpu) {
474	struct GB* gb = (struct GB*) cpu->master;
475	do {
476		int32_t cycles = cpu->nextEvent;
477		int32_t nextEvent = INT_MAX;
478		int32_t testEvent;
479
480		if (gb->eiPending != INT_MAX) {
481			gb->eiPending -= cycles;
482			if (gb->eiPending <= 0) {
483				gb->memory.ime = true;
484				GBUpdateIRQs(gb);
485				gb->eiPending = INT_MAX;
486			} else {
487				nextEvent = gb->eiPending;
488			}
489		}
490
491		testEvent = GBVideoProcessEvents(&gb->video, cycles >> gb->doubleSpeed);
492		if (testEvent != INT_MAX) {
493			testEvent <<= gb->doubleSpeed;
494			if (testEvent < nextEvent) {
495				nextEvent = testEvent;
496			}
497		}
498
499		testEvent = GBAudioProcessEvents(&gb->audio, cycles >> gb->doubleSpeed);
500		if (testEvent != INT_MAX) {
501			testEvent <<= gb->doubleSpeed;
502			if (testEvent < nextEvent) {
503				nextEvent = testEvent;
504			}
505		}
506
507		testEvent = GBTimerProcessEvents(&gb->timer, cycles);
508		if (testEvent < nextEvent) {
509			nextEvent = testEvent;
510		}
511
512		testEvent = GBSIOProcessEvents(&gb->sio, cycles);
513		if (testEvent < nextEvent) {
514			nextEvent = testEvent;
515		}
516
517		testEvent = GBMemoryProcessEvents(gb, cycles);
518		if (testEvent < nextEvent) {
519			nextEvent = testEvent;
520		}
521
522		cpu->cycles -= cycles;
523		cpu->nextEvent = nextEvent;
524
525		if (cpu->halted) {
526			cpu->cycles = cpu->nextEvent;
527		}
528	} while (cpu->cycles >= cpu->nextEvent);
529}
530
531void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {
532	struct GB* gb = (struct GB*) cpu->master;
533	if (!enable) {
534		gb->memory.ime = enable;
535		gb->eiPending = INT_MAX;
536		GBUpdateIRQs(gb);
537	} else {
538		if (cpu->nextEvent > cpu->cycles + 4) {
539			cpu->nextEvent = cpu->cycles + 4;
540		}
541		gb->eiPending = cpu->cycles + 4;
542	}
543}
544
545void GBHalt(struct LR35902Core* cpu) {
546	if (!cpu->irqPending) {
547		cpu->cycles = cpu->nextEvent;
548		cpu->halted = true;
549	}
550}
551
552void GBStop(struct LR35902Core* cpu) {
553	struct GB* gb = (struct GB*) cpu->master;
554	if (cpu->bus) {
555		mLOG(GB, GAME_ERROR, "Hit illegal stop at address %04X:%02X\n", cpu->pc, cpu->bus);
556		if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) {
557			struct mDebuggerEntryInfo info = {
558				.address = cpu->pc - 1,
559				.opcode = 0x1000 | cpu->bus
560			};
561			mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info);
562		}
563		// Hang forever
564		gb->memory.ime = 0;
565		cpu->pc -= 2;
566	} else if (gb->memory.io[REG_KEY1] & 1) {
567		gb->doubleSpeed ^= 1;
568		gb->memory.io[REG_KEY1] &= 1;
569		gb->memory.io[REG_KEY1] |= gb->doubleSpeed << 7;
570	}
571	// TODO: Actually stop
572}
573
574void GBIllegal(struct LR35902Core* cpu) {
575	struct GB* gb = (struct GB*) cpu->master;
576	mLOG(GB, GAME_ERROR, "Hit illegal opcode at address %04X:%02X\n", cpu->pc, cpu->bus);
577	if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) {
578		struct mDebuggerEntryInfo info = {
579			.address = cpu->pc,
580			.opcode = cpu->bus
581		};
582		mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info);
583	}
584	// Hang forever
585	gb->memory.ime = 0;
586	--cpu->pc;
587}
588
589bool GBIsROM(struct VFile* vf) {
590	vf->seek(vf, 0x104, SEEK_SET);
591	uint8_t header[4];
592	static const uint8_t knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66};
593
594	if (vf->read(vf, &header, sizeof(header)) < (ssize_t) sizeof(header)) {
595		return false;
596	}
597	if (memcmp(header, knownHeader, sizeof(header))) {
598		return false;
599	}
600	return true;
601}
602
603void GBGetGameTitle(struct GB* gb, char* out) {
604	const struct GBCartridge* cart = NULL;
605	if (gb->memory.rom) {
606		cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
607	}
608	if (gb->pristineRom) {
609		cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
610	}
611	if (!cart) {
612		return;
613	}
614	if (cart->oldLicensee != 0x33) {
615		memcpy(out, cart->titleLong, 16);
616	} else {
617		memcpy(out, cart->titleShort, 11);
618	}
619}
620
621void GBGetGameCode(struct GB* gb, char* out) {
622	memset(out, 0, 8);
623	const struct GBCartridge* cart = NULL;
624	if (gb->memory.rom) {
625		cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
626	}
627	if (gb->pristineRom) {
628		cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
629	}
630	if (!cart) {
631		return;
632	}
633	if (cart->cgb == 0xC0) {
634		memcpy(out, "CGB-????", 8);
635	} else {
636		memcpy(out, "DMG-????", 8);
637	}
638	if (cart->oldLicensee == 0x33) {
639		memcpy(&out[4], cart->maker, 4);
640	}
641}
642
643void GBFrameEnded(struct GB* gb) {
644	GBSramClean(gb, gb->video.frameCounter);
645
646	if (gb->cpu->components && gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
647		struct mCheatDevice* device = (struct mCheatDevice*) gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
648		size_t i;
649		for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
650			struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
651			mCheatRefresh(device, cheats);
652		}
653	}
654}