all repos — mgba @ 742538dc432a3d85b66318eded435a73dfce0f5c

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	// TODO: error check
106	return true;
107}
108
109bool GBLoadSave(struct GB* gb, struct VFile* vf) {
110	gb->sramVf = vf;
111	gb->sramRealVf = vf;
112	return vf;
113}
114
115static void GBSramDeinit(struct GB* gb) {
116	if (gb->sramVf) {
117		gb->sramVf->unmap(gb->sramVf, gb->memory.sram, gb->sramSize);
118		if (gb->memory.mbcType == GB_MBC3_RTC) {
119			GBMBCRTCWrite(gb);
120		}
121		gb->sramVf = NULL;
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, bool writeback) {
213	GBSramDeinit(gb);
214	gb->sramVf = vf;
215	gb->sramMaskWriteback = writeback;
216	gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ);
217}
218
219void GBSavedataUnmask(struct GB* gb) {
220	if (gb->sramVf == gb->sramRealVf) {
221		return;
222	}
223	struct VFile* vf = gb->sramVf;
224	GBSramDeinit(gb);
225	gb->sramVf = gb->sramRealVf;
226	gb->memory.sram = gb->sramVf->map(gb->sramVf, gb->sramSize, MAP_WRITE);
227	if (gb->sramMaskWriteback) {
228		vf->read(vf, gb->memory.sram, gb->sramSize);
229	}
230	vf->close(vf);
231}
232
233void GBUnloadROM(struct GB* gb) {
234	// TODO: Share with GBAUnloadROM
235	if (gb->memory.rom && gb->pristineRom != gb->memory.rom) {
236		if (gb->yankedRomSize) {
237			gb->yankedRomSize = 0;
238		}
239		mappedMemoryFree(gb->memory.rom, GB_SIZE_CART_MAX);
240		gb->memory.rom = gb->pristineRom;
241	}
242	if (gb->memory.rom && gb->memory.romBase != gb->memory.rom) {
243		free(gb->memory.romBase);
244	}
245	gb->memory.rom = 0;
246
247	if (gb->romVf) {
248#ifndef _3DS
249		gb->romVf->unmap(gb->romVf, gb->pristineRom, gb->pristineRomSize);
250#endif
251		gb->romVf->close(gb->romVf);
252		gb->romVf = 0;
253	}
254	gb->pristineRom = 0;
255
256	struct VFile* vf = gb->sramVf;
257	GBSramDeinit(gb);
258	if (vf) {
259		vf->close(vf);
260	}
261}
262
263void GBLoadBIOS(struct GB* gb, struct VFile* vf) {
264	gb->biosVf = vf;
265}
266
267void GBApplyPatch(struct GB* gb, struct Patch* patch) {
268	size_t patchedSize = patch->outputSize(patch, gb->memory.romSize);
269	if (!patchedSize) {
270		return;
271	}
272	if (patchedSize > GB_SIZE_CART_MAX) {
273		patchedSize = GB_SIZE_CART_MAX;
274	}
275	gb->memory.rom = anonymousMemoryMap(GB_SIZE_CART_MAX);
276	if (!patch->applyPatch(patch, gb->pristineRom, gb->pristineRomSize, gb->memory.rom, patchedSize)) {
277		mappedMemoryFree(gb->memory.rom, patchedSize);
278		gb->memory.rom = gb->pristineRom;
279		return;
280	}
281	gb->memory.romSize = patchedSize;
282	gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
283}
284
285void GBDestroy(struct GB* gb) {
286	GBUnloadROM(gb);
287
288	if (gb->biosVf) {
289		gb->biosVf->close(gb->biosVf);
290		gb->biosVf = 0;
291	}
292
293	GBMemoryDeinit(gb);
294	GBVideoDeinit(&gb->video);
295	GBSIODeinit(&gb->sio);
296}
297
298void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh) {
299	irqh->reset = GBReset;
300	irqh->processEvents = GBProcessEvents;
301	irqh->setInterrupts = GBSetInterrupts;
302	irqh->hitIllegal = GBIllegal;
303	irqh->stop = GBStop;
304	irqh->halt = GBHalt;
305}
306
307static uint32_t _GBBiosCRC32(struct VFile* vf) {
308	ssize_t size = vf->size(vf);
309	if (size <= 0 || size > GB_SIZE_CART_BANK0) {
310		return 0;
311	}
312	void* bios = vf->map(vf, size, MAP_READ);
313	uint32_t biosCrc = doCrc32(bios, size);
314	vf->unmap(vf, bios, size);
315	return biosCrc;
316}
317
318bool GBIsBIOS(struct VFile* vf) {
319	switch (_GBBiosCRC32(vf)) {
320	case DMG_BIOS_CHECKSUM:
321	case DMG_2_BIOS_CHECKSUM:
322	case CGB_BIOS_CHECKSUM:
323		return true;
324	default:
325		return false;
326	}
327}
328
329void GBReset(struct LR35902Core* cpu) {
330	struct GB* gb = (struct GB*) cpu->master;
331	GBDetectModel(gb);
332	if (gb->biosVf) {
333		if (!GBIsBIOS(gb->biosVf)) {
334			gb->biosVf->close(gb->biosVf);
335			gb->biosVf = NULL;
336		} else {
337			gb->biosVf->seek(gb->biosVf, 0, SEEK_SET);
338			gb->memory.romBase = malloc(GB_SIZE_CART_BANK0);
339			ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0);
340			memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size);
341			if (size > 0x100) {
342				memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge));
343			}
344
345			cpu->a = 0;
346			cpu->f.packed = 0;
347			cpu->c = 0;
348			cpu->e = 0;
349			cpu->h = 0;
350			cpu->l = 0;
351			cpu->sp = 0;
352			cpu->pc = 0;
353		}
354	}
355
356	cpu->b = 0;
357	cpu->d = 0;
358
359	if (!gb->biosVf) {
360		switch (gb->model) {
361		case GB_MODEL_DMG:
362			// TODO: SGB
363		case GB_MODEL_SGB:
364		case GB_MODEL_AUTODETECT: // Silence warnings
365			gb->model = GB_MODEL_DMG;
366			cpu->a = 1;
367			cpu->f.packed = 0xB0;
368			cpu->c = 0x13;
369			cpu->e = 0xD8;
370			cpu->h = 1;
371			cpu->l = 0x4D;
372			break;
373		case GB_MODEL_AGB:
374			cpu->b = 1;
375			// Fall through
376		case GB_MODEL_CGB:
377			cpu->a = 0x11;
378			cpu->f.packed = 0x80;
379			cpu->c = 0;
380			cpu->e = 0x08;
381			cpu->h = 0;
382			cpu->l = 0x7C;
383			break;
384		}
385
386		cpu->sp = 0xFFFE;
387		cpu->pc = 0x100;
388	}
389
390	gb->eiPending = INT_MAX;
391	gb->doubleSpeed = 0;
392
393	cpu->memory.setActiveRegion(cpu, cpu->pc);
394
395	if (gb->yankedRomSize) {
396		gb->memory.romSize = gb->yankedRomSize;
397		gb->yankedRomSize = 0;
398	}
399	GBMemoryReset(gb);
400	GBVideoReset(&gb->video);
401	GBTimerReset(&gb->timer);
402	GBAudioReset(&gb->audio);
403	GBIOReset(gb);
404	GBSIOReset(&gb->sio);
405
406	GBSavedataUnmask(gb);
407}
408
409void GBDetectModel(struct GB* gb) {
410	if (gb->model != GB_MODEL_AUTODETECT) {
411		return;
412	}
413	if (gb->biosVf) {
414		switch (_GBBiosCRC32(gb->biosVf)) {
415		case DMG_BIOS_CHECKSUM:
416		case DMG_2_BIOS_CHECKSUM:
417			gb->model = GB_MODEL_DMG;
418			break;
419		case CGB_BIOS_CHECKSUM:
420			gb->model = GB_MODEL_CGB;
421			break;
422		default:
423			gb->biosVf->close(gb->biosVf);
424			gb->biosVf = NULL;
425		}
426	}
427	if (gb->model == GB_MODEL_AUTODETECT && gb->memory.rom) {
428		const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
429		if (cart->cgb & 0x80) {
430			gb->model = GB_MODEL_CGB;
431		} else {
432			gb->model = GB_MODEL_DMG;
433		}
434	}
435
436	switch (gb->model) {
437	case GB_MODEL_DMG:
438	case GB_MODEL_SGB:
439	case GB_MODEL_AUTODETECT: //Silence warnings
440		gb->audio.style = GB_AUDIO_DMG;
441		break;
442	case GB_MODEL_AGB:
443	case GB_MODEL_CGB:
444		gb->audio.style = GB_AUDIO_CGB;
445		break;
446	}
447}
448
449void GBUpdateIRQs(struct GB* gb) {
450	int irqs = gb->memory.ie & gb->memory.io[REG_IF];
451	if (!irqs) {
452		return;
453	}
454	gb->cpu->halted = false;
455
456	if (!gb->memory.ime || gb->cpu->irqPending) {
457		return;
458	}
459
460	if (irqs & (1 << GB_IRQ_VBLANK)) {
461		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_VBLANK);
462		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_VBLANK);
463		return;
464	}
465	if (irqs & (1 << GB_IRQ_LCDSTAT)) {
466		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_LCDSTAT);
467		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_LCDSTAT);
468		return;
469	}
470	if (irqs & (1 << GB_IRQ_TIMER)) {
471		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_TIMER);
472		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_TIMER);
473		return;
474	}
475	if (irqs & (1 << GB_IRQ_SIO)) {
476		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_SIO);
477		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_SIO);
478		return;
479	}
480	if (irqs & (1 << GB_IRQ_KEYPAD)) {
481		LR35902RaiseIRQ(gb->cpu, GB_VECTOR_KEYPAD);
482		gb->memory.io[REG_IF] &= ~(1 << GB_IRQ_KEYPAD);
483	}
484}
485
486void GBProcessEvents(struct LR35902Core* cpu) {
487	struct GB* gb = (struct GB*) cpu->master;
488	do {
489		int32_t cycles = cpu->nextEvent;
490		int32_t nextEvent = INT_MAX;
491		int32_t testEvent;
492
493		if (gb->eiPending != INT_MAX) {
494			gb->eiPending -= cycles;
495			if (gb->eiPending <= 0) {
496				gb->memory.ime = true;
497				GBUpdateIRQs(gb);
498				gb->eiPending = INT_MAX;
499			} else {
500				nextEvent = gb->eiPending;
501			}
502		}
503
504		testEvent = GBVideoProcessEvents(&gb->video, cycles >> gb->doubleSpeed);
505		if (testEvent != INT_MAX) {
506			testEvent <<= gb->doubleSpeed;
507			if (testEvent < nextEvent) {
508				nextEvent = testEvent;
509			}
510		}
511
512		testEvent = GBAudioProcessEvents(&gb->audio, cycles >> gb->doubleSpeed);
513		if (testEvent != INT_MAX) {
514			testEvent <<= gb->doubleSpeed;
515			if (testEvent < nextEvent) {
516				nextEvent = testEvent;
517			}
518		}
519
520		testEvent = GBTimerProcessEvents(&gb->timer, cycles);
521		if (testEvent < nextEvent) {
522			nextEvent = testEvent;
523		}
524
525		testEvent = GBSIOProcessEvents(&gb->sio, cycles);
526		if (testEvent < nextEvent) {
527			nextEvent = testEvent;
528		}
529
530		testEvent = GBMemoryProcessEvents(gb, cycles);
531		if (testEvent < nextEvent) {
532			nextEvent = testEvent;
533		}
534
535		cpu->cycles -= cycles;
536		cpu->nextEvent = nextEvent;
537
538		if (cpu->halted) {
539			cpu->cycles = cpu->nextEvent;
540			if (!gb->memory.ie || !gb->memory.ime) {
541				break;
542			}
543		}
544	} while (cpu->cycles >= cpu->nextEvent);
545}
546
547void GBSetInterrupts(struct LR35902Core* cpu, bool enable) {
548	struct GB* gb = (struct GB*) cpu->master;
549	if (!enable) {
550		gb->memory.ime = enable;
551		gb->eiPending = INT_MAX;
552		GBUpdateIRQs(gb);
553	} else {
554		if (cpu->nextEvent > cpu->cycles + 4) {
555			cpu->nextEvent = cpu->cycles + 4;
556		}
557		gb->eiPending = cpu->cycles + 4;
558	}
559}
560
561void GBHalt(struct LR35902Core* cpu) {
562	if (!cpu->irqPending) {
563		cpu->cycles = cpu->nextEvent;
564		cpu->halted = true;
565	}
566}
567
568void GBStop(struct LR35902Core* cpu) {
569	struct GB* gb = (struct GB*) cpu->master;
570	if (cpu->bus) {
571		mLOG(GB, GAME_ERROR, "Hit illegal stop at address %04X:%02X\n", cpu->pc, cpu->bus);
572	}
573	if (gb->memory.io[REG_KEY1] & 1) {
574		gb->doubleSpeed ^= 1;
575		gb->memory.io[REG_KEY1] = 0;
576		gb->memory.io[REG_KEY1] |= gb->doubleSpeed << 7;
577	} else if (cpu->bus) {
578		if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) {
579			struct mDebuggerEntryInfo info = {
580				.address = cpu->pc - 1,
581				.opcode = 0x1000 | cpu->bus
582			};
583			mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info);
584		}
585		// Hang forever
586		gb->memory.ime = 0;
587		cpu->pc -= 2;
588	}
589	// TODO: Actually stop
590}
591
592void GBIllegal(struct LR35902Core* cpu) {
593	struct GB* gb = (struct GB*) cpu->master;
594	mLOG(GB, GAME_ERROR, "Hit illegal opcode at address %04X:%02X\n", cpu->pc, cpu->bus);
595	if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) {
596		struct mDebuggerEntryInfo info = {
597			.address = cpu->pc,
598			.opcode = cpu->bus
599		};
600		mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info);
601	}
602	// Hang forever
603	gb->memory.ime = 0;
604	--cpu->pc;
605}
606
607bool GBIsROM(struct VFile* vf) {
608	vf->seek(vf, 0x104, SEEK_SET);
609	uint8_t header[4];
610	static const uint8_t knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66};
611
612	if (vf->read(vf, &header, sizeof(header)) < (ssize_t) sizeof(header)) {
613		return false;
614	}
615	if (memcmp(header, knownHeader, sizeof(header))) {
616		return false;
617	}
618	return true;
619}
620
621void GBGetGameTitle(const struct GB* gb, char* out) {
622	const struct GBCartridge* cart = NULL;
623	if (gb->memory.rom) {
624		cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
625	}
626	if (gb->pristineRom) {
627		cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
628	}
629	if (!cart) {
630		return;
631	}
632	if (cart->oldLicensee != 0x33) {
633		memcpy(out, cart->titleLong, 16);
634	} else {
635		memcpy(out, cart->titleShort, 11);
636	}
637}
638
639void GBGetGameCode(const struct GB* gb, char* out) {
640	memset(out, 0, 8);
641	const struct GBCartridge* cart = NULL;
642	if (gb->memory.rom) {
643		cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
644	}
645	if (gb->pristineRom) {
646		cart = (const struct GBCartridge*) &((uint8_t*) gb->pristineRom)[0x100];
647	}
648	if (!cart) {
649		return;
650	}
651	if (cart->cgb == 0xC0) {
652		memcpy(out, "CGB-????", 8);
653	} else {
654		memcpy(out, "DMG-????", 8);
655	}
656	if (cart->oldLicensee == 0x33) {
657		memcpy(&out[4], cart->maker, 4);
658	}
659}
660
661void GBFrameEnded(struct GB* gb) {
662	GBSramClean(gb, gb->video.frameCounter);
663
664	if (gb->cpu->components && gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
665		struct mCheatDevice* device = (struct mCheatDevice*) gb->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
666		size_t i;
667		for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
668			struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
669			mCheatRefresh(device, cheats);
670		}
671	}
672}