all repos — mgba @ c4b38790f211b65cb15af26cebe9fc25e3c19914

mGBA Game Boy Advance Emulator

src/gb/core.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 <mgba/gb/core.h>
   7
   8#include <mgba/core/core.h>
   9#include <mgba/internal/debugger/symbols.h>
  10#include <mgba/internal/gb/cheats.h>
  11#include <mgba/internal/gb/debugger/debugger.h>
  12#include <mgba/internal/gb/debugger/symbols.h>
  13#include <mgba/internal/gb/extra/cli.h>
  14#include <mgba/internal/gb/io.h>
  15#include <mgba/internal/gb/gb.h>
  16#include <mgba/internal/gb/mbc.h>
  17#include <mgba/internal/gb/overrides.h>
  18#include <mgba/internal/gb/renderers/software.h>
  19#include <mgba/internal/gb/renderers/proxy.h>
  20#include <mgba/internal/gb/serialize.h>
  21#include <mgba/internal/sm83/sm83.h>
  22#include <mgba/internal/sm83/debugger/debugger.h>
  23#include <mgba-util/crc32.h>
  24#include <mgba-util/memory.h>
  25#include <mgba-util/patch.h>
  26#include <mgba-util/vfs.h>
  27
  28static const struct mCoreChannelInfo _GBVideoLayers[] = {
  29	{ GB_LAYER_BACKGROUND, "bg", "Background", NULL },
  30	{ GB_LAYER_WINDOW, "bgwin", "Window", NULL },
  31	{ GB_LAYER_OBJ, "obj", "Objects", NULL },
  32};
  33
  34static const struct mCoreChannelInfo _GBAudioChannels[] = {
  35	{ 0, "ch1", "Channel 1", "Square/Sweep" },
  36	{ 1, "ch2", "Channel 2", "Square" },
  37	{ 2, "ch3", "Channel 3", "PCM" },
  38	{ 3, "ch4", "Channel 4", "Noise" },
  39};
  40
  41static const struct mCoreMemoryBlock _GBMemoryBlocks[] = {
  42	{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
  43	{ GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_WORM | mCORE_MEMORY_MAPPED, 511, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 },
  44	{ GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  45	{ GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 },
  46	{ GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2 , GB_SIZE_WORKING_RAM_BANK0 * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  47	{ GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  48	{ GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  49	{ GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  50};
  51
  52static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
  53	{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL },
  54	{ GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_WORM | mCORE_MEMORY_MAPPED, 511, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 },
  55	{ GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
  56	{ GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 },
  57	{ GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 },
  58	{ GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  59	{ GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  60	{ GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
  61};
  62
  63struct mVideoLogContext;
  64struct GBCore {
  65	struct mCore d;
  66	struct GBVideoRenderer dummyRenderer;
  67	struct GBVideoSoftwareRenderer renderer;
  68#ifndef MINIMAL_CORE
  69	struct GBVideoProxyRenderer proxyRenderer;
  70	struct mVideoLogContext* logContext;
  71#endif
  72	struct mCoreCallbacks logCallbacks;
  73	uint8_t keys;
  74	struct mCPUComponent* components[CPU_COMPONENT_MAX];
  75	const struct Configuration* overrides;
  76	struct mDebuggerPlatform* debuggerPlatform;
  77	struct mCheatDevice* cheatDevice;
  78	struct mCoreMemoryBlock memoryBlocks[8];
  79};
  80
  81static bool _GBCoreInit(struct mCore* core) {
  82	struct GBCore* gbcore = (struct GBCore*) core;
  83
  84	struct SM83Core* cpu = anonymousMemoryMap(sizeof(struct SM83Core));
  85	struct GB* gb = anonymousMemoryMap(sizeof(struct GB));
  86	if (!cpu || !gb) {
  87		free(cpu);
  88		free(gb);
  89		return false;
  90	}
  91	core->cpu = cpu;
  92	core->board = gb;
  93	core->timing = &gb->timing;
  94	gbcore->overrides = NULL;
  95	gbcore->debuggerPlatform = NULL;
  96	gbcore->cheatDevice = NULL;
  97#ifndef MINIMAL_CORE
  98	gbcore->logContext = NULL;
  99#endif
 100	memcpy(gbcore->memoryBlocks, _GBMemoryBlocks, sizeof(_GBMemoryBlocks));
 101
 102	GBCreate(gb);
 103	memset(gbcore->components, 0, sizeof(gbcore->components));
 104	SM83SetComponents(cpu, &gb->d, CPU_COMPONENT_MAX, gbcore->components);
 105	SM83Init(cpu);
 106	mRTCGenericSourceInit(&core->rtc, core);
 107	gb->memory.rtc = &core->rtc.d;
 108
 109	GBVideoDummyRendererCreate(&gbcore->dummyRenderer);
 110	GBVideoAssociateRenderer(&gb->video, &gbcore->dummyRenderer);
 111
 112	GBVideoSoftwareRendererCreate(&gbcore->renderer);
 113	gbcore->renderer.outputBuffer = NULL;
 114
 115#ifndef MINIMAL_CORE
 116	gbcore->proxyRenderer.logger = NULL;
 117#endif
 118
 119	gbcore->keys = 0;
 120	gb->keySource = &gbcore->keys;
 121
 122#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
 123	mDirectorySetInit(&core->dirs);
 124#endif
 125	
 126	return true;
 127}
 128
 129static void _GBCoreDeinit(struct mCore* core) {
 130	SM83Deinit(core->cpu);
 131	GBDestroy(core->board);
 132	mappedMemoryFree(core->cpu, sizeof(struct SM83Core));
 133	mappedMemoryFree(core->board, sizeof(struct GB));
 134#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
 135	mDirectorySetDeinit(&core->dirs);
 136#endif
 137#ifdef USE_DEBUGGERS
 138	if (core->symbolTable) {
 139		mDebuggerSymbolTableDestroy(core->symbolTable);
 140	}
 141#endif
 142
 143	struct GBCore* gbcore = (struct GBCore*) core;
 144	free(gbcore->debuggerPlatform);
 145	if (gbcore->cheatDevice) {
 146		mCheatDeviceDestroy(gbcore->cheatDevice);
 147	}
 148	mCoreConfigFreeOpts(&core->opts);
 149	free(core);
 150}
 151
 152static enum mPlatform _GBCorePlatform(const struct mCore* core) {
 153	UNUSED(core);
 154	return mPLATFORM_GB;
 155}
 156
 157static bool _GBCoreSupportsFeature(const struct mCore* core, enum mCoreFeature feature) {
 158	UNUSED(core);
 159	switch (feature) {
 160	default:
 161		return false;
 162	}
 163}
 164
 165static void _GBCoreSetSync(struct mCore* core, struct mCoreSync* sync) {
 166	struct GB* gb = core->board;
 167	gb->sync = sync;
 168}
 169
 170static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* config) {
 171	UNUSED(config);
 172
 173	struct GB* gb = core->board;
 174	if (core->opts.mute) {
 175		gb->audio.masterVolume = 0;
 176	} else {
 177		gb->audio.masterVolume = core->opts.volume;
 178	}
 179	gb->video.frameskip = core->opts.frameskip;
 180
 181	int color;
 182	if (mCoreConfigGetIntValue(config, "gb.pal[0]", &color)) {
 183		GBVideoSetPalette(&gb->video, 0, color);
 184	}
 185	if (mCoreConfigGetIntValue(config, "gb.pal[1]", &color)) {
 186		GBVideoSetPalette(&gb->video, 1, color);
 187	}
 188	if (mCoreConfigGetIntValue(config, "gb.pal[2]", &color)) {
 189		GBVideoSetPalette(&gb->video, 2, color);
 190	}
 191	if (mCoreConfigGetIntValue(config, "gb.pal[3]", &color)) {
 192		GBVideoSetPalette(&gb->video, 3, color);
 193	}
 194	if (mCoreConfigGetIntValue(config, "gb.pal[4]", &color)) {
 195		GBVideoSetPalette(&gb->video, 4, color);
 196	}
 197	if (mCoreConfigGetIntValue(config, "gb.pal[5]", &color)) {
 198		GBVideoSetPalette(&gb->video, 5, color);
 199	}
 200	if (mCoreConfigGetIntValue(config, "gb.pal[6]", &color)) {
 201		GBVideoSetPalette(&gb->video, 6, color);
 202	}
 203	if (mCoreConfigGetIntValue(config, "gb.pal[7]", &color)) {
 204		GBVideoSetPalette(&gb->video, 7, color);
 205	}
 206	if (mCoreConfigGetIntValue(config, "gb.pal[8]", &color)) {
 207		GBVideoSetPalette(&gb->video, 8, color);
 208	}
 209	if (mCoreConfigGetIntValue(config, "gb.pal[9]", &color)) {
 210		GBVideoSetPalette(&gb->video, 9, color);
 211	}
 212	if (mCoreConfigGetIntValue(config, "gb.pal[10]", &color)) {
 213		GBVideoSetPalette(&gb->video, 10, color);
 214	}
 215	if (mCoreConfigGetIntValue(config, "gb.pal[11]", &color)) {
 216		GBVideoSetPalette(&gb->video, 11, color);
 217	}
 218
 219	mCoreConfigCopyValue(&core->config, config, "gb.bios");
 220	mCoreConfigCopyValue(&core->config, config, "sgb.bios");
 221	mCoreConfigCopyValue(&core->config, config, "gbc.bios");
 222	mCoreConfigCopyValue(&core->config, config, "gb.model");
 223	mCoreConfigCopyValue(&core->config, config, "sgb.model");
 224	mCoreConfigCopyValue(&core->config, config, "cgb.model");
 225	mCoreConfigCopyValue(&core->config, config, "cgb.hybridModel");
 226	mCoreConfigCopyValue(&core->config, config, "cgb.sgbModel");
 227	mCoreConfigCopyValue(&core->config, config, "useCgbColors");
 228	mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections");
 229
 230	int fakeBool = 0;
 231	mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool);
 232	gb->allowOpposingDirections = fakeBool;
 233
 234	if (mCoreConfigGetIntValue(config, "sgb.borders", &fakeBool)) {
 235		gb->video.sgbBorders = fakeBool;
 236		gb->video.renderer->enableSGBBorder(gb->video.renderer, fakeBool);
 237	}
 238
 239#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
 240	struct GBCore* gbcore = (struct GBCore*) core;
 241	gbcore->overrides = mCoreConfigGetOverridesConst(config);
 242#endif
 243}
 244
 245static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, const struct mCoreConfig* config) {
 246	struct GB* gb = core->board;
 247	if (!config) {
 248		config = &core->config;
 249	}
 250
 251	if (!option) {
 252		// Reload options from opts
 253		if (core->opts.mute) {
 254			gb->audio.masterVolume = 0;
 255		} else {
 256			gb->audio.masterVolume = core->opts.volume;
 257		}
 258		gb->video.frameskip = core->opts.frameskip;
 259		return;
 260	}
 261
 262	int fakeBool;
 263	if (strcmp("mute", option) == 0) {
 264		if (mCoreConfigGetIntValue(config, "mute", &fakeBool)) {
 265			core->opts.mute = fakeBool;
 266
 267			if (core->opts.mute) {
 268				gb->audio.masterVolume = 0;
 269			} else {
 270				gb->audio.masterVolume = core->opts.volume;
 271			}
 272		}
 273		return;
 274	}
 275	if (strcmp("volume", option) == 0) {
 276		if (mCoreConfigGetIntValue(config, "volume", &core->opts.volume) && !core->opts.mute) {
 277			gb->audio.masterVolume = core->opts.volume;
 278		}
 279		return;
 280	}
 281	if (strcmp("frameskip", option) == 0) {
 282		if (mCoreConfigGetIntValue(config, "frameskip", &core->opts.frameskip)) {
 283			gb->video.frameskip = core->opts.frameskip;
 284		}
 285		return;
 286	}
 287	if (strcmp("allowOpposingDirections", option) == 0) {
 288		if (config != &core->config) {
 289			mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections");
 290		}
 291		if (mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool)) {
 292			gb->allowOpposingDirections = fakeBool;
 293		}
 294		return;
 295	}
 296	if (strcmp("sgb.borders", option) == 0) {
 297		if (mCoreConfigGetIntValue(config, "sgb.borders", &fakeBool)) {
 298			gb->video.sgbBorders = fakeBool;
 299			gb->video.renderer->enableSGBBorder(gb->video.renderer, fakeBool);
 300		}
 301	}
 302}
 303
 304static void _GBCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) {
 305	const struct GB* gb = core->board;
 306	if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) {
 307		*width = GB_VIDEO_HORIZONTAL_PIXELS;
 308		*height = GB_VIDEO_VERTICAL_PIXELS;
 309	} else {
 310		*width = 256;
 311		*height = 224;
 312	}
 313}
 314
 315static void _GBCoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) {
 316	struct GBCore* gbcore = (struct GBCore*) core;
 317	gbcore->renderer.outputBuffer = buffer;
 318	gbcore->renderer.outputBufferStride = stride;
 319}
 320
 321static void _GBCoreSetVideoGLTex(struct mCore* core, unsigned texid) {
 322	UNUSED(core);
 323	UNUSED(texid);
 324}
 325
 326static void _GBCoreGetPixels(struct mCore* core, const void** buffer, size_t* stride) {
 327	struct GBCore* gbcore = (struct GBCore*) core;
 328	gbcore->renderer.d.getPixels(&gbcore->renderer.d, stride, buffer);
 329}
 330
 331static void _GBCorePutPixels(struct mCore* core, const void* buffer, size_t stride) {
 332	struct GBCore* gbcore = (struct GBCore*) core;
 333	gbcore->renderer.d.putPixels(&gbcore->renderer.d, stride, buffer);
 334}
 335
 336static struct blip_t* _GBCoreGetAudioChannel(struct mCore* core, int ch) {
 337	struct GB* gb = core->board;
 338	switch (ch) {
 339	case 0:
 340		return gb->audio.left;
 341	case 1:
 342		return gb->audio.right;
 343	default:
 344		return NULL;
 345	}
 346}
 347
 348static void _GBCoreSetAudioBufferSize(struct mCore* core, size_t samples) {
 349	struct GB* gb = core->board;
 350	GBAudioResizeBuffer(&gb->audio, samples);
 351}
 352
 353static size_t _GBCoreGetAudioBufferSize(struct mCore* core) {
 354	struct GB* gb = core->board;
 355	return gb->audio.samples;
 356}
 357
 358static void _GBCoreAddCoreCallbacks(struct mCore* core, struct mCoreCallbacks* coreCallbacks) {
 359	struct GB* gb = core->board;
 360	*mCoreCallbacksListAppend(&gb->coreCallbacks) = *coreCallbacks;
 361}
 362
 363static void _GBCoreClearCoreCallbacks(struct mCore* core) {
 364	struct GB* gb = core->board;
 365	mCoreCallbacksListClear(&gb->coreCallbacks);
 366}
 367
 368static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
 369	struct GB* gb = core->board;
 370	gb->stream = stream;
 371	if (stream && stream->videoDimensionsChanged) {
 372		unsigned width, height;
 373		core->desiredVideoDimensions(core, &width, &height);
 374		stream->videoDimensionsChanged(stream, width, height);
 375	}
 376}
 377
 378static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {
 379	return GBLoadROM(core->board, vf);
 380}
 381
 382static bool _GBCoreLoadBIOS(struct mCore* core, struct VFile* vf, int type) {
 383	UNUSED(type);
 384	GBLoadBIOS(core->board, vf);
 385	return true;
 386}
 387
 388static bool _GBCoreLoadSave(struct mCore* core, struct VFile* vf) {
 389	return GBLoadSave(core->board, vf);
 390}
 391
 392static bool _GBCoreLoadTemporarySave(struct mCore* core, struct VFile* vf) {
 393	struct GB* gb = core->board;
 394	GBSavedataMask(gb, vf, false);
 395	return true; // TODO: Return a real value
 396}
 397
 398static bool _GBCoreLoadPatch(struct mCore* core, struct VFile* vf) {
 399	if (!vf) {
 400		return false;
 401	}
 402	struct Patch patch;
 403	if (!loadPatch(vf, &patch)) {
 404		return false;
 405	}
 406	GBApplyPatch(core->board, &patch);
 407	return true;
 408}
 409
 410static void _GBCoreUnloadROM(struct mCore* core) {
 411	struct GBCore* gbcore = (struct GBCore*) core;
 412	struct SM83Core* cpu = core->cpu;
 413	if (gbcore->cheatDevice) {
 414		SM83HotplugDetach(cpu, CPU_COMPONENT_CHEAT_DEVICE);
 415		cpu->components[CPU_COMPONENT_CHEAT_DEVICE] = NULL;
 416		mCheatDeviceDestroy(gbcore->cheatDevice);
 417		gbcore->cheatDevice = NULL;
 418	}
 419	return GBUnloadROM(core->board);
 420}
 421
 422static void _GBCoreChecksum(const struct mCore* core, void* data, enum mCoreChecksumType type) {
 423	struct GB* gb = (struct GB*) core->board;
 424	switch (type) {
 425	case mCHECKSUM_CRC32:
 426		memcpy(data, &gb->romCrc32, sizeof(gb->romCrc32));
 427		break;
 428	}
 429	return;
 430}
 431
 432static void _GBCoreReset(struct mCore* core) {
 433	struct GBCore* gbcore = (struct GBCore*) core;
 434	struct GB* gb = (struct GB*) core->board;
 435	if (gbcore->renderer.outputBuffer) {
 436		GBVideoAssociateRenderer(&gb->video, &gbcore->renderer.d);
 437	}
 438
 439	if (gb->memory.rom) {
 440		int doColorOverride = 0;
 441		mCoreConfigGetIntValue(&core->config, "useCgbColors", &doColorOverride);
 442
 443		struct GBCartridgeOverride override;
 444		const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
 445		override.headerCrc32 = doCrc32(cart, sizeof(*cart));
 446		bool modelOverride = GBOverrideFind(gbcore->overrides, &override) || (doColorOverride && GBOverrideColorFind(&override));
 447		if (modelOverride) {
 448			GBOverrideApply(gb, &override);
 449		} else {
 450			const char* modelGB = mCoreConfigGetValue(&core->config, "gb.model");
 451			const char* modelSGB = mCoreConfigGetValue(&core->config, "sgb.model");
 452			const char* modelCGB = mCoreConfigGetValue(&core->config, "cgb.model");
 453			const char* modelCGBHybrid = mCoreConfigGetValue(&core->config, "cgb.hybridModel");
 454			const char* modelCGBSGB = mCoreConfigGetValue(&core->config, "cgb.sgbModel");
 455			if (modelGB || modelCGB || modelSGB || modelCGBHybrid || modelCGBSGB) {
 456				int models = GBValidModels(gb->memory.rom);
 457				switch (models) {
 458				case GB_MODEL_SGB | GB_MODEL_MGB:
 459					if (modelSGB) {
 460						gb->model = GBNameToModel(modelSGB);
 461					}
 462					break;
 463				case GB_MODEL_MGB:
 464					if (modelGB) {
 465						gb->model = GBNameToModel(modelGB);
 466					}
 467					break;
 468				case GB_MODEL_MGB | GB_MODEL_CGB:
 469					if (modelCGBHybrid) {
 470						gb->model = GBNameToModel(modelCGBHybrid);
 471					}
 472					break;
 473				case GB_MODEL_SGB | GB_MODEL_CGB: // TODO: Do these even exist?
 474				case GB_MODEL_MGB | GB_MODEL_SGB | GB_MODEL_CGB:
 475					if (modelCGBSGB) {
 476						gb->model = GBNameToModel(modelCGBSGB);
 477					}
 478					break;
 479				case GB_MODEL_CGB:
 480					if (modelCGB) {
 481						gb->model = GBNameToModel(modelCGB);
 482					}
 483					break;
 484				}
 485			}
 486		}
 487	}
 488
 489#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
 490	if (!gb->biosVf && core->opts.useBios) {
 491		struct VFile* bios = NULL;
 492		bool found = false;
 493		if (core->opts.bios) {
 494			bios = VFileOpen(core->opts.bios, O_RDONLY);
 495			if (bios && GBIsBIOS(bios)) {
 496				found = true;
 497			} else if (bios) {
 498				bios->close(bios);
 499				bios = NULL;
 500			}
 501		}
 502		if (!found) {
 503			GBDetectModel(gb);
 504			const char* configPath = NULL;
 505
 506			switch (gb->model) {
 507			case GB_MODEL_DMG:
 508			case GB_MODEL_MGB: // TODO
 509				configPath = mCoreConfigGetValue(&core->config, "gb.bios");
 510				break;
 511			case GB_MODEL_SGB:
 512			case GB_MODEL_SGB2: // TODO
 513				configPath = mCoreConfigGetValue(&core->config, "sgb.bios");
 514				break;
 515			case GB_MODEL_CGB:
 516			case GB_MODEL_AGB:
 517				configPath = mCoreConfigGetValue(&core->config, "gbc.bios");
 518				break;
 519			default:
 520				break;
 521			};
 522			if (configPath) {
 523				bios = VFileOpen(configPath, O_RDONLY);
 524			}
 525			if (bios && GBIsBIOS(bios)) {
 526				found = true;
 527			} else if (bios) {
 528				bios->close(bios);
 529				bios = NULL;
 530			}
 531		}
 532		if (!found) {
 533			char path[PATH_MAX];
 534			mCoreConfigDirectory(path, PATH_MAX);
 535			switch (gb->model) {
 536			case GB_MODEL_DMG:
 537			case GB_MODEL_MGB: // TODO
 538				strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path));
 539				break;
 540			case GB_MODEL_SGB:
 541			case GB_MODEL_SGB2: // TODO
 542				strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path));
 543				break;
 544			case GB_MODEL_CGB:
 545			case GB_MODEL_AGB:
 546				strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path));
 547				break;
 548			default:
 549				break;
 550			};
 551			bios = VFileOpen(path, O_RDONLY);
 552			if (bios && GBIsBIOS(bios)) {
 553				found = true;
 554			} else if (bios) {
 555				bios->close(bios);
 556				bios = NULL;
 557			}
 558		}
 559		if (found && bios) {
 560			GBLoadBIOS(gb, bios);
 561		}
 562	}
 563#endif
 564
 565	if (gb->model < GB_MODEL_CGB) {
 566		memcpy(gbcore->memoryBlocks, _GBMemoryBlocks, sizeof(_GBMemoryBlocks));
 567	} else {
 568		memcpy(gbcore->memoryBlocks, _GBCMemoryBlocks, sizeof(_GBCMemoryBlocks));
 569	}
 570
 571	size_t i;
 572	for (i = 0; i < sizeof(gbcore->memoryBlocks) / sizeof(*gbcore->memoryBlocks); ++i) {
 573		if (gbcore->memoryBlocks[i].id == GB_REGION_CART_BANK0) {
 574			gbcore->memoryBlocks[i].maxSegment = gb->memory.romSize / GB_SIZE_CART_BANK0;
 575		} else if (gbcore->memoryBlocks[i].id == GB_REGION_EXTERNAL_RAM) {
 576			gbcore->memoryBlocks[i].maxSegment = gb->sramSize / GB_SIZE_EXTERNAL_RAM;
 577		} else {
 578			continue;
 579		}
 580		if (gbcore->memoryBlocks[i].maxSegment) {
 581			--gbcore->memoryBlocks[i].maxSegment;
 582		}
 583	}
 584
 585	SM83Reset(core->cpu);
 586
 587	if (core->opts.skipBios) {
 588		GBSkipBIOS(core->board);
 589	}
 590}
 591
 592static void _GBCoreRunFrame(struct mCore* core) {
 593	struct GB* gb = core->board;
 594	int32_t frameCounter = gb->video.frameCounter;
 595	while (gb->video.frameCounter == frameCounter) {
 596		SM83Run(core->cpu);
 597	}
 598}
 599
 600static void _GBCoreRunLoop(struct mCore* core) {
 601	SM83Run(core->cpu);
 602}
 603
 604static void _GBCoreStep(struct mCore* core) {
 605	struct SM83Core* cpu = core->cpu;
 606	do {
 607		SM83Tick(cpu);
 608	} while (cpu->executionState != SM83_CORE_FETCH);
 609}
 610
 611static size_t _GBCoreStateSize(struct mCore* core) {
 612	UNUSED(core);
 613	return sizeof(struct GBSerializedState);
 614}
 615
 616static bool _GBCoreLoadState(struct mCore* core, const void* state) {
 617	return GBDeserialize(core->board, state);
 618}
 619
 620static bool _GBCoreSaveState(struct mCore* core, void* state) {
 621	struct SM83Core* cpu = core->cpu;
 622	while (cpu->executionState != SM83_CORE_FETCH) {
 623		SM83Tick(cpu);
 624	}
 625	GBSerialize(core->board, state);
 626	return true;
 627}
 628
 629static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) {
 630	struct GBCore* gbcore = (struct GBCore*) core;
 631	gbcore->keys = keys;
 632	GBTestKeypadIRQ(core->board);
 633}
 634
 635static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) {
 636	struct GBCore* gbcore = (struct GBCore*) core;
 637	gbcore->keys |= keys;
 638	GBTestKeypadIRQ(core->board);
 639}
 640
 641static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) {
 642	struct GBCore* gbcore = (struct GBCore*) core;
 643	gbcore->keys &= ~keys;
 644}
 645
 646static int32_t _GBCoreFrameCounter(const struct mCore* core) {
 647	const struct GB* gb = core->board;
 648	return gb->video.frameCounter;
 649}
 650
 651static int32_t _GBCoreFrameCycles(const  struct mCore* core) {
 652	UNUSED(core);
 653	return GB_VIDEO_TOTAL_LENGTH;
 654}
 655
 656static int32_t _GBCoreFrequency(const struct mCore* core) {
 657	UNUSED(core);
 658	// TODO: GB differences
 659	return DMG_SM83_FREQUENCY;
 660}
 661
 662static void _GBCoreGetGameTitle(const struct mCore* core, char* title) {
 663	GBGetGameTitle(core->board, title);
 664}
 665
 666static void _GBCoreGetGameCode(const struct mCore* core, char* title) {
 667	GBGetGameCode(core->board, title);
 668}
 669
 670static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) {
 671	struct GB* gb = core->board;
 672	switch (type) {
 673	case mPERIPH_ROTATION:
 674		gb->memory.rotation = periph;
 675		break;
 676	case mPERIPH_RUMBLE:
 677		gb->memory.rumble = periph;
 678		break;
 679	case mPERIPH_IMAGE_SOURCE:
 680		gb->memory.cam = periph;
 681		break;
 682	default:
 683		return;
 684	}
 685}
 686
 687static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) {
 688	struct SM83Core* cpu = core->cpu;
 689	return cpu->memory.load8(cpu, address);
 690}
 691
 692static uint32_t _GBCoreBusRead16(struct mCore* core, uint32_t address) {
 693	struct SM83Core* cpu = core->cpu;
 694	return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8);
 695}
 696
 697static uint32_t _GBCoreBusRead32(struct mCore* core, uint32_t address) {
 698	struct SM83Core* cpu = core->cpu;
 699	return cpu->memory.load8(cpu, address) | (cpu->memory.load8(cpu, address + 1) << 8) |
 700	       (cpu->memory.load8(cpu, address + 2) << 16) | (cpu->memory.load8(cpu, address + 3) << 24);
 701}
 702
 703static void _GBCoreBusWrite8(struct mCore* core, uint32_t address, uint8_t value) {
 704	struct SM83Core* cpu = core->cpu;
 705	cpu->memory.store8(cpu, address, value);
 706}
 707
 708static void _GBCoreBusWrite16(struct mCore* core, uint32_t address, uint16_t value) {
 709	struct SM83Core* cpu = core->cpu;
 710	cpu->memory.store8(cpu, address, value);
 711	cpu->memory.store8(cpu, address + 1, value >> 8);
 712}
 713
 714static void _GBCoreBusWrite32(struct mCore* core, uint32_t address, uint32_t value) {
 715	struct SM83Core* cpu = core->cpu;
 716	cpu->memory.store8(cpu, address, value);
 717	cpu->memory.store8(cpu, address + 1, value >> 8);
 718	cpu->memory.store8(cpu, address + 2, value >> 16);
 719	cpu->memory.store8(cpu, address + 3, value >> 24);
 720}
 721
 722static uint32_t _GBCoreRawRead8(struct mCore* core, uint32_t address, int segment) {
 723	struct SM83Core* cpu = core->cpu;
 724	return GBView8(cpu, address, segment);
 725}
 726
 727static uint32_t _GBCoreRawRead16(struct mCore* core, uint32_t address, int segment) {
 728	struct SM83Core* cpu = core->cpu;
 729	return GBView8(cpu, address, segment) | (GBView8(cpu, address + 1, segment) << 8);
 730}
 731
 732static uint32_t _GBCoreRawRead32(struct mCore* core, uint32_t address, int segment) {
 733	struct SM83Core* cpu = core->cpu;
 734	return GBView8(cpu, address, segment) | (GBView8(cpu, address + 1, segment) << 8) |
 735	       (GBView8(cpu, address + 2, segment) << 16) | (GBView8(cpu, address + 3, segment) << 24);
 736}
 737
 738static void _GBCoreRawWrite8(struct mCore* core, uint32_t address, int segment, uint8_t value) {
 739	struct SM83Core* cpu = core->cpu;
 740	GBPatch8(cpu, address, value, NULL, segment);
 741}
 742
 743static void _GBCoreRawWrite16(struct mCore* core, uint32_t address, int segment, uint16_t value) {
 744	struct SM83Core* cpu = core->cpu;
 745	GBPatch8(cpu, address, value, NULL, segment);
 746	GBPatch8(cpu, address + 1, value >> 8, NULL, segment);
 747}
 748
 749static void _GBCoreRawWrite32(struct mCore* core, uint32_t address, int segment, uint32_t value) {
 750	struct SM83Core* cpu = core->cpu;
 751	GBPatch8(cpu, address, value, NULL, segment);
 752	GBPatch8(cpu, address + 1, value >> 8, NULL, segment);
 753	GBPatch8(cpu, address + 2, value >> 16, NULL, segment);
 754	GBPatch8(cpu, address + 3, value >> 24, NULL, segment);
 755}
 756
 757size_t _GBListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) {
 758	struct GBCore* gbcore = (struct GBCore*) core;
 759	*blocks = gbcore->memoryBlocks;
 760	return sizeof(gbcore->memoryBlocks) / sizeof(*gbcore->memoryBlocks);
 761}
 762
 763void* _GBGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
 764	struct GB* gb = core->board;
 765	bool isCgb = gb->model >= GB_MODEL_CGB;
 766	switch (id) {
 767	default:
 768		return NULL;
 769	case GB_REGION_CART_BANK0:
 770		*sizeOut = gb->memory.romSize;
 771		return gb->memory.rom;
 772	case GB_REGION_VRAM:
 773		*sizeOut = GB_SIZE_WORKING_RAM_BANK0 * (isCgb ? 1 : 2);
 774		return gb->video.vram;
 775	case GB_REGION_EXTERNAL_RAM:
 776		*sizeOut = gb->sramSize;
 777		return gb->memory.sram;
 778	case GB_REGION_WORKING_RAM_BANK0:
 779		*sizeOut = GB_SIZE_VRAM * (isCgb ? 8 : 2);
 780		return gb->memory.wram;
 781	case GB_BASE_OAM:
 782		*sizeOut = GB_SIZE_OAM;
 783		return gb->video.oam.raw;
 784	case GB_BASE_HRAM:
 785		*sizeOut = GB_SIZE_HRAM;
 786		return gb->memory.hram;
 787	}
 788}
 789
 790#ifdef USE_DEBUGGERS
 791static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
 792	UNUSED(core);
 793	switch (type) {
 794	case DEBUGGER_CUSTOM:
 795	case DEBUGGER_CLI:
 796		return true;
 797	default:
 798		return false;
 799	}
 800}
 801
 802static struct mDebuggerPlatform* _GBCoreDebuggerPlatform(struct mCore* core) {
 803	struct GBCore* gbcore = (struct GBCore*) core;
 804	struct GB* gb = core->board;
 805	if (!gbcore->debuggerPlatform) {
 806		struct SM83Debugger* platform = (struct SM83Debugger*) GBDebuggerCreate(gb);
 807		gbcore->debuggerPlatform = &platform->d;
 808	}
 809	return gbcore->debuggerPlatform;
 810}
 811
 812static struct CLIDebuggerSystem* _GBCoreCliDebuggerSystem(struct mCore* core) {
 813	return GBCLIDebuggerCreate(core);
 814}
 815
 816static void _GBCoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) {
 817	struct SM83Core* cpu = core->cpu;
 818	if (core->debugger) {
 819		SM83HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
 820	}
 821	cpu->components[CPU_COMPONENT_DEBUGGER] = &debugger->d;
 822	SM83HotplugAttach(cpu, CPU_COMPONENT_DEBUGGER);
 823	core->debugger = debugger;
 824}
 825
 826static void _GBCoreDetachDebugger(struct mCore* core) {
 827	struct SM83Core* cpu = core->cpu;
 828	if (core->debugger) {
 829		SM83HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER);
 830	}
 831	cpu->components[CPU_COMPONENT_DEBUGGER] = NULL;
 832	core->debugger = NULL;
 833}
 834
 835static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) {
 836	core->symbolTable = mDebuggerSymbolTableCreate();
 837#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
 838	if (!vf) {
 839		vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
 840	}
 841#endif
 842	if (!vf) {
 843		return;
 844	}
 845	GBLoadSymbols(core->symbolTable, vf);
 846}
 847
 848static bool _GBCoreLookupIdentifier(struct mCore* core, const char* name, int32_t* value, int* segment) {
 849	UNUSED(core);
 850	*segment = -1;
 851	int i;
 852	for (i = 0; i < GB_REG_MAX; ++i) {
 853		const char* reg = GBIORegisterNames[i];
 854		if (reg && strcasecmp(reg, name) == 0) {
 855			*value = GB_BASE_IO | i;
 856			return true;
 857		}
 858	}
 859	return false;
 860}
 861#endif
 862
 863static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {
 864	struct GBCore* gbcore = (struct GBCore*) core;
 865	if (!gbcore->cheatDevice) {
 866		gbcore->cheatDevice = GBCheatDeviceCreate();
 867		((struct SM83Core*) core->cpu)->components[CPU_COMPONENT_CHEAT_DEVICE] = &gbcore->cheatDevice->d;
 868		SM83HotplugAttach(core->cpu, CPU_COMPONENT_CHEAT_DEVICE);
 869		gbcore->cheatDevice->p = core;
 870	}
 871	return gbcore->cheatDevice;
 872}
 873
 874static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) {
 875	struct GB* gb = core->board;
 876	struct VFile* vf = gb->sramVf;
 877	if (vf) {
 878		*sram = malloc(vf->size(vf));
 879		vf->seek(vf, 0, SEEK_SET);
 880		return vf->read(vf, *sram, vf->size(vf));
 881	}
 882	if (gb->sramSize) {
 883		*sram = malloc(gb->sramSize);
 884		memcpy(*sram, gb->memory.sram, gb->sramSize);
 885		return gb->sramSize;
 886	}
 887	*sram = NULL;
 888	return 0;
 889}
 890
 891static bool _GBCoreSavedataRestore(struct mCore* core, const void* sram, size_t size, bool writeback) {
 892	struct GB* gb = core->board;
 893	if (!writeback) {
 894		struct VFile* vf = VFileMemChunk(sram, size);
 895		GBSavedataMask(gb, vf, true);
 896		return true;
 897	}
 898	struct VFile* vf = gb->sramVf;
 899	if (vf) {
 900		vf->seek(vf, 0, SEEK_SET);
 901		return vf->write(vf, sram, size) > 0;
 902	}
 903	if (size > 0x20000) {
 904		size = 0x20000;
 905	}
 906	GBResizeSram(gb, size);
 907	memcpy(gb->memory.sram, sram, size);
 908	return true;
 909}
 910
 911static size_t _GBCoreListVideoLayers(const struct mCore* core, const struct mCoreChannelInfo** info) {
 912	UNUSED(core);
 913	if (info) {
 914		*info = _GBVideoLayers;
 915	}
 916	return sizeof(_GBVideoLayers) / sizeof(*_GBVideoLayers);
 917}
 918
 919static size_t _GBCoreListAudioChannels(const struct mCore* core, const struct mCoreChannelInfo** info) {
 920	UNUSED(core);
 921	if (info) {
 922		*info = _GBAudioChannels;
 923	}
 924	return sizeof(_GBAudioChannels) / sizeof(*_GBAudioChannels);
 925}
 926
 927static void _GBCoreEnableVideoLayer(struct mCore* core, size_t id, bool enable) {
 928	struct GB* gb = core->board;
 929	switch (id) {
 930	case GB_LAYER_BACKGROUND:
 931		gb->video.renderer->disableBG = !enable;
 932		break;
 933	case GB_LAYER_WINDOW:
 934		gb->video.renderer->disableWIN = !enable;
 935		break;
 936	case GB_LAYER_OBJ:
 937		gb->video.renderer->disableOBJ = !enable;
 938		break;
 939	default:
 940		break;
 941	}
 942}
 943
 944static void _GBCoreEnableAudioChannel(struct mCore* core, size_t id, bool enable) {
 945	struct GB* gb = core->board;
 946	switch (id) {
 947	case 0:
 948	case 1:
 949	case 2:
 950	case 3:
 951		gb->audio.forceDisableCh[id] = !enable;
 952		break;
 953	default:
 954		break;
 955	}
 956}
 957
 958static void _GBCoreAdjustVideoLayer(struct mCore* core, size_t id, int32_t x, int32_t y) {
 959	struct GBCore* gbcore = (struct GBCore*) core;
 960	switch (id) {
 961	case GB_LAYER_BACKGROUND:
 962		gbcore->renderer.offsetScx = x;
 963		gbcore->renderer.offsetScy = y;
 964		break;
 965	case GB_LAYER_WINDOW:
 966		gbcore->renderer.offsetWx = x;
 967		gbcore->renderer.offsetWy = y;
 968		break;
 969	case GB_LAYER_OBJ:
 970		gbcore->renderer.objOffsetX = x;
 971		gbcore->renderer.objOffsetY = y;
 972		break;
 973	default:
 974		return;
 975	}
 976}
 977
 978#ifndef MINIMAL_CORE
 979static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) {
 980	struct GBCore* gbcore = (struct GBCore*) core;
 981	struct GB* gb = core->board;
 982	gbcore->logContext = context;
 983
 984	int channelId = mVideoLoggerAddChannel(context);
 985	gbcore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
 986	mVideoLoggerRendererCreate(gbcore->proxyRenderer.logger, false);
 987	mVideoLoggerAttachChannel(gbcore->proxyRenderer.logger, context, channelId);
 988	gbcore->proxyRenderer.logger->block = false;
 989
 990	GBVideoProxyRendererCreate(&gbcore->proxyRenderer, &gbcore->renderer.d);
 991	GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
 992}
 993
 994static void _GBCoreEndVideoLog(struct mCore* core) {
 995	struct GBCore* gbcore = (struct GBCore*) core;
 996	struct GB* gb = core->board;
 997	if (gbcore->proxyRenderer.logger) {
 998		GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
 999		free(gbcore->proxyRenderer.logger);
1000		gbcore->proxyRenderer.logger = NULL;
1001	}
1002}
1003#endif
1004
1005struct mCore* GBCoreCreate(void) {
1006	struct GBCore* gbcore = malloc(sizeof(*gbcore));
1007	struct mCore* core = &gbcore->d;
1008	memset(&core->opts, 0, sizeof(core->opts));
1009	core->cpu = NULL;
1010	core->board = NULL;
1011	core->debugger = NULL;
1012	core->symbolTable = NULL;
1013	core->init = _GBCoreInit;
1014	core->deinit = _GBCoreDeinit;
1015	core->platform = _GBCorePlatform;
1016	core->supportsFeature = _GBCoreSupportsFeature;
1017	core->setSync = _GBCoreSetSync;
1018	core->loadConfig = _GBCoreLoadConfig;
1019	core->reloadConfigOption = _GBCoreReloadConfigOption;
1020	core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions;
1021	core->setVideoBuffer = _GBCoreSetVideoBuffer;
1022	core->setVideoGLTex = _GBCoreSetVideoGLTex;
1023	core->getPixels = _GBCoreGetPixels;
1024	core->putPixels = _GBCorePutPixels;
1025	core->getAudioChannel = _GBCoreGetAudioChannel;
1026	core->setAudioBufferSize = _GBCoreSetAudioBufferSize;
1027	core->getAudioBufferSize = _GBCoreGetAudioBufferSize;
1028	core->setAVStream = _GBCoreSetAVStream;
1029	core->addCoreCallbacks = _GBCoreAddCoreCallbacks;
1030	core->clearCoreCallbacks = _GBCoreClearCoreCallbacks;
1031	core->isROM = GBIsROM;
1032	core->loadROM = _GBCoreLoadROM;
1033	core->loadBIOS = _GBCoreLoadBIOS;
1034	core->loadSave = _GBCoreLoadSave;
1035	core->loadTemporarySave = _GBCoreLoadTemporarySave;
1036	core->loadPatch = _GBCoreLoadPatch;
1037	core->unloadROM = _GBCoreUnloadROM;
1038	core->checksum = _GBCoreChecksum;
1039	core->reset = _GBCoreReset;
1040	core->runFrame = _GBCoreRunFrame;
1041	core->runLoop = _GBCoreRunLoop;
1042	core->step = _GBCoreStep;
1043	core->stateSize = _GBCoreStateSize;
1044	core->loadState = _GBCoreLoadState;
1045	core->saveState = _GBCoreSaveState;
1046	core->setKeys = _GBCoreSetKeys;
1047	core->addKeys = _GBCoreAddKeys;
1048	core->clearKeys = _GBCoreClearKeys;
1049	core->frameCounter = _GBCoreFrameCounter;
1050	core->frameCycles = _GBCoreFrameCycles;
1051	core->frequency = _GBCoreFrequency;
1052	core->getGameTitle = _GBCoreGetGameTitle;
1053	core->getGameCode = _GBCoreGetGameCode;
1054	core->setPeripheral = _GBCoreSetPeripheral;
1055	core->busRead8 = _GBCoreBusRead8;
1056	core->busRead16 = _GBCoreBusRead16;
1057	core->busRead32 = _GBCoreBusRead32;
1058	core->busWrite8 = _GBCoreBusWrite8;
1059	core->busWrite16 = _GBCoreBusWrite16;
1060	core->busWrite32 = _GBCoreBusWrite32;
1061	core->rawRead8 = _GBCoreRawRead8;
1062	core->rawRead16 = _GBCoreRawRead16;
1063	core->rawRead32 = _GBCoreRawRead32;
1064	core->rawWrite8 = _GBCoreRawWrite8;
1065	core->rawWrite16 = _GBCoreRawWrite16;
1066	core->rawWrite32 = _GBCoreRawWrite32;
1067	core->listMemoryBlocks = _GBListMemoryBlocks;
1068	core->getMemoryBlock = _GBGetMemoryBlock;
1069#ifdef USE_DEBUGGERS
1070	core->supportsDebuggerType = _GBCoreSupportsDebuggerType;
1071	core->debuggerPlatform = _GBCoreDebuggerPlatform;
1072	core->cliDebuggerSystem = _GBCoreCliDebuggerSystem;
1073	core->attachDebugger = _GBCoreAttachDebugger;
1074	core->detachDebugger = _GBCoreDetachDebugger;
1075	core->loadSymbols = _GBCoreLoadSymbols;
1076	core->lookupIdentifier = _GBCoreLookupIdentifier;
1077#endif
1078	core->cheatDevice = _GBCoreCheatDevice;
1079	core->savedataClone = _GBCoreSavedataClone;
1080	core->savedataRestore = _GBCoreSavedataRestore;
1081	core->listVideoLayers = _GBCoreListVideoLayers;
1082	core->listAudioChannels = _GBCoreListAudioChannels;
1083	core->enableVideoLayer = _GBCoreEnableVideoLayer;
1084	core->enableAudioChannel = _GBCoreEnableAudioChannel;
1085	core->adjustVideoLayer = _GBCoreAdjustVideoLayer;
1086#ifndef MINIMAL_CORE
1087	core->startVideoLog = _GBCoreStartVideoLog;
1088	core->endVideoLog = _GBCoreEndVideoLog;
1089#endif
1090	return core;
1091}
1092
1093#ifndef MINIMAL_CORE
1094static void _GBVLPStartFrameCallback(void *context) {
1095	struct mCore* core = context;
1096	struct GBCore* gbcore = (struct GBCore*) core;
1097	struct GB* gb = core->board;
1098
1099	if (!mVideoLoggerRendererRun(gbcore->proxyRenderer.logger, true)) {
1100		GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
1101		mVideoLogContextRewind(gbcore->logContext, core);
1102		GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
1103		gb->earlyExit = true;
1104	}
1105}
1106
1107static bool _GBVLPInit(struct mCore* core) {
1108	struct GBCore* gbcore = (struct GBCore*) core;
1109	if (!_GBCoreInit(core)) {
1110		return false;
1111	}
1112	gbcore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger));
1113	mVideoLoggerRendererCreate(gbcore->proxyRenderer.logger, true);
1114	GBVideoProxyRendererCreate(&gbcore->proxyRenderer, NULL);
1115	memset(&gbcore->logCallbacks, 0, sizeof(gbcore->logCallbacks));
1116	gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback;
1117	gbcore->logCallbacks.context = core;
1118	core->addCoreCallbacks(core, &gbcore->logCallbacks);
1119	core->videoLogger = gbcore->proxyRenderer.logger;
1120	return true;
1121}
1122
1123static void _GBVLPDeinit(struct mCore* core) {
1124	struct GBCore* gbcore = (struct GBCore*) core;
1125	if (gbcore->logContext) {
1126		mVideoLogContextDestroy(core, gbcore->logContext, true);
1127	}
1128	_GBCoreDeinit(core);
1129}
1130
1131static void _GBVLPReset(struct mCore* core) {
1132	struct GBCore* gbcore = (struct GBCore*) core;
1133	struct GB* gb = (struct GB*) core->board;
1134	if (gb->video.renderer == &gbcore->proxyRenderer.d) {
1135		GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer);
1136	} else if (gbcore->renderer.outputBuffer) {
1137		struct GBVideoRenderer* renderer = &gbcore->renderer.d;
1138		GBVideoAssociateRenderer(&gb->video, renderer);
1139	}
1140
1141	SM83Reset(core->cpu);
1142	mVideoLogContextRewind(gbcore->logContext, core);
1143	GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer);
1144
1145	// Make sure CPU loop never spins
1146	gb->memory.ie = 0;
1147	gb->memory.ime = false;
1148	GBHalt(gb->cpu);
1149}
1150
1151static bool _GBVLPLoadROM(struct mCore* core, struct VFile* vf) {
1152	struct GBCore* gbcore = (struct GBCore*) core;
1153	gbcore->logContext = mVideoLogContextCreate(NULL);
1154	if (!mVideoLogContextLoad(gbcore->logContext, vf)) {
1155		mVideoLogContextDestroy(core, gbcore->logContext, false);
1156		gbcore->logContext = NULL;
1157		return false;
1158	}
1159	mVideoLoggerAttachChannel(gbcore->proxyRenderer.logger, gbcore->logContext, 0);
1160	return true;
1161}
1162
1163static bool _GBVLPLoadState(struct mCore* core, const void* buffer) {
1164	struct GB* gb = (struct GB*) core->board;
1165	const struct GBSerializedState* state = buffer;
1166
1167	gb->timing.root = NULL;
1168	gb->model = state->model;
1169
1170	gb->cpu->pc = GB_BASE_HRAM;
1171	gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
1172
1173	GBVideoReset(&gb->video);
1174	GBVideoDeserialize(&gb->video, state);
1175	GBIODeserialize(gb, state);
1176	GBAudioReset(&gb->audio);
1177	if (gb->model & GB_MODEL_SGB) {
1178		GBSGBDeserialize(gb, state);
1179	}
1180
1181	// Make sure CPU loop never spins
1182	gb->memory.ie = 0;
1183	gb->memory.ime = false;
1184	GBHalt(gb->cpu);
1185
1186	return true;
1187}
1188
1189static bool _returnTrue(struct VFile* vf) {
1190	UNUSED(vf);
1191	return true;
1192}
1193
1194struct mCore* GBVideoLogPlayerCreate(void) {
1195	struct mCore* core = GBCoreCreate();
1196	core->init = _GBVLPInit;
1197	core->deinit = _GBVLPDeinit;
1198	core->reset = _GBVLPReset;
1199	core->loadROM = _GBVLPLoadROM;
1200	core->loadState = _GBVLPLoadState;
1201	core->isROM = _returnTrue;
1202	return core;
1203}
1204#else
1205struct mCore* GBVideoLogPlayerCreate(void) {
1206	return false;
1207}
1208#endif