all repos — mgba @ 8615defda763c928eadb4c0919081a74b6bd28fc

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