all repos — mgba @ 0bb45dad0ded73335f30d09c5f06e1716c4b4e93

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