all repos — mgba @ b38cac3be37fa05982bd3204f0822b95260040d7

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			GX_InitTexObjFilterMode(&interframeTex, GX_NEAR, GX_NEAR);
 881			break;
 882		case FM_LINEAR_1x:
 883			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
 884			GX_InitTexObjFilterMode(&interframeTex, GX_LINEAR, GX_LINEAR);
 885			break;
 886		}
 887	}
 888	int fakeBool;
 889	if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
 890		interframeBlending = fakeBool;
 891	}
 892	if (mCoreConfigGetIntValue(&runner->config, "sgb.borderCrop", &fakeBool)) {
 893		sgbCrop = fakeBool;
 894	}
 895
 896	float stretch;
 897	if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
 898		wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 899	}
 900	if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) {
 901		hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 902	}
 903}
 904
 905void _prepareForFrame(struct mGUIRunner* runner) {
 906	if (interframeBlending) {
 907		memcpy(interframeTexmem, texmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
 908	}
 909}
 910
 911void _drawFrame(struct mGUIRunner* runner, bool faded) {
 912	runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
 913	uint32_t color = 0xFFFFFF3F;
 914	if (!faded) {
 915		color |= 0xC0;
 916	}
 917	size_t x, y;
 918	uint64_t* texdest = (uint64_t*) texmem;
 919	uint64_t* texsrc = (uint64_t*) outputBuffer;
 920	for (y = 0; y < coreh; y += 4) {
 921		for (x = 0; x < corew >> 2; ++x) {
 922			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
 923			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
 924			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
 925			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
 926		}
 927	}
 928	DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL);
 929	if (interframeBlending) {
 930		DCFlushRange(interframeTexmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
 931	}
 932
 933	if (faded || interframeBlending) {
 934		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
 935	} else {
 936		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
 937	}
 938	GX_InvalidateTexAll();
 939	if (interframeBlending) {
 940		GX_LoadTexObj(&interframeTex, GX_TEXMAP0);
 941		GX_LoadTexObj(&tex, GX_TEXMAP1);
 942		GX_SetNumTevStages(2);
 943	} else {
 944		GX_LoadTexObj(&tex, GX_TEXMAP0);
 945		GX_SetNumTevStages(1);
 946	}
 947
 948	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
 949	s16 vertWidth = corew;
 950	s16 vertHeight = coreh;
 951
 952	if (filterMode == FM_LINEAR_2x) {
 953		Mtx44 proj;
 954		guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300);
 955		GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 956
 957		GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
 958		GX_Position2s16(0, TEX_H * 2);
 959		GX_Color1u32(0xFFFFFFFF);
 960		GX_TexCoord2f32(0, 1);
 961
 962		GX_Position2s16(TEX_W * 2, TEX_H * 2);
 963		GX_Color1u32(0xFFFFFFFF);
 964		GX_TexCoord2f32(1, 1);
 965
 966		GX_Position2s16(TEX_W * 2, 0);
 967		GX_Color1u32(0xFFFFFFFF);
 968		GX_TexCoord2f32(1, 0);
 969
 970		GX_Position2s16(0, 0);
 971		GX_Color1u32(0xFFFFFFFF);
 972		GX_TexCoord2f32(0, 0);
 973		GX_End();
 974
 975		GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);
 976		GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE);
 977		GX_CopyTex(rescaleTexmem, GX_TRUE);
 978		GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
 979		GX_SetNumTevStages(1);
 980		if (!faded) {
 981			GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
 982		}
 983	}
 984
 985	if (screenMode == SM_PA) {
 986		unsigned factorWidth = corew;
 987		unsigned factorHeight = coreh;
 988		if (sgbCrop && factorWidth == 256 && factorHeight == 224) {
 989			factorWidth = GB_VIDEO_HORIZONTAL_PIXELS;
 990			factorHeight = GB_VIDEO_VERTICAL_PIXELS;
 991		}
 992
 993		int hfactor = (vmode->fbWidth * wStretch) / (factorWidth * wAdjust);
 994		int vfactor = (vmode->efbHeight * hStretch) / (factorHeight * hAdjust);
 995		if (hfactor > vfactor) {
 996			scaleFactor = vfactor;
 997		} else {
 998			scaleFactor = hfactor;
 999		}
1000
1001		vertWidth *= scaleFactor;
1002		vertHeight *= scaleFactor;
1003
1004		_reproj(corew * scaleFactor, coreh * scaleFactor);
1005	} else {
1006		_reproj2(corew, coreh);
1007	}
1008
1009	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
1010	GX_Position2s16(0, vertHeight);
1011	GX_Color1u32(color);
1012	GX_TexCoord2f32(0, coreh / (float) TEX_H);
1013
1014	GX_Position2s16(vertWidth, vertHeight);
1015	GX_Color1u32(color);
1016	GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H);
1017
1018	GX_Position2s16(vertWidth, 0);
1019	GX_Color1u32(color);
1020	GX_TexCoord2f32(corew / (float) TEX_W, 0);
1021
1022	GX_Position2s16(0, 0);
1023	GX_Color1u32(color);
1024	GX_TexCoord2f32(0, 0);
1025	GX_End();
1026}
1027
1028uint16_t _pollGameInput(struct mGUIRunner* runner) {
1029	UNUSED(runner);
1030	PAD_ScanPads();
1031	u16 padkeys = PAD_ButtonsHeld(0);
1032	WPAD_ScanPads();
1033	u32 wiiPad = WPAD_ButtonsHeld(0);
1034	u32 ext = 0;
1035	WPAD_Probe(0, &ext);
1036	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
1037	keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
1038	keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
1039
1040	enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
1041	if (angles != GBA_KEY_NONE) {
1042		keys |= 1 << angles;
1043	}
1044	angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
1045	if (angles != GBA_KEY_NONE) {
1046		keys |= 1 << angles;
1047	}
1048	if (ext == WPAD_EXP_CLASSIC) {
1049		keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
1050		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
1051		if (angles != GBA_KEY_NONE) {
1052			keys |= 1 << angles;
1053		}
1054		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
1055		if (angles != GBA_KEY_NONE) {
1056			keys |= 1 << angles;
1057		}
1058	}
1059
1060	return keys;
1061}
1062
1063void _incrementScreenMode(struct mGUIRunner* runner) {
1064	UNUSED(runner);
1065	int mode = screenMode | (filterMode << 1);
1066	++mode;
1067	screenMode = mode % SM_MAX;
1068	filterMode = (mode >> 1) % FM_MAX;
1069	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
1070	mCoreConfigSetUIntValue(&runner->config, "filter", filterMode);
1071	switch (filterMode) {
1072	case FM_NEAREST:
1073	case FM_LINEAR_2x:
1074	default:
1075		GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
1076		GX_InitTexObjFilterMode(&interframeTex, GX_NEAR, GX_NEAR);
1077		break;
1078	case FM_LINEAR_1x:
1079		GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
1080		GX_InitTexObjFilterMode(&interframeTex, GX_LINEAR, GX_LINEAR);
1081		break;
1082	}
1083}
1084
1085void _setRumble(struct mRumble* rumble, int enable) {
1086	UNUSED(rumble);
1087	WPAD_Rumble(0, enable);
1088	if (enable) {
1089		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
1090	} else {
1091		PAD_ControlMotor(0, PAD_MOTOR_STOP);
1092	}
1093}
1094
1095void _sampleRotation(struct mRotationSource* source) {
1096	UNUSED(source);
1097	vec3w_t accel;
1098	WPAD_Accel(0, &accel);
1099	// These are swapped
1100	tiltX = (0x1EA - accel.y) << 22;
1101	tiltY = (0x1EA - accel.x) << 22;
1102
1103	// This doesn't seem to work at all with -TR remotes
1104	struct expansion_t exp;
1105	WPAD_Expansion(0, &exp);
1106	if (exp.type != EXP_MOTION_PLUS) {
1107		return;
1108	}
1109	gyroZ = exp.mp.rz - 0x1FA0;
1110	gyroZ <<= 18;
1111}
1112
1113int32_t _readTiltX(struct mRotationSource* source) {
1114	UNUSED(source);
1115	return tiltX;
1116}
1117
1118int32_t _readTiltY(struct mRotationSource* source) {
1119	UNUSED(source);
1120	return tiltY;
1121}
1122
1123int32_t _readGyroZ(struct mRotationSource* source) {
1124	UNUSED(source);
1125	return gyroZ;
1126}
1127
1128static s8 WPAD_StickX(u8 chan, u8 right) {
1129	struct expansion_t exp;
1130	WPAD_Expansion(chan, &exp);
1131	struct joystick_t* js = NULL;
1132
1133	switch (exp.type)	{
1134	case WPAD_EXP_NUNCHUK:
1135	case WPAD_EXP_GUITARHERO3:
1136		if (right == 0) {
1137			js = &exp.nunchuk.js;
1138		}
1139		break;
1140	case WPAD_EXP_CLASSIC:
1141		if (right == 0) {
1142			js = &exp.classic.ljs;
1143		} else {
1144			js = &exp.classic.rjs;
1145		}
1146		break;
1147	default:
1148		break;
1149	}
1150
1151	if (!js) {
1152		return 0;
1153	}
1154	int centered = (int) js->pos.x - (int) js->center.x;
1155	int range = (int) js->max.x - (int) js->min.x;
1156	int value = (centered * 0xFF) / range;
1157	if (value > 0x7F) {
1158		return 0x7F;
1159	}
1160	if (value < -0x80) {
1161		return -0x80;
1162	}
1163	return value;
1164}
1165
1166static s8 WPAD_StickY(u8 chan, u8 right) {
1167	struct expansion_t exp;
1168	WPAD_Expansion(chan, &exp);
1169	struct joystick_t* js = NULL;
1170
1171	switch (exp.type)	{
1172	case WPAD_EXP_NUNCHUK:
1173	case WPAD_EXP_GUITARHERO3:
1174		if (right == 0) {
1175			js = &exp.nunchuk.js;
1176		}
1177		break;
1178	case WPAD_EXP_CLASSIC:
1179		if (right == 0) {
1180			js = &exp.classic.ljs;
1181		} else {
1182			js = &exp.classic.rjs;
1183		}
1184		break;
1185	default:
1186		break;
1187	}
1188
1189	if (!js) {
1190		return 0;
1191	}
1192	int centered = (int) js->pos.y - (int) js->center.y;
1193	int range = (int) js->max.y - (int) js->min.y;
1194	int value = (centered * 0xFF) / range;
1195	if (value > 0x7F) {
1196		return 0x7F;
1197	}
1198	if (value < -0x80) {
1199		return -0x80;
1200	}
1201	return value;
1202}
1203
1204void _retraceCallback(u32 count) {
1205	u32 level = 0;
1206	_CPU_ISR_Disable(level);
1207	retraceCount = count;
1208	_CPU_ISR_Restore(level);
1209}