/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2014 Franklin Wei, Benjamin Brown * Copyright (C) 2004 Gregory Montoir * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ***************************************************************************/ #include "plugin.h" #include "resource.h" #include "bank.h" #include "file.h" #include "serializer.h" #include "video.h" #include "util.h" #include "parts.h" #include "vm.h" #include "sys.h" void res_create(struct Resource* res, struct Video* vid, struct System* sys, const char* dataDir) { res->video = vid; res->sys = sys; res->_dataDir = dataDir; res->currentPartId = 0; res->requestedNextPart = 0; } void res_readBank(struct Resource* res, const MemEntry *me, uint8_t *dstBuf) { uint16_t n = me - res->_memList; debug(DBG_BANK, "res_readBank(%d)", n); struct Bank bk; bank_create(&bk, res->_dataDir); if (!bank_read(&bk, me, dstBuf)) { error("res_readBank() unable to unpack entry %d\n", n); } } #ifdef XWORLD_DEBUG static const char *resTypeToString(struct Resource* res, unsigned int type) { (void) res; static const char* resTypes[] = { "RT_SOUND", "RT_MUSIC", "RT_POLY_ANIM", "RT_PALETTE", "RT_BYTECODE", "RT_POLY_CINEMATIC" }; if (type >= (sizeof(resTypes) / sizeof(const char *))) return "RT_UNKNOWN"; return resTypes[type]; } #endif #define RES_SIZE 0 #define RES_COMPRESSED 1 int resourceSizeStats[7][2]; #define STATS_TOTAL_SIZE 6 int resourceUnitStats[7][2]; /* Read all entries from memlist.bin. Do not load anything in memory, this is just a fast way to access the data later based on their id. */ void res_readEntries(struct Resource* res) { File f; file_create(&f, false); int resourceCounter = 0; if (!file_open(&f, "memlist.bin", res->_dataDir, "rb")) { error("Could not open 'MEMLIST.BIN', data files missing"); /* error() will exit() no need to return or do anything else. */ } /* Prepare stats array */ rb->memset(resourceSizeStats, 0, sizeof(resourceSizeStats)); rb->memset(resourceUnitStats, 0, sizeof(resourceUnitStats)); res->_numMemList = 0; struct MemEntry *memEntry = res->_memList; while (1) { assert(res->_numMemList < ARRAYLEN(res->_memList)); memEntry->state = file_readByte(&f); memEntry->type = file_readByte(&f); memEntry->bufPtr = 0; file_readUint16BE(&f); memEntry->unk4 = file_readUint16BE(&f); memEntry->rankNum = file_readByte(&f); memEntry->bankId = file_readByte(&f); memEntry->bankOffset = file_readUint32BE(&f); memEntry->unkC = file_readUint16BE(&f); memEntry->packedSize = file_readUint16BE(&f); memEntry->unk10 = file_readUint16BE(&f); memEntry->size = file_readUint16BE(&f); debug(DBG_RES, "mementry state is %d", memEntry->state); if (memEntry->state == MEMENTRY_STATE_END_OF_MEMLIST) { break; } /* Memory tracking */ if (memEntry->packedSize == memEntry->size) { resourceUnitStats[memEntry->type][RES_SIZE] ++; resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] ++; } else { resourceUnitStats[memEntry->type][RES_COMPRESSED] ++; resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] ++; } resourceSizeStats[memEntry->type][RES_SIZE] += memEntry->size; resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] += memEntry->size; resourceSizeStats[memEntry->type][RES_COMPRESSED] += memEntry->packedSize; resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] += memEntry->packedSize; debug(DBG_RES, "R:0x%02X, %-17s size=%5d (compacted gain=%2.0f%%)", resourceCounter, resTypeToString(res, memEntry->type), memEntry->size, memEntry->size ? (memEntry->size - memEntry->packedSize) / (float)memEntry->size * 100.0f : 0.0f); resourceCounter++; res->_numMemList++; memEntry++; } debug(DBG_RES, "\n"); debug(DBG_RES, "Total # resources: %d", resourceCounter); debug(DBG_RES, "Compressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); debug(DBG_RES, "Uncompressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE]); debug(DBG_RES, "Note: %2.0f%% of resources are compressed.", 100 * resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] / (float)resourceCounter); debug(DBG_RES, "\n"); debug(DBG_RES, "Total size (uncompressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE]); debug(DBG_RES, "Total size (compressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); debug(DBG_RES, "Note: Overall compression gain is : %2.0f%%.", (resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] - resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]) / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100); debug(DBG_RES, "\n"); for(int i = 0 ; i < 6 ; i++) debug(DBG_RES, "Total %-17s unpacked size: %7d (%2.0f%% of total unpacked size) packedSize %7d (%2.0f%% of floppy space) gain:(%2.0f%%)", resTypeToString(res, i), resourceSizeStats[i][RES_SIZE], resourceSizeStats[i][RES_SIZE] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100.0f, resourceSizeStats[i][RES_COMPRESSED], resourceSizeStats[i][RES_COMPRESSED] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] * 100.0f, (resourceSizeStats[i][RES_SIZE] - resourceSizeStats[i][RES_COMPRESSED]) / (float)resourceSizeStats[i][RES_SIZE] * 100.0f); debug(DBG_RES, "Note: Damn you sound compression rate!"); debug(DBG_RES, "\nTotal bank files: %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] + resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); for(int i = 0 ; i < 6 ; i++) debug(DBG_RES, "Total %-17s files: %3d", resTypeToString(res, i), resourceUnitStats[i][RES_SIZE] + resourceUnitStats[i][RES_COMPRESSED]); file_close(&f); } /* Go over every resource and check if they are marked at "MEMENTRY_STATE_LOAD_ME". Load them in memory and mark them are MEMENTRY_STATE_LOADED */ void res_loadMarkedAsNeeded(struct Resource* res) { while (1) { struct MemEntry *me = NULL; /* get resource with max rankNum */ uint8_t maxNum = 0; uint16_t i = res->_numMemList; struct MemEntry *it = res->_memList; while (i--) { if (it->state == MEMENTRY_STATE_LOAD_ME && maxNum <= it->rankNum) { maxNum = it->rankNum; me = it; } it++; } if (me == NULL) { break; // no entry found } /* At this point the resource descriptor should be pointed to "me" */ uint8_t *loadDestination = NULL; if (me->type == RT_POLY_ANIM) { loadDestination = res->_vidCurPtr; } else { loadDestination = res->_scriptCurPtr; if (me->size > res->_vidBakPtr - res->_scriptCurPtr) { warning("res_load() not enough memory"); me->state = MEMENTRY_STATE_NOT_NEEDED; continue; } } if (me->bankId == 0) { warning("res_load() ec=0x%X (me->bankId == 0)", 0xF00); me->state = MEMENTRY_STATE_NOT_NEEDED; } else { 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); res_readBank(res, me, loadDestination); if(me->type == RT_POLY_ANIM) { video_copyPagePtr(res->video, res->_vidCurPtr); me->state = MEMENTRY_STATE_NOT_NEEDED; } else { me->bufPtr = loadDestination; me->state = MEMENTRY_STATE_LOADED; res->_scriptCurPtr += me->size; } } } } void res_invalidateRes(struct Resource* res) { struct MemEntry *me = res->_memList; uint16_t i = res->_numMemList; while (i--) { if (me->type <= RT_POLY_ANIM || me->type > 6) { /* 6 WTF ?!?! ResType goes up to 5 !! */ me->state = MEMENTRY_STATE_NOT_NEEDED; } ++me; } res->_scriptCurPtr = res->_scriptBakPtr; } void res_invalidateAll(struct Resource* res) { struct MemEntry *me = res->_memList; uint16_t i = res->_numMemList; while (i--) { me->state = MEMENTRY_STATE_NOT_NEEDED; ++me; } res->_scriptCurPtr = res->_memPtrStart; } /* This method serves two purpose: - Load parts in memory segments (palette,code,video1,video2) or - Load a resource in memory This is decided based on the resourceId. If it does not match a mementry id it is supposed to be a part id. */ void res_loadPartsOrMemoryEntry(struct Resource* res, uint16_t resourceId) { if (resourceId > res->_numMemList) { res->requestedNextPart = resourceId; } else { struct MemEntry *me = &res->_memList[resourceId]; if (me->state == MEMENTRY_STATE_NOT_NEEDED) { me->state = MEMENTRY_STATE_LOAD_ME; res_loadMarkedAsNeeded(res); } } } /* Protection screen and cinematic don't need the player and enemies polygon data so _memList[video2Index] is never loaded for those parts of the game. When needed (for action phrases) _memList[video2Index] is always loaded with 0x11 (as seen in memListParts). */ void res_setupPart(struct Resource* res, uint16_t partId) { if (partId == res->currentPartId) return; if (partId < GAME_PART_FIRST || partId > GAME_PART_LAST) error("res_setupPart() ec=0x%X invalid partId", partId); uint16_t memListPartIndex = partId - GAME_PART_FIRST; uint8_t paletteIndex = memListParts[memListPartIndex][MEMLIST_PART_PALETTE]; uint8_t codeIndex = memListParts[memListPartIndex][MEMLIST_PART_CODE]; uint8_t videoCinematicIndex = memListParts[memListPartIndex][MEMLIST_PART_POLY_CINEMATIC]; uint8_t video2Index = memListParts[memListPartIndex][MEMLIST_PART_VIDEO2]; /* Mark all resources as located on harddrive. */ res_invalidateAll(res); res->_memList[paletteIndex].state = MEMENTRY_STATE_LOAD_ME; res->_memList[codeIndex].state = MEMENTRY_STATE_LOAD_ME; res->_memList[videoCinematicIndex].state = MEMENTRY_STATE_LOAD_ME; /* This is probably a cinematic or a non interactive part of the game. */ /* Player and enemy polygons are not needed. */ if (video2Index != MEMLIST_PART_NONE) res->_memList[video2Index].state = MEMENTRY_STATE_LOAD_ME; res_loadMarkedAsNeeded(res); res->segPalettes = res->_memList[paletteIndex].bufPtr; debug(DBG_RES, "paletteIndex is 0x%08x", res->segPalettes); res->segBytecode = res->_memList[codeIndex].bufPtr; res->segCinematic = res->_memList[videoCinematicIndex].bufPtr; /* This is probably a cinematic or a non interactive part of the game. */ /* Player and enemy polygons are not needed. */ if (video2Index != MEMLIST_PART_NONE) res->_segVideo2 = res->_memList[video2Index].bufPtr; debug(DBG_RES, ""); debug(DBG_RES, "setupPart(%d)", partId - GAME_PART_FIRST); debug(DBG_RES, "Loaded resource %d (%s) in segPalettes.", paletteIndex, resTypeToString(res, res->_memList[paletteIndex].type)); debug(DBG_RES, "Loaded resource %d (%s) in segBytecode.", codeIndex, resTypeToString(res, res->_memList[codeIndex].type)); debug(DBG_RES, "Loaded resource %d (%s) in segCinematic.", videoCinematicIndex, resTypeToString(res, res->_memList[videoCinematicIndex].type)); /* prevent warnings: */ #ifdef XWORLD_DEBUG if (video2Index != MEMLIST_PART_NONE) debug(DBG_RES, "Loaded resource %d (%s) in _segVideo2.", video2Index, resTypeToString(res, res->_memList[video2Index].type)); #endif res->currentPartId = partId; /* _scriptCurPtr is changed in res->load(); */ res->_scriptBakPtr = res->_scriptCurPtr; } void res_allocMemBlock(struct Resource* res) { if(rb->audio_status()) rb->audio_stop(); /* steal the audio buffer */ size_t sz; /* memory usage is first statically allocated, then the remainder is used dynamically: * static: * [VM memory - 600K] * [Framebuffers - 128K] * [Temporary framebuffer - 192K] * dynamic: * [String table buffer] */ res->_memPtrStart = rb->plugin_get_audio_buffer(&sz); if(sz < MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)) { warning("res_allocMemBlock: can't allocate enough memory!"); } res->sys->membuf = res->_memPtrStart + ( MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); res->sys->bytes_left = sz - (MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); debug(DBG_RES, "audiobuf is %d bytes in size (%d bytes left)", sz, res->sys->bytes_left); res->_scriptBakPtr = res->_scriptCurPtr = res->_memPtrStart; res->_vidBakPtr = res->_vidCurPtr = res->_memPtrStart + MEM_BLOCK_SIZE - 0x800 * 16; //0x800 = 2048, so we have 32KB free for vidBack and vidCur res->_useSegVideo2 = false; } void res_freeMemBlock(struct Resource* res) { (void) res; /* there's no need to do anything to free the audio buffer */ return; } void res_saveOrLoad(struct Resource* res, struct Serializer *ser) { uint8_t loadedList[64]; if (ser->_mode == SM_SAVE) { rb->memset(loadedList, 0, sizeof(loadedList)); uint8_t *p = loadedList; uint8_t *q = res->_memPtrStart; while (1) { struct MemEntry *it = res->_memList; struct MemEntry *me = 0; uint16_t num = res->_numMemList; while (num--) { if (it->state == MEMENTRY_STATE_LOADED && it->bufPtr == q) { me = it; } ++it; } if (me == 0) { break; } else { assert(p < loadedList + 64); *p++ = me - res->_memList; q += me->size; } } } struct Entry entries[] = { SE_ARRAY(loadedList, 64, SES_INT8, VER(1)), SE_INT(&res->currentPartId, SES_INT16, VER(1)), SE_PTR(&res->_scriptBakPtr, VER(1)), SE_PTR(&res->_scriptCurPtr, VER(1)), SE_PTR(&res->_vidBakPtr, VER(1)), SE_PTR(&res->_vidCurPtr, VER(1)), SE_INT(&res->_useSegVideo2, SES_BOOL, VER(1)), SE_PTR(&res->segPalettes, VER(1)), SE_PTR(&res->segBytecode, VER(1)), SE_PTR(&res->segCinematic, VER(1)), SE_PTR(&res->_segVideo2, VER(1)), SE_END() }; ser_saveOrLoadEntries(ser, entries); if (ser->_mode == SM_LOAD) { uint8_t *p = loadedList; uint8_t *q = res->_memPtrStart; while (*p) { struct MemEntry *me = &res->_memList[*p++]; res_readBank(res, me, q); me->bufPtr = q; me->state = MEMENTRY_STATE_LOADED; q += me->size; } } } const char* res_getDataDir(struct Resource* res) { return res->_dataDir; }