all repos — mgba @ 2e9b88f5beba88685746af166744bb1af75eecb4

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