A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 445 lines 16 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 "resource.h" 25#include "bank.h" 26#include "file.h" 27#include "serializer.h" 28#include "video.h" 29#include "util.h" 30#include "parts.h" 31#include "vm.h" 32#include "sys.h" 33 34void res_create(struct Resource* res, struct Video* vid, struct System* sys, const char* dataDir) 35{ 36 res->video = vid; 37 res->sys = sys; 38 res->_dataDir = dataDir; 39 res->currentPartId = 0; 40 res->requestedNextPart = 0; 41} 42 43void res_readBank(struct Resource* res, const MemEntry *me, uint8_t *dstBuf) { 44 uint16_t n = me - res->_memList; 45 debug(DBG_BANK, "res_readBank(%d)", n); 46 47 struct Bank bk; 48 bank_create(&bk, res->_dataDir); 49 if (!bank_read(&bk, me, dstBuf)) { 50 error("res_readBank() unable to unpack entry %d\n", n); 51 } 52} 53 54#ifdef XWORLD_DEBUG 55static const char *resTypeToString(struct Resource* res, unsigned int type) 56{ 57 (void) res; 58 static const char* resTypes[] = 59 { 60 "RT_SOUND", 61 "RT_MUSIC", 62 "RT_POLY_ANIM", 63 "RT_PALETTE", 64 "RT_BYTECODE", 65 "RT_POLY_CINEMATIC" 66 }; 67 if (type >= (sizeof(resTypes) / sizeof(const char *))) 68 return "RT_UNKNOWN"; 69 return resTypes[type]; 70} 71#endif 72 73#define RES_SIZE 0 74#define RES_COMPRESSED 1 75int resourceSizeStats[7][2]; 76#define STATS_TOTAL_SIZE 6 77int resourceUnitStats[7][2]; 78 79/* 80 Read all entries from memlist.bin. Do not load anything in memory, 81 this is just a fast way to access the data later based on their id. 82*/ 83void res_readEntries(struct Resource* res) { 84 File f; 85 file_create(&f, false); 86 87 int resourceCounter = 0; 88 89 if (!file_open(&f, "memlist.bin", res->_dataDir, "rb")) { 90 error("Could not open 'MEMLIST.BIN', data files missing"); 91 /* error() will exit() no need to return or do anything else. */ 92 } 93 94 /* Prepare stats array */ 95 rb->memset(resourceSizeStats, 0, sizeof(resourceSizeStats)); 96 rb->memset(resourceUnitStats, 0, sizeof(resourceUnitStats)); 97 98 res->_numMemList = 0; 99 struct MemEntry *memEntry = res->_memList; 100 while (1) { 101 assert(res->_numMemList < ARRAYLEN(res->_memList)); 102 memEntry->state = file_readByte(&f); 103 memEntry->type = file_readByte(&f); 104 memEntry->bufPtr = 0; 105 file_readUint16BE(&f); 106 memEntry->unk4 = file_readUint16BE(&f); 107 memEntry->rankNum = file_readByte(&f); 108 memEntry->bankId = file_readByte(&f); 109 memEntry->bankOffset = file_readUint32BE(&f); 110 memEntry->unkC = file_readUint16BE(&f); 111 memEntry->packedSize = file_readUint16BE(&f); 112 memEntry->unk10 = file_readUint16BE(&f); 113 memEntry->size = file_readUint16BE(&f); 114 115 debug(DBG_RES, "mementry state is %d", memEntry->state); 116 if (memEntry->state == MEMENTRY_STATE_END_OF_MEMLIST) { 117 break; 118 } 119 120 /* Memory tracking */ 121 if (memEntry->packedSize == memEntry->size) 122 { 123 resourceUnitStats[memEntry->type][RES_SIZE] ++; 124 resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] ++; 125 } 126 else 127 { 128 resourceUnitStats[memEntry->type][RES_COMPRESSED] ++; 129 resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] ++; 130 } 131 132 resourceSizeStats[memEntry->type][RES_SIZE] += memEntry->size; 133 resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] += memEntry->size; 134 resourceSizeStats[memEntry->type][RES_COMPRESSED] += memEntry->packedSize; 135 resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] += memEntry->packedSize; 136 137 debug(DBG_RES, "R:0x%02X, %-17s size=%5d (compacted gain=%2.0f%%)", 138 resourceCounter, 139 resTypeToString(res, memEntry->type), 140 memEntry->size, 141 memEntry->size ? (memEntry->size - memEntry->packedSize) / (float)memEntry->size * 100.0f : 0.0f); 142 143 resourceCounter++; 144 145 res->_numMemList++; 146 memEntry++; 147 } 148 149 debug(DBG_RES, "\n"); 150 debug(DBG_RES, "Total # resources: %d", resourceCounter); 151 debug(DBG_RES, "Compressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); 152 debug(DBG_RES, "Uncompressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE]); 153 debug(DBG_RES, "Note: %2.0f%% of resources are compressed.", 100 * resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] / (float)resourceCounter); 154 debug(DBG_RES, "\n"); 155 debug(DBG_RES, "Total size (uncompressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE]); 156 debug(DBG_RES, "Total size (compressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); 157 debug(DBG_RES, "Note: Overall compression gain is : %2.0f%%.", 158 (resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] - resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]) / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100); 159 160 debug(DBG_RES, "\n"); 161 for(int i = 0 ; i < 6 ; i++) 162 debug(DBG_RES, "Total %-17s unpacked size: %7d (%2.0f%% of total unpacked size) packedSize %7d (%2.0f%% of floppy space) gain:(%2.0f%%)", 163 resTypeToString(res, i), 164 resourceSizeStats[i][RES_SIZE], 165 resourceSizeStats[i][RES_SIZE] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100.0f, 166 resourceSizeStats[i][RES_COMPRESSED], 167 resourceSizeStats[i][RES_COMPRESSED] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] * 100.0f, 168 (resourceSizeStats[i][RES_SIZE] - resourceSizeStats[i][RES_COMPRESSED]) / (float)resourceSizeStats[i][RES_SIZE] * 100.0f); 169 170 debug(DBG_RES, "Note: Damn you sound compression rate!"); 171 172 debug(DBG_RES, "\nTotal bank files: %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] + resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); 173 for(int i = 0 ; i < 6 ; i++) 174 debug(DBG_RES, "Total %-17s files: %3d", resTypeToString(res, i), resourceUnitStats[i][RES_SIZE] + resourceUnitStats[i][RES_COMPRESSED]); 175 176 file_close(&f); 177} 178 179/* 180 Go over every resource and check if they are marked at "MEMENTRY_STATE_LOAD_ME". 181 Load them in memory and mark them are MEMENTRY_STATE_LOADED 182*/ 183void res_loadMarkedAsNeeded(struct Resource* res) { 184 185 while (1) { 186 struct MemEntry *me = NULL; 187 188 /* get resource with max rankNum */ 189 uint8_t maxNum = 0; 190 uint16_t i = res->_numMemList; 191 struct MemEntry *it = res->_memList; 192 while (i--) { 193 if (it->state == MEMENTRY_STATE_LOAD_ME && maxNum <= it->rankNum) { 194 maxNum = it->rankNum; 195 me = it; 196 } 197 it++; 198 } 199 200 if (me == NULL) { 201 break; // no entry found 202 } 203 204 205 /* At this point the resource descriptor should be pointed to "me" */ 206 207 uint8_t *loadDestination = NULL; 208 if (me->type == RT_POLY_ANIM) { 209 loadDestination = res->_vidCurPtr; 210 } else { 211 loadDestination = res->_scriptCurPtr; 212 if (me->size > res->_vidBakPtr - res->_scriptCurPtr) { 213 warning("res_load() not enough memory"); 214 me->state = MEMENTRY_STATE_NOT_NEEDED; 215 continue; 216 } 217 } 218 219 220 if (me->bankId == 0) { 221 warning("res_load() ec=0x%X (me->bankId == 0)", 0xF00); 222 me->state = MEMENTRY_STATE_NOT_NEEDED; 223 } else { 224 debug(DBG_BANK, "res_load() bufPos=%X size=%X type=%X pos=%X bankId=%X", loadDestination - res->_memPtrStart, me->packedSize, me->type, me->bankOffset, me->bankId); 225 res_readBank(res, me, loadDestination); 226 if(me->type == RT_POLY_ANIM) { 227 video_copyPagePtr(res->video, res->_vidCurPtr); 228 me->state = MEMENTRY_STATE_NOT_NEEDED; 229 } else { 230 me->bufPtr = loadDestination; 231 me->state = MEMENTRY_STATE_LOADED; 232 res->_scriptCurPtr += me->size; 233 } 234 } 235 } 236} 237 238void res_invalidateRes(struct Resource* res) { 239 struct MemEntry *me = res->_memList; 240 uint16_t i = res->_numMemList; 241 while (i--) { 242 if (me->type <= RT_POLY_ANIM || me->type > 6) { /* 6 WTF ?!?! ResType goes up to 5 !! */ 243 me->state = MEMENTRY_STATE_NOT_NEEDED; 244 } 245 ++me; 246 } 247 res->_scriptCurPtr = res->_scriptBakPtr; 248} 249 250void res_invalidateAll(struct Resource* res) { 251 struct MemEntry *me = res->_memList; 252 uint16_t i = res->_numMemList; 253 while (i--) { 254 me->state = MEMENTRY_STATE_NOT_NEEDED; 255 ++me; 256 } 257 res->_scriptCurPtr = res->_memPtrStart; 258} 259 260/* This method serves two purpose: 261 - Load parts in memory segments (palette,code,video1,video2) 262 or 263 - Load a resource in memory 264 265 This is decided based on the resourceId. If it does not match a mementry id it is supposed to 266 be a part id. */ 267void res_loadPartsOrMemoryEntry(struct Resource* res, uint16_t resourceId) { 268 269 if (resourceId > res->_numMemList) { 270 271 res->requestedNextPart = resourceId; 272 273 } else { 274 275 struct MemEntry *me = &res->_memList[resourceId]; 276 277 if (me->state == MEMENTRY_STATE_NOT_NEEDED) { 278 me->state = MEMENTRY_STATE_LOAD_ME; 279 res_loadMarkedAsNeeded(res); 280 } 281 } 282 283} 284 285/* Protection screen and cinematic don't need the player and enemies polygon data 286 so _memList[video2Index] is never loaded for those parts of the game. When 287 needed (for action phrases) _memList[video2Index] is always loaded with 0x11 288 (as seen in memListParts). */ 289void res_setupPart(struct Resource* res, uint16_t partId) { 290 291 if (partId == res->currentPartId) 292 return; 293 294 if (partId < GAME_PART_FIRST || partId > GAME_PART_LAST) 295 error("res_setupPart() ec=0x%X invalid partId", partId); 296 297 uint16_t memListPartIndex = partId - GAME_PART_FIRST; 298 299 uint8_t paletteIndex = memListParts[memListPartIndex][MEMLIST_PART_PALETTE]; 300 uint8_t codeIndex = memListParts[memListPartIndex][MEMLIST_PART_CODE]; 301 uint8_t videoCinematicIndex = memListParts[memListPartIndex][MEMLIST_PART_POLY_CINEMATIC]; 302 uint8_t video2Index = memListParts[memListPartIndex][MEMLIST_PART_VIDEO2]; 303 304 /* Mark all resources as located on harddrive. */ 305 res_invalidateAll(res); 306 307 res->_memList[paletteIndex].state = MEMENTRY_STATE_LOAD_ME; 308 res->_memList[codeIndex].state = MEMENTRY_STATE_LOAD_ME; 309 res->_memList[videoCinematicIndex].state = MEMENTRY_STATE_LOAD_ME; 310 311 /* This is probably a cinematic or a non interactive part of the game. */ 312 /* Player and enemy polygons are not needed. */ 313 if (video2Index != MEMLIST_PART_NONE) 314 res->_memList[video2Index].state = MEMENTRY_STATE_LOAD_ME; 315 316 317 res_loadMarkedAsNeeded(res); 318 319 res->segPalettes = res->_memList[paletteIndex].bufPtr; 320 debug(DBG_RES, "paletteIndex is 0x%08x", res->segPalettes); 321 res->segBytecode = res->_memList[codeIndex].bufPtr; 322 res->segCinematic = res->_memList[videoCinematicIndex].bufPtr; 323 324 325 326 /* This is probably a cinematic or a non interactive part of the game. */ 327 /* Player and enemy polygons are not needed. */ 328 if (video2Index != MEMLIST_PART_NONE) 329 res->_segVideo2 = res->_memList[video2Index].bufPtr; 330 331 debug(DBG_RES, ""); 332 debug(DBG_RES, "setupPart(%d)", partId - GAME_PART_FIRST); 333 debug(DBG_RES, "Loaded resource %d (%s) in segPalettes.", paletteIndex, resTypeToString(res, res->_memList[paletteIndex].type)); 334 debug(DBG_RES, "Loaded resource %d (%s) in segBytecode.", codeIndex, resTypeToString(res, res->_memList[codeIndex].type)); 335 debug(DBG_RES, "Loaded resource %d (%s) in segCinematic.", videoCinematicIndex, resTypeToString(res, res->_memList[videoCinematicIndex].type)); 336 337 338 /* prevent warnings: */ 339#ifdef XWORLD_DEBUG 340 if (video2Index != MEMLIST_PART_NONE) 341 debug(DBG_RES, "Loaded resource %d (%s) in _segVideo2.", video2Index, resTypeToString(res, res->_memList[video2Index].type)); 342#endif 343 344 345 res->currentPartId = partId; 346 347 348 /* _scriptCurPtr is changed in res->load(); */ 349 res->_scriptBakPtr = res->_scriptCurPtr; 350} 351 352void res_allocMemBlock(struct Resource* res) { 353 if(rb->audio_status()) 354 rb->audio_stop(); 355 /* steal the audio buffer */ 356 size_t sz; 357 /* memory usage is first statically allocated, then the remainder is used dynamically: 358 * static: 359 * [VM memory - 600K] 360 * [Framebuffers - 128K] 361 * [Temporary framebuffer - 192K] 362 * dynamic: 363 * [String table buffer] 364 */ 365 res->_memPtrStart = rb->plugin_get_audio_buffer(&sz); 366 if(sz < MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)) 367 { 368 warning("res_allocMemBlock: can't allocate enough memory!"); 369 } 370 371 res->sys->membuf = res->_memPtrStart + ( MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); 372 res->sys->bytes_left = sz - (MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); 373 374 debug(DBG_RES, "audiobuf is %d bytes in size (%d bytes left)", sz, res->sys->bytes_left); 375 376 res->_scriptBakPtr = res->_scriptCurPtr = res->_memPtrStart; 377 res->_vidBakPtr = res->_vidCurPtr = res->_memPtrStart + MEM_BLOCK_SIZE - 0x800 * 16; //0x800 = 2048, so we have 32KB free for vidBack and vidCur 378 res->_useSegVideo2 = false; 379} 380 381void res_freeMemBlock(struct Resource* res) { 382 (void) res; 383 /* there's no need to do anything to free the audio buffer */ 384 return; 385} 386 387void res_saveOrLoad(struct Resource* res, struct Serializer *ser) { 388 uint8_t loadedList[64]; 389 if (ser->_mode == SM_SAVE) { 390 rb->memset(loadedList, 0, sizeof(loadedList)); 391 uint8_t *p = loadedList; 392 uint8_t *q = res->_memPtrStart; 393 while (1) { 394 struct MemEntry *it = res->_memList; 395 struct MemEntry *me = 0; 396 uint16_t num = res->_numMemList; 397 while (num--) { 398 if (it->state == MEMENTRY_STATE_LOADED && it->bufPtr == q) { 399 me = it; 400 } 401 ++it; 402 } 403 if (me == 0) { 404 break; 405 } else { 406 assert(p < loadedList + 64); 407 *p++ = me - res->_memList; 408 q += me->size; 409 } 410 } 411 } 412 413 struct Entry entries[] = { 414 SE_ARRAY(loadedList, 64, SES_INT8, VER(1)), 415 SE_INT(&res->currentPartId, SES_INT16, VER(1)), 416 SE_PTR(&res->_scriptBakPtr, VER(1)), 417 SE_PTR(&res->_scriptCurPtr, VER(1)), 418 SE_PTR(&res->_vidBakPtr, VER(1)), 419 SE_PTR(&res->_vidCurPtr, VER(1)), 420 SE_INT(&res->_useSegVideo2, SES_BOOL, VER(1)), 421 SE_PTR(&res->segPalettes, VER(1)), 422 SE_PTR(&res->segBytecode, VER(1)), 423 SE_PTR(&res->segCinematic, VER(1)), 424 SE_PTR(&res->_segVideo2, VER(1)), 425 SE_END() 426 }; 427 428 ser_saveOrLoadEntries(ser, entries); 429 if (ser->_mode == SM_LOAD) { 430 uint8_t *p = loadedList; 431 uint8_t *q = res->_memPtrStart; 432 while (*p) { 433 struct MemEntry *me = &res->_memList[*p++]; 434 res_readBank(res, me, q); 435 me->bufPtr = q; 436 me->state = MEMENTRY_STATE_LOADED; 437 q += me->size; 438 } 439 } 440} 441 442const char* res_getDataDir(struct Resource* res) 443{ 444 return res->_dataDir; 445}