all repos — mgba @ cdb93a1469dc7fa12eceb7847179eb0b7cd5b84a

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