all repos — mgba @ 72e5aa078281a797854e4bd4620489e956fce866

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