all repos — mgba @ 0d4f119ecd72fb94e2c3593a1a23d3df6fde2f3c

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