A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 Franklin Wei, Benjamin Brown
11 * Copyright (C) 2004 Gregory Montoir
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "plugin.h"
24#include "engine.h"
25#include "file.h"
26#include "serializer.h"
27#include "sys.h"
28#include "parts.h"
29#include "video_data.h"
30#include "video.h"
31
32void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir)
33{
34 e->sys = stub;
35 e->sys->e = e;
36 e->_dataDir = dataDir;
37 e->_saveDir = saveDir;
38
39 mixer_create(&e->mixer, e->sys);
40
41 /* this needs to be here and not engine_init() to ensure that it is not called on a reset */
42 res_create(&e->res, &e->video, e->sys, dataDir);
43
44 res_allocMemBlock(&e->res);
45
46 video_create(&e->video, &e->res, e->sys);
47
48 player_create(&e->player, &e->mixer, &e->res, e->sys);
49
50 vm_create(&e->vm, &e->mixer, &e->res, &e->player, &e->video, e->sys);
51}
52
53void engine_run(struct Engine* e) {
54
55 while (!e->sys->input.quit) {
56
57 vm_checkThreadRequests(&e->vm);
58
59 vm_inp_updatePlayer(&e->vm);
60
61 engine_processInput(e);
62
63 vm_hostFrame(&e->vm);
64
65 /* only yield() in the whole game :P */
66 rb->yield();
67 }
68
69}
70
71/*
72 * this function loads the font in XWORLD_FONT_FILE into video_font
73 */
74
75/*
76 * the file format for the font file is like this:
77 * "XFNT" magic
78 * 8-bit version number
79 * <768 bytes data>
80 * sum of data, XOR'ed by version number repeated 4 times (32-bit)
81 */
82bool engine_loadFontFile(struct Engine* e)
83{
84 uint8_t *old_font = sys_get_buffer(e->sys, sizeof(video_font));
85 rb->memcpy(old_font, video_font, sizeof(video_font));
86
87 File f;
88 file_create(&f, false);
89 if(!file_open(&f, XWORLD_FONT_FILE, e->_dataDir, "rb"))
90 {
91 goto fail;
92 }
93
94 /* read header */
95 char header[5];
96 int ret = file_read(&f, header, sizeof(header));
97 if(ret != sizeof(header) ||
98 header[0] != 'X' ||
99 header[1] != 'F' ||
100 header[2] != 'N' ||
101 header[3] != 'T')
102 {
103 warning("Invalid font file signature, falling back to alternate font");
104 goto fail;
105 }
106
107 if(header[4] != XWORLD_FONT_VERSION)
108 {
109 warning("Font file version mismatch (have=%d, need=%d), falling back to alternate font", header[4], XWORLD_FONT_VERSION);
110 goto fail;
111 }
112
113 uint32_t sum = 0;
114 for(unsigned int i = 0;i<sizeof(video_font);++i)
115 {
116 sum += video_font[i] = file_readByte(&f);
117 }
118
119 uint32_t mask = (header[4] << 24) |
120 (header[4] << 16) |
121 (header[4] << 8 ) |
122 (header[4] << 0 );
123 sum ^= mask;
124 uint32_t check = file_readUint32BE(&f);
125
126 if(check != sum)
127 {
128 warning("Bad font checksum, falling back to alternate font");
129 goto fail;
130 }
131
132 file_close(&f);
133 return true;
134
135fail:
136 file_close(&f);
137
138 memcpy(video_font, old_font, sizeof(video_font));
139 return false;
140}
141
142/*
143 * this function loads the string table in STRING_TABLE_FILE into
144 * video_stringsTableEng
145 */
146
147/*
148 * the file format for the string table is like this:
149 * "XWST" magic
150 * 8-bit version number
151 * 8-bit title length
152 * <title data (0-255 bytes, _NO NULL_)
153 * 16-bit number of string entries (currently limited to 255)
154 * entry format:
155 struct file_entry_t
156 {
157 uint16_t id;
158 uint16_t len; - length of str
159 char* str; - NO NULL
160 }
161*/
162bool engine_loadStringTable(struct Engine* e)
163{
164 File f;
165 file_create(&f, false);
166 if(!file_open(&f, STRING_TABLE_FILE, e->_dataDir, "rb"))
167 {
168 /*
169 * this gives verbose warnings while loadFontFile doesn't because the font looks similar
170 * enough to pass for the "original", but the strings don't
171 */
172 /* FW 2017-2-12: eliminated obnoxious warning */
173 /*warning("Unable to find string table, falling back to alternate strings");*/
174 goto fail;
175 }
176
177 /* read header */
178
179 char header[5];
180 int ret = file_read(&f, header, sizeof(header));
181 if(ret != sizeof(header) ||
182 header[0] != 'X' ||
183 header[1] != 'W' ||
184 header[2] != 'S' ||
185 header[3] != 'T')
186 {
187 warning("Invalid string table signature, falling back to alternate strings");
188 goto fail;
189 }
190
191 if(header[4] != STRING_TABLE_VERSION)
192 {
193 warning("String table version mismatch (have=%d, need=%d), falling back to alternate strings", header[4], STRING_TABLE_VERSION);
194 goto fail;
195 }
196
197 /* read title */
198
199 uint8_t title_length = file_readByte(&f);
200 char *title_buf;
201 if(title_length)
202 {
203 title_buf = sys_get_buffer(e->sys, (int32_t)title_length + 1); /* make room for the NULL */
204 ret = file_read(&f, title_buf, title_length);
205 if(ret != title_length)
206 {
207 warning("Title shorter than expected, falling back to alternate strings");
208 goto fail;
209 }
210 }
211 else
212 {
213 title_buf = "UNKNOWN";
214 }
215
216 /* read entries */
217
218 uint16_t num_entries = file_readUint16BE(&f);
219 for(unsigned int i = 0; i < num_entries && i < ARRAYLEN(video_stringsTableEng); ++i)
220 {
221 video_stringsTableEng[i].id = file_readUint16BE(&f);
222 uint16_t len = file_readUint16BE(&f);
223
224 if(file_ioErr(&f))
225 {
226 warning("Unexpected EOF in while parsing entry %d, falling back to alternate strings", i);
227 goto fail;
228 }
229
230 video_stringsTableEng[i].str = sys_get_buffer(e->sys, (int32_t)len + 1);
231
232 ret = file_read(&f, video_stringsTableEng[i].str, len);
233 if(ret != len)
234 {
235 warning("Entry %d too short, falling back to alternate strings", i);
236 goto fail;
237 }
238 }
239
240 file_close(&f);
241 rb->splashf(HZ, "String table '%s' loaded", title_buf);
242 return true;
243fail:
244 file_close(&f);
245 return false;
246}
247
248void engine_init(struct Engine* e) {
249 sys_init(e->sys, "Out Of This World");
250
251 res_readEntries(&e->res);
252
253 engine_loadStringTable(e);
254
255 engine_loadFontFile(e);
256
257 video_init(&e->video);
258
259 vm_init(&e->vm);
260
261 mixer_init(&e->mixer);
262
263 player_init(&e->player);
264
265 /* Init virtual machine, legacy way */
266 vm_initForPart(&e->vm, GAME_PART_FIRST); // This game part is the protection screen */
267
268 /* Try to cheat here. You can jump anywhere but the VM crashes afterward. */
269 /* Starting somewhere is probably not enough, the variables and calls return are probably missing. */
270 /* vm_initForPart(&e->vm, GAME_PART2); Skip protection screen and go directly to intro */
271 /* vm_initForPart(&e->vm, GAME_PART3); CRASH */
272 /* vm_initForPart(&e->vm, GAME_PART4); Start directly in jail but then crash */
273 /* vm->initForPart(&e->vm, GAME_PART5); CRASH */
274 /* vm->initForPart(GAME_PART6); Start in the battlechar but CRASH afteward */
275 /* vm->initForPart(GAME_PART7); CRASH */
276 /* vm->initForPart(GAME_PART8); CRASH */
277 /* vm->initForPart(GAME_PART9); Green screen not doing anything */
278}
279
280void engine_finish(struct Engine* e) {
281 player_free(&e->player);
282 mixer_free(&e->mixer);
283 res_freeMemBlock(&e->res);
284}
285
286void engine_processInput(struct Engine* e) {
287 if (e->sys->input.load) {
288 engine_loadGameState(e, e->_stateSlot);
289 e->sys->input.load = false;
290 }
291 if (e->sys->input.save) {
292 engine_saveGameState(e, e->_stateSlot, "quicksave");
293 e->sys->input.save = false;
294 }
295 if (e->sys->input.fastMode) {
296 e->vm._fastMode = !e->vm._fastMode;
297 e->sys->input.fastMode = false;
298 }
299 if (e->sys->input.stateSlot != 0) {
300 int8_t slot = e->_stateSlot + e->sys->input.stateSlot;
301 if (slot >= 0 && slot < MAX_SAVE_SLOTS) {
302 e->_stateSlot = slot;
303 debug(DBG_INFO, "Current game state slot is %d", e->_stateSlot);
304 }
305 e->sys->input.stateSlot = 0;
306 }
307}
308
309void engine_makeGameStateName(struct Engine* e, uint8_t slot, char *buf, int sz) {
310 (void) e;
311 rb->snprintf(buf, sz, "xworld_save.s%02d", slot);
312}
313
314void engine_saveGameState(struct Engine* e, uint8_t slot, const char *desc) {
315 char stateFile[20];
316 /* sizeof(char) is guaranteed to be 1 */
317 engine_makeGameStateName(e, slot, stateFile, sizeof(stateFile));
318 File f;
319 file_create(&f, false);
320 if (!file_open(&f, stateFile, e->_saveDir, "wb")) {
321 warning("Unable to save state file '%s'", stateFile);
322 } else {
323 /* header */
324 file_writeUint32BE(&f, SAVE_MAGIC);
325 file_writeUint16BE(&f, CUR_VER);
326 file_writeUint16BE(&f, 0);
327 char hdrdesc[32];
328 strncpy(hdrdesc, desc, sizeof(hdrdesc) - 1);
329 file_write(&f, hdrdesc, sizeof(hdrdesc));
330 /* contents */
331 struct Serializer s;
332 ser_create(&s, &f, SM_SAVE, e->res._memPtrStart, CUR_VER);
333 vm_saveOrLoad(&e->vm, &s);
334 res_saveOrLoad(&e->res, &s);
335 video_saveOrLoad(&e->video, &s);
336 player_saveOrLoad(&e->player, &s);
337 mixer_saveOrLoad(&e->mixer, &s);
338 if (file_ioErr(&f)) {
339 warning("I/O error when saving game state");
340 } else {
341 debug(DBG_INFO, "Saved state to slot %d", e->_stateSlot);
342 }
343 }
344 file_close(&f);
345}
346
347bool engine_loadGameState(struct Engine* e, uint8_t slot) {
348 char stateFile[20];
349 engine_makeGameStateName(e, slot, stateFile, 20);
350 File f;
351 file_create(&f, false);
352 if (!file_open(&f, stateFile, e->_saveDir, "rb")) {
353 debug(DBG_ENG, "Unable to open state file '%s'", stateFile);
354 goto fail;
355 } else {
356 uint32_t id = file_readUint32BE(&f);
357 if (id != SAVE_MAGIC) {
358 debug(DBG_ENG, "Bad savegame format");
359 goto fail;
360 } else {
361 /* mute */
362 player_stop(&e->player);
363 mixer_stopAll(&e->mixer);
364 /* header */
365 uint16_t ver = file_readUint16BE(&f);
366 file_readUint16BE(&f);
367 char hdrdesc[32];
368 file_read(&f, hdrdesc, sizeof(hdrdesc));
369 /* contents */
370 struct Serializer s;
371 ser_create(&s, &f, SM_LOAD, e->res._memPtrStart, ver);
372 vm_saveOrLoad(&e->vm, &s);
373 res_saveOrLoad(&e->res, &s);
374 video_saveOrLoad(&e->video, &s);
375 player_saveOrLoad(&e->player, &s);
376 mixer_saveOrLoad(&e->mixer, &s);
377 }
378 if (file_ioErr(&f)) {
379 debug(DBG_ENG, "I/O error when loading game state");
380 goto fail;
381 } else {
382 debug(DBG_INFO, "Loaded state from slot %d", e->_stateSlot);
383 }
384 }
385 file_close(&f);
386 return true;
387fail:
388 file_close(&f);
389 return false;
390}
391
392void engine_deleteGameState(struct Engine* e, uint8_t slot) {
393 char stateFile[20];
394 engine_makeGameStateName(e, slot, stateFile, 20);
395 file_remove(stateFile, e->_saveDir);
396}
397
398const char* engine_getDataDir(struct Engine* e)
399{
400 return e->_dataDir;
401}