all repos — mgba @ 0d7cdb9ed874a909ec71aa165f5280153761974d

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