all repos — mgba @ 6a4af381511e20c6cd8a462f1070c3292689c238

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