all repos — mgba @ 563aed44d854d528f5cee01b1bcddf007b48b4e2

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