all repos — mgba @ 0df180c0d010b4571ad024134dfbee23f9646728

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