all repos — mgba @ 5b4f8dfae3ae47e0fddb1d706feb42bd35e2fb7e

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