all repos — mgba @ 93e5b6da7b69d94bbe2e0783f05897b1adee755b

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