all repos — mgba @ 78b761cf017c6ee039810f29316b3736eee07fd4

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