all repos — mgba @ e063e056623dcd1ca1ed516e154a68e306b93595

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