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 <mgba-util/common.h>
16
17#include <mgba/core/blip_buf.h>
18#include <mgba/core/core.h>
19#include "feature/gui/gui-runner.h"
20#include <mgba/internal/gba/audio.h>
21#include <mgba/internal/gba/gba.h>
22#include <mgba/internal/gba/input.h>
23#include <mgba-util/gui.h>
24#include <mgba-util/gui/file-select.h>
25#include <mgba-util/gui/font.h>
26#include <mgba-util/gui/menu.h>
27#include <mgba-util/memory.h>
28#include <mgba-util/vfs.h>
29
30#define GCN1_INPUT 0x47434E31
31#define GCN2_INPUT 0x47434E32
32#define WIIMOTE_INPUT 0x5749494D
33#define CLASSIC_INPUT 0x57494943
34
35#define TEX_W 256
36#define TEX_H 224
37
38#define ANALOG_DEADZONE 0x30
39
40static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
41 mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
42}
43
44static enum ScreenMode {
45 SM_PA,
46 SM_SF,
47 SM_MAX
48} screenMode = SM_PA;
49
50static enum FilterMode {
51 FM_NEAREST,
52 FM_LINEAR_1x,
53 FM_LINEAR_2x,
54 FM_MAX
55} filterMode = FM_NEAREST;
56
57static enum VideoMode {
58 VM_AUTODETECT,
59 VM_480i,
60 VM_480p,
61 VM_240p,
62 // TODO: PAL support
63 VM_MAX
64} videoMode = VM_AUTODETECT;
65
66#define SAMPLES 1024
67#define GUI_SCALE 1.35f
68#define GUI_SCALE_240p 2.0f
69
70static void _retraceCallback(u32 count);
71
72static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
73static void _audioDMA(void);
74static void _setRumble(struct mRumble* rumble, int enable);
75static void _sampleRotation(struct mRotationSource* source);
76static int32_t _readTiltX(struct mRotationSource* source);
77static int32_t _readTiltY(struct mRotationSource* source);
78static int32_t _readGyroZ(struct mRotationSource* source);
79
80static void _drawStart(void);
81static void _drawEnd(void);
82static uint32_t _pollInput(const struct mInputMap*);
83static enum GUICursorState _pollCursor(unsigned* x, unsigned* y);
84static void _guiPrepare(void);
85
86static void _setup(struct mGUIRunner* runner);
87static void _gameLoaded(struct mGUIRunner* runner);
88static void _gameUnloaded(struct mGUIRunner* runner);
89static void _unpaused(struct mGUIRunner* runner);
90static void _drawFrame(struct mGUIRunner* runner, bool faded);
91static uint16_t _pollGameInput(struct mGUIRunner* runner);
92static void _setFrameLimiter(struct mGUIRunner* runner, bool limit);
93static void _incrementScreenMode(struct mGUIRunner* runner);
94
95static s8 WPAD_StickX(u8 chan, u8 right);
96static s8 WPAD_StickY(u8 chan, u8 right);
97
98static void* outputBuffer;
99static struct mAVStream stream;
100static struct mRumble rumble;
101static struct mRotationSource rotation;
102static GXRModeObj* vmode;
103static float wAdjust;
104static float hAdjust;
105static float wStretch = 1.0f;
106static float hStretch = 0.9f;
107static float guiScale = GUI_SCALE;
108static Mtx model, view, modelview;
109static uint16_t* texmem;
110static GXTexObj tex;
111static uint16_t* rescaleTexmem;
112static GXTexObj rescaleTex;
113static int32_t tiltX;
114static int32_t tiltY;
115static int32_t gyroZ;
116static uint32_t retraceCount;
117static uint32_t referenceRetraceCount;
118static bool frameLimiter = true;
119static int scaleFactor;
120static unsigned corew, coreh;
121
122uint32_t* romBuffer;
123size_t romBufferSize;
124
125static void* framebuffer[2] = { 0, 0 };
126static int whichFb = 0;
127
128static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
129static volatile size_t audioBufferSize = 0;
130static volatile int currentAudioBuffer = 0;
131static double audioSampleRate = 60.0 / 1.001;
132
133static struct GUIFont* font;
134
135static void reconfigureScreen(struct mGUIRunner* runner) {
136 if (runner) {
137 unsigned mode;
138 if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) {
139 videoMode = mode;
140 }
141 }
142 wAdjust = 1.f;
143 hAdjust = 1.f;
144 guiScale = GUI_SCALE;
145 audioSampleRate = 60.0 / 1.001;
146
147 s32 signalMode = CONF_GetVideo();
148
149 switch (videoMode) {
150 case VM_AUTODETECT:
151 default:
152 vmode = VIDEO_GetPreferredMode(0);
153 break;
154 case VM_480i:
155 switch (signalMode) {
156 case CONF_VIDEO_NTSC:
157 vmode = &TVNtsc480IntDf;
158 break;
159 case CONF_VIDEO_MPAL:
160 vmode = &TVMpal480IntDf;
161 break;
162 case CONF_VIDEO_PAL:
163 vmode = &TVEurgb60Hz480IntDf;
164 break;
165 }
166 break;
167 case VM_480p:
168 switch (signalMode) {
169 case CONF_VIDEO_NTSC:
170 vmode = &TVNtsc480Prog;
171 break;
172 case CONF_VIDEO_MPAL:
173 vmode = &TVMpal480Prog;
174 break;
175 case CONF_VIDEO_PAL:
176 vmode = &TVEurgb60Hz480Prog;
177 break;
178 }
179 break;
180 case VM_240p:
181 switch (signalMode) {
182 case CONF_VIDEO_NTSC:
183 vmode = &TVNtsc240Ds;
184 break;
185 case CONF_VIDEO_MPAL:
186 vmode = &TVMpal240Ds;
187 break;
188 case CONF_VIDEO_PAL:
189 vmode = &TVEurgb60Hz240Ds;
190 break;
191 }
192 wAdjust = 0.5f;
193 audioSampleRate = 90.0 / 1.50436;
194 guiScale = GUI_SCALE_240p;
195 break;
196 }
197
198 VIDEO_SetBlack(true);
199 VIDEO_Configure(vmode);
200
201 free(framebuffer[0]);
202 free(framebuffer[1]);
203
204 framebuffer[0] = SYS_AllocateFramebuffer(vmode);
205 framebuffer[1] = SYS_AllocateFramebuffer(vmode);
206 VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[0]), COLOR_BLACK);
207 VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[1]), COLOR_BLACK);
208
209 VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
210 VIDEO_Flush();
211 VIDEO_WaitVSync();
212 if (vmode->viTVMode & VI_NON_INTERLACE) {
213 VIDEO_WaitVSync();
214 }
215 GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
216
217 f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight);
218 u32 xfbHeight = GX_SetDispCopyYScale(yscale);
219 GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth);
220 GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight);
221 GX_SetDispCopyDst(vmode->fbWidth, xfbHeight);
222 GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter);
223 GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
224
225 if (runner) {
226 runner->params.width = vmode->fbWidth * guiScale * wAdjust;
227 runner->params.height = vmode->efbHeight * guiScale * hAdjust;
228 if (runner->core) {
229 double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1);
230 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
231 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
232 }
233 }
234}
235
236int main(int argc, char* argv[]) {
237 VIDEO_Init();
238 VIDEO_SetBlack(true);
239 VIDEO_Flush();
240 VIDEO_WaitVSync();
241 PAD_Init();
242 WPAD_Init();
243 WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
244 AUDIO_Init(0);
245 AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
246 AUDIO_RegisterDMACallback(_audioDMA);
247
248 memset(audioBuffer, 0, sizeof(audioBuffer));
249#ifdef FIXED_ROM_BUFFER
250 romBufferSize = SIZE_CART0;
251 romBuffer = SYS_GetArena2Lo();
252 SYS_SetArena2Lo((void*)((intptr_t) romBuffer + SIZE_CART0));
253#endif
254
255#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
256#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
257#endif
258
259 GXColor bg = { 0, 0, 0, 0xFF };
260 void* fifo = memalign(32, 0x40000);
261 memset(fifo, 0, 0x40000);
262 GX_Init(fifo, 0x40000);
263 GX_SetCopyClear(bg, 0x00FFFFFF);
264
265 GX_SetCullMode(GX_CULL_NONE);
266 GX_SetDispCopyGamma(GX_GM_1_0);
267
268 GX_ClearVtxDesc();
269 GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
270 GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
271 GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
272
273 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
274 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
275 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
276
277 GX_SetNumChans(1);
278 GX_SetNumTexGens(1);
279 GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
280 GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
281
282 GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
283 GX_InvVtxCache();
284 GX_InvalidateTexAll();
285
286 guVector cam = { 0.0f, 0.0f, 0.0f };
287 guVector up = { 0.0f, 1.0f, 0.0f };
288 guVector look = { 0.0f, 0.0f, -1.0f };
289 guLookAt(view, &cam, &up, &look);
290
291 guMtxIdentity(model);
292 guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
293 guMtxConcat(view, model, modelview);
294 GX_LoadPosMtxImm(modelview, GX_PNMTX0);
295
296 texmem = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
297 GX_InitTexObj(&tex, texmem, TEX_W, TEX_H, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
298 rescaleTexmem = memalign(32, TEX_W * TEX_H * 4 * BYTES_PER_PIXEL);
299 GX_InitTexObj(&rescaleTex, rescaleTexmem, TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
300 GX_InitTexObjFilterMode(&rescaleTex, GX_LINEAR, GX_LINEAR);
301
302 VIDEO_SetPostRetraceCallback(_retraceCallback);
303
304 font = GUIFontCreate();
305
306 fatInitDefault();
307
308 rumble.setRumble = _setRumble;
309
310 rotation.sample = _sampleRotation;
311 rotation.readTiltX = _readTiltX;
312 rotation.readTiltY = _readTiltY;
313 rotation.readGyroZ = _readGyroZ;
314
315 stream.videoDimensionsChanged = NULL;
316 stream.postVideoFrame = NULL;
317 stream.postAudioFrame = NULL;
318 stream.postAudioBuffer = _postAudioBuffer;
319
320 struct mGUIRunner runner = {
321 .params = {
322 720, 480,
323 font, "",
324 _drawStart, _drawEnd,
325 _pollInput, _pollCursor,
326 0,
327 _guiPrepare, 0,
328 },
329 .keySources = (struct GUIInputKeys[]) {
330 {
331 .name = "GameCube Input (1)",
332 .id = GCN1_INPUT,
333 .keyNames = (const char*[]) {
334 "D-Pad Left",
335 "D-Pad Right",
336 "D-Pad Down",
337 "D-Pad Up",
338 "Z",
339 "R",
340 "L",
341 0,
342 "A",
343 "B",
344 "X",
345 "Y",
346 "Start"
347 },
348 .nKeys = 13
349 },
350 {
351 .name = "GameCube Input (2)",
352 .id = GCN2_INPUT,
353 .keyNames = (const char*[]) {
354 "D-Pad Left",
355 "D-Pad Right",
356 "D-Pad Down",
357 "D-Pad Up",
358 "Z",
359 "R",
360 "L",
361 0,
362 "A",
363 "B",
364 "X",
365 "Y",
366 "Start"
367 },
368 .nKeys = 13
369 },
370 {
371 .name = "Wii Remote Input",
372 .id = WIIMOTE_INPUT,
373 .keyNames = (const char*[]) {
374 "2",
375 "1",
376 "B",
377 "A",
378 "-",
379 0,
380 0,
381 "\1\xE",
382 "Left",
383 "Right",
384 "Down",
385 "Up",
386 "+",
387 0,
388 0,
389 0,
390 "Z",
391 "C",
392 },
393 .nKeys = 18
394 },
395 {
396 .name = "Classic Controller Input",
397 .id = CLASSIC_INPUT,
398 .keyNames = (const char*[]) {
399 0,
400 0,
401 0,
402 0,
403 0,
404 0,
405 0,
406 0,
407 0,
408 0,
409 0,
410 0,
411 0,
412 0,
413 0,
414 0,
415 "Up",
416 "Left",
417 "ZR",
418 "X",
419 "A",
420 "Y",
421 "B",
422 "ZL",
423 0,
424 "R",
425 "+",
426 "\1\xE",
427 "-",
428 "L",
429 "Down",
430 "Right",
431 },
432 .nKeys = 32
433 },
434 { .id = 0 }
435 },
436 .configExtra = (struct GUIMenuItem[]) {
437 {
438 .title = "Video mode",
439 .data = "videoMode",
440 .submenu = 0,
441 .state = 0,
442 .validStates = (const char*[]) {
443 "Autodetect (recommended)",
444 "480i",
445 "480p",
446 "240p",
447 },
448 .nStates = 4
449 },
450 {
451 .title = "Screen mode",
452 .data = "screenMode",
453 .submenu = 0,
454 .state = 0,
455 .validStates = (const char*[]) {
456 "Pixel-Accurate",
457 "Stretched",
458 },
459 .nStates = 2
460 },
461 {
462 .title = "Filtering",
463 .data = "filter",
464 .submenu = 0,
465 .state = 0,
466 .validStates = (const char*[]) {
467 "Pixelated",
468 "Bilinear (smoother)",
469 "Bilinear (pixelated)",
470 },
471 .nStates = 3
472 },
473 {
474 .title = "Horizontal stretch",
475 .data = "stretchWidth",
476 .submenu = 0,
477 .state = 7,
478 .validStates = (const char*[]) {
479 "1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x"
480 },
481 .stateMappings = (const struct GUIVariant[]) {
482 GUI_V_F(0.5f),
483 GUI_V_F(0.6f),
484 GUI_V_F(1.f / 3.f),
485 GUI_V_F(0.7f),
486 GUI_V_F(0.75f),
487 GUI_V_F(0.8f),
488 GUI_V_F(0.9f),
489 GUI_V_F(1.0f),
490 },
491 .nStates = 8
492 },
493 {
494 .title = "Vertical stretch",
495 .data = "stretchHeight",
496 .submenu = 0,
497 .state = 6,
498 .validStates = (const char*[]) {
499 "1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x"
500 },
501 .stateMappings = (const struct GUIVariant[]) {
502 GUI_V_F(0.5f),
503 GUI_V_F(0.6f),
504 GUI_V_F(1.f / 3.f),
505 GUI_V_F(0.7f),
506 GUI_V_F(0.75f),
507 GUI_V_F(0.8f),
508 GUI_V_F(0.9f),
509 GUI_V_F(1.0f),
510 },
511 .nStates = 8
512 },
513 },
514 .nConfigExtra = 5,
515 .setup = _setup,
516 .teardown = 0,
517 .gameLoaded = _gameLoaded,
518 .gameUnloaded = _gameUnloaded,
519 .prepareForFrame = 0,
520 .drawFrame = _drawFrame,
521 .paused = _gameUnloaded,
522 .unpaused = _unpaused,
523 .incrementScreenMode = _incrementScreenMode,
524 .setFrameLimiter = _setFrameLimiter,
525 .pollGameInput = _pollGameInput
526 };
527 mGUIInit(&runner, "wii");
528 reconfigureScreen(&runner);
529
530 // Make sure screen is properly initialized by drawing a blank frame
531 _drawStart();
532 _drawEnd();
533
534 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_A, GUI_INPUT_SELECT);
535 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_B, GUI_INPUT_BACK);
536 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_TRIGGER_Z, GUI_INPUT_CANCEL);
537 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_UP, GUI_INPUT_UP);
538 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_DOWN, GUI_INPUT_DOWN);
539 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_LEFT, GUI_INPUT_LEFT);
540 _mapKey(&runner.params.keyMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GUI_INPUT_RIGHT);
541
542 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GUI_INPUT_SELECT);
543 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GUI_INPUT_BACK);
544 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_HOME, GUI_INPUT_CANCEL);
545 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GUI_INPUT_UP);
546 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GUI_INPUT_DOWN);
547 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GUI_INPUT_LEFT);
548 _mapKey(&runner.params.keyMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GUI_INPUT_RIGHT);
549
550 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GUI_INPUT_SELECT);
551 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GUI_INPUT_BACK);
552 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_HOME, GUI_INPUT_CANCEL);
553 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GUI_INPUT_UP);
554 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GUI_INPUT_DOWN);
555 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT);
556 _mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT);
557
558
559 float stretch = 0;
560 if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) {
561 wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
562 }
563 if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) {
564 hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
565 }
566
567 if (argc > 1) {
568 size_t i;
569 for (i = 0; runner.keySources[i].id; ++i) {
570 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
571 }
572 mGUIRun(&runner, argv[1]);
573 } else {
574 mGUIRunloop(&runner);
575 }
576 VIDEO_SetBlack(true);
577 VIDEO_Flush();
578 VIDEO_WaitVSync();
579 mGUIDeinit(&runner);
580
581 free(fifo);
582 free(texmem);
583 free(rescaleTexmem);
584
585 free(outputBuffer);
586 GUIFontDestroy(font);
587
588 free(framebuffer[0]);
589 free(framebuffer[1]);
590
591 return 0;
592}
593
594static void _audioDMA(void) {
595 if (!audioBufferSize) {
596 return;
597 }
598 DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
599 AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
600 currentAudioBuffer = (currentAudioBuffer + 1) % 3;
601 audioBufferSize = 0;
602}
603
604static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
605 UNUSED(stream);
606 int available = blip_samples_avail(left);
607 if (available + audioBufferSize > SAMPLES) {
608 available = SAMPLES - audioBufferSize;
609 }
610 available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
611 if (available > 0) {
612 // These appear to be reversed for AUDIO_InitDMA
613 blip_read_samples(left, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
614 blip_read_samples(right, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
615 audioBufferSize += available;
616 }
617 if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
618 _audioDMA();
619 AUDIO_StartDMA();
620 }
621}
622
623static void _drawStart(void) {
624 VIDEO_SetBlack(false);
625
626 u32 level = 0;
627 _CPU_ISR_Disable(level);
628 if (referenceRetraceCount > retraceCount) {
629 if (frameLimiter) {
630 VIDEO_WaitVSync();
631 }
632 referenceRetraceCount = retraceCount;
633 }
634 _CPU_ISR_Restore(level);
635
636 GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
637 GX_SetColorUpdate(GX_TRUE);
638
639 GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1);
640}
641
642static void _drawEnd(void) {
643 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
644 GX_DrawDone();
645 VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
646 VIDEO_Flush();
647 whichFb = !whichFb;
648
649 u32 level = 0;
650 _CPU_ISR_Disable(level);
651 ++referenceRetraceCount;
652 _CPU_ISR_Restore(level);
653}
654
655static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
656 UNUSED(runner);
657 frameLimiter = limit;
658}
659
660static uint32_t _pollInput(const struct mInputMap* map) {
661 PAD_ScanPads();
662 u16 padkeys = PAD_ButtonsHeld(0);
663
664 WPAD_ScanPads();
665 u32 wiiPad = WPAD_ButtonsHeld(0);
666 u32 ext = 0;
667 WPAD_Probe(0, &ext);
668
669 int keys = 0;
670 keys |= mInputMapKeyBits(map, GCN1_INPUT, padkeys, 0);
671 keys |= mInputMapKeyBits(map, GCN2_INPUT, padkeys, 0);
672 keys |= mInputMapKeyBits(map, WIIMOTE_INPUT, wiiPad, 0);
673 if (ext == WPAD_EXP_CLASSIC) {
674 keys |= mInputMapKeyBits(map, CLASSIC_INPUT, wiiPad, 0);
675 }
676 int x = PAD_StickX(0);
677 int y = PAD_StickY(0);
678 int w_x = WPAD_StickX(0, 0);
679 int w_y = WPAD_StickY(0, 0);
680 if (x < -ANALOG_DEADZONE || w_x < -ANALOG_DEADZONE) {
681 keys |= 1 << GUI_INPUT_LEFT;
682 }
683 if (x > ANALOG_DEADZONE || w_x > ANALOG_DEADZONE) {
684 keys |= 1 << GUI_INPUT_RIGHT;
685 }
686 if (y < -ANALOG_DEADZONE || w_y < -ANALOG_DEADZONE) {
687 keys |= 1 << GUI_INPUT_DOWN;
688 }
689 if (y > ANALOG_DEADZONE || w_y > ANALOG_DEADZONE) {
690 keys |= 1 << GUI_INPUT_UP;
691 }
692 return keys;
693}
694
695static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
696 ir_t ir;
697 WPAD_IR(0, &ir);
698 if (!ir.smooth_valid) {
699 return GUI_CURSOR_NOT_PRESENT;
700 }
701 *x = ir.sx;
702 *y = ir.sy;
703 WPAD_ScanPads();
704 u32 wiiPad = WPAD_ButtonsHeld(0);
705 if (wiiPad & WPAD_BUTTON_A) {
706 return GUI_CURSOR_DOWN;
707 }
708 return GUI_CURSOR_UP;
709}
710
711void _reproj(int w, int h) {
712 Mtx44 proj;
713 int top = (vmode->efbHeight * hAdjust - h) / 2;
714 int left = (vmode->fbWidth * wAdjust - w) / 2;
715 guOrtho(proj, -top, top + h, -left, left + w, 0, 300);
716 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
717}
718
719void _reproj2(int w, int h) {
720 Mtx44 proj;
721 int top = h * (1.0 - hStretch) / 2;
722 int left = w * (1.0 - wStretch) / 2;
723 guOrtho(proj, -top, h + top, -left, w + left, 0, 300);
724 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
725}
726
727void _guiPrepare(void) {
728 _reproj2(vmode->fbWidth * guiScale * wAdjust, vmode->efbHeight * guiScale * hAdjust);
729}
730
731void _setup(struct mGUIRunner* runner) {
732 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
733 runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble);
734 runner->core->setAVStream(runner->core, &stream);
735
736 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A);
737 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
738 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_START, GBA_KEY_START);
739 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_X, GBA_KEY_SELECT);
740 _mapKey(&runner->core->inputMap, GCN2_INPUT, PAD_BUTTON_Y, GBA_KEY_SELECT);
741 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_UP, GBA_KEY_UP);
742 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_DOWN, GBA_KEY_DOWN);
743 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_LEFT, GBA_KEY_LEFT);
744 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_RIGHT, GBA_KEY_RIGHT);
745 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_L, GBA_KEY_L);
746 _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_TRIGGER_R, GBA_KEY_R);
747
748 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_2, GBA_KEY_A);
749 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_1, GBA_KEY_B);
750 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_PLUS, GBA_KEY_START);
751 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_MINUS, GBA_KEY_SELECT);
752 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_RIGHT, GBA_KEY_UP);
753 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_LEFT, GBA_KEY_DOWN);
754 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_UP, GBA_KEY_LEFT);
755 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_DOWN, GBA_KEY_RIGHT);
756 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_B, GBA_KEY_L);
757 _mapKey(&runner->core->inputMap, WIIMOTE_INPUT, WPAD_BUTTON_A, GBA_KEY_R);
758
759 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_A, GBA_KEY_A);
760 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_B, GBA_KEY_B);
761 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_PLUS, GBA_KEY_START);
762 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_MINUS, GBA_KEY_SELECT);
763 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_UP, GBA_KEY_UP);
764 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_DOWN, GBA_KEY_DOWN);
765 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GBA_KEY_LEFT);
766 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GBA_KEY_RIGHT);
767 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_L, GBA_KEY_L);
768 _mapKey(&runner->core->inputMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_FULL_R, GBA_KEY_R);
769
770 struct mInputAxis desc = { GBA_KEY_RIGHT, GBA_KEY_LEFT, ANALOG_DEADZONE, -ANALOG_DEADZONE };
771 mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 0, &desc);
772 mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, &desc);
773 desc = (struct mInputAxis) { GBA_KEY_UP, GBA_KEY_DOWN, ANALOG_DEADZONE, -ANALOG_DEADZONE };
774 mInputBindAxis(&runner->core->inputMap, GCN1_INPUT, 1, &desc);
775 mInputBindAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, &desc);
776
777 outputBuffer = memalign(32, TEX_W * TEX_H * BYTES_PER_PIXEL);
778 runner->core->setVideoBuffer(runner->core, outputBuffer, TEX_W);
779
780 runner->core->setAudioBufferSize(runner->core, SAMPLES);
781
782 double ratio = GBAAudioCalculateRatio(1, audioSampleRate, 1);
783 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 48000 * ratio);
784 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 48000 * ratio);
785
786 frameLimiter = true;
787}
788
789void _gameUnloaded(struct mGUIRunner* runner) {
790 UNUSED(runner);
791 AUDIO_StopDMA();
792 frameLimiter = true;
793 VIDEO_SetBlack(true);
794 VIDEO_Flush();
795 VIDEO_WaitVSync();
796}
797
798void _gameLoaded(struct mGUIRunner* runner) {
799 reconfigureScreen(runner);
800 if (runner->core->platform(runner->core) == PLATFORM_GBA && ((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
801 int i;
802 for (i = 0; i < 6; ++i) {
803 u32 result = WPAD_SetMotionPlus(0, 1);
804 if (result == WPAD_ERR_NONE) {
805 break;
806 }
807 sleep(1);
808 }
809 }
810 memset(texmem, 0, TEX_W * TEX_H * BYTES_PER_PIXEL);
811 _unpaused(runner);
812}
813
814void _unpaused(struct mGUIRunner* runner) {
815 u32 level = 0;
816 VIDEO_WaitVSync();
817 _CPU_ISR_Disable(level);
818 referenceRetraceCount = retraceCount;
819 _CPU_ISR_Restore(level);
820
821 unsigned mode;
822 if (mCoreConfigGetUIntValue(&runner->config, "videoMode", &mode) && mode < VM_MAX) {
823 if (mode != videoMode) {
824 reconfigureScreen(runner);
825 }
826 }
827 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
828 screenMode = mode;
829 }
830 if (mCoreConfigGetUIntValue(&runner->config, "filter", &mode) && mode < FM_MAX) {
831 filterMode = mode;
832 switch (mode) {
833 case FM_NEAREST:
834 case FM_LINEAR_2x:
835 default:
836 GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
837 break;
838 case FM_LINEAR_1x:
839 GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
840 break;
841 }
842 }
843 float stretch;
844 if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
845 wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
846 }
847 if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) {
848 hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
849 }
850}
851
852void _drawFrame(struct mGUIRunner* runner, bool faded) {
853 runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
854 uint32_t color = 0xFFFFFF3F;
855 if (!faded) {
856 color |= 0xC0;
857 }
858 size_t x, y;
859 uint64_t* texdest = (uint64_t*) texmem;
860 uint64_t* texsrc = (uint64_t*) outputBuffer;
861 for (y = 0; y < coreh; y += 4) {
862 for (x = 0; x < corew >> 2; ++x) {
863 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
864 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
865 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
866 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
867 }
868 }
869 DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL);
870
871 if (faded) {
872 GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
873 } else {
874 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
875 }
876 GX_InvalidateTexAll();
877 GX_LoadTexObj(&tex, GX_TEXMAP0);
878
879 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
880 s16 vertWidth = corew;
881 s16 vertHeight = coreh;
882
883 if (filterMode == FM_LINEAR_2x) {
884 Mtx44 proj;
885 guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300);
886 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
887
888 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
889 GX_Position2s16(0, TEX_H * 2);
890 GX_Color1u32(0xFFFFFFFF);
891 GX_TexCoord2f32(0, 1);
892
893 GX_Position2s16(TEX_W * 2, TEX_H * 2);
894 GX_Color1u32(0xFFFFFFFF);
895 GX_TexCoord2f32(1, 1);
896
897 GX_Position2s16(TEX_W * 2, 0);
898 GX_Color1u32(0xFFFFFFFF);
899 GX_TexCoord2f32(1, 0);
900
901 GX_Position2s16(0, 0);
902 GX_Color1u32(0xFFFFFFFF);
903 GX_TexCoord2f32(0, 0);
904 GX_End();
905
906 GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);
907 GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE);
908 GX_CopyTex(rescaleTexmem, GX_TRUE);
909 GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
910 }
911
912 int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust);
913 int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
914 if (hfactor > vfactor) {
915 scaleFactor = vfactor;
916 } else {
917 scaleFactor = hfactor;
918 }
919
920 if (screenMode == SM_PA) {
921 vertWidth *= scaleFactor;
922 vertHeight *= scaleFactor;
923 }
924
925 if (screenMode == SM_PA) {
926 _reproj(corew * scaleFactor, coreh * scaleFactor);
927 } else {
928 _reproj2(corew, coreh);
929 }
930
931 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
932 GX_Position2s16(0, vertHeight);
933 GX_Color1u32(color);
934 GX_TexCoord2f32(0, coreh / (float) TEX_H);
935
936 GX_Position2s16(vertWidth, vertHeight);
937 GX_Color1u32(color);
938 GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H);
939
940 GX_Position2s16(vertWidth, 0);
941 GX_Color1u32(color);
942 GX_TexCoord2f32(corew / (float) TEX_W, 0);
943
944 GX_Position2s16(0, 0);
945 GX_Color1u32(color);
946 GX_TexCoord2f32(0, 0);
947 GX_End();
948}
949
950uint16_t _pollGameInput(struct mGUIRunner* runner) {
951 UNUSED(runner);
952 PAD_ScanPads();
953 u16 padkeys = PAD_ButtonsHeld(0);
954 WPAD_ScanPads();
955 u32 wiiPad = WPAD_ButtonsHeld(0);
956 u32 ext = 0;
957 WPAD_Probe(0, &ext);
958 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
959 keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
960 keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
961
962 enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
963 if (angles != GBA_KEY_NONE) {
964 keys |= 1 << angles;
965 }
966 angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
967 if (angles != GBA_KEY_NONE) {
968 keys |= 1 << angles;
969 }
970 if (ext == WPAD_EXP_CLASSIC) {
971 keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
972 angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
973 if (angles != GBA_KEY_NONE) {
974 keys |= 1 << angles;
975 }
976 angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
977 if (angles != GBA_KEY_NONE) {
978 keys |= 1 << angles;
979 }
980 }
981
982 return keys;
983}
984
985void _incrementScreenMode(struct mGUIRunner* runner) {
986 UNUSED(runner);
987 int mode = screenMode | (filterMode << 1);
988 ++mode;
989 screenMode = mode % SM_MAX;
990 filterMode = (mode >> 1) % FM_MAX;
991 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
992 mCoreConfigSetUIntValue(&runner->config, "filter", filterMode);
993 switch (filterMode) {
994 case FM_NEAREST:
995 case FM_LINEAR_2x:
996 default:
997 GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
998 break;
999 case FM_LINEAR_1x:
1000 GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
1001 break;
1002 }
1003}
1004
1005void _setRumble(struct mRumble* rumble, int enable) {
1006 UNUSED(rumble);
1007 WPAD_Rumble(0, enable);
1008 if (enable) {
1009 PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
1010 } else {
1011 PAD_ControlMotor(0, PAD_MOTOR_STOP);
1012 }
1013}
1014
1015void _sampleRotation(struct mRotationSource* source) {
1016 UNUSED(source);
1017 vec3w_t accel;
1018 WPAD_Accel(0, &accel);
1019 // These are swapped
1020 tiltX = (0x1EA - accel.y) << 22;
1021 tiltY = (0x1EA - accel.x) << 22;
1022
1023 // This doesn't seem to work at all with -TR remotes
1024 struct expansion_t exp;
1025 WPAD_Expansion(0, &exp);
1026 if (exp.type != EXP_MOTION_PLUS) {
1027 return;
1028 }
1029 gyroZ = exp.mp.rz - 0x1FA0;
1030 gyroZ <<= 18;
1031}
1032
1033int32_t _readTiltX(struct mRotationSource* source) {
1034 UNUSED(source);
1035 return tiltX;
1036}
1037
1038int32_t _readTiltY(struct mRotationSource* source) {
1039 UNUSED(source);
1040 return tiltY;
1041}
1042
1043int32_t _readGyroZ(struct mRotationSource* source) {
1044 UNUSED(source);
1045 return gyroZ;
1046}
1047
1048static s8 WPAD_StickX(u8 chan, u8 right) {
1049 struct expansion_t exp;
1050 WPAD_Expansion(chan, &exp);
1051 struct joystick_t* js = NULL;
1052
1053 switch (exp.type) {
1054 case WPAD_EXP_NUNCHUK:
1055 case WPAD_EXP_GUITARHERO3:
1056 if (right == 0) {
1057 js = &exp.nunchuk.js;
1058 }
1059 break;
1060 case WPAD_EXP_CLASSIC:
1061 if (right == 0) {
1062 js = &exp.classic.ljs;
1063 } else {
1064 js = &exp.classic.rjs;
1065 }
1066 break;
1067 default:
1068 break;
1069 }
1070
1071 if (!js) {
1072 return 0;
1073 }
1074 int centered = (int) js->pos.x - (int) js->center.x;
1075 int range = (int) js->max.x - (int) js->min.x;
1076 int value = (centered * 0xFF) / range;
1077 if (value > 0x7F) {
1078 return 0x7F;
1079 }
1080 if (value < -0x80) {
1081 return -0x80;
1082 }
1083 return value;
1084}
1085
1086static s8 WPAD_StickY(u8 chan, u8 right) {
1087 struct expansion_t exp;
1088 WPAD_Expansion(chan, &exp);
1089 struct joystick_t* js = NULL;
1090
1091 switch (exp.type) {
1092 case WPAD_EXP_NUNCHUK:
1093 case WPAD_EXP_GUITARHERO3:
1094 if (right == 0) {
1095 js = &exp.nunchuk.js;
1096 }
1097 break;
1098 case WPAD_EXP_CLASSIC:
1099 if (right == 0) {
1100 js = &exp.classic.ljs;
1101 } else {
1102 js = &exp.classic.rjs;
1103 }
1104 break;
1105 default:
1106 break;
1107 }
1108
1109 if (!js) {
1110 return 0;
1111 }
1112 int centered = (int) js->pos.y - (int) js->center.y;
1113 int range = (int) js->max.y - (int) js->min.y;
1114 int value = (centered * 0xFF) / range;
1115 if (value > 0x7F) {
1116 return 0x7F;
1117 }
1118 if (value < -0x80) {
1119 return -0x80;
1120 }
1121 return value;
1122}
1123
1124void _retraceCallback(u32 count) {
1125 u32 level = 0;
1126 _CPU_ISR_Disable(level);
1127 retraceCount = count;
1128 _CPU_ISR_Restore(level);
1129}