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 <malloc.h>
11#include <wiiuse/wpad.h>
12
13#include "util/common.h"
14
15#include "gba/renderers/video-software.h"
16#include "gba/context/context.h"
17#include "util/gui.h"
18#include "util/gui/file-select.h"
19#include "util/gui/font.h"
20#include "util/vfs.h"
21
22#define SAMPLES 1024
23
24static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
25static void GBAWiiFrame(void);
26static bool GBAWiiLoadGame(const char* path);
27
28static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
29static void _audioDMA(void);
30static void _setRumble(struct GBARumble* rumble, int enable);
31
32static void _drawStart(void);
33static void _drawEnd(void);
34static int _pollInput(void);
35
36static struct GBAContext context;
37static struct GBAVideoSoftwareRenderer renderer;
38static struct GBAAVStream stream;
39static struct GBARumble rumble;
40static FILE* logfile;
41static GXRModeObj* mode;
42static Mtx model, view, modelview;
43static uint16_t* texmem;
44static GXTexObj tex;
45
46static void* framebuffer[2];
47static int whichFb = 0;
48
49static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));
50static volatile size_t audioBufferSize = 0;
51static volatile int currentAudioBuffer = 0;
52
53static struct GUIFont* font;
54
55int main() {
56 VIDEO_Init();
57 PAD_Init();
58 WPAD_Init();
59 AUDIO_Init(0);
60 AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ);
61 AUDIO_RegisterDMACallback(_audioDMA);
62
63 memset(audioBuffer, 0, sizeof(audioBuffer));
64
65#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
66#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
67#endif
68
69 mode = VIDEO_GetPreferredMode(0);
70 framebuffer[0] = SYS_AllocateFramebuffer(mode);
71 framebuffer[1] = SYS_AllocateFramebuffer(mode);
72
73 VIDEO_Configure(mode);
74 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
75 VIDEO_SetBlack(FALSE);
76 VIDEO_Flush();
77 VIDEO_WaitVSync();
78 if (mode->viTVMode & VI_NON_INTERLACE) {
79 VIDEO_WaitVSync();
80 }
81
82 GXColor bg = { 0, 0, 0, 0xFF };
83 void* fifo = memalign(32, 0x40000);
84 memset(fifo, 0, 0x40000);
85 GX_Init(fifo, 0x40000);
86 GX_SetCopyClear(bg, 0x00FFFFFF);
87 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
88
89 f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight);
90 u32 xfbHeight = GX_SetDispCopyYScale(yscale);
91 GX_SetScissor(0, 0, mode->viWidth, mode->viWidth);
92 GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight);
93 GX_SetDispCopyDst(mode->fbWidth, xfbHeight);
94 GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter);
95 GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
96
97 GX_SetCullMode(GX_CULL_NONE);
98 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
99 GX_SetDispCopyGamma(GX_GM_1_0);
100
101 GX_ClearVtxDesc();
102 GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
103 GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
104
105 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
106 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
107
108 GX_SetNumChans(1);
109 GX_SetNumTexGens(1);
110 GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
111 GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
112
113 GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
114 GX_InvVtxCache();
115 GX_InvalidateTexAll();
116
117 Mtx44 proj;
118
119 guVector cam = { 0.0f, 0.0f, 0.0f };
120 guVector up = { 0.0f, 1.0f, 0.0f };
121 guVector look = { 0.0f, 0.0f, -1.0f };
122 guLookAt(view, &cam, &up, &look);
123
124 guMtxIdentity(model);
125 guMtxTransApply(model, model, 0.0f, 0.0f, -6.0f);
126 guMtxConcat(view, model, modelview);
127 GX_LoadPosMtxImm(modelview, GX_PNMTX0);
128
129 texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
130 memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL);
131 GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
132
133 font = GUIFontCreate();
134
135 fatInitDefault();
136
137 logfile = fopen("/mgba.log", "w");
138
139 stream.postAudioFrame = 0;
140 stream.postAudioBuffer = 0;
141 stream.postVideoFrame = _postVideoFrame;
142
143 rumble.setRumble = _setRumble;
144
145 GBAContextInit(&context, 0);
146 struct GBAOptions opts = {
147 .useBios = true,
148 .logLevel = 0,
149 .idleOptimization = IDLE_LOOP_DETECT
150 };
151 GBAConfigLoadDefaults(&context.config, &opts);
152 context.gba->logHandler = GBAWiiLog;
153 context.gba->stream = &stream;
154 context.gba->rumble = &rumble;
155
156 GBAVideoSoftwareRendererCreate(&renderer);
157 renderer.outputBuffer = memalign(32, 256 * 256 * BYTES_PER_PIXEL);
158 renderer.outputBufferStride = 256;
159 GBAVideoAssociateRenderer(&context.gba->video, &renderer.d);
160
161 GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
162
163#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
164 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 48000);
165 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 48000);
166#endif
167
168 char currentPath[256] = "";
169 while (true) {
170 char path[256];
171 guOrtho(proj, -20, 240, 0, 352, 0, 300);
172 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
173 GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
174
175 struct GUIParams params = {
176 352, 230,
177 font, _drawStart, _drawEnd, _pollInput
178 };
179 if (!selectFile(¶ms, "/", path, currentPath, sizeof(path), GBAIsROM) || !GBAWiiLoadGame(path)) {
180 break;
181 }
182 GBAContextStart(&context);
183
184 guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300);
185 GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC);
186 GX_SetVtxDesc(GX_VA_CLR0, GX_NONE);
187
188 while (true) {
189 #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
190 int available = blip_samples_avail(context.gba->audio.left);
191 if (available + audioBufferSize > SAMPLES) {
192 available = SAMPLES - audioBufferSize;
193 }
194 available &= ~((32 / sizeof(struct GBAStereoSample)) - 1); // Force align to 32 bytes
195 if (available > 0) {
196 blip_read_samples(context.gba->audio.left, &audioBuffer[currentAudioBuffer][audioBufferSize].left, available, true);
197 blip_read_samples(context.gba->audio.right, &audioBuffer[currentAudioBuffer][audioBufferSize].right, available, true);
198 audioBufferSize += available;
199 }
200 if (audioBufferSize == SAMPLES && !AUDIO_GetDMAEnableFlag()) {
201 _audioDMA();
202 AUDIO_StartDMA();
203 }
204 #endif
205 PAD_ScanPads();
206 u16 padkeys = PAD_ButtonsHeld(0);
207 WPAD_ScanPads();
208 u32 wiiPad = WPAD_ButtonsHeld(0);
209 u32 ext = 0;
210 uint16_t keys = 0;
211 WPAD_Probe(0, &ext);
212
213 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
214 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
215 keys |= 1 << GBA_KEY_A;
216 }
217 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
218 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
219 keys |= 1 << GBA_KEY_B;
220 }
221 if ((padkeys & PAD_TRIGGER_L) || (wiiPad & WPAD_BUTTON_B) ||
222 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_L))) {
223 keys |= 1 << GBA_KEY_L;
224 }
225 if ((padkeys & PAD_TRIGGER_R) || (wiiPad & WPAD_BUTTON_A) ||
226 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_FULL_R))) {
227 keys |= 1 << GBA_KEY_R;
228 }
229 if ((padkeys & PAD_BUTTON_START) || (wiiPad & WPAD_BUTTON_PLUS) ||
230 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_PLUS))) {
231 keys |= 1 << GBA_KEY_START;
232 }
233 if ((padkeys & (PAD_BUTTON_X | PAD_BUTTON_Y)) || (wiiPad & WPAD_BUTTON_MINUS) ||
234 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_MINUS))) {
235 keys |= 1 << GBA_KEY_SELECT;
236 }
237 if ((padkeys & PAD_BUTTON_LEFT) || (wiiPad & WPAD_BUTTON_UP) ||
238 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
239 keys |= 1 << GBA_KEY_LEFT;
240 }
241 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
242 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
243 keys |= 1 << GBA_KEY_RIGHT;
244 }
245 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
246 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
247 keys |= 1 << GBA_KEY_UP;
248 }
249 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
250 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
251 keys |= 1 << GBA_KEY_DOWN;
252 }
253 int x = PAD_StickX(0);
254 int y = PAD_StickY(0);
255 if (x < -0x40) {
256 keys |= 1 << GBA_KEY_LEFT;
257 }
258 if (x > 0x40) {
259 keys |= 1 << GBA_KEY_RIGHT;
260 }
261 if (y < -0x40) {
262 keys |= 1 << GBA_KEY_DOWN;
263 }
264 if (y > 0x40) {
265 keys |= 1 << GBA_KEY_UP;
266 }
267 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) || (wiiPad & WPAD_CLASSIC_BUTTON_HOME)) {
268 break;
269 }
270 GBAContextFrame(&context, keys);
271 }
272 AUDIO_StopDMA();
273 GBAContextStop(&context);
274 }
275
276 fclose(logfile);
277 free(fifo);
278
279 GBAContextDeinit(&context);
280
281 free(renderer.outputBuffer);
282 GUIFontDestroy(font);
283
284 return 0;
285}
286
287static void GBAWiiFrame(void) {
288 size_t x, y;
289 uint64_t* texdest = (uint64_t*) texmem;
290 uint64_t* texsrc = (uint64_t*) renderer.outputBuffer;
291 for (y = 0; y < VIDEO_VERTICAL_PIXELS; y += 4) {
292 for (x = 0; x < VIDEO_HORIZONTAL_PIXELS >> 2; ++x) {
293 texdest[0 + x * 4 + y * 64] = texsrc[0 + x + y * 64];
294 texdest[1 + x * 4 + y * 64] = texsrc[64 + x + y * 64];
295 texdest[2 + x * 4 + y * 64] = texsrc[128 + x + y * 64];
296 texdest[3 + x * 4 + y * 64] = texsrc[192 + x + y * 64];
297 }
298 }
299 DCFlushRange(texdest, 256 * 256 * BYTES_PER_PIXEL);
300
301 _drawStart();
302
303 GX_SetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
304 GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S16, 0);
305 GX_InvalidateTexAll();
306 GX_LoadTexObj(&tex, GX_TEXMAP0);
307
308 GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
309 GX_Position2s16(0, 256);
310 GX_TexCoord2s16(0, 1);
311
312 GX_Position2s16(256, 256);
313 GX_TexCoord2s16(1, 1);
314
315 GX_Position2s16(256, 0);
316 GX_TexCoord2s16(1, 0);
317
318 GX_Position2s16(0, 0);
319 GX_TexCoord2s16(0, 0);
320 GX_End();
321
322 _drawEnd();
323}
324
325bool GBAWiiLoadGame(const char* path) {
326 _drawStart();
327 GUIFontPrintf(font, 176, 120, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
328 _drawEnd();
329
330 return GBAContextLoadROM(&context, path, true);
331}
332
333void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
334 UNUSED(thread);
335 UNUSED(level);
336 vfprintf(logfile, format, args);
337 fprintf(logfile, "\n");
338 fflush(logfile);
339}
340
341static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
342 UNUSED(stream);
343 UNUSED(renderer);
344 GBAWiiFrame();
345}
346
347static void _audioDMA(void) {
348 if (!audioBufferSize) {
349 return;
350 }
351 DCFlushRange(audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
352 AUDIO_InitDMA((u32) audioBuffer[currentAudioBuffer], audioBufferSize * sizeof(struct GBAStereoSample));
353 currentAudioBuffer = (currentAudioBuffer + 1) % 3;
354 audioBufferSize = 0;
355}
356
357static void _drawStart(void) {
358 VIDEO_WaitVSync();
359 GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
360 GX_SetColorUpdate(GX_TRUE);
361
362 GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1);
363}
364
365static void _drawEnd(void) {
366 GX_DrawDone();
367
368 whichFb = !whichFb;
369
370 GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
371 VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
372 VIDEO_Flush();
373}
374
375static int _pollInput(void) {
376 PAD_ScanPads();
377 u16 padkeys = PAD_ButtonsHeld(0);
378
379 WPAD_ScanPads();
380 u32 wiiPad = WPAD_ButtonsHeld(0);
381 u32 ext = 0;
382 WPAD_Probe(0, &ext);
383
384 int keys = 0;
385 int x = PAD_StickX(0);
386 int y = PAD_StickY(0);
387 if (x < -0x40) {
388 keys |= 1 << GUI_INPUT_LEFT;
389 }
390 if (x > 0x40) {
391 keys |= 1 << GUI_INPUT_RIGHT;
392 }
393 if (y < -0x40) {
394 keys |= 1 << GUI_INPUT_DOWN;
395 }
396 if (y > 0x40) {
397 keys |= 1 << GUI_INPUT_UP;
398 }
399 if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) ||
400 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) {
401 keys |= 1 << GUI_INPUT_SELECT;
402 }
403 if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) ||
404 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) {
405 keys |= 1 << GUI_INPUT_BACK;
406 }
407 if ((padkeys & PAD_TRIGGER_Z) || (wiiPad & WPAD_BUTTON_HOME) ||
408 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_HOME)))) {
409 keys |= 1 << GUI_INPUT_CANCEL;
410 }
411 if ((padkeys & PAD_BUTTON_LEFT)|| (wiiPad & WPAD_BUTTON_UP) ||
412 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_LEFT))) {
413 keys |= 1 << GUI_INPUT_LEFT;
414 }
415 if ((padkeys & PAD_BUTTON_RIGHT) || (wiiPad & WPAD_BUTTON_DOWN) ||
416 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_RIGHT))) {
417 keys |= 1 << GUI_INPUT_RIGHT;
418 }
419 if ((padkeys & PAD_BUTTON_UP) || (wiiPad & WPAD_BUTTON_RIGHT) ||
420 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_UP))) {
421 keys |= 1 << GUI_INPUT_UP;
422 }
423 if ((padkeys & PAD_BUTTON_DOWN) || (wiiPad & WPAD_BUTTON_LEFT) ||
424 ((ext == WPAD_EXP_CLASSIC) && (wiiPad & WPAD_CLASSIC_BUTTON_DOWN))) {
425 keys |= 1 << GUI_INPUT_DOWN;
426 }
427 return keys;
428}
429
430void _setRumble(struct GBARumble* rumble, int enable) {
431 UNUSED(rumble);
432 WPAD_Rumble(0, enable);
433 if (enable) {
434 PAD_ControlMotor(0, PAD_MOTOR_RUMBLE);
435 } else {
436 PAD_ControlMotor(0, PAD_MOTOR_STOP);
437 }
438}