/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/scummsys.h" #include "scumm/scumm.h" #include "scumm/util.h" #include "scumm/file.h" #include "scumm/imuse_digi/dimuse_bndmgr.h" #include "scumm/imuse_digi/dimuse_codecs.h" namespace Scumm { BundleDirCache::BundleDirCache(const ScummEngine *vm) : _vm(vm) { for (int fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) { _bundleDirCache[fileId].bundleTable = nullptr; _bundleDirCache[fileId].fileName[0] = 0; _bundleDirCache[fileId].numFiles = 0; _bundleDirCache[fileId].isCompressed = false; _bundleDirCache[fileId].indexTable = nullptr; } } BundleDirCache::~BundleDirCache() { for (int fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) { free(_bundleDirCache[fileId].bundleTable); free(_bundleDirCache[fileId].indexTable); } } BundleDirCache::AudioTable *BundleDirCache::getTable(int slot) { return _bundleDirCache[slot].bundleTable; } int32 BundleDirCache::getNumFiles(int slot) { return _bundleDirCache[slot].numFiles; } BundleDirCache::IndexNode *BundleDirCache::getIndexTable(int slot) { return _bundleDirCache[slot].indexTable; } bool BundleDirCache::isSndDataExtComp(int slot) { return _bundleDirCache[slot].isCompressed; } int BundleDirCache::matchFile(const char *filename) { int32 tag, offset; bool found = false; int freeSlot = -1; int fileId; for (fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) { if ((_bundleDirCache[fileId].bundleTable == nullptr) && (freeSlot == -1)) { freeSlot = fileId; } if (scumm_stricmp(filename, _bundleDirCache[fileId].fileName) == 0) { found = true; break; } } if (!found) { ScummFile file(_vm); if (g_scumm->openFile(file, filename) == false) { error("BundleDirCache::matchFile() Can't open bundle file: %s", filename); return false; } if (freeSlot == -1) error("BundleDirCache::matchFileFile() Can't find free slot for file bundle dir cache"); tag = file.readUint32BE(); if (tag == MKTAG('L','B','2','3')) _bundleDirCache[freeSlot].isCompressed = true; offset = file.readUint32BE(); Common::strlcpy(_bundleDirCache[freeSlot].fileName, filename, sizeof(_bundleDirCache[freeSlot].fileName)); _bundleDirCache[freeSlot].numFiles = file.readUint32BE(); _bundleDirCache[freeSlot].bundleTable = (AudioTable *)malloc(_bundleDirCache[freeSlot].numFiles * sizeof(AudioTable)); assert(_bundleDirCache[freeSlot].bundleTable); file.seek(offset, SEEK_SET); _bundleDirCache[freeSlot].indexTable = (IndexNode *)calloc(_bundleDirCache[freeSlot].numFiles, sizeof(IndexNode)); assert(_bundleDirCache[freeSlot].indexTable); for (int32 i = 0; i < _bundleDirCache[freeSlot].numFiles; i++) { char name[24], c; int32 z = 0; int32 z2; if (tag == MKTAG('L','B','2','3')) { file.read(_bundleDirCache[freeSlot].bundleTable[i].filename, 24); } else { for (z2 = 0; z2 < 8; z2++) if ((c = file.readByte()) != 0) name[z++] = c; name[z++] = '.'; for (z2 = 0; z2 < 4; z2++) if ((c = file.readByte()) != 0) name[z++] = c; name[z] = '\0'; Common::strlcpy(_bundleDirCache[freeSlot].bundleTable[i].filename, name, sizeof(_bundleDirCache[freeSlot].bundleTable[i].filename)); } _bundleDirCache[freeSlot].bundleTable[i].offset = file.readUint32BE(); _bundleDirCache[freeSlot].bundleTable[i].size = file.readUint32BE(); Common::strlcpy(_bundleDirCache[freeSlot].indexTable[i].filename, _bundleDirCache[freeSlot].bundleTable[i].filename, sizeof(_bundleDirCache[freeSlot].indexTable[i].filename)); _bundleDirCache[freeSlot].indexTable[i].index = i; } qsort(_bundleDirCache[freeSlot].indexTable, _bundleDirCache[freeSlot].numFiles, sizeof(IndexNode), (int (*)(const void *, const void *))scumm_stricmp); return freeSlot; } else { return fileId; } } BundleMgr::BundleMgr(const ScummEngine *vm, BundleDirCache *cache) { _cache = cache; _bundleTable = nullptr; _compTable = nullptr; _numFiles = 0; _numCompItems = 0; _lastBlockDecompressedSize = 0; _curSampleId = -1; _fileBundleId = -1; _file = new ScummFile(vm); _compInputBuff = nullptr; } BundleMgr::~BundleMgr() { close(); delete _file; } Common::SeekableReadStream *BundleMgr::getFile(const char *filename, int32 &offset, int32 &size) { BundleDirCache::IndexNode target; Common::strlcpy(target.filename, filename, sizeof(target.filename)); BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles, sizeof(BundleDirCache::IndexNode), (int (*)(const void *, const void *))scumm_stricmp); if (found) { _file->seek(_bundleTable[found->index].offset, SEEK_SET); offset = _bundleTable[found->index].offset; size = _bundleTable[found->index].size; return _file; } return nullptr; } bool BundleMgr::open(const char *filename, bool &isCompressed, bool errorFlag) { if (_file->isOpen()) return true; if (g_scumm->openFile(*_file, filename) == false) { if (errorFlag) { error("BundleMgr::open() Can't open bundle file: %s", filename); } else { warning("BundleMgr::open() Can't open bundle file: %s", filename); } return false; } int slot = _cache->matchFile(filename); assert(slot != -1); isCompressed = _cache->isSndDataExtComp(slot); _numFiles = _cache->getNumFiles(slot); assert(_numFiles); _bundleTable = _cache->getTable(slot); _indexTable = _cache->getIndexTable(slot); assert(_bundleTable); _compTableLoaded = false; _isUncompressed = false; _outputSize = 0; _lastBlockDecompressedSize = 0; _curDecompressedFilePos = 0; _lastBlock = -1; return true; } void BundleMgr::close() { if (_file->isOpen()) { _file->close(); _bundleTable = nullptr; _numFiles = 0; _numCompItems = 0; _lastBlockDecompressedSize = 0; _curDecompressedFilePos = 0; _compTableLoaded = false; _isUncompressed = false; _lastBlock = -1; _outputSize = 0; _curSampleId = -1; free(_compTable); _compTable = nullptr; free(_compInputBuff); _compInputBuff = nullptr; } } bool BundleMgr::loadCompTable(int32 index) { _file->seek(_bundleTable[index].offset, SEEK_SET); uint32 tag = _file->readUint32BE(); if (tag == MKTAG('i','M','U','S')) { _isUncompressed = true; return true; } _numCompItems = _file->readUint32BE(); assert(_numCompItems > 0); _file->seek(4, SEEK_CUR); _lastBlockDecompressedSize = _file->readUint32BE(); if (tag != MKTAG('C','O','M','P')) { debug("BundleMgr::loadCompTable() Compressed sound %d (%s:%d) invalid (%s)", index, _file->getDebugName().c_str(), _bundleTable[index].offset, tag2str(tag)); return false; } _compTable = (CompTable *)malloc(sizeof(CompTable) * _numCompItems); assert(_compTable); int32 maxSize = 0; for (int i = 0; i < _numCompItems; i++) { _compTable[i].offset = _file->readUint32BE(); _compTable[i].size = _file->readUint32BE(); _compTable[i].codec = _file->readUint32BE(); _file->seek(4, SEEK_CUR); if (_compTable[i].size > maxSize) maxSize = _compTable[i].size; } // CMI hack: one more byte at the end of input buffer _compInputBuff = (byte *)malloc(maxSize + 1); assert(_compInputBuff); return true; } int32 BundleMgr::seekFile(int32 offset, int mode) { // We don't actually seek the file, but instead try to find that the specified offset exists // within the decompressed blocks, and save that offset in _curDecompressedFilePos int result = 0; switch (mode) { case SEEK_END: if (_isUncompressed) { result = offset + _bundleTable[_curSampleId].size; } else { result = offset + ((_numCompItems - 1) * DIMUSE_BUN_CHUNK_SIZE) + _lastBlockDecompressedSize; } _curDecompressedFilePos = result; break; case SEEK_SET: default: if (_isUncompressed) { result = offset; _curDecompressedFilePos = result; } else { int destBlock = offset / DIMUSE_BUN_CHUNK_SIZE + (offset % DIMUSE_BUN_CHUNK_SIZE != 0); if (destBlock <= _numCompItems) { result = offset; _curDecompressedFilePos = result; } } break; } return result; } int32 BundleMgr::readFile(const char *name, int32 size, byte **comp_final, bool header_outside) { int32 final_size = 0; if (!_file->isOpen()) { error("BundleMgr::readFile() File is not open"); return 0; } // Find the sound in the bundle BundleDirCache::IndexNode target; strncpy(target.filename, name, sizeof(target.filename)); target.filename[sizeof(target.filename) - 1] = '\0'; BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles, sizeof(BundleDirCache::IndexNode), (int(*)(const void *, const void *))scumm_stricmp); if (found) { int32 i, finalSize, outputSize; int skip, firstBlock, lastBlock; int headerSize = 0; assert(0 <= found->index && found->index < _numFiles); if (_file->isOpen() == false) { error("BundleMgr::readFile() File is not open"); return 0; } if (_curSampleId == -1) _curSampleId = found->index; assert(_curSampleId == found->index); if (!_compTableLoaded) { _compTableLoaded = loadCompTable(found->index); if (!_compTableLoaded) return 0; } if (_isUncompressed) { _file->seek(_bundleTable[found->index].offset + _curDecompressedFilePos + headerSize, SEEK_SET); *comp_final = (byte *)malloc(size); assert(*comp_final); _file->read(*comp_final, size); _curDecompressedFilePos += size; return size; } firstBlock = (_curDecompressedFilePos + headerSize) / DIMUSE_BUN_CHUNK_SIZE; lastBlock = (_curDecompressedFilePos + headerSize + size - 1) / DIMUSE_BUN_CHUNK_SIZE; // Clip last_block by the total number of blocks (= "comp items") if ((lastBlock >= _numCompItems) && (_numCompItems > 0)) lastBlock = _numCompItems - 1; int32 blocksFinalSize = DIMUSE_BUN_CHUNK_SIZE * (1 + lastBlock - firstBlock); *comp_final = (byte *)malloc(blocksFinalSize); assert(*comp_final); finalSize = 0; skip = (_curDecompressedFilePos + headerSize) % DIMUSE_BUN_CHUNK_SIZE; // Excess length after the last block for (i = firstBlock; i <= lastBlock; i++) { if (_lastBlock != i) { // CMI hack: one more zero byte at the end of input buffer _compInputBuff[_compTable[i].size] = 0; _file->seek(_bundleTable[found->index].offset + _compTable[i].offset, SEEK_SET); _file->read(_compInputBuff, _compTable[i].size); _outputSize = BundleCodecs::decompressCodec(_compTable[i].codec, _compInputBuff, _compOutputBuff, _compTable[i].size); if (_outputSize > DIMUSE_BUN_CHUNK_SIZE) { error("_outputSize: %d", _outputSize); } _lastBlock = i; } outputSize = _outputSize; if (header_outside) { outputSize -= skip; } else { if ((headerSize != 0) && (skip >= headerSize)) outputSize -= skip; } if ((outputSize + skip) > DIMUSE_BUN_CHUNK_SIZE) // workaround outputSize -= (outputSize + skip) - DIMUSE_BUN_CHUNK_SIZE; if (outputSize > size) outputSize = size; assert(finalSize + outputSize <= blocksFinalSize); memcpy(*comp_final + finalSize, _compOutputBuff + skip, outputSize); finalSize += outputSize; size -= outputSize; assert(size >= 0); if (size == 0) break; skip = 0; } _curDecompressedFilePos += finalSize; return finalSize; } debug(2, "BundleMgr::readFile() Failed finding sound %s", name); return final_size; } bool BundleMgr::isExtCompBun(byte gameId) { bool isExtComp = false; if (gameId == GID_CMI) { bool isExtComp1 = false, isExtComp2 = false, isExtComp3 = false, isExtComp4 = false; this->open("voxdisk1.bun", isExtComp1); this->close(); this->open("voxdisk2.bun", isExtComp2); this->close(); this->open("musdisk1.bun", isExtComp3); this->close(); this->open("musdisk2.bun", isExtComp4); this->close(); isExtComp = isExtComp1 | isExtComp2 | isExtComp3 | isExtComp4; } else { bool isExtComp1 = false, isExtComp2 = false; this->open("digvoice.bun", isExtComp1); this->close(); this->open("digmusic.bun", isExtComp2); this->close(); isExtComp = isExtComp1 | isExtComp2; } return isExtComp; } } // End of namespace Scumm