all repos — mgba @ 811d8281c3f285e9e7421cf290ecefaefb46069d

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(int argc, char* argv[]) {
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					"-",
268					0,
269					0,
270					"\1\xE",
271					"Left",
272					"Right",
273					"Down",
274					"Up",
275					"+",
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					"+",
315					"\1\xE",
316					"-",
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	if (argc > 1) {
362		GBAGUIRun(&runner, argv[1]);
363	} else {
364		GBAGUIRunloop(&runner);
365	}
366	GBAGUIDeinit(&runner);
367
368	free(fifo);
369
370	free(renderer.outputBuffer);
371	GUIFontDestroy(font);
372
373	free(framebuffer[0]);
374	free(framebuffer[1]);
375
376	return 0;
377}
378
379static void _audioDMA(void) {
380	if (!audioBufferSize) {
381		return;
382	}
383	DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
384	AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
385	currentAudioBuffer = (currentAudioBuffer + 1) % 3;
386	audioBufferSize = 0;
387}
388
389static void _drawStart(void) {
390	u32 level = 0;
391	_CPU_ISR_Disable(level);
392	if (referenceRetraceCount >= retraceCount) {
393		VIDEO_WaitVSync();
394	}
395	_CPU_ISR_Restore(level);
396
397	GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
398	GX_SetColorUpdate(GX_TRUE);
399
400	GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
401}
402
403static void _drawEnd(void) {
404	whichFb = !whichFb;
405
406	GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
407	GX_DrawDone();
408	VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
409	VIDEO_Flush();
410
411	u32 level = 0;
412	_CPU_ISR_Disable(level);
413	++referenceRetraceCount;
414	_CPU_ISR_Restore(level);
415}
416
417static uint32_t _pollInput(void) {
418	PAD_ScanPads();
419	u16 padkeys = PAD_ButtonsHeld(0);
420
421	WPAD_ScanPads();
422	u32 wiiPad = WPAD_ButtonsHeld(0);
423	u32 ext = 0;
424	WPAD_Probe(0, &ext);
425
426	int keys = 0;
427	int x = PAD_StickX(0);
428	int y = PAD_StickY(0);
429	int w_x = WPAD_StickX(0, 0);
430	int w_y = WPAD_StickY(0, 0);
431	if (x < -0x20 || w_x < -0x20) {
432		keys |= 1 << GUI_INPUT_LEFT;
433	}
434	if (x > 0x20 || w_x > 0x20) {
435		keys |= 1 << GUI_INPUT_RIGHT;
436	}
437	if (y < -0x20 || w_y <- 0x20) {
438		keys |= 1 << GUI_INPUT_DOWN;
439	}
440	if (y > 0x20 || w_y > 0x20) {
441		keys |= 1 << GUI_INPUT_UP;
442	}
443	if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || 
444	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
445		keys |= 1 << GUI_INPUT_SELECT;
446	}
447	if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || (wiiPad & WPAD_BUTTON_B) ||
448	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
449		keys |= 1 << GUI_INPUT_BACK;
450	}
451	if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
452	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
453		keys |= 1 << GUI_INPUT_CANCEL;
454	}
455	if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
456	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
457		keys |= 1 << GUI_INPUT_LEFT;
458	}
459	if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
460	   ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
461		keys |= 1 << GUI_INPUT_RIGHT;
462	}
463	if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
464	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
465		keys |= 1 << GUI_INPUT_UP;
466	}
467	if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
468	    ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
469		keys |= 1 << GUI_INPUT_DOWN;
470	}
471	return keys;
472}
473
474static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
475	ir_t ir;
476	WPAD_IR(0, &ir);
477	if (!ir.smooth_valid) {
478		return GUI_CURSOR_NOT_PRESENT;
479	}
480	*x = ir.sx;
481	*y = ir.sy;
482	WPAD_ScanPads();
483	u32 wiiPad = WPAD_ButtonsHeld(0);
484	if (wiiPad & WPAD_BUTTON_A) {
485		return GUI_CURSOR_DOWN;
486	}
487	return GUI_CURSOR_UP;
488}
489
490void _reproj(int w, int h) {
491	Mtx44 proj;
492	int top = (vmode->efbHeight - h) / 2;
493	int left = (vmode->fbWidth - w) / 2;
494	guOrtho(proj, -top, top + h, -left, left + w, 0, 300);
495	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
496}
497
498void _reproj2(int w, int h) {
499	Mtx44 proj;
500	s16 top = 20;
501	guOrtho(proj, -top, top + h, 0, w, 0, 300);
502	GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
503}
504
505void _guiPrepare(void) {
506	_reproj2(vmode->fbWidth * GUI_SCALE, vmode->efbHeight * GUI_SCALE);
507}
508
509void _guiFinish(void) {
510	if (screenMode == SM_PA) {
511		_reproj(VIDEO_HORIZONTAL_PIXELS * scaleFactor, VIDEO_VERTICAL_PIXELS * scaleFactor);
512	} else {
513		_reproj2(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
514	}
515}
516
517void _setup(struct GBAGUIRunner* runner) {
518	runner->context.gba->rumble = &rumble;
519	runner->context.gba->rotationSource = &rotation;
520
521	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
522	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
523	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
524	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
525	_mapKey(&runner->context.inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
526	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
527	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
528	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
529	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
530	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
531	_mapKey(&runner->context.inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
532
533	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
534	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
535	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
536	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
537	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
538	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
539	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
540	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
541	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
542	_mapKey(&runner->context.inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
543
544	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
545	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
546	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
547	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
548	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
549	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
550	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
551	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
552	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
553	_mapKey(&runner->context.inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
554
555	struct GBAAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x20, -0x20 };
556	GBAInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 0, &desc);
557	GBAInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 0, &desc);
558	desc = (struct GBAAxis) { GBA_KEY_UP, GBA_KEY_DOWN, 0x20, -0x20 };
559	GBAInputBindAxis(&runner->context.inputMap, GCN1_INPUT, 1, &desc);
560	GBAInputBindAxis(&runner->context.inputMap, CLASSIC_INPUT, 1, &desc);
561
562	GBAVideoSoftwareRendererCreate(&renderer);
563	renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
564	renderer.outputBufferStride = 256;
565	runner->context.renderer = &renderer.d;
566
567	GBAAudioResizeBuffer(&runner->context.gba->audio, SAMPLES);
568
569#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
570	double ratio = GBAAudioCalculateRatio(1, 60 / 1.001, 1);
571	blip_set_rates(runner->context.gba->audio.left,  GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
572	blip_set_rates(runner->context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000 * ratio);
573#endif
574}
575
576void _gameUnloaded(struct GBAGUIRunner* runner) {
577	UNUSED(runner);
578	AUDIO_StopDMA();
579}
580
581void _gameLoaded(struct GBAGUIRunner* runner) {
582	if (runner->context.gba->memory.hw.devices & HW_GYRO) {
583		int i;
584		for (i = 0; i < 6; ++i) {
585			u32 result = WPAD_SetMotionPlus(0, 1);
586			if (result == WPAD_ERR_NONE) {
587				break;
588			}
589			sleep(1);
590		}
591	}
592	_unpaused(runner);
593}
594
595void _unpaused(struct GBAGUIRunner* runner) {
596	u32 level = 0;
597	_CPU_ISR_Disable(level);
598	referenceRetraceCount = retraceCount;
599	_CPU_ISR_Restore(level);
600
601	unsigned mode;
602	if (mCoreConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode < SM_MAX) {
603		screenMode = mode;
604	}
605	if (mCoreConfigGetUIntValue(&runner->context.config, "filter", &mode) && mode < FM_MAX) {
606		switch (mode) {
607		case FM_NEAREST:
608		default:
609			GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
610			break;
611		case FM_LINEAR:
612			GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
613			break;
614		}
615	}
616	_guiFinish();
617}
618
619void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
620#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
621	int available = blip_samples_avail(runner->context.gba->audio.left);
622	if (available + audioBufferSize > SAMPLES) {
623		available = SAMPLES - audioBufferSize;
624	}
625	available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
626	if (available > 0) {
627		// These appear to be reversed for AUDIO_InitDMA
628		blip_read_samples(runner->context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
629		blip_read_samples(runner->context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
630		audioBufferSize += available;
631	}
632	if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
633		_audioDMA();
634		AUDIO_StartDMA();
635	}
636#endif
637
638	uint32_t color = 0xFFFFFF3F;
639	if (!faded) {
640		color |= 0xC0;
641	}
642	size_t x, y;
643	uint64_t* texdest = (uint64_t*) texmem;
644	uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
645	for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
646		for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
647			texdest[0 + x * 4 + y * 64] = texsrc[0   + x + y * 64];
648			texdest[1 + x * 4 + y * 64] = texsrc[64  + x + y * 64];
649			texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
650			texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
651		}
652	}
653	DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
654
655	if (faded) {
656		GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
657	} else {
658		GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
659	}
660	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
661	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
662	GX_InvalidateTexAll();
663	GX_LoadTexObj(&tex, GX_TEXMAP0);
664
665	s16 vertSize = 256;
666	if (screenMode == SM_PA) {
667		vertSize *= scaleFactor;
668	}
669
670	GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
671	GX_Position2s16(0, vertSize);
672	GX_Color1u32(color);
673	GX_TexCoord2s16(0, 1);
674
675	GX_Position2s16(vertSize, vertSize);
676	GX_Color1u32(color);
677	GX_TexCoord2s16(1, 1);
678
679	GX_Position2s16(vertSize, 0);
680	GX_Color1u32(color);
681	GX_TexCoord2s16(1, 0);
682
683	GX_Position2s16(0, 0);
684	GX_Color1u32(color);
685	GX_TexCoord2s16(0, 0);
686	GX_End();
687}
688
689uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
690	UNUSED(runner);
691	PAD_ScanPads();
692	u16 padkeys = PAD_ButtonsHeld(0);
693	WPAD_ScanPads();
694	u32 wiiPad = WPAD_ButtonsHeld(0);
695	u32 ext = 0;
696	WPAD_Probe(0, &ext);
697	uint16_t keys = GBAInputMapKeyBits(&runner->context.inputMap, GCN1_INPUT, padkeys, 0);
698	keys |= GBAInputMapKeyBits(&runner->context.inputMap, GCN2_INPUT, padkeys, 0);
699	keys |= GBAInputMapKeyBits(&runner->context.inputMap, WIIMOTE_INPUT, wiiPad, 0);
700
701	enum GBAKey angles = GBAInputMapAxis(&runner->context.inputMap, GCN1_INPUT, 0, PAD_StickX(0));
702	if (angles != GBA_KEY_NONE) {
703		keys |= 1 << angles;
704	}
705	angles = GBAInputMapAxis(&runner->context.inputMap, GCN1_INPUT, 1, PAD_StickY(0));
706	if (angles != GBA_KEY_NONE) {
707		keys |= 1 << angles;
708	}
709	if (ext == WPAD_EXP_CLASSIC) {
710		keys |= GBAInputMapKeyBits(&runner->context.inputMap, CLASSIC_INPUT, wiiPad, 0);
711		angles = GBAInputMapAxis(&runner->context.inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
712		if (angles != GBA_KEY_NONE) {
713			keys |= 1 << angles;
714		}
715		angles = GBAInputMapAxis(&runner->context.inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
716		if (angles != GBA_KEY_NONE) {
717			keys |= 1 << angles;
718		}
719	}
720
721	return keys;
722}
723
724void _setRumble(struct GBARumble* rumble, int enable) {
725	UNUSED(rumble);
726	WPAD_Rumble(0, enable);
727	if (enable) {
728		PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
729	} else {
730		PAD_ControlMotor(0, PAD_MOTOR_STOP);
731	}
732}
733
734void _sampleRotation(struct GBARotationSource* source) {
735	UNUSED(source);
736	vec3w_t accel;
737	WPAD_Accel(0, &accel);
738	// These are swapped
739	tiltX = (accel.y - 0x1EA) << 22;
740	tiltY = (accel.x - 0x1EA) << 22;
741
742	// This doesn't seem to work at all with -TR remotes
743	struct expansion_t exp;
744	WPAD_Expansion(0, &exp);
745	if (exp.type != EXP_MOTION_PLUS) {
746		return;
747	}
748	gyroZ = exp.mp.rz - 0x1FA0;
749	gyroZ <<= 18;
750}
751
752int32_t _readTiltX(struct GBARotationSource* source) {
753	UNUSED(source);
754	return tiltX;
755}
756
757int32_t _readTiltY(struct GBARotationSource* source) {
758	UNUSED(source);
759	return tiltY;
760}
761
762int32_t _readGyroZ(struct GBARotationSource* source) {
763	UNUSED(source);
764	return gyroZ;
765}
766
767static s8 WPAD_StickX(u8 chan, u8 right) {
768	float mag = 0.0;
769	float ang = 0.0;
770	WPADData *data = WPAD_Data(chan);
771
772	switch (data->exp.type)	{
773	case WPAD_EXP_NUNCHUK:
774	case WPAD_EXP_GUITARHERO3:
775		if (right == 0) {
776			mag = data->exp.nunchuk.js.mag;
777			ang = data->exp.nunchuk.js.ang;
778		}
779		break;
780	case WPAD_EXP_CLASSIC:
781		if (right == 0) {
782			mag = data->exp.classic.ljs.mag;
783			ang = data->exp.classic.ljs.ang;
784		} else {
785			mag = data->exp.classic.rjs.mag;
786			ang = data->exp.classic.rjs.ang;
787		}
788		break;
789	default:
790		break;
791	}
792
793	/* calculate X value (angle need to be converted into radian) */
794	if (mag > 1.0) {
795		mag = 1.0;
796	} else if (mag < -1.0) {
797		mag = -1.0;
798	}
799	double val = mag * sinf(M_PI * ang / 180.0f);
800 
801	return (s8)(val * 128.0f);
802}
803
804static s8 WPAD_StickY(u8 chan, u8 right) {
805	float mag = 0.0;
806	float ang = 0.0;
807	WPADData *data = WPAD_Data(chan);
808
809	switch (data->exp.type) {
810	case WPAD_EXP_NUNCHUK:
811	case WPAD_EXP_GUITARHERO3:
812		if (right == 0) {
813			mag = data->exp.nunchuk.js.mag;
814			ang = data->exp.nunchuk.js.ang;
815		}
816		break;
817	case WPAD_EXP_CLASSIC:
818		if (right == 0) {
819			mag = data->exp.classic.ljs.mag;
820			ang = data->exp.classic.ljs.ang;
821		} else {
822			mag = data->exp.classic.rjs.mag;
823			ang = data->exp.classic.rjs.ang;
824		}
825		break;
826	default:
827		break;
828	}
829
830	/* calculate X value (angle need to be converted into radian) */
831	if (mag > 1.0) { 
832		mag = 1.0;
833	} else if (mag < -1.0) {
834		mag = -1.0;
835	}
836	double val = mag * cosf(M_PI * ang / 180.0f);
837 
838	return (s8)(val * 128.0f);
839}
840
841void _retraceCallback(u32 count) {
842	u32 level = 0;
843	_CPU_ISR_Disable(level);
844	retraceCount = count;
845	_CPU_ISR_Restore(level);
846}