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 if (mCoreConfigGetIntValue(&runner->config, "interframeBlending", &fakeBool)) {
863 interframeBlending = fakeBool;
864 }
865
866 float stretch;
867 if (mCoreConfigGetFloatValue(&runner->config, "stretchWidth", &stretch)) {
868 wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
869 }
870 if (mCoreConfigGetFloatValue(&runner->config, "stretchHeight", &stretch)) {
871 hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
872 }
873}
874
875void _prepareForFrame(struct mGUIRunner* runner) {
876 if (interframeBlending) {
877 memcpy(interframeTexmem, texmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
878 }
879}
880
881void _drawFrame(struct mGUIRunner* runner, bool faded) {
882 runner->core->desiredVideoDimensions(runner->core, &corew, &coreh);
883 uint32_t color = 0xFFFFFF3F;
884 if (!faded) {
885 color |= 0xC0;
886 }
887 size_t x, y;
888 uint64_t* texdest = (uint64_t*) texmem;
889 uint64_t* texsrc = (uint64_t*) outputBuffer;
890 for (y = 0; y < coreh; y += 4) {
891 for (x = 0; x < corew >> 2; ++x) {
892 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
893 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
894 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
895 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
896 }
897 }
898 DCFlushRange(texdest, TEX_W * TEX_H * BYTES_PER_PIXEL);
899 if (interframeBlending) {
900 DCFlushRange(interframeTexmem, TEX_W * TEX_H * BYTES_PER_PIXEL);
901 }
902
903 if (faded || interframeBlending) {
904 GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_NOOP);
905 } else {
906 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
907 }
908 GX_InvalidateTexAll();
909 if (interframeBlending) {
910 GX_LoadTexObj(&interframeTex, GX_TEXMAP0);
911 GX_LoadTexObj(&tex, GX_TEXMAP1);
912 GX_SetNumTevStages(2);
913 } else {
914 GX_LoadTexObj(&tex, GX_TEXMAP0);
915 GX_SetNumTevStages(1);
916 }
917
918 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
919 s16 vertWidth = corew;
920 s16 vertHeight = coreh;
921
922 if (filterMode == FM_LINEAR_2x) {
923 Mtx44 proj;
924 guOrtho(proj, 0, vmode->efbHeight, 0, vmode->fbWidth, 0, 300);
925 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
926
927 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
928 GX_Position2s16(0, TEX_H * 2);
929 GX_Color1u32(0xFFFFFFFF);
930 GX_TexCoord2f32(0, 1);
931
932 GX_Position2s16(TEX_W * 2, TEX_H * 2);
933 GX_Color1u32(0xFFFFFFFF);
934 GX_TexCoord2f32(1, 1);
935
936 GX_Position2s16(TEX_W * 2, 0);
937 GX_Color1u32(0xFFFFFFFF);
938 GX_TexCoord2f32(1, 0);
939
940 GX_Position2s16(0, 0);
941 GX_Color1u32(0xFFFFFFFF);
942 GX_TexCoord2f32(0, 0);
943 GX_End();
944
945 GX_SetTexCopySrc(0, 0, TEX_W * 2, TEX_H * 2);
946 GX_SetTexCopyDst(TEX_W * 2, TEX_H * 2, GX_TF_RGB565, GX_FALSE);
947 GX_CopyTex(rescaleTexmem, GX_TRUE);
948 GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
949 GX_SetNumTevStages(1);
950 if (!faded) {
951 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);
952 }
953 }
954
955 int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust);
956 int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
957 if (hfactor > vfactor) {
958 scaleFactor = vfactor;
959 } else {
960 scaleFactor = hfactor;
961 }
962
963 if (screenMode == SM_PA) {
964 vertWidth *= scaleFactor;
965 vertHeight *= scaleFactor;
966 }
967
968 if (screenMode == SM_PA) {
969 _reproj(corew * scaleFactor, coreh * scaleFactor);
970 } else {
971 _reproj2(corew, coreh);
972 }
973
974 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
975 GX_Position2s16(0, vertHeight);
976 GX_Color1u32(color);
977 GX_TexCoord2f32(0, coreh / (float) TEX_H);
978
979 GX_Position2s16(vertWidth, vertHeight);
980 GX_Color1u32(color);
981 GX_TexCoord2f32(corew / (float) TEX_W, coreh / (float) TEX_H);
982
983 GX_Position2s16(vertWidth, 0);
984 GX_Color1u32(color);
985 GX_TexCoord2f32(corew / (float) TEX_W, 0);
986
987 GX_Position2s16(0, 0);
988 GX_Color1u32(color);
989 GX_TexCoord2f32(0, 0);
990 GX_End();
991}
992
993uint16_t _pollGameInput(struct mGUIRunner* runner) {
994 UNUSED(runner);
995 PAD_ScanPads();
996 u16 padkeys = PAD_ButtonsHeld(0);
997 WPAD_ScanPads();
998 u32 wiiPad = WPAD_ButtonsHeld(0);
999 u32 ext = 0;
1000 WPAD_Probe(0, &ext);
1001 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, GCN1_INPUT, padkeys, 0);
1002 keys |= mInputMapKeyBits(&runner->core->inputMap, GCN2_INPUT, padkeys, 0);
1003 keys |= mInputMapKeyBits(&runner->core->inputMap, WIIMOTE_INPUT, wiiPad, 0);
1004
1005 enum GBAKey angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 0, PAD_StickX(0));
1006 if (angles != GBA_KEY_NONE) {
1007 keys |= 1 << angles;
1008 }
1009 angles = mInputMapAxis(&runner->core->inputMap, GCN1_INPUT, 1, PAD_StickY(0));
1010 if (angles != GBA_KEY_NONE) {
1011 keys |= 1 << angles;
1012 }
1013 if (ext == WPAD_EXP_CLASSIC) {
1014 keys |= mInputMapKeyBits(&runner->core->inputMap, CLASSIC_INPUT, wiiPad, 0);
1015 angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 0, WPAD_StickX(0, 0));
1016 if (angles != GBA_KEY_NONE) {
1017 keys |= 1 << angles;
1018 }
1019 angles = mInputMapAxis(&runner->core->inputMap, CLASSIC_INPUT, 1, WPAD_StickY(0, 0));
1020 if (angles != GBA_KEY_NONE) {
1021 keys |= 1 << angles;
1022 }
1023 }
1024
1025 return keys;
1026}
1027
1028void _incrementScreenMode(struct mGUIRunner* runner) {
1029 UNUSED(runner);
1030 int mode = screenMode | (filterMode << 1);
1031 ++mode;
1032 screenMode = mode % SM_MAX;
1033 filterMode = (mode >> 1) % FM_MAX;
1034 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
1035 mCoreConfigSetUIntValue(&runner->config, "filter", filterMode);
1036 switch (filterMode) {
1037 case FM_NEAREST:
1038 case FM_LINEAR_2x:
1039 default:
1040 GX_InitTexObjFilterMode(&tex, GX_NEAR, GX_NEAR);
1041 break;
1042 case FM_LINEAR_1x:
1043 GX_InitTexObjFilterMode(&tex, GX_LINEAR, GX_LINEAR);
1044 break;
1045 }
1046}
1047
1048void _setRumble(struct mRumble* rumble, int enable) {
1049 UNUSED(rumble);
1050 WPAD_Rumble(0, enable);
1051 if (enable) {
1052 PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
1053 } else {
1054 PAD_ControlMotor(0, PAD_MOTOR_STOP);
1055 }
1056}
1057
1058void _sampleRotation(struct mRotationSource* source) {
1059 UNUSED(source);
1060 vec3w_t accel;
1061 WPAD_Accel(0, &accel);
1062 // These are swapped
1063 tiltX = (0x1EA - accel.y) << 22;
1064 tiltY = (0x1EA - accel.x) << 22;
1065
1066 // This doesn't seem to work at all with -TR remotes
1067 struct expansion_t exp;
1068 WPAD_Expansion(0, &exp);
1069 if (exp.type != EXP_MOTION_PLUS) {
1070 return;
1071 }
1072 gyroZ = exp.mp.rz - 0x1FA0;
1073 gyroZ <<= 18;
1074}
1075
1076int32_t _readTiltX(struct mRotationSource* source) {
1077 UNUSED(source);
1078 return tiltX;
1079}
1080
1081int32_t _readTiltY(struct mRotationSource* source) {
1082 UNUSED(source);
1083 return tiltY;
1084}
1085
1086int32_t _readGyroZ(struct mRotationSource* source) {
1087 UNUSED(source);
1088 return gyroZ;
1089}
1090
1091static s8 WPAD_StickX(u8 chan, u8 right) {
1092 struct expansion_t exp;
1093 WPAD_Expansion(chan, &exp);
1094 struct joystick_t* js = NULL;
1095
1096 switch (exp.type) {
1097 case WPAD_EXP_NUNCHUK:
1098 case WPAD_EXP_GUITARHERO3:
1099 if (right == 0) {
1100 js = &exp.nunchuk.js;
1101 }
1102 break;
1103 case WPAD_EXP_CLASSIC:
1104 if (right == 0) {
1105 js = &exp.classic.ljs;
1106 } else {
1107 js = &exp.classic.rjs;
1108 }
1109 break;
1110 default:
1111 break;
1112 }
1113
1114 if (!js) {
1115 return 0;
1116 }
1117 int centered = (int) js->pos.x - (int) js->center.x;
1118 int range = (int) js->max.x - (int) js->min.x;
1119 int value = (centered * 0xFF) / range;
1120 if (value > 0x7F) {
1121 return 0x7F;
1122 }
1123 if (value < -0x80) {
1124 return -0x80;
1125 }
1126 return value;
1127}
1128
1129static s8 WPAD_StickY(u8 chan, u8 right) {
1130 struct expansion_t exp;
1131 WPAD_Expansion(chan, &exp);
1132 struct joystick_t* js = NULL;
1133
1134 switch (exp.type) {
1135 case WPAD_EXP_NUNCHUK:
1136 case WPAD_EXP_GUITARHERO3:
1137 if (right == 0) {
1138 js = &exp.nunchuk.js;
1139 }
1140 break;
1141 case WPAD_EXP_CLASSIC:
1142 if (right == 0) {
1143 js = &exp.classic.ljs;
1144 } else {
1145 js = &exp.classic.rjs;
1146 }
1147 break;
1148 default:
1149 break;
1150 }
1151
1152 if (!js) {
1153 return 0;
1154 }
1155 int centered = (int) js->pos.y - (int) js->center.y;
1156 int range = (int) js->max.y - (int) js->min.y;
1157 int value = (centered * 0xFF) / range;
1158 if (value > 0x7F) {
1159 return 0x7F;
1160 }
1161 if (value < -0x80) {
1162 return -0x80;
1163 }
1164 return value;
1165}
1166
1167void _retraceCallback(u32 count) {
1168 u32 level = 0;
1169 _CPU_ISR_Disable(level);
1170 retraceCount = count;
1171 _CPU_ISR_Restore(level);
1172}