A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 401 lines 12 kB view raw
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}