all repos — mgba @ 536dc8f7ab9ec2512b90505a30eb14a4c0a6b4f6

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