all repos — mgba @ 32e058a0df5d2c4f1a9ea4e6c5c4861a9e327fd9

mGBA Game Boy Advance Emulator

src/platform/wii/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#define asm __asm__
   7
   8#include <fat.h>
   9#include <gccore.h>
  10#include <ogc/machine/processor.h>
  11#include <malloc.h>
  12#include <unistd.h>
  13#include <wiiuse/wpad.h>
  14
  15#include <mgba-util/common.h>
  16
  17#include <mgba/core/blip_buf.h>
  18#include <mgba/core/core.h>
  19#include "feature/gui/gui-runner.h"
  20#include <mgba/internal/gb/video.h>
  21#include <mgba/internal/gba/audio.h>
  22#include <mgba/internal/gba/gba.h>
  23#include <mgba/internal/gba/input.h>
  24#include <mgba-util/gui.h>
  25#include <mgba-util/gui/file-select.h>
  26#include <mgba-util/gui/font.h>
  27#include <mgba-util/gui/menu.h>
  28#include <mgba-util/memory.h>
  29#include <mgba-util/vfs.h>
  30
  31#define GCN1_INPUT 0x47434E31
  32#define GCN2_INPUT 0x47434E32
  33#define WIIMOTE_INPUT 0x5749494D
  34#define CLASSIC_INPUT 0x57494943
  35
  36#define TEX_W 256
  37#define TEX_H 224
  38
  39#define ANALOG_DEADZONE 0x30
  40
  41static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
  42	mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
  43}
  44
  45static enum ScreenMode {
  46	SM_PA,
  47	SM_SF,
  48	SM_MAX
  49} screenMode = SM_PA;
  50
  51static enum FilterMode {
  52	FM_NEAREST,
  53	FM_LINEAR_1x,
  54	FM_LINEAR_2x,
  55	FM_MAX
  56} filterMode = FM_NEAREST;
  57
  58static enum VideoMode {
  59	VM_AUTODETECT,
  60	VM_480i,
  61	VM_480p,
  62	VM_240p,
  63	// TODO: PAL support
  64	VM_MAX
  65} videoMode = VM_AUTODETECT;
  66
  67#define SAMPLES 512
  68#define BUFFERS 8
  69#define GUI_SCALE 1.35f
  70#define GUI_SCALE_240p 2.0f
  71
  72static void _retraceCallback(u32 count);
  73
  74static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
  75static void _audioDMA(void);
  76static void _setRumble(struct mRumble* rumble, int enable);
  77static void _sampleRotation(struct mRotationSource* source);
  78static int32_t _readTiltX(struct mRotationSource* source);
  79static int32_t _readTiltY(struct mRotationSource* source);
  80static int32_t _readGyroZ(struct mRotationSource* source);
  81
  82static void _drawStart(void);
  83static void _drawEnd(void);
  84static uint32_t _pollInput(const struct mInputMap*);
  85static enum GUICursorState _pollCursor(unsigned* x, unsigned* y);
  86static void _guiPrepare(void);
  87
  88static void _setup(struct mGUIRunner* runner);
  89static void _gameLoaded(struct mGUIRunner* runner);
  90static void _gameUnloaded(struct mGUIRunner* runner);
  91static void _unpaused(struct mGUIRunner* runner);
  92static void _prepareForFrame(struct mGUIRunner* runner);
  93static void _drawFrame(struct mGUIRunner* runner, bool faded);
  94static uint16_t _pollGameInput(struct mGUIRunner* runner);
  95static void _setFrameLimiter(struct mGUIRunner* runner, bool limit);
  96static void _incrementScreenMode(struct mGUIRunner* runner);
  97
  98static s8 WPAD_StickX(u8 chan, u8 right);
  99static s8 WPAD_StickY(u8 chan, u8 right);
 100
 101static void* outputBuffer;
 102static struct mAVStream stream;
 103static struct mRumble rumble;
 104static struct mRotationSource rotation;
 105static GXRModeObj* vmode;
 106static float wAdjust;
 107static float hAdjust;
 108static float wStretch = 0.9f;
 109static float hStretch = 0.9f;
 110static float guiScale = GUI_SCALE;
 111static Mtx model, view, modelview;
 112static uint16_t* texmem;
 113static GXTexObj tex;
 114static uint16_t* rescaleTexmem;
 115static GXTexObj rescaleTex;
 116static uint16_t* interframeTexmem;
 117static GXTexObj interframeTex;
 118static bool sgbCrop = false;
 119static int32_t tiltX;
 120static int32_t tiltY;
 121static int32_t gyroZ;
 122static uint32_t retraceCount;
 123static uint32_t referenceRetraceCount;
 124static bool frameLimiter = true;
 125static int scaleFactor;
 126static unsigned corew, coreh;
 127static bool interframeBlending = true;
 128
 129uint32_t* romBuffer;
 130size_t romBufferSize;
 131
 132static void* framebuffer[2] = { 0, 0 };
 133static int whichFb = 0;
 134
 135static struct AudioBuffer {
 136	struct GBAStereoSample samples[SAMPLES] __attribute__((__aligned__(32)));
 137	volatile size_t size;
 138} audioBuffer[BUFFERS] = {0};
 139static volatile int currentAudioBuffer = 0;
 140static volatile int nextAudioBuffer = 0;
 141static double audioSampleRate = 60.0 / 1.001;
 142
 143static struct GUIFont* font;
 144
 145static void reconfigureScreen(struct mGUIRunner* runner) {
 146	if (runner) {
 147		unsigned mode;
 148		if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) {
 149			videoMode = mode;
 150		}
 151	}
 152	wAdjust = 1.f;
 153	hAdjust = 1.f;
 154	guiScale = GUI_SCALE;
 155	audioSampleRate = 60.0 / 1.001;
 156
 157	s32 signalMode = CONF_GetVideo();
 158
 159	switch (videoMode) {
 160	case VM_AUTODETECT:
 161	default:
 162		vmode = VIDEO_GetPreferredMode(0);
 163		break;
 164	case VM_480i:
 165		switch (signalMode) {
 166		case CONF_VIDEO_NTSC:
 167			vmode = &TVNtsc480IntDf;
 168			break;
 169		case CONF_VIDEO_MPAL:
 170			vmode = &TVMpal480IntDf;
 171			break;
 172		case CONF_VIDEO_PAL:
 173			vmode = &TVEurgb60Hz480IntDf;
 174			break;
 175		}
 176		break;
 177	case VM_480p:
 178		switch (signalMode) {
 179		case CONF_VIDEO_NTSC:
 180			vmode = &TVNtsc480Prog;
 181			break;
 182		case CONF_VIDEO_MPAL:
 183			vmode = &TVMpal480Prog;
 184			break;
 185		case CONF_VIDEO_PAL:
 186			vmode = &TVEurgb60Hz480Prog;
 187			break;
 188		}
 189		break;
 190	case VM_240p:
 191		switch (signalMode) {
 192		case CONF_VIDEO_NTSC:
 193			vmode = &TVNtsc240Ds;
 194			break;
 195		case CONF_VIDEO_MPAL:
 196			vmode = &TVMpal240Ds;
 197			break;
 198		case CONF_VIDEO_PAL:
 199			vmode = &TVEurgb60Hz240Ds;
 200			break;
 201		}
 202		wAdjust = 0.5f;
 203		audioSampleRate = 90.0 / 1.50436;
 204		guiScale = GUI_SCALE_240p;
 205		break;
 206	}
 207
 208	vmode->viWidth = 704;
 209	vmode->viXOrigin = 8;
 210
 211	VIDEO_SetBlack(true);
 212	VIDEO_Configure(vmode);
 213
 214	free(framebuffer[0]);
 215	free(framebuffer[1]);
 216
 217	framebuffer[0] = SYS_AllocateFramebuffer(vmode);
 218	framebuffer[1] = SYS_AllocateFramebuffer(vmode);
 219	VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[0]), COLOR_BLACK);
 220	VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[1]), COLOR_BLACK);
 221
 222	VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
 223	VIDEO_Flush();
 224	VIDEO_WaitVSync();
 225	if (vmode->viTVMode & VI_NON_INTERLACE) {
 226		VIDEO_WaitVSync();
 227	}
 228	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
 229
 230	f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight);
 231	u32 xfbHeight = GX_SetDispCopyYScale(yscale);
 232	GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth);
 233	GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight);
 234	GX_SetDispCopyDst(vmode->fbWidth, xfbHeight);
 235	GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter);
 236	GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
 237
 238	if (runner) {
 239		runner->params.width = vmode->fbWidth * guiScale * wAdjust;
 240		runner->params.height = vmode->efbHeight * guiScale * hAdjust;
 241		if (runner->core) {
 242			double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1);
 243			blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
 244			blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
 245		}
 246	}
 247}
 248
 249int main(int argc, char* argv[]) {
 250	VIDEO_Init();
 251	VIDEO_SetBlack(true);
 252	VIDEO_Flush();
 253	VIDEO_WaitVSync();
 254	PAD_Init();
 255	WPAD_Init();
 256	WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
 257	AUDIO_Init(0);
 258	AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
 259	AUDIO_RegisterDMACallback(_audioDMA);
 260
 261	memset(audioBuffer, 0, sizeof(audioBuffer));
 262#ifdef FIXED_ROM_BUFFER
 263	romBufferSize = SIZE_CART0;
 264	romBuffer = SYS_GetArena2Lo();
 265	SYS_SetArena2Lo((void*)((intptr_t) romBuffer + romBufferSize));
 266#endif
 267
 268#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
 269#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
 270#endif
 271
 272	GXColor bg = { 0, 0, 0, 0xFF };
 273	void* fifo = memalign(32, 0x40000);
 274	memset(fifo, 0, 0x40000);
 275	GX_Init(fifo, 0x40000);
 276	GX_SetCopyClear(bg, 0x00FFFFFF);
 277
 278	GX_SetCullMode(GX_CULL_NONE);
 279	GX_SetDispCopyGamma(GX_GM_1_0);
 280
 281	GX_ClearVtxDesc();
 282	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
 283	GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
 284	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
 285
 286	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
 287	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
 288	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
 289
 290	GX_SetNumTevStages(1);
 291	GX_SetNumChans(1);
 292	GX_SetNumTexGens(1);
 293	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
 294	GX_SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0);
 295	GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
 296	GX_SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_DIVIDE_2, GX_TRUE, GX_TEVPREV);
 297	GX_SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
 298	GX_SetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ONE, GX_CC_CPREV);
 299	GX_SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV);
 300
 301	GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
 302	GX_InvVtxCache();
 303	GX_InvalidateTexAll();
 304
 305	guVector cam = { 0.0f, 0.0f, 0.0f };
 306	guVector up = { 0.0f, 1.0f, 0.0f };
 307	guVector look = { 0.0f, 0.0f, -1.0f };
 308	guLookAt(view, &cam, &up, &look);
 309
 310	guMtxIdentity(model);
 311	guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
 312	guMtxConcat(view, model, modelview);
 313	GX_LoadPosMtxImm(modelview, GX_PNMTX0);
 314
 315	texmem = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
 316	GX_InitTexObj(&tex, texmem, TEX_W, TEX_H, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
 317	interframeTexmem = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
 318	GX_InitTexObj(&interframeTex, interframeTexmem, TEX_W, TEX_H, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
 319	rescaleTexmem = memalign(32, TEX_W * TEX_H * 4 * BYTES_PER_PIXEL);
 320	GX_InitTexObj(&rescaleTex, rescaleTexmem, TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
 321	GX_InitTexObjFilterMode(&rescaleTex, GX_LINEAR, GX_LINEAR);
 322
 323	VIDEO_SetPostRetraceCallback(_retraceCallback);
 324
 325	font = GUIFontCreate();
 326
 327	fatInitDefault();
 328
 329	rumble.setRumble = _setRumble;
 330
 331	rotation.sample = _sampleRotation;
 332	rotation.readTiltX = _readTiltX;
 333	rotation.readTiltY = _readTiltY;
 334	rotation.readGyroZ = _readGyroZ;
 335
 336	stream.videoDimensionsChanged = NULL;
 337	stream.postVideoFrame = NULL;
 338	stream.postAudioFrame = NULL;
 339	stream.postAudioBuffer = _postAudioBuffer;
 340
 341	struct mGUIRunner runner = {
 342		.params = {
 343			640, 480,
 344			font, "",
 345			_drawStart, _drawEnd,
 346			_pollInput, _pollCursor,
 347			0,
 348			_guiPrepare, 0,
 349		},
 350		.keySources = (struct GUIInputKeys[]) {
 351			{
 352				.name = "GameCube Input (1)",
 353				.id = GCN1_INPUT,
 354				.keyNames = (const char*[]) {
 355					"D-Pad Left",
 356					"D-Pad Right",
 357					"D-Pad Down",
 358					"D-Pad Up",
 359					"Z",
 360					"R",
 361					"L",
 362					0,
 363					"A",
 364					"B",
 365					"X",
 366					"Y",
 367					"Start"
 368				},
 369				.nKeys = 13
 370			},
 371			{
 372				.name = "GameCube Input (2)",
 373				.id = GCN2_INPUT,
 374				.keyNames = (const char*[]) {
 375					"D-Pad Left",
 376					"D-Pad Right",
 377					"D-Pad Down",
 378					"D-Pad Up",
 379					"Z",
 380					"R",
 381					"L",
 382					0,
 383					"A",
 384					"B",
 385					"X",
 386					"Y",
 387					"Start"
 388				},
 389				.nKeys = 13
 390			},
 391			{
 392				.name = "Wii Remote Input",
 393				.id = WIIMOTE_INPUT,
 394				.keyNames = (const char*[]) {
 395					"2",
 396					"1",
 397					"B",
 398					"A",
 399					"-",
 400					0,
 401					0,
 402					"\1\xE",
 403					"Left",
 404					"Right",
 405					"Down",
 406					"Up",
 407					"+",
 408					0,
 409					0,
 410					0,
 411					"Z",
 412					"C",
 413				},
 414				.nKeys = 18
 415			},
 416			{
 417				.name = "Classic Controller Input",
 418				.id = CLASSIC_INPUT,
 419				.keyNames = (const char*[]) {
 420					0,
 421					0,
 422					0,
 423					0,
 424					0,
 425					0,
 426					0,
 427					0,
 428					0,
 429					0,
 430					0,
 431					0,
 432					0,
 433					0,
 434					0,
 435					0,
 436					"Up",
 437					"Left",
 438					"ZR",
 439					"X",
 440					"A",
 441					"Y",
 442					"B",
 443					"ZL",
 444					0,
 445					"R",
 446					"+",
 447					"\1\xE",
 448					"-",
 449					"L",
 450					"Down",
 451					"Right",
 452				},
 453				.nKeys = 32
 454			},
 455			{ .id = 0 }
 456		},
 457		.configExtra = (struct GUIMenuItem[]) {
 458			{
 459				.title = "Video mode",
 460				.data = "videoMode",
 461				.submenu = 0,
 462				.state = 0,
 463				.validStates = (const char*[]) {
 464					"Autodetect (recommended)",
 465					"480i",
 466					"480p",
 467					"240p",
 468				},
 469				.nStates = 4
 470			},
 471			{
 472				.title = "Screen mode",
 473				.data = "screenMode",
 474				.submenu = 0,
 475				.state = 0,
 476				.validStates = (const char*[]) {
 477					"Pixel-Accurate",
 478					"Stretched",
 479				},
 480				.nStates = 2
 481			},
 482			{
 483				.title = "Filtering",
 484				.data = "filter",
 485				.submenu = 0,
 486				.state = 0,
 487				.validStates = (const char*[]) {
 488					"Pixelated",
 489					"Bilinear (smoother)",
 490					"Bilinear (pixelated)",
 491				},
 492				.nStates = 3
 493			},
 494			{
 495				.title = "Horizontal stretch",
 496				.data = "stretchWidth",
 497				.submenu = 0,
 498				.state = 7,
 499				.validStates = (const char*[]) {
 500					"1/2x", "0.6x", "2/3x", "0.7x", "3/4x", "0.8x", "0.9x", "1.0x"
 501				},
 502				.stateMappings = (const struct GUIVariant[]) {
 503					GUI_V_F(0.5f),
 504					GUI_V_F(0.6f),
 505					GUI_V_F(2.f / 3.f),
 506					GUI_V_F(0.7f),
 507					GUI_V_F(0.75f),
 508					GUI_V_F(0.8f),
 509					GUI_V_F(0.9f),
 510					GUI_V_F(1.0f),
 511				},
 512				.nStates = 8
 513			},
 514			{
 515				.title = "Vertical stretch",
 516				.data = "stretchHeight",
 517				.submenu = 0,
 518				.state = 6,
 519				.validStates = (const char*[]) {
 520					"1/2x", "0.6x", "2/3x", "0.7x", "3/4x", "0.8x", "0.9x", "1.0x"
 521				},
 522				.stateMappings = (const struct GUIVariant[]) {
 523					GUI_V_F(0.5f),
 524					GUI_V_F(0.6f),
 525					GUI_V_F(2.f / 3.f),
 526					GUI_V_F(0.7f),
 527					GUI_V_F(0.75f),
 528					GUI_V_F(0.8f),
 529					GUI_V_F(0.9f),
 530					GUI_V_F(1.0f),
 531				},
 532				.nStates = 8
 533			},
 534		},
 535		.nConfigExtra = 5,
 536		.setup = _setup,
 537		.teardown = 0,
 538		.gameLoaded = _gameLoaded,
 539		.gameUnloaded = _gameUnloaded,
 540		.prepareForFrame = _prepareForFrame,
 541		.drawFrame = _drawFrame,
 542		.paused = _gameUnloaded,
 543		.unpaused = _unpaused,
 544		.incrementScreenMode = _incrementScreenMode,
 545		.setFrameLimiter = _setFrameLimiter,
 546		.pollGameInput = _pollGameInput
 547	};
 548	mGUIInit(&runner, "wii");
 549	reconfigureScreen(&runner);
 550
 551	// Make sure screen is properly initialized by drawing a blank frame
 552	_drawStart();
 553	_drawEnd();
 554
 555	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_A, GUI_INPUT_SELECT);
 556	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_B, GUI_INPUT_BACK);
 557	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_TRIGGER_Z, GUI_INPUT_CANCEL);
 558	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_UP, GUI_INPUT_UP);
 559	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_DOWN, GUI_INPUT_DOWN);
 560	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_LEFT, GUI_INPUT_LEFT);
 561	_mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GUI_INPUT_RIGHT);
 562
 563	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GUI_INPUT_SELECT);
 564	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GUI_INPUT_BACK);
 565	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_HOME, GUI_INPUT_CANCEL);
 566	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GUI_INPUT_UP);
 567	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GUI_INPUT_DOWN);
 568	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GUI_INPUT_LEFT);
 569	_mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GUI_INPUT_RIGHT);
 570
 571	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GUI_INPUT_SELECT);
 572	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GUI_INPUT_BACK);
 573	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_HOME, GUI_INPUT_CANCEL);
 574	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GUI_INPUT_UP);
 575	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GUI_INPUT_DOWN);
 576	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT);
 577	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT);
 578
 579
 580	float stretch = 0;
 581	if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) {
 582		wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 583	}
 584	if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) {
 585		hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 586	}
 587
 588	if (argc > 1) {
 589		size_t i;
 590		for (i = 0; runner.keySources[i].id; ++i) {
 591			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
 592		}
 593		mGUIRun(&runner, argv[1]);
 594	} else {
 595		mGUIRunloop(&runner);
 596	}
 597	VIDEO_SetBlack(true);
 598	VIDEO_Flush();
 599	VIDEO_WaitVSync();
 600	mGUIDeinit(&runner);
 601
 602	free(fifo);
 603	free(texmem);
 604	free(rescaleTexmem);
 605	free(interframeTexmem);
 606
 607	free(outputBuffer);
 608	GUIFontDestroy(font);
 609
 610	free(framebuffer[0]);
 611	free(framebuffer[1]);
 612
 613	return 0;
 614}
 615
 616static void _audioDMA(void) {
 617	struct AudioBuffer* buffer = &audioBuffer[currentAudioBuffer];
 618	if (buffer->size != SAMPLES) {
 619		return;
 620	}
 621	DCFlushRange(buffer->samples, SAMPLES * sizeof(struct GBAStereoSample));
 622	AUDIO_InitDMA((u32) buffer->samples, SAMPLES * sizeof(struct GBAStereoSample));
 623	buffer->size = 0;
 624	currentAudioBuffer = (currentAudioBuffer + 1) % BUFFERS;
 625}
 626
 627static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
 628	UNUSED(stream);
 629
 630	u32 level = 0;
 631	_CPU_ISR_Disable(level);
 632	struct AudioBuffer* buffer = &audioBuffer[nextAudioBuffer];
 633	int available = blip_samples_avail(left);
 634	if (available + buffer->size > SAMPLES) {
 635		available = SAMPLES - buffer->size;
 636	}
 637	if (available > 0) {
 638		// These appear to be reversed for AUDIO_InitDMA
 639		blip_read_samples(left, &buffer->samples[buffer->size].right, available, true);
 640		blip_read_samples(right, &buffer->samples[buffer->size].left, available, true);
 641		buffer->size += available;
 642	}
 643	if (buffer->size == SAMPLES) {
 644		int next = (nextAudioBuffer + 1) % BUFFERS;
 645		if ((currentAudioBuffer + BUFFERS - next) % BUFFERS != 1) {
 646			nextAudioBuffer = next;
 647		}
 648		if (!AUDIO_GetDMAEnableFlag()) {
 649			_audioDMA();
 650			AUDIO_StartDMA();
 651		}
 652	}
 653	_CPU_ISR_Restore(level);
 654}
 655
 656static void _drawStart(void) {
 657	VIDEO_SetBlack(false);
 658
 659	u32 level = 0;
 660	_CPU_ISR_Disable(level);
 661	if (referenceRetraceCount > retraceCount) {
 662		if (frameLimiter) {
 663			VIDEO_WaitVSync();
 664		}
 665		referenceRetraceCount = retraceCount;
 666	} else if (frameLimiter && referenceRetraceCount < retraceCount - 1) {
 667		referenceRetraceCount = retraceCount - 1;
 668	}
 669	_CPU_ISR_Restore(level);
 670
 671	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
 672	GX_SetColorUpdate(GX_TRUE);
 673
 674	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
 675}
 676
 677static void _drawEnd(void) {
 678	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
 679	GX_DrawDone();
 680	VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
 681	VIDEO_Flush();
 682	whichFb = !whichFb;
 683
 684	u32 level = 0;
 685	_CPU_ISR_Disable(level);
 686	++referenceRetraceCount;
 687	_CPU_ISR_Restore(level);
 688}
 689
 690static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
 691	UNUSED(runner);
 692	frameLimiter = limit;
 693}
 694
 695static uint32_t _pollInput(const struct mInputMap* map) {
 696	PAD_ScanPads();
 697	u16 padkeys = PAD_ButtonsHeld(0);
 698
 699	WPAD_ScanPads();
 700	u32 wiiPad = WPAD_ButtonsHeld(0);
 701	u32 ext = 0;
 702	WPAD_Probe(0, &ext);
 703
 704	int keys = 0;
 705	keys |= mInputMapKeyBits(map, GCN1_INPUT, padkeys, 0);
 706	keys |= mInputMapKeyBits(map, GCN2_INPUT, padkeys, 0);
 707	keys |= mInputMapKeyBits(map, WIIMOTE_INPUT, wiiPad, 0);
 708	if (ext == WPAD_EXP_CLASSIC) {
 709		keys |= mInputMapKeyBits(map, CLASSIC_INPUT, wiiPad, 0);
 710	}
 711	int x = PAD_StickX(0);
 712	int y = PAD_StickY(0);
 713	int w_x = WPAD_StickX(0, 0);
 714	int w_y = WPAD_StickY(0, 0);
 715	if (x < -ANALOG_DEADZONE || w_x < -ANALOG_DEADZONE) {
 716		keys |= 1 << GUI_INPUT_LEFT;
 717	}
 718	if (x > ANALOG_DEADZONE || w_x > ANALOG_DEADZONE) {
 719		keys |= 1 << GUI_INPUT_RIGHT;
 720	}
 721	if (y < -ANALOG_DEADZONE || w_y < -ANALOG_DEADZONE) {
 722		keys |= 1 << GUI_INPUT_DOWN;
 723	}
 724	if (y > ANALOG_DEADZONE || w_y > ANALOG_DEADZONE) {
 725		keys |= 1 << GUI_INPUT_UP;
 726	}
 727	return keys;
 728}
 729
 730static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
 731	ir_t ir;
 732	WPAD_IR(0, &ir);
 733	if (!ir.smooth_valid) {
 734		return GUI_CURSOR_NOT_PRESENT;
 735	}
 736	*x = ir.sx;
 737	*y = ir.sy;
 738	WPAD_ScanPads();
 739	u32 wiiPad = WPAD_ButtonsHeld(0);
 740	if (wiiPad & WPAD_BUTTON_A) {
 741		return GUI_CURSOR_DOWN;
 742	}
 743	return GUI_CURSOR_UP;
 744}
 745
 746void _reproj(int w, int h) {
 747	Mtx44 proj;
 748	int top = (vmode->efbHeight * hAdjust - h) / 2;
 749	int left = (vmode->fbWidth * wAdjust - w) / 2;
 750	guOrtho(proj, -top, top + h, -left, left + w, 0, 300);
 751	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 752}
 753
 754void _reproj2(int w, int h) {
 755	Mtx44 proj;
 756	int top = h * (1.0 - hStretch) / 2;
 757	int left = w * (1.0 - wStretch) / 2;
 758	guOrtho(proj, -top, h + top, -left, w + left, 0, 300);
 759	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 760}
 761
 762void _guiPrepare(void) {
 763	GX_SetNumTevStages(1);
 764	_reproj2(vmode->fbWidth * guiScale * wAdjust, vmode->efbHeight * guiScale * hAdjust);
 765}
 766
 767void _setup(struct mGUIRunner* runner) {
 768	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
 769	runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble);
 770	runner->core->setAVStream(runner->core, &stream);
 771
 772	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
 773	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
 774	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
 775	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
 776	_mapKey(&runner->core->inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
 777	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
 778	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
 779	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
 780	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
 781	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
 782	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
 783
 784	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
 785	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
 786	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
 787	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
 788	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
 789	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
 790	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
 791	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
 792	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
 793	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
 794
 795	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
 796	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
 797	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
 798	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
 799	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
 800	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
 801	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
 802	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
 803	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
 804	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
 805
 806	struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, ANALOG_DEADZONE, -ANALOG_DEADZONE };
 807	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 0, &desc);
 808	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, &desc);
 809	desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, ANALOG_DEADZONE, -ANALOG_DEADZONE };
 810	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 1, &desc);
 811	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, &desc);
 812
 813	outputBuffer = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
 814	runner->core->setVideoBuffer(runner->core, outputBuffer, TEX_W);
 815
 816	nextAudioBuffer = 0;
 817	currentAudioBuffer = 0;
 818	int i;
 819	for (i = 0; i < BUFFERS; ++i) {
 820		audioBuffer[i].size = 0;
 821	}
 822	runner->core->setAudioBufferSize(runner->core, SAMPLES);
 823
 824	double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1);
 825	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
 826	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
 827
 828	frameLimiter = true;
 829}
 830
 831void _gameUnloaded(struct mGUIRunner* runner) {
 832	UNUSED(runner);
 833	AUDIO_StopDMA();
 834	frameLimiter = true;
 835	VIDEO_SetBlack(true);
 836	VIDEO_Flush();
 837	VIDEO_WaitVSync();
 838}
 839
 840void _gameLoaded(struct mGUIRunner* runner) {
 841	reconfigureScreen(runner);
 842	if (runner->core->platform(runner->core) == PLATFORM_GBA && ((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
 843		int i;
 844		for (i = 0; i < 6; ++i) {
 845			u32 result = WPAD_SetMotionPlus(0, 1);
 846			if (result == WPAD_ERR_NONE) {
 847				break;
 848			}
 849			sleep(1);
 850		}
 851	}
 852	memset(texmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL);
 853	memset(interframeTexmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL);
 854	_unpaused(runner);
 855}
 856
 857void _unpaused(struct mGUIRunner* runner) {
 858	u32 level = 0;
 859	VIDEO_WaitVSync();
 860	_CPU_ISR_Disable(level);
 861	referenceRetraceCount = retraceCount;
 862	_CPU_ISR_Restore(level);
 863
 864	unsigned mode;
 865	if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) {
 866		if (mode != videoMode) {
 867			reconfigureScreen(runner);
 868		}
 869	}
 870	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
 871		screenMode = mode;
 872	}
 873	if (mCoreConfigGetUIntValue(&runner->config, "filter", &mode) && mode < FM_MAX) {
 874		filterMode = mode;
 875		switch (mode) {
 876		case FM_NEAREST:
 877		case FM_LINEAR_2x:
 878		default:
 879			GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
 880			break;
 881		case FM_LINEAR_1x:
 882			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
 883			break;
 884		}
 885	}
 886	int fakeBool;
 887	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
 888		interframeBlending = fakeBool;
 889	}
 890	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
 891		sgbCrop = fakeBool;
 892	}
 893
 894	float stretch;
 895	if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
 896		wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 897	}
 898	if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) {
 899		hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 900	}
 901}
 902
 903void _prepareForFrame(struct mGUIRunner* runner) {
 904	if (interframeBlending) {
 905		memcpy(interframeTexmem, texmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
 906	}
 907}
 908
 909void _drawFrame(struct mGUIRunner* runner, bool faded) {
 910	runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
 911	uint32_t color = 0xFFFFFF3F;
 912	if (!faded) {
 913		color |= 0xC0;
 914	}
 915	size_t x, y;
 916	uint64_t* texdest = (uint64_t*) texmem;
 917	uint64_t* texsrc = (uint64_t*) outputBuffer;
 918	for (y = 0; y < coreh; y += 4) {
 919		for (x = 0; x < corew >> 2; ++x) {
 920			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
 921			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
 922			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
 923			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
 924		}
 925	}
 926	DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL);
 927	if (interframeBlending) {
 928		DCFlushRange(interframeTexmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
 929	}
 930
 931	if (faded || interframeBlending) {
 932		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
 933	} else {
 934		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
 935	}
 936	GX_InvalidateTexAll();
 937	if (interframeBlending) {
 938		GX_LoadTexObj(&interframeTex, GX_TEXMAP0);
 939		GX_LoadTexObj(&tex, GX_TEXMAP1);
 940		GX_SetNumTevStages(2);
 941	} else {
 942		GX_LoadTexObj(&tex, GX_TEXMAP0);
 943		GX_SetNumTevStages(1);
 944	}
 945
 946	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
 947	s16 vertWidth = corew;
 948	s16 vertHeight = coreh;
 949
 950	if (filterMode == FM_LINEAR_2x) {
 951		Mtx44 proj;
 952		guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300);
 953		GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 954
 955		GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
 956		GX_Position2s16(0, TEX_H * 2);
 957		GX_Color1u32(0xFFFFFFFF);
 958		GX_TexCoord2f32(0, 1);
 959
 960		GX_Position2s16(TEX_W * 2, TEX_H * 2);
 961		GX_Color1u32(0xFFFFFFFF);
 962		GX_TexCoord2f32(1, 1);
 963
 964		GX_Position2s16(TEX_W * 2, 0);
 965		GX_Color1u32(0xFFFFFFFF);
 966		GX_TexCoord2f32(1, 0);
 967
 968		GX_Position2s16(0, 0);
 969		GX_Color1u32(0xFFFFFFFF);
 970		GX_TexCoord2f32(0, 0);
 971		GX_End();
 972
 973		GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);
 974		GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE);
 975		GX_CopyTex(rescaleTexmem, GX_TRUE);
 976		GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
 977		GX_SetNumTevStages(1);
 978		if (!faded) {
 979			GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
 980		}
 981	}
 982
 983	if (screenMode == SM_PA) {
 984		unsigned factorWidth = corew;
 985		unsigned factorHeight = coreh;
 986		if (sgbCrop && factorWidth == 256 && factorHeight == 224) {
 987			factorWidth = GB_VIDEO_HORIZONTAL_PIXELS;
 988			factorHeight = GB_VIDEO_VERTICAL_PIXELS;
 989		}
 990
 991		int hfactor = (vmode->fbWidth * wStretch) / (factorWidth * wAdjust);
 992		int vfactor = (vmode->efbHeight * hStretch) / (factorHeight * hAdjust);
 993		if (hfactor > vfactor) {
 994			scaleFactor = vfactor;
 995		} else {
 996			scaleFactor = hfactor;
 997		}
 998
 999		vertWidth *= scaleFactor;
1000		vertHeight *= scaleFactor;
1001
1002		_reproj(corew * scaleFactor, coreh * scaleFactor);
1003	} else {
1004		_reproj2(corew, coreh);
1005	}
1006
1007	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
1008	GX_Position2s16(0, vertHeight);
1009	GX_Color1u32(color);
1010	GX_TexCoord2f32(0, coreh / (float) TEX_H);
1011
1012	GX_Position2s16(vertWidth, vertHeight);
1013	GX_Color1u32(color);
1014	GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H);
1015
1016	GX_Position2s16(vertWidth, 0);
1017	GX_Color1u32(color);
1018	GX_TexCoord2f32(corew / (float) TEX_W, 0);
1019
1020	GX_Position2s16(0, 0);
1021	GX_Color1u32(color);
1022	GX_TexCoord2f32(0, 0);
1023	GX_End();
1024}
1025
1026uint16_t _pollGameInput(struct mGUIRunner* runner) {
1027	UNUSED(runner);
1028	PAD_ScanPads();
1029	u16 padkeys = PAD_ButtonsHeld(0);
1030	WPAD_ScanPads();
1031	u32 wiiPad = WPAD_ButtonsHeld(0);
1032	u32 ext = 0;
1033	WPAD_Probe(0, &ext);
1034	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
1035	keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
1036	keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
1037
1038	enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
1039	if (angles != GBA_KEY_NONE) {
1040		keys |= 1 << angles;
1041	}
1042	angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
1043	if (angles != GBA_KEY_NONE) {
1044		keys |= 1 << angles;
1045	}
1046	if (ext == WPAD_EXP_CLASSIC) {
1047		keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
1048		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
1049		if (angles != GBA_KEY_NONE) {
1050			keys |= 1 << angles;
1051		}
1052		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
1053		if (angles != GBA_KEY_NONE) {
1054			keys |= 1 << angles;
1055		}
1056	}
1057
1058	return keys;
1059}
1060
1061void _incrementScreenMode(struct mGUIRunner* runner) {
1062	UNUSED(runner);
1063	int mode = screenMode | (filterMode << 1);
1064	++mode;
1065	screenMode = mode % SM_MAX;
1066	filterMode = (mode >> 1) % FM_MAX;
1067	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
1068	mCoreConfigSetUIntValue(&runner->config, "filter", filterMode);
1069	switch (filterMode) {
1070	case FM_NEAREST:
1071	case FM_LINEAR_2x:
1072	default:
1073		GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
1074		break;
1075	case FM_LINEAR_1x:
1076		GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
1077		break;
1078	}
1079}
1080
1081void _setRumble(struct mRumble* rumble, int enable) {
1082	UNUSED(rumble);
1083	WPAD_Rumble(0, enable);
1084	if (enable) {
1085		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
1086	} else {
1087		PAD_ControlMotor(0, PAD_MOTOR_STOP);
1088	}
1089}
1090
1091void _sampleRotation(struct mRotationSource* source) {
1092	UNUSED(source);
1093	vec3w_t accel;
1094	WPAD_Accel(0, &accel);
1095	// These are swapped
1096	tiltX = (0x1EA - accel.y) << 22;
1097	tiltY = (0x1EA - accel.x) << 22;
1098
1099	// This doesn't seem to work at all with -TR remotes
1100	struct expansion_t exp;
1101	WPAD_Expansion(0, &exp);
1102	if (exp.type != EXP_MOTION_PLUS) {
1103		return;
1104	}
1105	gyroZ = exp.mp.rz - 0x1FA0;
1106	gyroZ <<= 18;
1107}
1108
1109int32_t _readTiltX(struct mRotationSource* source) {
1110	UNUSED(source);
1111	return tiltX;
1112}
1113
1114int32_t _readTiltY(struct mRotationSource* source) {
1115	UNUSED(source);
1116	return tiltY;
1117}
1118
1119int32_t _readGyroZ(struct mRotationSource* source) {
1120	UNUSED(source);
1121	return gyroZ;
1122}
1123
1124static s8 WPAD_StickX(u8 chan, u8 right) {
1125	struct expansion_t exp;
1126	WPAD_Expansion(chan, &exp);
1127	struct joystick_t* js = NULL;
1128
1129	switch (exp.type)	{
1130	case WPAD_EXP_NUNCHUK:
1131	case WPAD_EXP_GUITARHERO3:
1132		if (right == 0) {
1133			js = &exp.nunchuk.js;
1134		}
1135		break;
1136	case WPAD_EXP_CLASSIC:
1137		if (right == 0) {
1138			js = &exp.classic.ljs;
1139		} else {
1140			js = &exp.classic.rjs;
1141		}
1142		break;
1143	default:
1144		break;
1145	}
1146
1147	if (!js) {
1148		return 0;
1149	}
1150	int centered = (int) js->pos.x - (int) js->center.x;
1151	int range = (int) js->max.x - (int) js->min.x;
1152	int value = (centered * 0xFF) / range;
1153	if (value > 0x7F) {
1154		return 0x7F;
1155	}
1156	if (value < -0x80) {
1157		return -0x80;
1158	}
1159	return value;
1160}
1161
1162static s8 WPAD_StickY(u8 chan, u8 right) {
1163	struct expansion_t exp;
1164	WPAD_Expansion(chan, &exp);
1165	struct joystick_t* js = NULL;
1166
1167	switch (exp.type)	{
1168	case WPAD_EXP_NUNCHUK:
1169	case WPAD_EXP_GUITARHERO3:
1170		if (right == 0) {
1171			js = &exp.nunchuk.js;
1172		}
1173		break;
1174	case WPAD_EXP_CLASSIC:
1175		if (right == 0) {
1176			js = &exp.classic.ljs;
1177		} else {
1178			js = &exp.classic.rjs;
1179		}
1180		break;
1181	default:
1182		break;
1183	}
1184
1185	if (!js) {
1186		return 0;
1187	}
1188	int centered = (int) js->pos.y - (int) js->center.y;
1189	int range = (int) js->max.y - (int) js->min.y;
1190	int value = (centered * 0xFF) / range;
1191	if (value > 0x7F) {
1192		return 0x7F;
1193	}
1194	if (value < -0x80) {
1195		return -0x80;
1196	}
1197	return value;
1198}
1199
1200void _retraceCallback(u32 count) {
1201	u32 level = 0;
1202	_CPU_ISR_Disable(level);
1203	retraceCount = count;
1204	_CPU_ISR_Restore(level);
1205}