all repos — mgba @ 456dbc482f9ee76e8b632e296ff4162ac38f18a1

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