all repos — mgba @ 50402c830729f2ba5a6fc3e6facfd8b258f7f97d

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