src/gba/gui/gui-runner.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#include "gui-runner.h"
7
8#include "gba/interface.h"
9#include "gba/serialize.h"
10#include "util/gui/file-select.h"
11#include "util/gui/font.h"
12#include "util/gui/menu.h"
13#include "util/memory.h"
14#include "util/png-io.h"
15#include "util/vfs.h"
16
17enum {
18 RUNNER_CONTINUE = 1,
19 RUNNER_EXIT = 2,
20 RUNNER_SAVE_STATE = 3,
21 RUNNER_LOAD_STATE = 4,
22 RUNNER_COMMAND_MASK = 0xFFFF,
23
24 RUNNER_STATE_1 = 0x10000,
25 RUNNER_STATE_2 = 0x20000,
26 RUNNER_STATE_3 = 0x30000,
27 RUNNER_STATE_4 = 0x40000,
28 RUNNER_STATE_5 = 0x50000,
29 RUNNER_STATE_6 = 0x60000,
30 RUNNER_STATE_7 = 0x70000,
31 RUNNER_STATE_8 = 0x80000,
32 RUNNER_STATE_9 = 0x90000,
33};
34
35static void _drawBackground(struct GUIBackground* background, void* context) {
36 UNUSED(context);
37 struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
38 if (gbaBackground->p->drawFrame) {
39 gbaBackground->p->drawFrame(gbaBackground->p, true);
40 }
41}
42
43static void _drawState(struct GUIBackground* background, void* id) {
44 struct GBAGUIBackground* gbaBackground = (struct GBAGUIBackground*) background;
45 int stateId = ((int) id) >> 16;
46 if (gbaBackground->p->drawScreenshot) {
47 if (gbaBackground->screenshot && gbaBackground->screenshotId == (int) id) {
48 gbaBackground->p->drawScreenshot(gbaBackground->p, gbaBackground->screenshot, true);
49 return;
50 }
51 struct VFile* vf = GBAGetState(gbaBackground->p->context.gba, 0, stateId, false);
52 uint32_t* pixels = gbaBackground->screenshot;
53 if (!pixels) {
54 pixels = anonymousMemoryMap(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
55 gbaBackground->screenshot = pixels;
56 }
57 bool success = false;
58 if (vf && isPNG(vf) && pixels) {
59 png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
60 png_infop info = png_create_info_struct(png);
61 png_infop end = png_create_info_struct(png);
62 if (png && info && end) {
63 success = PNGReadHeader(png, info);
64 success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
65 success = success && PNGReadFooter(png, end);
66 }
67 PNGReadClose(png, info, end);
68 }
69 if (vf) {
70 vf->close(vf);
71 }
72 if (success) {
73 gbaBackground->p->drawScreenshot(gbaBackground->p, pixels, true);
74 gbaBackground->screenshotId = (int) id;
75 } else if (gbaBackground->p->drawFrame) {
76 gbaBackground->p->drawFrame(gbaBackground->p, true);
77 }
78 }
79}
80
81static void _updateLux(struct GBALuminanceSource* lux) {
82 UNUSED(lux);
83}
84
85static uint8_t _readLux(struct GBALuminanceSource* lux) {
86 struct GBAGUIRunnerLux* runnerLux = (struct GBAGUIRunnerLux*) lux;
87 int value = 0x16;
88 if (runnerLux->luxLevel > 0) {
89 value += GBA_LUX_LEVELS[runnerLux->luxLevel - 1];
90 }
91 return 0xFF - value;
92}
93
94void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) {
95 GUIInit(&runner->params);
96 GBAContextInit(&runner->context, port);
97 runner->luminanceSource.d.readLuminance = _readLux;
98 runner->luminanceSource.d.sample = _updateLux;
99 runner->luminanceSource.luxLevel = 0;
100 runner->context.gba->luminanceSource = &runner->luminanceSource.d;
101 runner->background.d.draw = _drawBackground;
102 runner->background.p = runner;
103 if (runner->setup) {
104 runner->setup(runner);
105 }
106}
107
108void GBAGUIDeinit(struct GBAGUIRunner* runner) {
109 if (runner->teardown) {
110 runner->teardown(runner);
111 }
112 GBAContextDeinit(&runner->context);
113}
114
115void GBAGUIRunloop(struct GBAGUIRunner* runner) {
116 struct GBAGUIBackground drawState = {
117 .d = {
118 .draw = _drawState
119 },
120 .p = runner,
121 .screenshot = 0,
122 .screenshotId = 0
123 };
124 struct GUIMenu pauseMenu = {
125 .title = "Game Paused",
126 .index = 0,
127 .background = &runner->background.d
128 };
129 struct GUIMenu stateSaveMenu = {
130 .title = "Save state",
131 .index = 0,
132 .background = &drawState.d
133 };
134 struct GUIMenu stateLoadMenu = {
135 .title = "Load state",
136 .index = 0,
137 .background = &drawState.d
138 };
139 GUIMenuItemListInit(&pauseMenu.items, 0);
140 GUIMenuItemListInit(&stateSaveMenu.items, 9);
141 GUIMenuItemListInit(&stateLoadMenu.items, 9);
142 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Unpause", .data = (void*) RUNNER_CONTINUE };
143#if !(defined(__POWERPC__) || defined(__PPC__))
144 // PPC doesn't have working savestates yet
145 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu };
146 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu };
147
148 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) };
149 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) };
150 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) };
151 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) };
152 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) };
153 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) };
154 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) };
155 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) };
156 *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) };
157
158 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) };
159 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) };
160 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) };
161 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) };
162 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) };
163 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) };
164 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };
165 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) };
166 *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) };
167#endif
168 *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT };
169
170 while (true) {
171 char path[256];
172 if (!GUISelectFile(&runner->params, path, sizeof(path), GBAIsROM)) {
173 if (runner->params.guiFinish) {
174 runner->params.guiFinish();
175 }
176 break;
177 }
178
179 if (runner->params.guiPrepare) {
180 runner->params.guiPrepare();
181 }
182 // TODO: Message box API
183 runner->params.drawStart();
184 GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
185 runner->params.drawEnd();
186 runner->params.drawStart();
187 GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Loading...");
188 runner->params.drawEnd();
189
190 if (!GBAContextLoadROM(&runner->context, path, true)) {
191 int i;
192 for (i = 0; i < 300; ++i) {
193 runner->params.drawStart();
194 GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Load failed!");
195 runner->params.drawEnd();
196 }
197 }
198 if (runner->params.guiFinish) {
199 runner->params.guiFinish();
200 }
201 GBAContextStart(&runner->context);
202 if (runner->gameLoaded) {
203 runner->gameLoaded(runner);
204 }
205 bool running = true;
206 while (running) {
207 while (true) {
208 uint32_t guiKeys;
209 GUIPollInput(&runner->params, &guiKeys, 0);
210 if (guiKeys & (1 << GUI_INPUT_CANCEL)) {
211 break;
212 }
213 if (guiKeys & (1 << GBA_GUI_INPUT_INCREASE_BRIGHTNESS)) {
214 if (runner->luminanceSource.luxLevel < 10) {
215 ++runner->luminanceSource.luxLevel;
216 }
217 }
218 if (guiKeys & (1 << GBA_GUI_INPUT_DECREASE_BRIGHTNESS)) {
219 if (runner->luminanceSource.luxLevel > 0) {
220 --runner->luminanceSource.luxLevel;
221 }
222 }
223 if (guiKeys & (1 << GBA_GUI_INPUT_SCREEN_MODE) && runner->incrementScreenMode) {
224 runner->incrementScreenMode(runner);
225 }
226 uint16_t keys = runner->pollGameInput(runner);
227 if (runner->prepareForFrame) {
228 runner->prepareForFrame(runner);
229 }
230 GBAContextFrame(&runner->context, keys);
231 if (runner->drawFrame) {
232 runner->params.drawStart();
233 runner->drawFrame(runner, false);
234 runner->params.drawEnd();
235 }
236 }
237
238 if (runner->paused) {
239 runner->paused(runner);
240 }
241 GUIInvalidateKeys(&runner->params);
242 uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable!
243 struct GUIMenuItem item;
244 enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item);
245 if (reason == GUI_MENU_EXIT_ACCEPT) {
246 struct VFile* vf;
247 switch (((int) item.data) & RUNNER_COMMAND_MASK) {
248 case RUNNER_EXIT:
249 running = false;
250 keys = 0;
251 break;
252 case RUNNER_SAVE_STATE:
253 vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, true);
254 if (vf) {
255 GBASaveStateNamed(runner->context.gba, vf, true);
256 vf->close(vf);
257 }
258 break;
259 case RUNNER_LOAD_STATE:
260 vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, false);
261 if (vf) {
262 GBALoadStateNamed(runner->context.gba, vf);
263 vf->close(vf);
264 }
265 break;
266 case RUNNER_CONTINUE:
267 break;
268 }
269 }
270 while (keys) {
271 GUIPollInput(&runner->params, 0, &keys);
272 }
273 if (runner->params.guiFinish) {
274 runner->params.guiFinish();
275 }
276 if (runner->unpaused) {
277 runner->unpaused(runner);
278 }
279 }
280 GBAContextStop(&runner->context);
281 if (runner->gameUnloaded) {
282 runner->gameUnloaded(runner);
283 }
284 GBAContextUnloadROM(&runner->context);
285 drawState.screenshotId = 0;
286 }
287 if (drawState.screenshot) {
288 mappedMemoryFree(drawState.screenshot, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
289 }
290 GUIMenuItemListDeinit(&pauseMenu.items);
291 GUIMenuItemListDeinit(&stateSaveMenu.items);
292 GUIMenuItemListDeinit(&stateLoadMenu.items);
293}