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