all repos — mgba @ a64236ce21800d5960f852176cfaa9f80c9b0dbd

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_B, GUI_INPUT_BACK);
 552	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_HOME, GUI_INPUT_CANCEL);
 553	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GUI_INPUT_UP);
 554	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GUI_INPUT_DOWN);
 555	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT);
 556	_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT);
 557
 558
 559	float stretch = 0;
 560	if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) {
 561		wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 562	}
 563	if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) {
 564		hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 565	}
 566
 567	if (argc > 1) {
 568		size_t i;
 569		for (i = 0; runner.keySources[i].id; ++i) {
 570			mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
 571		}
 572		mGUIRun(&runner, argv[1]);
 573	} else {
 574		mGUIRunloop(&runner);
 575	}
 576	VIDEO_SetBlack(true);
 577	VIDEO_Flush();
 578	VIDEO_WaitVSync();
 579	mGUIDeinit(&runner);
 580
 581	free(fifo);
 582	free(texmem);
 583	free(rescaleTexmem);
 584
 585	free(outputBuffer);
 586	GUIFontDestroy(font);
 587
 588	free(framebuffer[0]);
 589	free(framebuffer[1]);
 590
 591	return 0;
 592}
 593
 594static void _audioDMA(void) {
 595	if (!audioBufferSize) {
 596		return;
 597	}
 598	DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
 599	AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
 600	currentAudioBuffer = (currentAudioBuffer + 1) % 3;
 601	audioBufferSize = 0;
 602}
 603
 604static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
 605	UNUSED(stream);
 606	int available = blip_samples_avail(left);
 607	if (available + audioBufferSize > SAMPLES) {
 608		available = SAMPLES - audioBufferSize;
 609	}
 610	available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
 611	if (available > 0) {
 612		// These appear to be reversed for AUDIO_InitDMA
 613		blip_read_samples(left, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
 614		blip_read_samples(right, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
 615		audioBufferSize += available;
 616	}
 617	if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
 618		_audioDMA();
 619		AUDIO_StartDMA();
 620	}
 621}
 622
 623static void _drawStart(void) {
 624	VIDEO_SetBlack(false);
 625
 626	u32 level = 0;
 627	_CPU_ISR_Disable(level);
 628	if (referenceRetraceCount > retraceCount) {
 629		if (frameLimiter) {
 630			VIDEO_WaitVSync();
 631		}
 632		referenceRetraceCount = retraceCount;
 633	}
 634	_CPU_ISR_Restore(level);
 635
 636	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
 637	GX_SetColorUpdate(GX_TRUE);
 638
 639	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
 640}
 641
 642static void _drawEnd(void) {
 643	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
 644	GX_DrawDone();
 645	VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
 646	VIDEO_Flush();
 647	whichFb = !whichFb;
 648
 649	u32 level = 0;
 650	_CPU_ISR_Disable(level);
 651	++referenceRetraceCount;
 652	_CPU_ISR_Restore(level);
 653}
 654
 655static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
 656	UNUSED(runner);
 657	frameLimiter = limit;
 658}
 659
 660static uint32_t _pollInput(const struct mInputMap* map) {
 661	PAD_ScanPads();
 662	u16 padkeys = PAD_ButtonsHeld(0);
 663
 664	WPAD_ScanPads();
 665	u32 wiiPad = WPAD_ButtonsHeld(0);
 666	u32 ext = 0;
 667	WPAD_Probe(0, &ext);
 668
 669	int keys = 0;
 670	keys |= mInputMapKeyBits(map, GCN1_INPUT, padkeys, 0);
 671	keys |= mInputMapKeyBits(map, GCN2_INPUT, padkeys, 0);
 672	keys |= mInputMapKeyBits(map, WIIMOTE_INPUT, wiiPad, 0);
 673	if (ext == WPAD_EXP_CLASSIC) {
 674		keys |= mInputMapKeyBits(map, CLASSIC_INPUT, wiiPad, 0);
 675	}
 676	int x = PAD_StickX(0);
 677	int y = PAD_StickY(0);
 678	int w_x = WPAD_StickX(0, 0);
 679	int w_y = WPAD_StickY(0, 0);
 680	if (x < -ANALOG_DEADZONE || w_x < -ANALOG_DEADZONE) {
 681		keys |= 1 << GUI_INPUT_LEFT;
 682	}
 683	if (x > ANALOG_DEADZONE || w_x > ANALOG_DEADZONE) {
 684		keys |= 1 << GUI_INPUT_RIGHT;
 685	}
 686	if (y < -ANALOG_DEADZONE || w_y < -ANALOG_DEADZONE) {
 687		keys |= 1 << GUI_INPUT_DOWN;
 688	}
 689	if (y > ANALOG_DEADZONE || w_y > ANALOG_DEADZONE) {
 690		keys |= 1 << GUI_INPUT_UP;
 691	}
 692	return keys;
 693}
 694
 695static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
 696	ir_t ir;
 697	WPAD_IR(0, &ir);
 698	if (!ir.smooth_valid) {
 699		return GUI_CURSOR_NOT_PRESENT;
 700	}
 701	*x = ir.sx;
 702	*y = ir.sy;
 703	WPAD_ScanPads();
 704	u32 wiiPad = WPAD_ButtonsHeld(0);
 705	if (wiiPad & WPAD_BUTTON_A) {
 706		return GUI_CURSOR_DOWN;
 707	}
 708	return GUI_CURSOR_UP;
 709}
 710
 711void _reproj(int w, int h) {
 712	Mtx44 proj;
 713	int top = (vmode->efbHeight * hAdjust - h) / 2;
 714	int left = (vmode->fbWidth * wAdjust - w) / 2;
 715	guOrtho(proj, -top, top + h, -left, left + w, 0, 300);
 716	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 717}
 718
 719void _reproj2(int w, int h) {
 720	Mtx44 proj;
 721	int top = h * (1.0 - hStretch) / 2;
 722	int left = w * (1.0 - wStretch) / 2;
 723	guOrtho(proj, -top, h + top, -left, w + left, 0, 300);
 724	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 725}
 726
 727void _guiPrepare(void) {
 728	_reproj2(vmode->fbWidth * guiScale * wAdjust, vmode->efbHeight * guiScale * hAdjust);
 729}
 730
 731void _setup(struct mGUIRunner* runner) {
 732	runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
 733	runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble);
 734	runner->core->setAVStream(runner->core, &stream);
 735
 736	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
 737	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
 738	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
 739	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
 740	_mapKey(&runner->core->inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
 741	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
 742	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
 743	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
 744	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
 745	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
 746	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
 747
 748	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
 749	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
 750	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
 751	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
 752	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
 753	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
 754	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
 755	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
 756	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
 757	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
 758
 759	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
 760	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
 761	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
 762	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
 763	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
 764	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
 765	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
 766	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
 767	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
 768	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
 769
 770	struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, ANALOG_DEADZONE, -ANALOG_DEADZONE };
 771	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 0, &desc);
 772	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, &desc);
 773	desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, ANALOG_DEADZONE, -ANALOG_DEADZONE };
 774	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 1, &desc);
 775	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, &desc);
 776
 777	outputBuffer = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
 778	runner->core->setVideoBuffer(runner->core, outputBuffer, TEX_W);
 779
 780	runner->core->setAudioBufferSize(runner->core, SAMPLES);
 781
 782	double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1);
 783	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
 784	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
 785
 786	frameLimiter = true;
 787}
 788
 789void _gameUnloaded(struct mGUIRunner* runner) {
 790	UNUSED(runner);
 791	AUDIO_StopDMA();
 792	frameLimiter = true;
 793	VIDEO_SetBlack(true);
 794	VIDEO_Flush();
 795	VIDEO_WaitVSync();
 796}
 797
 798void _gameLoaded(struct mGUIRunner* runner) {
 799	reconfigureScreen(runner);
 800	if (runner->core->platform(runner->core) == PLATFORM_GBA && ((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
 801		int i;
 802		for (i = 0; i < 6; ++i) {
 803			u32 result = WPAD_SetMotionPlus(0, 1);
 804			if (result == WPAD_ERR_NONE) {
 805				break;
 806			}
 807			sleep(1);
 808		}
 809	}
 810	memset(texmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL);
 811	_unpaused(runner);
 812}
 813
 814void _unpaused(struct mGUIRunner* runner) {
 815	u32 level = 0;
 816	VIDEO_WaitVSync();
 817	_CPU_ISR_Disable(level);
 818	referenceRetraceCount = retraceCount;
 819	_CPU_ISR_Restore(level);
 820
 821	unsigned mode;
 822	if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) {
 823		if (mode != videoMode) {
 824			reconfigureScreen(runner);
 825		}
 826	}
 827	if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
 828		screenMode = mode;
 829	}
 830	if (mCoreConfigGetUIntValue(&runner->config, "filter", &mode) && mode < FM_MAX) {
 831		filterMode = mode;
 832		switch (mode) {
 833		case FM_NEAREST:
 834		case FM_LINEAR_2x:
 835		default:
 836			GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
 837			break;
 838		case FM_LINEAR_1x:
 839			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
 840			break;
 841		}
 842	}
 843	float stretch;
 844	if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
 845		wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 846	}
 847	if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) {
 848		hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
 849	}
 850}
 851
 852void _drawFrame(struct mGUIRunner* runner, bool faded) {
 853	runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
 854	uint32_t color = 0xFFFFFF3F;
 855	if (!faded) {
 856		color |= 0xC0;
 857	}
 858	size_t x, y;
 859	uint64_t* texdest = (uint64_t*) texmem;
 860	uint64_t* texsrc = (uint64_t*) outputBuffer;
 861	for (y = 0; y < coreh; y += 4) {
 862		for (x = 0; x < corew >> 2; ++x) {
 863			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
 864			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
 865			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
 866			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
 867		}
 868	}
 869	DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL);
 870
 871	if (faded) {
 872		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
 873	} else {
 874		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
 875	}
 876	GX_InvalidateTexAll();
 877	GX_LoadTexObj(&tex, GX_TEXMAP0);
 878
 879	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
 880	s16 vertWidth = corew;
 881	s16 vertHeight = coreh;
 882
 883	if (filterMode == FM_LINEAR_2x) {
 884		Mtx44 proj;
 885		guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300);
 886		GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
 887
 888		GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
 889		GX_Position2s16(0, TEX_H * 2);
 890		GX_Color1u32(0xFFFFFFFF);
 891		GX_TexCoord2f32(0, 1);
 892
 893		GX_Position2s16(TEX_W * 2, TEX_H * 2);
 894		GX_Color1u32(0xFFFFFFFF);
 895		GX_TexCoord2f32(1, 1);
 896
 897		GX_Position2s16(TEX_W * 2, 0);
 898		GX_Color1u32(0xFFFFFFFF);
 899		GX_TexCoord2f32(1, 0);
 900
 901		GX_Position2s16(0, 0);
 902		GX_Color1u32(0xFFFFFFFF);
 903		GX_TexCoord2f32(0, 0);
 904		GX_End();
 905
 906		GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);
 907		GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE);
 908		GX_CopyTex(rescaleTexmem, GX_TRUE);
 909		GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
 910	}
 911
 912	int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust);
 913	int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
 914	if (hfactor > vfactor) {
 915		scaleFactor = vfactor;
 916	} else {
 917		scaleFactor = hfactor;
 918	}
 919
 920	if (screenMode == SM_PA) {
 921		vertWidth *= scaleFactor;
 922		vertHeight *= scaleFactor;
 923	}
 924
 925	if (screenMode == SM_PA) {
 926		_reproj(corew * scaleFactor, coreh * scaleFactor);
 927	} else {
 928		_reproj2(corew, coreh);
 929	}
 930
 931	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
 932	GX_Position2s16(0, vertHeight);
 933	GX_Color1u32(color);
 934	GX_TexCoord2f32(0, coreh / (float) TEX_H);
 935
 936	GX_Position2s16(vertWidth, vertHeight);
 937	GX_Color1u32(color);
 938	GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H);
 939
 940	GX_Position2s16(vertWidth, 0);
 941	GX_Color1u32(color);
 942	GX_TexCoord2f32(corew / (float) TEX_W, 0);
 943
 944	GX_Position2s16(0, 0);
 945	GX_Color1u32(color);
 946	GX_TexCoord2f32(0, 0);
 947	GX_End();
 948}
 949
 950uint16_t _pollGameInput(struct mGUIRunner* runner) {
 951	UNUSED(runner);
 952	PAD_ScanPads();
 953	u16 padkeys = PAD_ButtonsHeld(0);
 954	WPAD_ScanPads();
 955	u32 wiiPad = WPAD_ButtonsHeld(0);
 956	u32 ext = 0;
 957	WPAD_Probe(0, &ext);
 958	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
 959	keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
 960	keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
 961
 962	enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
 963	if (angles != GBA_KEY_NONE) {
 964		keys |= 1 << angles;
 965	}
 966	angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
 967	if (angles != GBA_KEY_NONE) {
 968		keys |= 1 << angles;
 969	}
 970	if (ext == WPAD_EXP_CLASSIC) {
 971		keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
 972		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
 973		if (angles != GBA_KEY_NONE) {
 974			keys |= 1 << angles;
 975		}
 976		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
 977		if (angles != GBA_KEY_NONE) {
 978			keys |= 1 << angles;
 979		}
 980	}
 981
 982	return keys;
 983}
 984
 985void _incrementScreenMode(struct mGUIRunner* runner) {
 986	UNUSED(runner);
 987	int mode = screenMode | (filterMode << 1);
 988	++mode;
 989	screenMode = mode % SM_MAX;
 990	filterMode = (mode >> 1) % FM_MAX;
 991	mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
 992	mCoreConfigSetUIntValue(&runner->config, "filter", filterMode);
 993	switch (filterMode) {
 994	case FM_NEAREST:
 995	case FM_LINEAR_2x:
 996	default:
 997		GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
 998		break;
 999	case FM_LINEAR_1x:
1000		GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
1001		break;
1002	}
1003}
1004
1005void _setRumble(struct mRumble* rumble, int enable) {
1006	UNUSED(rumble);
1007	WPAD_Rumble(0, enable);
1008	if (enable) {
1009		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
1010	} else {
1011		PAD_ControlMotor(0, PAD_MOTOR_STOP);
1012	}
1013}
1014
1015void _sampleRotation(struct mRotationSource* source) {
1016	UNUSED(source);
1017	vec3w_t accel;
1018	WPAD_Accel(0, &accel);
1019	// These are swapped
1020	tiltX = (0x1EA - accel.y) << 22;
1021	tiltY = (0x1EA - accel.x) << 22;
1022
1023	// This doesn't seem to work at all with -TR remotes
1024	struct expansion_t exp;
1025	WPAD_Expansion(0, &exp);
1026	if (exp.type != EXP_MOTION_PLUS) {
1027		return;
1028	}
1029	gyroZ = exp.mp.rz - 0x1FA0;
1030	gyroZ <<= 18;
1031}
1032
1033int32_t _readTiltX(struct mRotationSource* source) {
1034	UNUSED(source);
1035	return tiltX;
1036}
1037
1038int32_t _readTiltY(struct mRotationSource* source) {
1039	UNUSED(source);
1040	return tiltY;
1041}
1042
1043int32_t _readGyroZ(struct mRotationSource* source) {
1044	UNUSED(source);
1045	return gyroZ;
1046}
1047
1048static s8 WPAD_StickX(u8 chan, u8 right) {
1049	struct expansion_t exp;
1050	WPAD_Expansion(chan, &exp);
1051	struct joystick_t* js = NULL;
1052
1053	switch (exp.type)	{
1054	case WPAD_EXP_NUNCHUK:
1055	case WPAD_EXP_GUITARHERO3:
1056		if (right == 0) {
1057			js = &exp.nunchuk.js;
1058		}
1059		break;
1060	case WPAD_EXP_CLASSIC:
1061		if (right == 0) {
1062			js = &exp.classic.ljs;
1063		} else {
1064			js = &exp.classic.rjs;
1065		}
1066		break;
1067	default:
1068		break;
1069	}
1070
1071	if (!js) {
1072		return 0;
1073	}
1074	int centered = (int) js->pos.x - (int) js->center.x;
1075	int range = (int) js->max.x - (int) js->min.x;
1076	int value = (centered * 0xFF) / range;
1077	if (value > 0x7F) {
1078		return 0x7F;
1079	}
1080	if (value < -0x80) {
1081		return -0x80;
1082	}
1083	return value;
1084}
1085
1086static s8 WPAD_StickY(u8 chan, u8 right) {
1087	struct expansion_t exp;
1088	WPAD_Expansion(chan, &exp);
1089	struct joystick_t* js = NULL;
1090
1091	switch (exp.type)	{
1092	case WPAD_EXP_NUNCHUK:
1093	case WPAD_EXP_GUITARHERO3:
1094		if (right == 0) {
1095			js = &exp.nunchuk.js;
1096		}
1097		break;
1098	case WPAD_EXP_CLASSIC:
1099		if (right == 0) {
1100			js = &exp.classic.ljs;
1101		} else {
1102			js = &exp.classic.rjs;
1103		}
1104		break;
1105	default:
1106		break;
1107	}
1108
1109	if (!js) {
1110		return 0;
1111	}
1112	int centered = (int) js->pos.y - (int) js->center.y;
1113	int range = (int) js->max.y - (int) js->min.y;
1114	int value = (centered * 0xFF) / range;
1115	if (value > 0x7F) {
1116		return 0x7F;
1117	}
1118	if (value < -0x80) {
1119		return -0x80;
1120	}
1121	return value;
1122}
1123
1124void _retraceCallback(u32 count) {
1125	u32 level = 0;
1126	_CPU_ISR_Disable(level);
1127	retraceCount = count;
1128	_CPU_ISR_Restore(level);
1129}