all repos — mgba @ cabbffad416193029e3e4fac56d2ee99119f56bb

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