all repos — mgba @ 38a7a56ee81a4a8f6b511aada0fa0e5f9f88c109

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