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