/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "sherlock/resources.h" #include "sherlock/screen.h" #include "sherlock/sherlock.h" #include "common/debug.h" #include "common/memstream.h" namespace Sherlock { Cache::Cache(SherlockEngine *vm) : _vm(vm) { } bool Cache::isCached(const Common::String &filename) const { return _resources.contains(filename); } void Cache::load(const Common::String &name) { // First check if the entry already exists if (_resources.contains(name)) return; // Open the file for reading Common::File f; if (!f.open(name)) error("Could not read file - %s", name.c_str()); load(name, f); f.close(); } void Cache::load(const Common::String &name, Common::SeekableReadStream &stream) { // First check if the entry already exists if (_resources.contains(name)) return; int32 signature = stream.readUint32BE(); stream.seek(0); // Allocate a new cache entry _resources[name] = CacheEntry(); CacheEntry &cacheEntry = _resources[name]; // Check whether the file is compressed if (signature == MKTAG('L', 'Z', 'V', 26)) { // It's compressed, so decompress the file and store it's data in the cache entry Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream); cacheEntry.resize(decompressed->size()); decompressed->read(&cacheEntry[0], decompressed->size()); delete decompressed; } else { // It's not, so read the raw data of the file into the cache entry cacheEntry.resize(stream.size()); stream.read(&cacheEntry[0], stream.size()); } } Common::SeekableReadStream *Cache::get(const Common::String &filename) const { // Return a memory stream that encapsulates the data const CacheEntry &cacheEntry = _resources[filename]; return new Common::MemoryReadStream(&cacheEntry[0], cacheEntry.size()); } /*----------------------------------------------------------------*/ Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) { _resourceIndex = -1; if (_vm->_interactiveFl) { if (_vm->getPlatform() != Common::kPlatform3DO) { addToCache("vgs.lib"); addToCache("talk.lib"); addToCache("journal.txt"); if (IS_SERRATED_SCALPEL) { addToCache("sequence.txt"); addToCache("portrait.lib"); } } else { // 3DO // ITEM data from VGS.LIB is in ITEM.LIB // resources themselves start with width and height as UINT16BE // but after that they are different. Don't seem to be raw data //addToCache("item.lib"); // talk.lib - resources themselves seem to be the same, although a few texts were slightly changed addToCache("talk.lib"); // remaining files are missing // portraits were replaced with FMV } } } void Resources::addToCache(const Common::String &filename) { _cache.load(filename); // Check to see if the file is a library Common::SeekableReadStream *stream = load(filename); uint32 header = stream->readUint32BE(); if (header == MKTAG('L', 'I', 'B', 26)) loadLibraryIndex(filename, stream, false); else if (header == MKTAG('L', 'I', 'C', 26)) loadLibraryIndex(filename, stream, true); delete stream; } void Resources::addToCache(const Common::String &filename, const Common::String &libFilename) { // Get the resource Common::SeekableReadStream *stream = load(filename, libFilename); _cache.load(filename, *stream); delete stream; } void Resources::addToCache(const Common::String &filename, Common::SeekableReadStream &stream) { _cache.load(filename, stream); } Common::SeekableReadStream *Resources::load(const Common::String &filename) { // First check if the file is directly in the cache if (_cache.isCached(filename)) return _cache.get(filename); // Secondly, iterate through any loaded library file looking for a resource // that has the same name for (LibraryIndexes::iterator i = _indexes.begin(); i != _indexes.end(); ++i) { if (i->_value.contains(filename)) { // Get a stream reference to the given library file Common::SeekableReadStream *stream = load(i->_key); LibraryEntry &entry = i->_value[filename]; _resourceIndex = entry._index; stream->seek(entry._offset); Common::SeekableReadStream *resStream = stream->readStream(entry._size); decompressIfNecessary(resStream); delete stream; return resStream; } } // At this point, fall back on a physical file with the given name Common::File f; if (!f.open(filename)) error("Could not load file - %s", filename.c_str()); Common::SeekableReadStream *stream = f.readStream(f.size()); f.close(); decompressIfNecessary(stream); return stream; } void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) { bool isCompressed = stream->readUint32BE() == MKTAG('L', 'Z', 'V', 26); if (isCompressed) { int outSize = stream->readUint32LE(); Common::SeekableReadStream *newStream = decompressLZ(*stream, outSize); delete stream; stream = newStream; } else { stream->seek(-4, SEEK_CUR); } } Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile) { // Open up the library for access Common::SeekableReadStream *libStream = load(libraryFile); // Check if the library has already had it's index read, and if not, load it if (!_indexes.contains(libraryFile)) loadLibraryIndex(libraryFile, libStream, false); // Extract the data for the specified resource and return it LibraryEntry &entry = _indexes[libraryFile][filename]; libStream->seek(entry._offset); Common::SeekableReadStream *stream = libStream->readStream(entry._size); decompressIfNecessary(stream); delete libStream; return stream; } bool Resources::exists(const Common::String &filename) const { Common::File f; return f.exists(filename) || _cache.isCached(filename); } void Resources::loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle) { uint32 offset, nextOffset; // Create an index entry _indexes[libFilename] = LibraryIndex(); LibraryIndex &index = _indexes[libFilename]; // Read in the number of resources stream->seek(4); int count = 0; if (_vm->getPlatform() != Common::kPlatform3DO) { count = stream->readUint16LE(); if (isNewStyle) stream->seek((count + 1) * 8, SEEK_CUR); // Loop through reading in the entries for (int idx = 0; idx < count; ++idx) { // Read the name of the resource char resName[13]; stream->read(resName, 13); resName[12] = '\0'; // Read the offset offset = stream->readUint32LE(); if (idx == (count - 1)) { nextOffset = stream->size(); } else { // Read the size by jumping forward to read the next entry's offset stream->seek(13, SEEK_CUR); nextOffset = stream->readUint32LE(); stream->seek(-17, SEEK_CUR); } // Add the entry to the index index[resName] = LibraryEntry(idx, offset, nextOffset - offset); } } else { count = stream->readUint16BE(); // 3DO header // Loop through reading in the entries for (int idx = 0; idx < count; ++idx) { // Read the offset offset = stream->readUint32BE(); // Read the name of the resource char resName[13]; stream->read(resName, 13); resName[12] = '\0'; if (idx == (count - 1)) { nextOffset = stream->size(); } else { // Read the size by jumping forward to read the next entry's offset stream->seek(13, SEEK_CUR); nextOffset = stream->readUint32BE(); stream->seek(-17, SEEK_CUR); } // Add the entry to the index index[resName] = LibraryEntry(idx, offset, nextOffset - offset); } } } int Resources::resourceIndex() const { return _resourceIndex; } Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source) { // This variation can't be used by Rose Tattoo, since compressed resources include the input size, // not the output size. Which means their decompression has to be done via passed buffers assert(_vm->getGameID() == GType_SerratedScalpel); uint32 id = source.readUint32BE(); assert(id == MKTAG('L', 'Z', 'V', 0x1A)); uint32 outputSize = source.readUint32LE(); return decompressLZ(source, outputSize); } Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) { int inSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; byte *outBuffer = (byte *)malloc(outSize); Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); decompressLZ(source, outBuffer, outSize, inSize); return outStream; } void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) { int inputSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; decompressLZ(source, buffer, outSize, inputSize); } Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream &source, uint32 outSize) { byte *dataOut = (byte *)malloc(outSize); decompressLZ(source, dataOut, outSize, -1); return new Common::MemoryReadStream(dataOut, outSize, DisposeAfterUse::YES); } void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize) { byte lzWindow[4096]; uint16 lzWindowPos; uint16 cmd; byte *outBufferEnd = outBuffer + outSize; int endPos = source.pos() + inSize; memset(lzWindow, 0xFF, 0xFEE); lzWindowPos = 0xFEE; cmd = 0; do { cmd >>= 1; if (!(cmd & 0x100)) cmd = source.readByte() | 0xFF00; if (cmd & 1) { byte literal = source.readByte(); *outBuffer++ = literal; lzWindow[lzWindowPos] = literal; lzWindowPos = (lzWindowPos + 1) & 0x0FFF; } else { int copyPos, copyLen; copyPos = source.readByte(); copyLen = source.readByte(); copyPos = copyPos | ((copyLen & 0xF0) << 4); copyLen = (copyLen & 0x0F) + 3; while (copyLen--) { byte literal = lzWindow[copyPos]; copyPos = (copyPos + 1) & 0x0FFF; *outBuffer++ = literal; lzWindow[lzWindowPos] = literal; lzWindowPos = (lzWindowPos + 1) & 0x0FFF; } } } while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos)); } /*----------------------------------------------------------------*/ SherlockEngine *ImageFile::_vm; void ImageFile::setVm(SherlockEngine *vm) { _vm = vm; } ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) { Common::SeekableReadStream *stream = _vm->_res->load(name); Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); load(*stream, skipPal, animImages); delete stream; } ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) { Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); load(stream, skipPal, false); } ImageFile::~ImageFile() { for (uint idx = 0; idx < size(); ++idx) (*this)[idx]._frame.free(); } void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) { loadPalette(stream); int streamSize = stream.size(); while (stream.pos() < streamSize) { ImageFrame frame; frame._width = stream.readUint16LE() + 1; frame._height = stream.readUint16LE() + 1; frame._paletteBase = stream.readByte(); if (animImages) { // Animation cutscene image files use a 16-bit x offset frame._offset.x = stream.readUint16LE(); frame._rleEncoded = (frame._offset.x & 0xff) == 1; frame._offset.y = stream.readByte(); } else { // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset frame._rleEncoded = stream.readByte() == 1; frame._offset.x = stream.readByte(); frame._offset.y = stream.readByte(); } frame._rleEncoded = !skipPalette && frame._rleEncoded; if (frame._paletteBase) { // Nibble packed frame data frame._size = (frame._width * frame._height) / 2; } else if (frame._rleEncoded) { // This size includes the header size, which we subtract frame._size = stream.readUint16LE() - 11; frame._rleMarker = stream.readByte(); } else { // Uncompressed data frame._size = frame._width * frame._height; } // Load data for frame and decompress it byte *data = new byte[frame._size]; stream.read(data, frame._size); decompressFrame(frame, data); delete[] data; push_back(frame); } } void ImageFile::loadPalette(Common::SeekableReadStream &stream) { // Check for palette int v1 = stream.readUint16LE() + 1; int v2 = stream.readUint16LE() + 1; stream.skip(1); // Skip paletteBase byte bool rleEncoded = stream.readByte() == 1; int palSize = v1 * v2; if ((palSize - 12) == PALETTE_SIZE && !rleEncoded) { // Found palette, so read it in stream.seek(2 + 12, SEEK_CUR); for (int idx = 0; idx < PALETTE_SIZE; ++idx) _palette[idx] = VGA_COLOR_TRANS(stream.readByte()); } else { // Not a palette, so rewind to start of frame data for normal frame processing stream.seek(-6, SEEK_CUR); } } void ImageFile::decompressFrame(ImageFrame &frame, const byte *src) { frame._frame.create(frame._width, frame._height, Graphics::PixelFormat::createFormatCLUT8()); byte *dest = (byte *)frame._frame.getPixels(); Common::fill(dest, dest + frame._width * frame._height, 0xff); if (frame._paletteBase) { // Nibble-packed for (uint idx = 0; idx < frame._size; ++idx, ++src) { *dest++ = *src & 0xF; *dest++ = (*src >> 4); } } else if (frame._rleEncoded && _vm->getGameID() == GType_RoseTattoo) { // Rose Tattoo run length encoding doesn't use the RLE marker byte for (int yp = 0; yp < frame._height; ++yp) { int xSize = frame._width; while (xSize > 0) { // Skip a given number of pixels byte skip = *src++; dest += skip; xSize -= skip; if (!xSize) break; // Get a run length, and copy the following number of pixels int rleCount = *src++; xSize -= rleCount; while (rleCount-- > 0) *dest++ = *src++; } assert(xSize == 0); } } else if (frame._rleEncoded) { // RLE encoded int frameSize = frame._width * frame._height; while (frameSize > 0) { if (*src == frame._rleMarker) { byte rleColor = src[1]; byte rleCount = src[2]; src += 3; frameSize -= rleCount; while (rleCount--) *dest++ = rleColor; } else { *dest++ = *src++; --frameSize; } } assert(frameSize == 0); } else { // Uncompressed frame Common::copy(src, src + frame._width * frame._height, dest); } } /*----------------------------------------------------------------*/ int ImageFrame::sDrawXSize(int scaleVal) const { int width = _width; int scale = scaleVal == 0 ? 1 : scaleVal; if (scaleVal >= SCALE_THRESHOLD) --width; int result = width * SCALE_THRESHOLD / scale; if (scaleVal >= SCALE_THRESHOLD) ++result; return result; } int ImageFrame::sDrawYSize(int scaleVal) const { int height = _height; int scale = scaleVal == 0 ? 1 : scaleVal; if (scaleVal >= SCALE_THRESHOLD) --height; int result = height * SCALE_THRESHOLD / scale; if (scaleVal >= SCALE_THRESHOLD) ++result; return result; } int ImageFrame::sDrawXOffset(int scaleVal) const { int width = _offset.x; int scale = scaleVal == 0 ? 1 : scaleVal; if (scaleVal >= SCALE_THRESHOLD) --width; int result = width * SCALE_THRESHOLD / scale; if (scaleVal >= SCALE_THRESHOLD) ++result; return result; } int ImageFrame::sDrawYOffset(int scaleVal) const { int height = _offset.y; int scale = scaleVal == 0 ? 1 : scaleVal; if (scaleVal >= SCALE_THRESHOLD) --height; int result = height * SCALE_THRESHOLD / scale; if (scaleVal >= SCALE_THRESHOLD) ++result; return result; } // ******************************************************* /*----------------------------------------------------------------*/ SherlockEngine *ImageFile3DO::_vm; void ImageFile3DO::setVm(SherlockEngine *vm) { _vm = vm; } ImageFile3DO::ImageFile3DO(const Common::String &name, bool animImages) { Common::File *dataStream = new Common::File(); if (!dataStream->open(name)) { error("unable to open %s\n", name.c_str()); } load(*dataStream, animImages); delete dataStream; } ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream) { load(stream, false); } ImageFile3DO::~ImageFile3DO() { for (uint idx = 0; idx < size(); ++idx) (*this)[idx]._frame.free(); } void ImageFile3DO::load(Common::SeekableReadStream &stream, bool animImages) { int streamSize = stream.size(); uint32 compressedSize = 0; while (stream.pos() < streamSize) { ImageFrame frame; compressedSize = stream.readUint16BE(); frame._width = stream.readUint16BE() + 1; // 2 bytes BE width frame._height = stream.readByte() + 1; // 1 byte BE height frame._paletteBase = 0; if (animImages) { // Animation cutscene image files use a 16-bit x offset frame._offset.x = stream.readUint16BE(); frame._rleEncoded = true; // always compressed if (frame._width & 0x8000) { frame._width &= 0x7FFF; compressedSize += 0x10000; } frame._offset.y = stream.readByte(); } else { // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset //frame._rleEncoded = stream.readByte() == 1; //frame._offset.x = stream.readByte(); //frame._offset.y = stream.readByte(); } frame._size = 0; //warning("getting frame %d from offset %d", this->size(), stream.pos()); // Load data for frame and decompress it byte *data = new byte[compressedSize]; stream.read(data, compressedSize); decompressFrame(&stream, frame, data); delete[] data; push_back(frame); } } // 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) { byte red = (pixel3DO >> 10) & 0x1F; byte green = (pixel3DO >> 5) & 0x1F; byte blue = pixel3DO & 0x1F;; return ((red << 11) | (green << 6) | (blue)); } void ImageFile3DO::decompressFrame(Common::SeekableReadStream *stream, ImageFrame &frame, const byte *src) { frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); uint16 *dest = (uint16 *)frame._frame.getPixels(); Common::fill(dest, dest + frame._width * frame._height, 0); const byte *srcSeeker = src; // CEL compression int frameHeightLeft = frame._height; int frameWidthLeft = frame._width; while (frameHeightLeft > 0) { frameWidthLeft = frame._width; uint16 dwordSize = READ_BE_UINT16(srcSeeker); dwordSize += 2; uint16 lineByteSize = dwordSize * 4; // debug //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize); const byte *srcLine = srcSeeker + 2; // start at 3rd byte while (frameWidthLeft > 0) { byte compressionByte = *srcLine++; byte compressionType = compressionByte >> 6; // upper 2 bits == type byte compressionPixels = (compressionByte & 0x3F) + 1; // lower 6 bits == length (0 = 1 pixel) uint16 pixelCount; uint16 pixel; if (!compressionType) // end of line break; switch(compressionType) { case 1: // simple copy for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { pixel = READ_BE_UINT16(srcLine); srcLine += 2; *dest++ = convertPixel(pixel); } break; case 2: // transparent for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { *dest++ = 0; } break; case 3: // duplicate pixels pixel = READ_BE_UINT16(srcLine); srcLine += 2; pixel = convertPixel(pixel); for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { *dest++ = pixel; } break; default: break; } frameWidthLeft -= compressionPixels; } assert(frameWidthLeft >= 0); if (frameWidthLeft > 0) { // still pixels left? skip them dest += frameWidthLeft; } frameHeightLeft--; // Seek to next line start srcSeeker += lineByteSize; } } } // End of namespace Sherlock