all repos — mgba @ 0511d0a69e4e86f111cdd5bf60ad46f7bdb0ff5f

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