src/feature/gui/gui-runner.c (view raw)
1/* Copyright (c) 2013-2016 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#include "gui-runner.h"
7
8#include "core/core.h"
9#include "core/serialize.h"
10#include "feature/gui/gui-config.h"
11#include "gba/gba.h"
12#include "gba/input.h"
13#include "gba/interface.h"
14#include "util/gui/file-select.h"
15#include "util/gui/font.h"
16#include "util/gui/menu.h"
17#include "util/memory.h"
18#include "util/png-io.h"
19#include "util/vfs.h"
20
21#ifdef _3DS
22#include <3ds.h>
23#endif
24
25#include <sys/time.h>
26
27mLOG_DECLARE_CATEGORY(GUI_RUNNER);
28mLOG_DEFINE_CATEGORY(GUI_RUNNER, "GUI Runner");
29
30#define FPS_GRANULARITY 120
31#define FPS_BUFFER_SIZE 3
32
33enum {
34 RUNNER_CONTINUE = 1,
35 RUNNER_EXIT,
36 RUNNER_SAVE_STATE,
37 RUNNER_LOAD_STATE,
38 RUNNER_SCREENSHOT,
39 RUNNER_CONFIG,
40 RUNNER_RESET,
41 RUNNER_COMMAND_MASK = 0xFFFF,
42
43 RUNNER_STATE_1 = 0x10000,
44 RUNNER_STATE_2 = 0x20000,
45 RUNNER_STATE_3 = 0x30000,
46 RUNNER_STATE_4 = 0x40000,
47 RUNNER_STATE_5 = 0x50000,
48 RUNNER_STATE_6 = 0x60000,
49 RUNNER_STATE_7 = 0x70000,
50 RUNNER_STATE_8 = 0x80000,
51 RUNNER_STATE_9 = 0x90000,
52};
53
54static void _log(struct mLogger*, int category, enum mLogLevel level, const char* format, va_list args);
55
56static struct mGUILogger {
57 struct mLogger d;
58 struct VFile* vf;
59 int logLevel;
60} logger = {
61 .d = {
62 .log = _log
63 },
64 .vf = NULL,
65 .logLevel = 0
66};
67
68static void _drawBackground(struct GUIBackground* background, void* context) {
69 UNUSED(context);
70 struct mGUIBackground* gbaBackground = (struct mGUIBackground*) background;
71 if (gbaBackground->p->drawFrame) {
72 gbaBackground->p->drawFrame(gbaBackground->p, true);
73 }
74}
75
76static void _drawState(struct GUIBackground* background, void* id) {
77 struct mGUIBackground* gbaBackground = (struct mGUIBackground*) background;
78 int stateId = ((int) id) >> 16;
79 if (gbaBackground->p->drawScreenshot) {
80 unsigned w, h;
81 gbaBackground->p->core->desiredVideoDimensions(gbaBackground->p->core, &w, &h);
82 if (gbaBackground->screenshot && gbaBackground->screenshotId == (int) id) {
83 gbaBackground->p->drawScreenshot(gbaBackground->p, gbaBackground->screenshot, w, h, true);
84 return;
85 }
86 struct VFile* vf = mCoreGetState(gbaBackground->p->core, stateId, false);
87 uint32_t* pixels = gbaBackground->screenshot;
88 if (!pixels) {
89 pixels = anonymousMemoryMap(w * h * 4);
90 gbaBackground->screenshot = pixels;
91 }
92 bool success = false;
93 if (vf && isPNG(vf) && pixels) {
94 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
95 png_infop info = png_create_info_struct(png);
96 png_infop end = png_create_info_struct(png);
97 if (png && info && end) {
98 success = PNGReadHeader(png, info);
99 success = success && PNGReadPixels(png, info, pixels, w, h, w);
100 success = success && PNGReadFooter(png, end);
101 }
102 PNGReadClose(png, info, end);
103 }
104 if (vf) {
105 vf->close(vf);
106 }
107 if (success) {
108 gbaBackground->p->drawScreenshot(gbaBackground->p, pixels, w, h, true);
109 gbaBackground->screenshotId = (int) id;
110 } else if (gbaBackground->p->drawFrame) {
111 gbaBackground->p->drawFrame(gbaBackground->p, true);
112 }
113 }
114}
115
116static void _updateLux(struct GBALuminanceSource* lux) {
117 UNUSED(lux);
118}
119
120static uint8_t _readLux(struct GBALuminanceSource* lux) {
121 struct mGUIRunnerLux* runnerLux = (struct mGUIRunnerLux*) lux;
122 int value = 0x16;
123 if (runnerLux->luxLevel > 0) {
124 value += GBA_LUX_LEVELS[runnerLux->luxLevel - 1];
125 }
126 return 0xFF - value;
127}
128
129void mGUIInit(struct mGUIRunner* runner, const char* port) {
130 GUIInit(&runner->params);
131 runner->port = port;
132 runner->core = NULL;
133 runner->luminanceSource.d.readLuminance = _readLux;
134 runner->luminanceSource.d.sample = _updateLux;
135 runner->luminanceSource.luxLevel = 0;
136 runner->background.d.draw = _drawBackground;
137 runner->background.p = runner;
138 runner->fps = 0;
139 runner->lastFpsCheck = 0;
140 runner->totalDelta = 0;
141 CircleBufferInit(&runner->fpsBuffer, FPS_BUFFER_SIZE * sizeof(uint32_t));
142
143 char path[PATH_MAX];
144 mCoreConfigDirectory(path, PATH_MAX);
145 strncat(path, PATH_SEP "log", PATH_MAX - strlen(path));
146 logger.vf = VFileOpen(path, O_CREAT | O_WRONLY | O_APPEND);
147 mLogSetDefaultLogger(&logger.d);
148}
149
150void mGUIDeinit(struct mGUIRunner* runner) {
151 if (runner->teardown) {
152 runner->teardown(runner);
153 }
154 CircleBufferDeinit(&runner->fpsBuffer);
155 if (logger.vf) {
156 logger.vf->close(logger.vf);
157 logger.vf = NULL;
158 }
159}
160
161static void _log(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
162 struct mGUILogger* guiLogger = (struct mGUILogger*) logger;
163 if (!guiLogger->vf) {
164 return;
165 }
166 if (!(guiLogger->logLevel & level)) {
167 return;
168 }
169
170 char log[256] = {0};
171 vsnprintf(log, sizeof(log) - 1, format, args);
172 char log2[256] = {0};
173 size_t len = snprintf(log2, sizeof(log2) - 1, "%s: %s\n", mLogCategoryName(category), log);
174 if (len >= sizeof(log2)) {
175 len = sizeof(log2) - 1;
176 }
177 guiLogger->vf->write(guiLogger->vf, log2, len);
178}
179
180void mGUIRun(struct mGUIRunner* runner, const char* path) {
181 struct mGUIBackground drawState = {
182 .d = {
183 .draw = _drawState
184 },
185 .p = runner,
186 .screenshot = 0,
187 .screenshotId = 0
188 };
189 struct GUIMenu pauseMenu = {
190 .title = "Game Paused",
191 .index = 0,
192 .background = &runner->background.d
193 };
194 struct GUIMenu stateSaveMenu = {
195 .title = "Save state",
196 .index = 0,
197 .background = &drawState.d
198 };
199 struct GUIMenu stateLoadMenu = {
200 .title = "Load state",
201 .index = 0,
202 .background = &drawState.d
203 };
204 GUIMenuItemListInit(&pauseMenu.items, 0);
205 GUIMenuItemListInit(&stateSaveMenu.items, 9);
206 GUIMenuItemListInit(&stateLoadMenu.items, 9);
207 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };
208 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu };
209 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu };
210
211 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) };
212 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) };
213 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) };
214 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) };
215 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) };
216 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) };
217 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) };
218 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) };
219 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) };
220
221 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) };
222 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) };
223 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) };
224 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) };
225 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) };
226 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) };
227 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };
228 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) };
229 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) };
230
231 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT };
232 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG };
233 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Reset game", .data = (void*) RUNNER_RESET };
234 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT };
235
236 // TODO: Message box API
237 runner->params.drawStart();
238 if (runner->params.guiPrepare) {
239 runner->params.guiPrepare();
240 }
241 GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Loading...");
242 if (runner->params.guiFinish) {
243 runner->params.guiFinish();
244 }
245 runner->params.drawEnd();
246
247 bool found = false;
248 mLOG(GUI_RUNNER, INFO, "Attempting to load %s", path);
249 runner->core = mCoreFind(path);
250 if (runner->core) {
251 mLOG(GUI_RUNNER, INFO, "Found core");
252 runner->core->init(runner->core);
253 mInputMapInit(&runner->core->inputMap, &GBAInputInfo);
254 mCoreInitConfig(runner->core, runner->port);
255 found = mCoreLoadFile(runner->core, path);
256 if (!found) {
257 mLOG(GUI_RUNNER, WARN, "Failed to load %s!", path);
258 runner->core->deinit(runner->core);
259 }
260 }
261
262 if (!found) {
263 mLOG(GUI_RUNNER, WARN, "Failed to find core for %s!", path);
264 int i;
265 for (i = 0; i < 240; ++i) {
266 runner->params.drawStart();
267 if (runner->params.guiPrepare) {
268 runner->params.guiPrepare();
269 }
270 GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_ALIGN_HCENTER, 0xFFFFFFFF, "Load failed!");
271 if (runner->params.guiFinish) {
272 runner->params.guiFinish();
273 }
274 runner->params.drawEnd();
275 }
276 return;
277 }
278 if (runner->core->platform(runner->core) == PLATFORM_GBA) {
279 ((struct GBA*) runner->core->board)->luminanceSource = &runner->luminanceSource.d;
280 }
281 if (runner->core->config.port && runner->keySources) {
282 mLOG(GUI_RUNNER, DEBUG, "Loading key sources for %s...", runner->core->config.port);
283 size_t i;
284 for (i = 0; runner->keySources[i].id; ++i) {
285 mInputMapLoad(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->core->config));
286 }
287 }
288 // TODO: Do we need to load more defaults?
289 mCoreConfigSetDefaultIntValue(&runner->core->config, "volume", 0x100);
290 mCoreConfigSetDefaultValue(&runner->core->config, "idleOptimization", "detect");
291 mLOG(GUI_RUNNER, DEBUG, "Loading config...");
292 mCoreLoadConfig(runner->core);
293 logger.logLevel = runner->core->opts.logLevel;
294
295 mLOG(GUI_RUNNER, DEBUG, "Loading save...");
296 mCoreAutoloadSave(runner->core);
297 if (runner->setup) {
298 mLOG(GUI_RUNNER, DEBUG, "Setting up runner...");
299 runner->setup(runner);
300 }
301 mLOG(GUI_RUNNER, DEBUG, "Reseting...");
302 runner->core->reset(runner->core);
303 mLOG(GUI_RUNNER, DEBUG, "Reset!");
304 bool running = true;
305 if (runner->gameLoaded) {
306 runner->gameLoaded(runner);
307 }
308 mLOG(GUI_RUNNER, INFO, "Game starting");
309 while (running) {
310 CircleBufferClear(&runner->fpsBuffer);
311 runner->totalDelta = 0;
312 runner->fps = 0;
313 struct timeval tv;
314 gettimeofday(&tv, 0);
315 runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
316
317 while (true) {
318#ifdef _3DS
319 running = aptMainLoop();
320 if (!running) {
321 break;
322 }
323#endif
324 uint32_t guiKeys;
325 GUIPollInput(&runner->params, &guiKeys, 0);
326 if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
327 break;
328 }
329 if (guiKeys & (1 << mGUI_INPUT_INCREASE_BRIGHTNESS)) {
330 if (runner->luminanceSource.luxLevel < 10) {
331 ++runner->luminanceSource.luxLevel;
332 }
333 }
334 if (guiKeys & (1 << mGUI_INPUT_DECREASE_BRIGHTNESS)) {
335 if (runner->luminanceSource.luxLevel > 0) {
336 --runner->luminanceSource.luxLevel;
337 }
338 }
339 if (guiKeys & (1 << mGUI_INPUT_SCREEN_MODE) && runner->incrementScreenMode) {
340 runner->incrementScreenMode(runner);
341 }
342 uint16_t keys = runner->pollGameInput(runner);
343 if (runner->prepareForFrame) {
344 runner->prepareForFrame(runner);
345 }
346 runner->core->setKeys(runner->core, keys);
347 runner->core->runFrame(runner->core);
348 if (runner->drawFrame) {
349 int drawFps = false;
350 mCoreConfigGetIntValue(&runner->core->config, "fpsCounter", &drawFps);
351
352 runner->params.drawStart();
353 runner->drawFrame(runner, false);
354 if (drawFps) {
355 if (runner->params.guiPrepare) {
356 runner->params.guiPrepare();
357 }
358 GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_ALIGN_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps);
359 if (runner->params.guiFinish) {
360 runner->params.guiFinish();
361 }
362 }
363 runner->params.drawEnd();
364
365 if (runner->core->frameCounter(runner->core) % FPS_GRANULARITY == 0) {
366 if (drawFps) {
367 struct timeval tv;
368 gettimeofday(&tv, 0);
369 uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec;
370 uint64_t delta = t - runner->lastFpsCheck;
371 runner->lastFpsCheck = t;
372 if (delta > 0x7FFFFFFFLL) {
373 CircleBufferClear(&runner->fpsBuffer);
374 runner->fps = 0;
375 }
376 if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) {
377 int32_t last;
378 CircleBufferRead32(&runner->fpsBuffer, &last);
379 runner->totalDelta -= last;
380 }
381 CircleBufferWrite32(&runner->fpsBuffer, delta);
382 runner->totalDelta += delta;
383 runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t));
384 }
385 }
386 }
387 }
388
389 if (runner->paused) {
390 runner->paused(runner);
391 }
392 GUIInvalidateKeys(&runner->params);
393 uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable!
394 struct GUIMenuItem* item;
395 enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item);
396 if (reason == GUI_MENU_EXIT_ACCEPT) {
397 switch (((int) item->data) & RUNNER_COMMAND_MASK) {
398 case RUNNER_EXIT:
399 running = false;
400 keys = 0;
401 break;
402 case RUNNER_RESET:
403 runner->core->reset(runner->core);
404 break;
405 case RUNNER_SAVE_STATE:
406 mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT);
407 break;
408 case RUNNER_LOAD_STATE:
409 mCoreLoadState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT);
410 break;
411 case RUNNER_SCREENSHOT:
412 mCoreTakeScreenshot(runner->core);
413 break;
414 case RUNNER_CONFIG:
415 mGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra);
416 mCoreLoadConfig(runner->core);
417 break;
418 case RUNNER_CONTINUE:
419 break;
420 }
421 }
422 int frames = 0;
423 GUIPollInput(&runner->params, 0, &keys);
424 while (keys && frames < 30) {
425 ++frames;
426 runner->params.drawStart();
427 runner->drawFrame(runner, true);
428 runner->params.drawEnd();
429 GUIPollInput(&runner->params, 0, &keys);
430 }
431 if (runner->unpaused) {
432 runner->unpaused(runner);
433 }
434 }
435 mLOG(GUI_RUNNER, DEBUG, "Shutting down...");
436 if (runner->gameUnloaded) {
437 runner->gameUnloaded(runner);
438 }
439 mLOG(GUI_RUNNER, DEBUG, "Unloading game...");
440 runner->core->unloadROM(runner->core);
441 drawState.screenshotId = 0;
442 if (drawState.screenshot) {
443 unsigned w, h;
444 runner->core->desiredVideoDimensions(runner->core, &w, &h);
445 mappedMemoryFree(drawState.screenshot, w * h * 4);
446 }
447
448 if (runner->core->config.port) {
449 mLOG(GUI_RUNNER, DEBUG, "Saving key sources...");
450 if (runner->keySources) {
451 size_t i;
452 for (i = 0; runner->keySources[i].id; ++i) {
453 mInputMapSave(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->core->config));
454 }
455 }
456 mCoreConfigSave(&runner->core->config);
457 }
458 mLOG(GUI_RUNNER, DEBUG, "Deinitializing core...");
459 runner->core->deinit(runner->core);
460
461 GUIMenuItemListDeinit(&pauseMenu.items);
462 GUIMenuItemListDeinit(&stateSaveMenu.items);
463 GUIMenuItemListDeinit(&stateLoadMenu.items);
464 mLOG(GUI_RUNNER, INFO, "Game stopped!");
465}
466
467void mGUIRunloop(struct mGUIRunner* runner) {
468 while (true) {
469 char path[PATH_MAX];
470 if (!GUISelectFile(&runner->params, path, sizeof(path), 0)) {
471 break;
472 }
473 mGUIRun(runner, path);
474 }
475}