all repos — mgba @ bdfb1b72be8f2441c205856f614e6ce463cc7ced

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