all repos — mgba @ a260b4cf91649498b32e3d10a06117175c9cb75f

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 "gba/audio.h"
 19#include "gba/gba.h"
 20#include "gba/gui/gui-runner.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	if (runner->core->platform(runner->core) == PLATFORM_GBA) {
525		((struct GBA*) runner->core->board)->rumble = &rumble;
526	}
527	runner->core->setRotation(runner->core, &rotation);
528
529	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
530	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
531	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
532	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
533	_mapKey(&runner->core->inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
534	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
535	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
536	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
537	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
538	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
539	_mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
540
541	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
542	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
543	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
544	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
545	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
546	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
547	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
548	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
549	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
550	_mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
551
552	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
553	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
554	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
555	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
556	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
557	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
558	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
559	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
560	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
561	_mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
562
563	struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x20, -0x20 };
564	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 0, &desc);
565	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, &desc);
566	desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, 0x20, -0x20 };
567	mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 1, &desc);
568	mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, &desc);
569
570	outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
571	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
572
573	runner->core->setAudioBufferSize(runner->core, SAMPLES);
574
575	double ratio = GBAAudioCalculateRatio(1, 60 / 1.001, 1);
576	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
577	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
578}
579
580void _gameUnloaded(struct mGUIRunner* runner) {
581	UNUSED(runner);
582	AUDIO_StopDMA();
583}
584
585void _gameLoaded(struct mGUIRunner* runner) {
586	reconfigureScreen(runner->core, vmode);
587	if (runner->core->platform(runner->core) == PLATFORM_GBA && ((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
588		int i;
589		for (i = 0; i < 6; ++i) {
590			u32 result = WPAD_SetMotionPlus(0, 1);
591			if (result == WPAD_ERR_NONE) {
592				break;
593			}
594			sleep(1);
595		}
596	}
597	_unpaused(runner);
598}
599
600void _unpaused(struct mGUIRunner* runner) {
601	u32 level = 0;
602	_CPU_ISR_Disable(level);
603	referenceRetraceCount = retraceCount;
604	_CPU_ISR_Restore(level);
605
606	unsigned mode;
607	if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode < SM_MAX) {
608		screenMode = mode;
609	}
610	if (mCoreConfigGetUIntValue(&runner->core->config, "filter", &mode) && mode < FM_MAX) {
611		switch (mode) {
612		case FM_NEAREST:
613		default:
614			GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
615			break;
616		case FM_LINEAR:
617			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
618			break;
619		}
620	}
621	_guiFinish();
622}
623
624void _drawFrame(struct mGUIRunner* runner, bool faded) {
625	int available = blip_samples_avail(runner->core->getAudioChannel(runner->core, 0));
626	if (available + audioBufferSize > SAMPLES) {
627		available = SAMPLES - audioBufferSize;
628	}
629	available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
630	if (available > 0) {
631		// These appear to be reversed for AUDIO_InitDMA
632		blip_read_samples(runner->core->getAudioChannel(runner->core, 0), &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
633		blip_read_samples(runner->core->getAudioChannel(runner->core, 1), &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
634		audioBufferSize += available;
635	}
636	if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
637		_audioDMA();
638		AUDIO_StartDMA();
639	}
640
641	uint32_t color = 0xFFFFFF3F;
642	if (!faded) {
643		color |= 0xC0;
644	}
645	size_t x, y;
646	uint64_t* texdest = (uint64_t*) texmem;
647	uint64_t* texsrc = (uint64_t*) outputBuffer;
648	for (y = 0; y < coreh; y += 4) {
649		for (x = 0; x < corew >> 2; ++x) {
650			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
651			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
652			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
653			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
654		}
655	}
656	DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
657
658	if (faded) {
659		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
660	} else {
661		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
662	}
663	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
664	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
665	GX_InvalidateTexAll();
666	GX_LoadTexObj(&tex, GX_TEXMAP0);
667
668	s16 vertSize = 256;
669	if (screenMode == SM_PA) {
670		vertSize *= scaleFactor;
671	}
672
673	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
674	GX_Position2s16(0, vertSize);
675	GX_Color1u32(color);
676	GX_TexCoord2s16(0, 1);
677
678	GX_Position2s16(vertSize, vertSize);
679	GX_Color1u32(color);
680	GX_TexCoord2s16(1, 1);
681
682	GX_Position2s16(vertSize, 0);
683	GX_Color1u32(color);
684	GX_TexCoord2s16(1, 0);
685
686	GX_Position2s16(0, 0);
687	GX_Color1u32(color);
688	GX_TexCoord2s16(0, 0);
689	GX_End();
690}
691
692uint16_t _pollGameInput(struct mGUIRunner* runner) {
693	UNUSED(runner);
694	PAD_ScanPads();
695	u16 padkeys = PAD_ButtonsHeld(0);
696	WPAD_ScanPads();
697	u32 wiiPad = WPAD_ButtonsHeld(0);
698	u32 ext = 0;
699	WPAD_Probe(0, &ext);
700	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
701	keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
702	keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
703
704	enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
705	if (angles != GBA_KEY_NONE) {
706		keys |= 1 << angles;
707	}
708	angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
709	if (angles != GBA_KEY_NONE) {
710		keys |= 1 << angles;
711	}
712	if (ext == WPAD_EXP_CLASSIC) {
713		keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
714		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
715		if (angles != GBA_KEY_NONE) {
716			keys |= 1 << angles;
717		}
718		angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
719		if (angles != GBA_KEY_NONE) {
720			keys |= 1 << angles;
721		}
722	}
723
724	return keys;
725}
726
727void _setRumble(struct mRumble* rumble, int enable) {
728	UNUSED(rumble);
729	WPAD_Rumble(0, enable);
730	if (enable) {
731		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
732	} else {
733		PAD_ControlMotor(0, PAD_MOTOR_STOP);
734	}
735}
736
737void _sampleRotation(struct mRotationSource* source) {
738	UNUSED(source);
739	vec3w_t accel;
740	WPAD_Accel(0, &accel);
741	// These are swapped
742	tiltX = (0x1EA - accel.y) << 22;
743	tiltY = (0x1EA - accel.x) << 22;
744
745	// This doesn't seem to work at all with -TR remotes
746	struct expansion_t exp;
747	WPAD_Expansion(0, &exp);
748	if (exp.type != EXP_MOTION_PLUS) {
749		return;
750	}
751	gyroZ = exp.mp.rz - 0x1FA0;
752	gyroZ <<= 18;
753}
754
755int32_t _readTiltX(struct mRotationSource* source) {
756	UNUSED(source);
757	return tiltX;
758}
759
760int32_t _readTiltY(struct mRotationSource* source) {
761	UNUSED(source);
762	return tiltY;
763}
764
765int32_t _readGyroZ(struct mRotationSource* source) {
766	UNUSED(source);
767	return gyroZ;
768}
769
770static s8 WPAD_StickX(u8 chan, u8 right) {
771	float mag = 0.0;
772	float ang = 0.0;
773	WPADData *data = WPAD_Data(chan);
774
775	switch (data->exp.type)	{
776	case WPAD_EXP_NUNCHUK:
777	case WPAD_EXP_GUITARHERO3:
778		if (right == 0) {
779			mag = data->exp.nunchuk.js.mag;
780			ang = data->exp.nunchuk.js.ang;
781		}
782		break;
783	case WPAD_EXP_CLASSIC:
784		if (right == 0) {
785			mag = data->exp.classic.ljs.mag;
786			ang = data->exp.classic.ljs.ang;
787		} else {
788			mag = data->exp.classic.rjs.mag;
789			ang = data->exp.classic.rjs.ang;
790		}
791		break;
792	default:
793		break;
794	}
795
796	/* calculate X value (angle need to be converted into radian) */
797	if (mag > 1.0) {
798		mag = 1.0;
799	} else if (mag < -1.0) {
800		mag = -1.0;
801	}
802	double val = mag * sinf(M_PI * ang / 180.0f);
803 
804	return (s8)(val * 128.0f);
805}
806
807static s8 WPAD_StickY(u8 chan, u8 right) {
808	float mag = 0.0;
809	float ang = 0.0;
810	WPADData *data = WPAD_Data(chan);
811
812	switch (data->exp.type) {
813	case WPAD_EXP_NUNCHUK:
814	case WPAD_EXP_GUITARHERO3:
815		if (right == 0) {
816			mag = data->exp.nunchuk.js.mag;
817			ang = data->exp.nunchuk.js.ang;
818		}
819		break;
820	case WPAD_EXP_CLASSIC:
821		if (right == 0) {
822			mag = data->exp.classic.ljs.mag;
823			ang = data->exp.classic.ljs.ang;
824		} else {
825			mag = data->exp.classic.rjs.mag;
826			ang = data->exp.classic.rjs.ang;
827		}
828		break;
829	default:
830		break;
831	}
832
833	/* calculate X value (angle need to be converted into radian) */
834	if (mag > 1.0) { 
835		mag = 1.0;
836	} else if (mag < -1.0) {
837		mag = -1.0;
838	}
839	double val = mag * cosf(M_PI * ang / 180.0f);
840 
841	return (s8)(val * 128.0f);
842}
843
844void _retraceCallback(u32 count) {
845	u32 level = 0;
846	_CPU_ISR_Disable(level);
847	retraceCount = count;
848	_CPU_ISR_Restore(level);
849}