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