all repos — mgba @ 42fdc758d788e7608688715f74047e419d9cf9c5

mGBA Game Boy Advance Emulator

src/platform/3ds/main.c (view raw)

   1/* Copyright (c) 2013-2015 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
   7#include <mgba/core/blip_buf.h>
   8#include <mgba/core/core.h>
   9#include <mgba/core/serialize.h>
  10#ifdef M_CORE_GBA
  11#include <mgba/gba/interface.h>
  12#include <mgba/internal/gba/gba.h>
  13#include <mgba/internal/gba/input.h>
  14#endif
  15#ifdef M_CORE_GB
  16#include <mgba/internal/gb/gb.h>
  17#endif
  18#include "feature/gui/gui-runner.h"
  19#include <mgba-util/gui.h>
  20#include <mgba-util/gui/file-select.h>
  21#include <mgba-util/gui/font.h>
  22#include <mgba-util/gui/menu.h>
  23#include <mgba-util/memory.h>
  24
  25#include <mgba-util/platform/3ds/3ds-vfs.h>
  26#include <mgba-util/threading.h>
  27#include "ctr-gpu.h"
  28
  29#include <3ds.h>
  30#include <3ds/gpu/gx.h>
  31
  32mLOG_DECLARE_CATEGORY(GUI_3DS);
  33mLOG_DEFINE_CATEGORY(GUI_3DS, "3DS", "gui.3ds");
  34
  35static enum ScreenMode {
  36	SM_PA_BOTTOM,
  37	SM_AF_BOTTOM,
  38	SM_SF_BOTTOM,
  39	SM_PA_TOP,
  40	SM_AF_TOP,
  41	SM_SF_TOP,
  42	SM_MAX
  43} screenMode = SM_PA_TOP;
  44
  45static enum FilterMode {
  46	FM_NEAREST,
  47	FM_LINEAR_1x,
  48	FM_LINEAR_2x,
  49	FM_MAX
  50} filterMode = FM_LINEAR_2x;
  51
  52static enum DarkenMode {
  53	DM_NATIVE,
  54	DM_MULT,
  55	DM_MULT_SCALE,
  56	DM_MULT_SCALE_BIAS,
  57	DM_MAX
  58} darkenMode = DM_NATIVE;
  59
  60#define _3DS_INPUT 0x3344534B
  61
  62#define AUDIO_SAMPLES 384
  63#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
  64#define DSP_BUFFERS 4
  65
  66static struct m3DSRotationSource {
  67	struct mRotationSource d;
  68	accelVector accel;
  69	angularRate gyro;
  70} rotation;
  71
  72static struct m3DSImageSource {
  73	struct mImageSource d;
  74	Handle handles[2];
  75	u32 bufferSize;
  76	u32 transferSize;
  77	void* buffer;
  78	unsigned cam;
  79} camera;
  80
  81static enum {
  82	NO_SOUND,
  83	DSP_SUPPORTED
  84} hasSound;
  85
  86// TODO: Move into context
  87static color_t* outputBuffer = NULL;
  88static color_t* screenshotBuffer = NULL;
  89static struct mAVStream stream;
  90static int16_t* audioLeft = 0;
  91static size_t audioPos = 0;
  92static C3D_Tex outputTexture[2];
  93static int activeOutputTexture = 0;
  94static ndspWaveBuf dspBuffer[DSP_BUFFERS];
  95static int bufferId = 0;
  96static bool frameLimiter = true;
  97static u64 tickCounter;
  98
  99static C3D_RenderTarget* topScreen[2];
 100static C3D_RenderTarget* bottomScreen[2];
 101static int doubleBuffer = 0;
 102static bool frameStarted = false;
 103
 104static C3D_RenderTarget* upscaleBuffer;
 105static C3D_Tex upscaleBufferTex;
 106static bool interframeBlending = false;
 107static bool sgbCrop = false;
 108
 109static aptHookCookie cookie;
 110static bool core2;
 111
 112extern bool allocateRomBuffer(void);
 113
 114static bool _initGpu(void) {
 115	if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
 116		return false;
 117	}
 118
 119	topScreen[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
 120	topScreen[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
 121	bottomScreen[0] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
 122	bottomScreen[1] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
 123	if (!topScreen[0] || !topScreen[1] || !bottomScreen[0] || !bottomScreen[1]) {
 124		return false;
 125	}
 126
 127	if (!C3D_TexInitVRAM(&upscaleBufferTex, 512, 512, GPU_RB_RGB8)) {
 128		return false;
 129	}
 130	upscaleBuffer = C3D_RenderTargetCreateFromTex(&upscaleBufferTex, GPU_TEXFACE_2D, 0, 0);
 131	if (!upscaleBuffer) {
 132		return false;
 133	}
 134
 135	return ctrInitGpu();
 136}
 137
 138static void _cleanup(void) {
 139	ctrDeinitGpu();
 140
 141	if (outputBuffer) {
 142		linearFree(outputBuffer);
 143		outputBuffer = NULL;
 144	}
 145	if (screenshotBuffer) {
 146		linearFree(screenshotBuffer);
 147		screenshotBuffer = NULL;
 148	}
 149
 150	C3D_RenderTargetDelete(topScreen[0]);
 151	C3D_RenderTargetDelete(topScreen[1]);
 152	C3D_RenderTargetDelete(bottomScreen[0]);
 153	C3D_RenderTargetDelete(bottomScreen[1]);
 154	C3D_RenderTargetDelete(upscaleBuffer);
 155	C3D_TexDelete(&upscaleBufferTex);
 156	C3D_TexDelete(&outputTexture[0]);
 157	C3D_TexDelete(&outputTexture[1]);
 158	C3D_Fini();
 159
 160	gfxExit();
 161
 162	if (hasSound != NO_SOUND) {
 163		linearFree(audioLeft);
 164	}
 165
 166	if (hasSound == DSP_SUPPORTED) {
 167		ndspExit();
 168	}
 169
 170	camExit();
 171	ndspExit();
 172	ptmuExit();
 173}
 174
 175static void _aptHook(APT_HookType hook, void* user) {
 176	UNUSED(user);
 177	switch (hook) {
 178	case APTHOOK_ONEXIT:
 179		_cleanup();
 180		exit(0);
 181		break;
 182	default:
 183		break;
 184	}
 185}
 186
 187static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
 188	mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
 189}
 190
 191static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
 192
 193static void _drawStart(void) {
 194	if (frameStarted) {
 195		return;
 196	}
 197	frameStarted = true;
 198	u8 flags = 0;
 199	if (!frameLimiter) {
 200		if (tickCounter + 4481000 > svcGetSystemTick()) {
 201			flags = C3D_FRAME_NONBLOCK;
 202		}
 203		tickCounter = svcGetSystemTick();
 204	}
 205	C3D_FrameBegin(flags);
 206	ctrStartFrame();
 207
 208	C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
 209	C3D_RenderTargetClear(bottomScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0);
 210	C3D_FrameDrawOn(topScreen[doubleBuffer]);
 211	C3D_RenderTargetClear(topScreen[doubleBuffer], C3D_CLEAR_COLOR, 0, 0);
 212}
 213
 214static void _drawEnd(void) {
 215	if (!frameStarted) {
 216		return;
 217	}
 218	ctrEndFrame();
 219	C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
 220	C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
 221	C3D_FrameEnd(0);
 222	frameStarted = false;
 223
 224	doubleBuffer ^= 1;
 225}
 226
 227static int _batteryState(void) {
 228	u8 charge;
 229	u8 adapter;
 230	PTMU_GetBatteryLevel(&charge);
 231	PTMU_GetBatteryChargeState(&adapter);
 232	int state = 0;
 233	if (adapter) {
 234		state |= BATTERY_CHARGING;
 235	}
 236	if (charge > 0) {
 237		--charge;
 238	}
 239	return state | charge;
 240}
 241
 242static void _guiPrepare(void) {
 243	C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
 244	ctrSetViewportSize(320, 240, true);
 245}
 246
 247static void _guiFinish(void) {
 248	ctrFlushBatch();
 249}
 250
 251static void _resetCamera(struct m3DSImageSource* imageSource) {
 252	if (!imageSource->cam) {
 253		return;
 254	}
 255	CAMU_SetSize(imageSource->cam, SIZE_QCIF, CONTEXT_A);
 256	CAMU_SetOutputFormat(imageSource->cam, OUTPUT_RGB_565, CONTEXT_A);
 257	CAMU_SetFrameRate(imageSource->cam, FRAME_RATE_30);
 258	CAMU_FlipImage(imageSource->cam, FLIP_NONE, CONTEXT_A);
 259
 260	CAMU_SetNoiseFilter(imageSource->cam, true);
 261	CAMU_SetAutoExposure(imageSource->cam, false);
 262	CAMU_SetAutoWhiteBalance(imageSource->cam, false);
 263}
 264
 265static void _setup(struct mGUIRunner* runner) {
 266	if (core2) {
 267		mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
 268		mCoreLoadForeignConfig(runner->core, &runner->config);
 269	}
 270
 271	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
 272	runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d);
 273	if (hasSound != NO_SOUND) {
 274		runner->core->setAVStream(runner->core, &stream);
 275	}
 276
 277	_map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
 278	_map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
 279	_map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
 280	_map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
 281	_map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
 282	_map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
 283	_map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
 284	_map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
 285	_map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
 286	_map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
 287
 288	outputBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
 289	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
 290
 291	unsigned mode;
 292	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
 293		screenMode = mode;
 294	}
 295	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
 296		filterMode = mode;
 297		if (filterMode == FM_NEAREST) {
 298			C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
 299		} else {
 300			C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
 301		}
 302	}
 303	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
 304		darkenMode = mode;
 305	}
 306	frameLimiter = true;
 307
 308	runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
 309}
 310
 311static void _gameLoaded(struct mGUIRunner* runner) {
 312	switch (runner->core->platform(runner->core)) {
 313#ifdef M_CORE_GBA
 314		// TODO: Move these to callbacks
 315	case PLATFORM_GBA:
 316		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
 317			HIDUSER_EnableAccelerometer();
 318		}
 319		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
 320			HIDUSER_EnableGyroscope();
 321		}
 322		break;
 323#endif
 324#ifdef M_CORE_GB
 325	case PLATFORM_GB:
 326		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
 327			HIDUSER_EnableAccelerometer();
 328		}
 329		break;
 330#endif
 331	default:
 332		break;
 333	}
 334	osSetSpeedupEnable(true);
 335
 336	double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
 337	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
 338	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
 339	if (hasSound != NO_SOUND) {
 340		audioPos = 0;
 341	}
 342	if (hasSound == DSP_SUPPORTED) {
 343		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
 344	}
 345	unsigned mode;
 346	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
 347		screenMode = mode;
 348	}
 349	if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
 350		filterMode = mode;
 351		if (filterMode == FM_NEAREST) {
 352			C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
 353		} else {
 354			C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
 355		}
 356	}
 357	if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
 358		darkenMode = mode;
 359	}
 360	if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) {
 361		switch (mode) {
 362		case 0:
 363		default:
 364			mode = SELECT_NONE;
 365			break;
 366		case 1:
 367			mode = SELECT_IN1;
 368			break;
 369		case 2:
 370			mode = SELECT_OUT1;
 371			break;
 372		}
 373		if (mode != camera.cam) {
 374			camera.cam = mode;
 375			if (camera.buffer) {
 376				_resetCamera(&camera);
 377				CAMU_Activate(camera.cam);
 378			}
 379		}
 380	}
 381
 382	int fakeBool;
 383	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
 384		interframeBlending = fakeBool;
 385	}
 386
 387	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
 388		sgbCrop = fakeBool;
 389	}
 390}
 391
 392static void _gameUnloaded(struct mGUIRunner* runner) {
 393	osSetSpeedupEnable(false);
 394	frameLimiter = true;
 395
 396	switch (runner->core->platform(runner->core)) {
 397#ifdef M_CORE_GBA
 398		// TODO: Move these to callbacks
 399	case PLATFORM_GBA:
 400		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
 401			HIDUSER_DisableAccelerometer();
 402		}
 403		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
 404			HIDUSER_DisableGyroscope();
 405		}
 406		break;
 407#endif
 408#ifdef M_CORE_GB
 409	case PLATFORM_GB:
 410		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
 411			HIDUSER_DisableAccelerometer();
 412		}
 413		break;
 414#endif
 415	default:
 416		break;
 417	}
 418}
 419
 420static u32 _setupTex(int out, bool faded) {
 421	ctrActivateTexture(&outputTexture[out]);
 422	u32 color;
 423	if (!faded) {
 424		color = 0xFFFFFFFF;
 425		switch (darkenMode) {
 426		case DM_NATIVE:
 427		case DM_MAX:
 428			break;
 429		case DM_MULT_SCALE_BIAS:
 430			ctrTextureBias(0x070707);
 431			// Fall through
 432		case DM_MULT_SCALE:
 433			color = 0xFF707070;
 434			// Fall through
 435		case DM_MULT:
 436			ctrTextureMultiply();
 437			break;
 438		}
 439	} else {
 440		color = 0xFF484848;
 441		switch (darkenMode) {
 442		case DM_NATIVE:
 443		case DM_MAX:
 444			break;
 445		case DM_MULT_SCALE_BIAS:
 446			ctrTextureBias(0x030303);
 447			// Fall through
 448		case DM_MULT_SCALE:
 449			color = 0xFF303030;
 450			// Fall through
 451		case DM_MULT:
 452			ctrTextureMultiply();
 453			break;
 454		}
 455
 456	}
 457	return color;
 458}
 459
 460static void _drawTex(struct mCore* core, bool faded, bool both) {
 461	unsigned screen_w, screen_h;
 462	switch (screenMode) {
 463	case SM_PA_BOTTOM:
 464		C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
 465		screen_w = 320;
 466		screen_h = 240;
 467		break;
 468	case SM_PA_TOP:
 469		C3D_FrameDrawOn(topScreen[doubleBuffer]);
 470		screen_w = 400;
 471		screen_h = 240;
 472		break;
 473	default:
 474		C3D_FrameDrawOn(upscaleBuffer);
 475		screen_w = 512;
 476		screen_h = 512;
 477		break;
 478	}
 479
 480	unsigned corew, coreh;
 481	core->desiredVideoDimensions(core, &corew, &coreh);
 482
 483	int w = corew;
 484	int h = coreh;
 485	if (sgbCrop && w == 256 && h == 224) {
 486		w = GB_VIDEO_HORIZONTAL_PIXELS;
 487		h = GB_VIDEO_VERTICAL_PIXELS;
 488	}
 489	int innerw = w;
 490	int innerh = h;
 491	// Get greatest common divisor
 492	while (w != 0) {
 493		int temp = h % w;
 494		h = w;
 495		w = temp;
 496	}
 497	int gcd = h;
 498	unsigned aspectw = innerw / gcd;
 499	unsigned aspecth = innerh / gcd;
 500	int x = 0;
 501	int y = 0;
 502
 503	switch (screenMode) {
 504	case SM_PA_TOP:
 505	case SM_PA_BOTTOM:
 506		w = corew;
 507		h = coreh;
 508		x = (screen_w - w) / 2;
 509		y = (screen_h - h) / 2;
 510		ctrSetViewportSize(screen_w, screen_h, true);
 511		break;
 512	case SM_AF_TOP:
 513	case SM_AF_BOTTOM:
 514	case SM_SF_TOP:
 515	case SM_SF_BOTTOM:
 516	default:
 517		if (filterMode == FM_LINEAR_1x) {
 518			w = corew;
 519			h = coreh;
 520		} else {
 521			w = corew * 2;
 522			h = coreh * 2;
 523		}
 524		ctrSetViewportSize(screen_w, screen_h, false);
 525		break;
 526	}
 527
 528	uint32_t color = _setupTex(activeOutputTexture, faded);
 529	ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
 530	if (both) {
 531		color = _setupTex(activeOutputTexture ^ 1, faded);
 532		ctrAddRectEx(color & 0x7FFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
 533	}
 534	ctrFlushBatch();
 535
 536	innerw = corew;
 537	innerh = coreh;
 538	corew = w;
 539	coreh = h;
 540	screen_h = 240;
 541	if (screenMode < SM_PA_TOP) {
 542		C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
 543		screen_w = 320;
 544	} else {
 545		C3D_FrameDrawOn(topScreen[doubleBuffer]);
 546		screen_w = 400;
 547	}
 548	ctrSetViewportSize(screen_w, screen_h, true);
 549
 550	float afw, afh;
 551	switch (screenMode) {
 552	default:
 553		return;
 554	case SM_AF_TOP:
 555	case SM_AF_BOTTOM:
 556		afw = screen_w / (float) aspectw;
 557		afh = screen_h / (float) aspecth;
 558		if (afw * aspecth > screen_h) {
 559			w = innerw * afh / gcd;
 560			h = innerh * afh / gcd;
 561		} else {
 562			h = innerh * afw / gcd;
 563			w = innerw * afw / gcd;
 564		}
 565		break;
 566	case SM_SF_TOP:
 567	case SM_SF_BOTTOM:
 568		w = screen_w;
 569		h = screen_h;
 570		break;
 571	}
 572
 573	x = (screen_w - w) / 2;
 574	y = (screen_h - h) / 2;
 575	ctrActivateTexture(&upscaleBufferTex);
 576	ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
 577	ctrFlushBatch();
 578}
 579
 580static void _prepareForFrame(struct mGUIRunner* runner) {
 581	UNUSED(runner);
 582	activeOutputTexture ^= 1;
 583}
 584
 585static void _drawFrame(struct mGUIRunner* runner, bool faded) {
 586	UNUSED(runner);
 587	C3D_Tex* tex = &outputTexture[activeOutputTexture];
 588
 589	GSPGPU_FlushDataCache(outputBuffer, 256 * GBA_VIDEO_VERTICAL_PIXELS * 2);
 590	C3D_SyncDisplayTransfer(
 591			(u32*) outputBuffer, GX_BUFFER_DIM(256, GBA_VIDEO_VERTICAL_PIXELS),
 592			tex->data, GX_BUFFER_DIM(256, 256),
 593			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
 594				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
 595				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
 596
 597	if (hasSound == NO_SOUND) {
 598		blip_clear(runner->core->getAudioChannel(runner->core, 0));
 599		blip_clear(runner->core->getAudioChannel(runner->core, 1));
 600	}
 601
 602	_drawTex(runner->core, faded, interframeBlending);
 603}
 604
 605static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
 606	C3D_Tex* tex = &outputTexture[activeOutputTexture];
 607
 608	if (!screenshotBuffer) {
 609		screenshotBuffer = linearMemAlign(256 * 224 * sizeof(color_t), 0x80);
 610	}
 611	unsigned y;
 612	for (y = 0; y < height; ++y) {
 613		memcpy(&screenshotBuffer[y * 256], &pixels[y * width], width * sizeof(color_t));
 614		memset(&screenshotBuffer[y * 256 + width], 0, (256 - width) * sizeof(color_t));
 615	}
 616
 617	GSPGPU_FlushDataCache(screenshotBuffer, 256 * height * sizeof(color_t));
 618	C3D_SyncDisplayTransfer(
 619			(u32*) screenshotBuffer, GX_BUFFER_DIM(256, height),
 620			tex->data, GX_BUFFER_DIM(256, 256),
 621			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
 622				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
 623				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
 624
 625	_drawTex(runner->core, faded, false);
 626}
 627
 628static uint16_t _pollGameInput(struct mGUIRunner* runner) {
 629	UNUSED(runner);
 630
 631	hidScanInput();
 632	uint32_t activeKeys = hidKeysHeld();
 633	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
 634	keys |= (activeKeys >> 24) & 0xF0;
 635	return keys;
 636}
 637
 638static void _incrementScreenMode(struct mGUIRunner* runner) {
 639	UNUSED(runner);
 640	screenMode = (screenMode + 1) % SM_MAX;
 641	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
 642}
 643
 644static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
 645	UNUSED(runner);
 646	if (frameLimiter == limit) {
 647		return;
 648	}
 649	frameLimiter = limit;
 650	tickCounter = svcGetSystemTick();
 651}
 652
 653static bool _running(struct mGUIRunner* runner) {
 654	UNUSED(runner);
 655	return aptMainLoop();
 656}
 657
 658static uint32_t _pollInput(const struct mInputMap* map) {
 659	hidScanInput();
 660	int activeKeys = hidKeysHeld();
 661	return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
 662}
 663
 664static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
 665	hidScanInput();
 666	if (!(hidKeysHeld() & KEY_TOUCH)) {
 667		return GUI_CURSOR_NOT_PRESENT;
 668	}
 669	touchPosition pos;
 670	hidTouchRead(&pos);
 671	*x = pos.px;
 672	*y = pos.py;
 673	return GUI_CURSOR_DOWN;
 674}
 675
 676static void _sampleRotation(struct mRotationSource* source) {
 677	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
 678	// Work around ctrulib getting the entries wrong
 679	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
 680	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
 681}
 682
 683static int32_t _readTiltX(struct mRotationSource* source) {
 684	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
 685	return rotation->accel.x << 18L;
 686}
 687
 688static int32_t _readTiltY(struct mRotationSource* source) {
 689	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
 690	return rotation->accel.y << 18L;
 691}
 692
 693static int32_t _readGyroZ(struct mRotationSource* source) {
 694	struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
 695	return rotation->gyro.y << 18L; // Yes, y
 696}
 697
 698static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
 699	UNUSED(colorFormats);
 700	struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
 701
 702	_resetCamera(imageSource);
 703
 704	CAMU_SetTrimming(PORT_CAM1, true);
 705	CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144);
 706	CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1);
 707
 708	if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) {
 709		free(imageSource->buffer);
 710		imageSource->buffer = NULL;
 711	}
 712	imageSource->bufferSize = w * h * 2;
 713	if (!imageSource->buffer) {
 714		imageSource->buffer = malloc(imageSource->bufferSize);
 715	}
 716	CAMU_GetMaxBytes(&imageSource->transferSize, w, h);
 717	CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h);
 718	CAMU_Activate(imageSource->cam);
 719	CAMU_ClearBuffer(PORT_CAM1);
 720	CAMU_StartCapture(PORT_CAM1);
 721
 722	if (imageSource->cam) {
 723		CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
 724	}
 725}
 726
 727static void _stopRequestImage(struct mImageSource* source) {
 728	struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
 729
 730	free(imageSource->buffer);
 731	imageSource->buffer = NULL;
 732	svcCloseHandle(imageSource->handles[0]);
 733	svcCloseHandle(imageSource->handles[1]);
 734
 735	CAMU_StopCapture(PORT_CAM1);
 736	CAMU_Activate(SELECT_NONE);
 737}
 738
 739
 740static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
 741	struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source;
 742
 743	if (!imageSource->cam) {
 744		memset(imageSource->buffer, 0, imageSource->bufferSize);
 745		*buffer = imageSource->buffer;
 746		*stride = 128;
 747		*colorFormat = mCOLOR_RGB565;
 748		return;
 749	}
 750
 751	s32 i;
 752	svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX);
 753
 754	if (i == 0) {
 755		*buffer = imageSource->buffer;
 756		*stride = 128;
 757		*colorFormat = mCOLOR_RGB565;
 758	} else {
 759		CAMU_ClearBuffer(PORT_CAM1);
 760		CAMU_StartCapture(PORT_CAM1);
 761	}
 762
 763	svcCloseHandle(imageSource->handles[0]);
 764	CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize);
 765}
 766
 767static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
 768	UNUSED(stream);
 769	if (hasSound == DSP_SUPPORTED) {
 770		int startId = bufferId;
 771		while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
 772			bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
 773			if (bufferId == startId) {
 774				blip_clear(left);
 775				blip_clear(right);
 776				return;
 777			}
 778		}
 779		void* tmpBuf = dspBuffer[bufferId].data_pcm16;
 780		memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
 781		dspBuffer[bufferId].data_pcm16 = tmpBuf;
 782		dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
 783		blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
 784		blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
 785		DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
 786		ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
 787	}
 788}
 789
 790THREAD_ENTRY _core2Test(void* context) {
 791	UNUSED(context);
 792}
 793
 794int main() {
 795	rotation.d.sample = _sampleRotation;
 796	rotation.d.readTiltX = _readTiltX;
 797	rotation.d.readTiltY = _readTiltY;
 798	rotation.d.readGyroZ = _readGyroZ;
 799
 800	stream.videoDimensionsChanged = 0;
 801	stream.postVideoFrame = 0;
 802	stream.postAudioFrame = 0;
 803	stream.postAudioBuffer = _postAudioBuffer;
 804
 805	camera.d.startRequestImage = _startRequestImage;
 806	camera.d.stopRequestImage = _stopRequestImage;
 807	camera.d.requestImage = _requestImage;
 808	camera.buffer = NULL;
 809	camera.bufferSize = 0;
 810	camera.cam = SELECT_IN1;
 811
 812	if (!allocateRomBuffer()) {
 813		return 1;
 814	}
 815
 816	aptHook(&cookie, _aptHook, 0);
 817
 818	ptmuInit();
 819	camInit();
 820
 821	hasSound = NO_SOUND;
 822	if (!ndspInit()) {
 823		hasSound = DSP_SUPPORTED;
 824		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
 825		ndspSetOutputCount(1);
 826		ndspChnReset(0);
 827		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
 828		ndspChnSetInterp(0, NDSP_INTERP_NONE);
 829		ndspChnSetRate(0, 0x8000);
 830		ndspChnWaveBufClear(0);
 831		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
 832		memset(dspBuffer, 0, sizeof(dspBuffer));
 833		int i;
 834		for (i = 0; i < DSP_BUFFERS; ++i) {
 835			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
 836			dspBuffer[i].nsamples = AUDIO_SAMPLES;
 837		}
 838	}
 839
 840	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
 841
 842	if (!_initGpu()) {
 843		outputTexture[0].data = 0;
 844		_cleanup();
 845		return 1;
 846	}
 847
 848	C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
 849
 850	int i;
 851	for (i = 0; i < 2; ++i) {
 852		if (!C3D_TexInitVRAM(&outputTexture[i], 256, 256, GPU_RGB565)) {
 853			_cleanup();
 854			return 1;
 855		}
 856		C3D_TexSetWrap(&outputTexture[i], GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
 857		C3D_TexSetFilter(&outputTexture[i], GPU_NEAREST, GPU_NEAREST);
 858		void* outputTextureEnd = (u8*)outputTexture[i].data + 256 * 256 * 2;
 859
 860		// Zero texture data to make sure no garbage around the border interferes with filtering
 861		GX_MemoryFill(
 862				outputTexture[i].data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
 863				NULL, 0, NULL, 0);
 864		gspWaitForPSC0();
 865	}
 866
 867	struct GUIFont* font = GUIFontCreate();
 868
 869	if (!font) {
 870		_cleanup();
 871		return 1;
 872	}
 873
 874	struct mGUIRunner runner = {
 875		.params = {
 876			320, 240,
 877			font, "/",
 878			_drawStart, _drawEnd,
 879			_pollInput, _pollCursor,
 880			_batteryState,
 881			_guiPrepare, _guiFinish,
 882		},
 883		.keySources = (struct GUIInputKeys[]) {
 884			{
 885				.name = "3DS Input",
 886				.id = _3DS_INPUT,
 887				.keyNames = (const char*[]) {
 888					"A",
 889					"B",
 890					"Select",
 891					"Start",
 892					"D-Pad Right",
 893					"D-Pad Left",
 894					"D-Pad Up",
 895					"D-Pad Down",
 896					"R",
 897					"L",
 898					"X",
 899					"Y",
 900					0,
 901					0,
 902					"ZL",
 903					"ZR",
 904					0,
 905					0,
 906					0,
 907					0,
 908					0,
 909					0,
 910					0,
 911					0,
 912					"C-Stick Right",
 913					"C-Stick Left",
 914					"C-Stick Up",
 915					"C-Stick Down",
 916				},
 917				.nKeys = 28
 918			},
 919			{ .id = 0 }
 920		},
 921		.configExtra = (struct GUIMenuItem[]) {
 922			{
 923				.title = "Screen mode",
 924				.data = "screenMode",
 925				.submenu = 0,
 926				.state = SM_PA_TOP,
 927				.validStates = (const char*[]) {
 928					"Pixel-Accurate/Bottom",
 929					"Aspect-Ratio Fit/Bottom",
 930					"Stretched/Bottom",
 931					"Pixel-Accurate/Top",
 932					"Aspect-Ratio Fit/Top",
 933					"Stretched/Top",
 934				},
 935				.nStates = 6
 936			},
 937			{
 938				.title = "Filtering",
 939				.data = "filterMode",
 940				.submenu = 0,
 941				.state = FM_LINEAR_2x,
 942				.validStates = (const char*[]) {
 943					NULL, // Disable choosing nearest neighbor; it always looks bad
 944					"Bilinear (smoother)",
 945					"Bilinear (pixelated)",
 946				},
 947				.nStates = 3
 948			},
 949			{
 950				.title = "Screen darkening",
 951				.data = "darkenMode",
 952				.submenu = 0,
 953				.state = DM_NATIVE,
 954				.validStates = (const char*[]) {
 955					"None",
 956					"Dark",
 957					"Very dark",
 958					"Grayed",
 959				},
 960				.nStates = 4
 961			},
 962			{
 963				.title = "Camera",
 964				.data = "camera",
 965				.submenu = 0,
 966				.state = 1,
 967				.validStates = (const char*[]) {
 968					"None",
 969					"Inner",
 970					"Outer",
 971				},
 972				.nStates = 3
 973			}
 974		},
 975		.nConfigExtra = 4,
 976		.setup = _setup,
 977		.teardown = 0,
 978		.gameLoaded = _gameLoaded,
 979		.gameUnloaded = _gameUnloaded,
 980		.prepareForFrame = _prepareForFrame,
 981		.drawFrame = _drawFrame,
 982		.drawScreenshot = _drawScreenshot,
 983		.paused = _gameUnloaded,
 984		.unpaused = _gameLoaded,
 985		.incrementScreenMode = _incrementScreenMode,
 986		.setFrameLimiter = _setFrameLimiter,
 987		.pollGameInput = _pollGameInput,
 988		.running = _running
 989	};
 990
 991	runner.autosave.running = true;
 992	MutexInit(&runner.autosave.mutex);
 993	ConditionInit(&runner.autosave.cond);
 994
 995	APT_SetAppCpuTimeLimit(20);
 996	runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true);
 997
 998	Thread thread2;
 999	if (ThreadCreate(&thread2, _core2Test, NULL) == 0) {
1000		core2 = true;
1001		ThreadJoin(&thread2);
1002	}
1003
1004	mGUIInit(&runner, "3ds");
1005
1006	_map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
1007	_map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
1008	_map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
1009	_map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
1010	_map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
1011	_map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
1012	_map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
1013	_map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
1014	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
1015	_map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
1016
1017	mGUIRunloop(&runner);
1018	mGUIDeinit(&runner);
1019
1020	_cleanup();
1021	return 0;
1022}