src/platform/3ds/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
7#include "gba/renderers/video-software.h"
8#include "gba/context/context.h"
9#include "gba/video.h"
10#include "util/gui.h"
11#include "util/gui/file-select.h"
12#include "util/gui/font.h"
13#include "util/memory.h"
14
15#include "3ds-vfs.h"
16
17#include <3ds.h>
18#include <sf2d.h>
19
20#define AUDIO_SAMPLES 0x800
21
22FS_archive sdmcArchive;
23
24struct GBA3DSRotationSource {
25 struct GBARotationSource d;
26 accelVector accel;
27 angularRate gyro;
28};
29
30extern bool allocateRomBuffer(void);
31static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
32static struct VFile* logFile;
33
34static void _drawStart(void) {
35 sf2d_start_frame(GFX_BOTTOM, GFX_LEFT);
36}
37static void _drawEnd(void) {
38 sf2d_end_frame();
39 sf2d_swapbuffers();
40}
41
42static int _pollInput(void) {
43 hidScanInput();
44 int keys = 0;
45 int activeKeys = hidKeysHeld();
46 if (activeKeys & KEY_X) {
47 keys |= 1 << GUI_INPUT_CANCEL;
48 }
49 if (activeKeys & KEY_B) {
50 keys |= 1 << GUI_INPUT_BACK;
51 }
52 if (activeKeys & KEY_A) {
53 keys |= 1 << GUI_INPUT_SELECT;
54 }
55 if (activeKeys & KEY_LEFT) {
56 keys |= 1 << GUI_INPUT_LEFT;
57 }
58 if (activeKeys & KEY_RIGHT) {
59 keys |= 1 << GUI_INPUT_RIGHT;
60 }
61 if (activeKeys & KEY_UP) {
62 keys |= 1 << GUI_INPUT_UP;
63 }
64 if (activeKeys & KEY_DOWN) {
65 keys |= 1 << GUI_INPUT_DOWN;
66 }
67 return keys;
68}
69
70static void _sampleRotation(struct GBARotationSource* source) {
71 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
72 // Work around ctrulib getting the entries wrong
73 rotation->accel = *(accelVector*)& hidSharedMem[0x48];
74 rotation->gyro = *(angularRate*)& hidSharedMem[0x5C];
75}
76
77static int32_t _readTiltX(struct GBARotationSource* source) {
78 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
79 return rotation->accel.x << 18L;
80}
81
82static int32_t _readTiltY(struct GBARotationSource* source) {
83 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
84 return rotation->accel.y << 18L;
85}
86
87static int32_t _readGyroZ(struct GBARotationSource* source) {
88 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
89 return rotation->gyro.y << 18L; // Yes, y
90}
91
92int main() {
93 bool hasSound = !csndInit();
94
95 struct GBAContext context;
96 struct GBA3DSRotationSource rotation;
97 rotation.d.sample = _sampleRotation;
98 rotation.d.readTiltX = _readTiltX;
99 rotation.d.readTiltY = _readTiltY;
100 rotation.d.readGyroZ = _readGyroZ;
101
102 if (!allocateRomBuffer()) {
103 return 1;
104 }
105
106 int16_t* audioLeft = 0;
107 int16_t* audioRight = 0;
108 size_t audioPos = 0;
109 if (hasSound) {
110 audioLeft = linearAlloc(AUDIO_SAMPLES * 2 * sizeof(int16_t));
111 audioRight = linearAlloc(AUDIO_SAMPLES * 2 * sizeof(int16_t));
112 }
113
114 sf2d_init();
115 sf2d_set_clear_color(0);
116 sf2d_texture* tex = sf2d_create_texture(256, 256, TEXFMT_RGB565, SF2D_PLACE_RAM);
117 memset(tex->data, 0, 256 * 256 * 2);
118
119 sdmcArchive = (FS_archive) {
120 ARCH_SDMC,
121 (FS_path) { PATH_EMPTY, 1, (const u8*)"" },
122 0, 0
123 };
124 FSUSER_OpenArchive(0, &sdmcArchive);
125
126 logFile = VFileOpen("/mgba.log", O_WRONLY | O_CREAT | O_TRUNC);
127 struct GUIFont* font = GUIFontCreate();
128
129 GBAContextInit(&context, 0);
130 struct GBAOptions opts = {
131 .useBios = true,
132 .logLevel = 0,
133 .idleOptimization = IDLE_LOOP_DETECT
134 };
135 GBAConfigLoadDefaults(&context.config, &opts);
136 context.gba->logHandler = GBA3DSLog;
137 context.gba->rotationSource = &rotation.d;
138
139 struct GBAVideoSoftwareRenderer renderer;
140 GBAVideoSoftwareRendererCreate(&renderer);
141 renderer.outputBuffer = anonymousMemoryMap(256 * VIDEO_VERTICAL_PIXELS * 2);
142 renderer.outputBufferStride = 256;
143 context.renderer = &renderer.d;
144
145 if (!font) {
146 goto cleanup;
147 }
148
149 struct GUIParams params = {
150 320, 240,
151 font, "/", _drawStart, _drawEnd, _pollInput, 0, 0,
152
153 GUI_PARAMS_TRAIL
154 };
155 GUIInit(¶ms);
156
157 while (aptMainLoop()) {
158 char path[256];
159 if (!GUISelectFile(¶ms, path, sizeof(path), GBAIsROM)) {
160 break;
161 }
162 _drawStart();
163 GUIFontPrintf(font, 160, (GUIFontHeight(font) + 240) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
164 _drawEnd();
165 if (!GBAContextLoadROM(&context, path, true)) {
166 continue;
167 }
168 if (!GBAContextStart(&context)) {
169 continue;
170 }
171
172 if (context.gba->memory.hw.devices & HW_TILT) {
173 HIDUSER_EnableAccelerometer();
174 }
175 if (context.gba->memory.hw.devices & HW_GYRO) {
176 HIDUSER_EnableGyroscope();
177 }
178
179#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
180 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 0x8000);
181 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 0x8000);
182#endif
183 memset(audioLeft, 0, AUDIO_SAMPLES * 2 * sizeof(int16_t));
184 memset(audioRight, 0, AUDIO_SAMPLES * 2 * sizeof(int16_t));
185
186 while (aptMainLoop()) {
187 hidScanInput();
188 uint32_t activeKeys = hidKeysHeld() & 0xF00003FF;
189 activeKeys |= activeKeys >> 24;
190 if (hidKeysDown() & KEY_X) {
191 break;
192 }
193 GBAContextFrame(&context, activeKeys);
194 GX_SetDisplayTransfer(0, renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202);
195 GSPGPU_FlushDataCache(0, tex->data, 256 * VIDEO_VERTICAL_PIXELS * 2);
196#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
197 if (hasSound) {
198 memset(&audioLeft[audioPos], 0, AUDIO_SAMPLES);
199 memset(&audioRight[audioPos], 0, AUDIO_SAMPLES);
200 size_t samples = blip_read_samples(context.gba->audio.left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
201 blip_read_samples(context.gba->audio.right, &audioRight[audioPos], AUDIO_SAMPLES, false);
202 size_t audioPosNew = (audioPos + AUDIO_SAMPLES) % (AUDIO_SAMPLES * 2);
203 GSPGPU_FlushDataCache(0, (void*) audioLeft, AUDIO_SAMPLES * 2 * sizeof(int16_t));
204 GSPGPU_FlushDataCache(0, (void*) audioRight, AUDIO_SAMPLES * 2 * sizeof(int16_t));
205 csndPlaySound(0x8, SOUND_ONE_SHOT | SOUND_FORMAT_16BIT, 0x8000, 1.0, -1.0, &audioLeft[audioPos], &audioLeft[audioPosNew], samples * 2);
206 csndPlaySound(0x9, SOUND_ONE_SHOT | SOUND_FORMAT_16BIT, 0x8000, 1.0, 1.0, &audioRight[audioPos], &audioLeft[audioPosNew], samples * 2);
207 audioPos = audioPosNew;
208 } else {
209 blip_clear(context.gba->audio.left);
210 blip_clear(context.gba->audio.right);
211 }
212#endif
213 gspWaitForPPF();
214 _drawStart();
215 sf2d_draw_texture_scale(tex, 40, 296, 1, -1);
216 _drawEnd();
217 }
218 GBAContextStop(&context);
219
220 CSND_SetPlayState(8, 0);
221 CSND_SetPlayState(9, 0);
222 csndExecCmds(0);
223
224 if (context.gba->memory.hw.devices & HW_TILT) {
225 HIDUSER_DisableAccelerometer();
226 }
227 if (context.gba->memory.hw.devices & HW_GYRO) {
228 HIDUSER_DisableGyroscope();
229 }
230 }
231 GBAContextDeinit(&context);
232
233cleanup:
234 mappedMemoryFree(renderer.outputBuffer, 0);
235
236 if (logFile) {
237 logFile->close(logFile);
238 }
239
240 sf2d_free_texture(tex);
241 sf2d_fini();
242
243 if (hasSound) {
244 linearFree(audioLeft);
245 linearFree(audioRight);
246 }
247 csndExit();
248 return 0;
249}
250
251static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
252 UNUSED(thread);
253 UNUSED(level);
254 if (!logFile) {
255 return;
256 }
257 char out[256];
258 size_t len = vsnprintf(out, sizeof(out), format, args);
259 if (len >= sizeof(out)) {
260 len = sizeof(out) - 1;
261 }
262 out[len] = '\n';
263 logFile->write(logFile, out, len + 1);
264}