all repos — mgba @ 0b40fb0704300fadd40ff798295711b1c14f3c57

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