all repos — mgba @ dd1514cb8aa41d0071c1f756c86a0acbbbfa3a0e

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