all repos — mgba @ 18c6e6c330749abe3105a24cbe6571a85bbb2de4

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