/* 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 "made/resource.h" #include "made/graphics.h" #include "common/file.h" #include "common/memstream.h" #include "common/debug.h" #include "graphics/surface.h" #include "audio/decoders/raw.h" #include "audio/audiostream.h" namespace Made { /* Resource */ Resource::~Resource() { } /* PictureResource */ PictureResource::PictureResource() : _picture(nullptr), _picturePalette(nullptr) { _hasPalette = false; _paletteColorCount = 0; } PictureResource::~PictureResource() { if (_picture) { _picture->free(); delete _picture; _picture = nullptr; } delete[] _picturePalette; _picturePalette = nullptr; } void PictureResource::load(byte *source, int size) { if (READ_BE_UINT32(source) == MKTAG('F','l','e','x')) { loadChunked(source, size); } else { loadRaw(source, size); } } void PictureResource::loadRaw(byte *source, int size) { // Loads a "raw" picture as used in RtZ, LGoP2, Manhole:N&E and Rodney's Funscreen Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size); _hasPalette = (sourceS->readByte() != 0); byte cmdFlags = sourceS->readByte(); byte pixelFlags = sourceS->readByte(); byte maskFlags = sourceS->readByte(); uint16 cmdOffs = sourceS->readUint16LE(); uint16 pixelOffs = sourceS->readUint16LE(); uint16 maskOffs = sourceS->readUint16LE(); uint16 lineSize = sourceS->readUint16LE(); /*uint16 u = */sourceS->readUint16LE(); uint16 width = sourceS->readUint16LE(); uint16 height = sourceS->readUint16LE(); if (cmdFlags || pixelFlags || maskFlags) { warning("PictureResource::loadRaw() Graphic has flags set (%d, %d, %d)", cmdFlags, pixelFlags, maskFlags); } _paletteColorCount = (cmdOffs - 18) / 3; // 18 = sizeof header debug(2, "width = %d; height = %d\n", width, height); if (_hasPalette) { _picturePalette = new byte[_paletteColorCount * 3]; sourceS->read(_picturePalette, _paletteColorCount * 3); } _picture = new Graphics::Surface(); _picture->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); decompressImage(source, *_picture, cmdOffs, pixelOffs, maskOffs, lineSize, cmdFlags, pixelFlags, maskFlags); delete sourceS; } void PictureResource::loadChunked(byte *source, int size) { // Loads a "chunked" picture as used in Manhole EGA Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size); byte cmdFlags = 0, pixelFlags = 0, maskFlags = 0; uint16 cmdOffs = 0, pixelOffs = 0, maskOffs = 0; uint16 lineSize = 0, width = 0, height = 0; sourceS->skip(36); // skip the "Flex" header _hasPalette = false; while (!sourceS->eos()) { uint32 chunkType = sourceS->readUint32BE(); uint32 chunkSize = sourceS->readUint32BE(); if (sourceS->eos()) break; debug(0, "chunkType = %08X; chunkSize = %d", chunkType, chunkSize); if (chunkType == MKTAG('R','e','c','t')) { debug(0, "Rect"); sourceS->skip(4); height = sourceS->readUint16BE(); width = sourceS->readUint16BE(); debug(0, "width = %d; height = %d", width, height); } else if (chunkType == MKTAG('f','M','a','p')) { debug(0, "fMap"); lineSize = sourceS->readUint16BE(); sourceS->skip(11); cmdFlags = sourceS->readByte(); cmdOffs = sourceS->pos(); sourceS->skip(chunkSize - 14); debug(0, "lineSize = %d; cmdFlags = %d; cmdOffs = %04X", lineSize, cmdFlags, cmdOffs); } else if (chunkType == MKTAG('f','L','C','o')) { debug(0, "fLCo"); sourceS->skip(9); pixelFlags = sourceS->readByte(); pixelOffs = sourceS->pos(); sourceS->skip(chunkSize - 10); debug(0, "pixelFlags = %d; pixelOffs = %04X", pixelFlags, pixelOffs); } else if (chunkType == MKTAG('f','P','i','x')) { debug(0, "fPix"); sourceS->skip(9); maskFlags = sourceS->readByte(); maskOffs = sourceS->pos(); sourceS->skip(chunkSize - 10); debug(0, "maskFlags = %d; maskOffs = %04X", maskFlags, maskOffs); } else if (chunkType == MKTAG('f','G','C','o')) { debug(0, "fGCo"); _hasPalette = true; _paletteColorCount = chunkSize / 3; _picturePalette = new byte[_paletteColorCount * 3]; sourceS->read(_picturePalette, _paletteColorCount * 3); } else { error("PictureResource::loadChunked() Invalid chunk %08X at %08X", chunkType, (int)sourceS->pos()); } } if (!cmdOffs || !pixelOffs /*|| !maskOffs*/ || !lineSize || !width || !height) { error("PictureResource::loadChunked() Error parsing the picture data, one or more chunks/parameters are missing"); } _picture = new Graphics::Surface(); _picture->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); decompressImage(source, *_picture, cmdOffs, pixelOffs, maskOffs, lineSize, cmdFlags, pixelFlags, maskFlags); delete sourceS; } /* AnimationResource */ AnimationResource::AnimationResource() { _flags = 0; _width = 0; _height = 0; } AnimationResource::~AnimationResource() { for (uint i = 0; i < _frames.size(); i++) { _frames[i]->free(); delete _frames[i]; } } void AnimationResource::load(byte *source, int size) { Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size); sourceS->readUint32LE(); sourceS->readUint32LE(); sourceS->readUint16LE(); _flags = sourceS->readUint16LE(); _width = sourceS->readUint16LE(); _height = sourceS->readUint16LE(); sourceS->readUint32LE(); uint16 frameCount = sourceS->readUint16LE(); sourceS->readUint16LE(); sourceS->readUint16LE(); for (uint16 i = 0; i < frameCount; i++) { sourceS->seek(26 + i * 4); uint32 frameOffs = sourceS->readUint32LE(); sourceS->seek(frameOffs); sourceS->readUint32LE(); sourceS->readUint32LE(); uint16 frameWidth = sourceS->readUint16LE(); uint16 frameHeight = sourceS->readUint16LE(); uint16 cmdOffs = sourceS->readUint16LE(); sourceS->readUint16LE(); uint16 pixelOffs = sourceS->readUint16LE(); sourceS->readUint16LE(); uint16 maskOffs = sourceS->readUint16LE(); sourceS->readUint16LE(); uint16 lineSize = sourceS->readUint16LE(); Graphics::Surface *frame = new Graphics::Surface(); frame->create(frameWidth, frameHeight, Graphics::PixelFormat::createFormatCLUT8()); decompressImage(source + frameOffs, *frame, cmdOffs, pixelOffs, maskOffs, lineSize, 0, 0, 0, _flags & 1); _frames.push_back(frame); } delete sourceS; } /* SoundResource */ SoundResource::SoundResource() : _soundSize(0), _soundData(nullptr) { _soundEnergyArray = nullptr; } SoundResource::~SoundResource() { delete[] _soundData; delete _soundEnergyArray; } void SoundResource::load(byte *source, int size) { uint16 chunkCount = READ_LE_UINT16(source + 8); uint16 chunkSize = READ_LE_UINT16(source + 12); _soundSize = chunkCount * chunkSize; _soundData = new byte[_soundSize]; _soundEnergyArray = new SoundEnergyArray; decompressSound(source + 14, _soundData, chunkSize, chunkCount, _soundEnergyArray); } Audio::AudioStream *SoundResource::getAudioStream(int soundRate, bool loop) { Audio::RewindableAudioStream *stream = Audio::makeRawStream(_soundData, _soundSize, soundRate, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO); if (loop) return Audio::makeLoopingAudioStream(stream, 0); else return stream; } void SoundResourceV1::load(byte *source, int size) { _soundSize = size * 4; _soundData = new byte[_soundSize]; ManholeEgaSoundDecompressor dec; dec.decompress(source, _soundData, size); } /* MenuResource */ MenuResource::MenuResource() { } MenuResource::~MenuResource() { } void MenuResource::load(byte *source, int size) { _strings.clear(); Common::MemoryReadStream *sourceS = new Common::MemoryReadStream(source, size); sourceS->skip(4); // skip "MENU" uint16 count = sourceS->readUint16LE(); for (uint16 i = 0; i < count; i++) { uint16 offs = sourceS->readUint16LE(); const char *string = (const char*)(source + offs); _strings.push_back(string); debug(2, "%02d: %s\n", i, string); } delete sourceS; } const char *MenuResource::getString(uint index) const { if (index < _strings.size()) return _strings[index].c_str(); else return nullptr; } /* FontResource */ FontResource::FontResource() : _data(nullptr), _size(0) { } FontResource::~FontResource() { delete[] _data; } void FontResource::load(byte *source, int size) { _data = new byte[size]; _size = size; memcpy(_data, source, size); } int FontResource::getHeight() const { return _data[0]; } int FontResource::getCharWidth(uint c) const { byte *charData = getCharData(c); if (charData) return charData[0]; else return 0; } byte *FontResource::getChar(uint c) const { byte *charData = getCharData(c); if (charData) return charData + 1; else return nullptr; } int FontResource::getTextWidth(const char *text) { int width = 0; if (text) { int len = strlen(text); for (int pos = 0; pos < len; pos++) width += getCharWidth(text[pos]); } return width; } byte *FontResource::getCharData(uint c) const { if (c < 28 || c > 255) return nullptr; return _data + 1 + (c - 28) * (getHeight() + 1); } /* GenericResource */ GenericResource::GenericResource() : _data(nullptr), _size(0) { } GenericResource::~GenericResource() { delete[] _data; } void GenericResource::load(byte *source, int size) { _data = new byte[size]; _size = size; memcpy(_data, source, size); } /* ResourceReader */ ResourceReader::ResourceReader() { _isV1 = false; _cacheDataSize = 0; _fd = _fdMusic = _fdPics = _fdSounds = nullptr; _cacheCount = 0; } ResourceReader::~ResourceReader() { if (!_isV1) { delete _fd; } else { delete _fdPics; delete _fdSounds; delete _fdMusic; } } // V2 void ResourceReader::open(const char *filename) { _fd = new Common::File(); if (!_fd->open(filename)) error("ResourceReader::open() Could not open '%s'", filename); _fd->skip(0x18); // skip header for now uint16 indexCount = _fd->readUint16LE(); for (uint16 i = 0; i < indexCount; i++) { uint32 resType = _fd->readUint32BE(); uint32 indexOffs = _fd->readUint32LE(); _fd->readUint32LE(); _fd->readUint32LE(); _fd->readUint32LE(); _fd->readUint16LE(); _fd->readUint16LE(); // We don't need ARCH, FREE and OMNI resources if (resType == kResARCH || resType == kResFREE || resType == kResOMNI) continue; //debug(2, "resType = %08X; indexOffs = %d\n", resType, indexOffs); uint32 oldOffs = _fd->pos(); ResourceSlots *resSlots = new ResourceSlots(); _fd->seek(indexOffs); loadIndex(resSlots); _resSlots[resType] = resSlots; _fd->seek(oldOffs); } _cacheCount = 0; } // V1 void ResourceReader::openResourceBlocks() { _isV1 = true; _fdPics = new Common::File(); _fdSounds = new Common::File(); _fdMusic = new Common::File(); openResourceBlock("pics.blk", _fdPics, kResFLEX); openResourceBlock("snds.blk", _fdSounds, kResSNDS); openResourceBlock("music.blk", _fdMusic, kResMIDI); } void ResourceReader::openResourceBlock(const char *filename, Common::File *blockFile, uint32 resType) { if (!blockFile->open(filename)) error("Failed to open '%s'", filename); blockFile->readUint16LE(); // Skip unused uint16 count = blockFile->readUint16LE(); blockFile->readUint16LE(); // Skip unused uint32 type = blockFile->readUint32BE(); if (type != kResFLEX) warning("openResourceBlocks: resource header is not 'FLEX'"); _resSlots[resType] = new ResourceSlots(); // Add dummy entry since the resources are 1-based _resSlots[resType]->push_back(ResourceSlot(0, 0)); for (uint16 i = 0; i < count; i++) { uint32 offset = blockFile->readUint32LE(); blockFile->readUint32LE(); uint32 size = blockFile->readUint32LE(); _resSlots[resType]->push_back(ResourceSlot(offset, size)); } } PictureResource *ResourceReader::getPicture(int index) { return createResource(kResFLEX, index); } AnimationResource *ResourceReader::getAnimation(int index) { return createResource(kResANIM, index); } SoundResource *ResourceReader::getSound(int index) { if (!_isV1) return createResource(kResSNDS, index); else return createResource(kResSNDS, index); } MenuResource *ResourceReader::getMenu(int index) { return createResource(kResMENU, index); } FontResource *ResourceReader::getFont(int index) { return createResource(kResFONT, index); } GenericResource *ResourceReader::getXmidi(int index) { return createResource(kResXMID, index); } GenericResource *ResourceReader::getMidi(int index) { return createResource(kResMIDI, index); } void ResourceReader::loadIndex(ResourceSlots *slots) { _fd->readUint32LE(); // skip INDX _fd->readUint32LE(); // skip index size _fd->readUint32LE(); // skip unknown _fd->readUint32LE(); // skip res type uint16 count1 = _fd->readUint16LE(); uint16 count2 = _fd->readUint16LE(); uint16 count = MAX(count1, count2); _fd->readUint16LE(); // skip unknown count for (uint16 i = 0; i < count; i++) { uint32 offs = _fd->readUint32LE(); uint32 size = _fd->readUint32LE(); slots->push_back(ResourceSlot(offs, size)); } } void ResourceReader::freeResource(Resource *resource) { tossResourceFromCache(resource->_slot); } bool ResourceReader::loadResource(ResourceSlot *slot, byte *&buffer, uint32 &size) { int offset = !_isV1 ? 62 : 0; if (slot && slot->size > 0) { size = slot->size - offset; buffer = new byte[size]; debug(2, "ResourceReader::loadResource() %08X", slot->offs + offset); _fd->seek(slot->offs + offset); _fd->read(buffer, size); return true; } else { return false; } } ResourceSlot *ResourceReader::getResourceSlot(uint32 resType, uint index) { ResourceSlots *slots = _resSlots[resType]; if (!slots) return nullptr; if (index >= 1 && index < slots->size()) { return &(*slots)[index]; } else { return nullptr; } } Resource *ResourceReader::getResourceFromCache(ResourceSlot *slot) { if (slot->res) slot->refCount++; return slot->res; } void ResourceReader::addResourceToCache(ResourceSlot *slot, Resource *res) { _cacheDataSize += slot->size; if (_cacheDataSize >= kMaxResourceCacheSize) { purgeCache(); } slot->res = res; slot->refCount = 1; _cacheCount++; } void ResourceReader::tossResourceFromCache(ResourceSlot *slot) { if (slot->res) slot->refCount--; } void ResourceReader::purgeCache() { debug(2, "ResourceReader::purgeCache()"); for (ResMap::const_iterator resTypeIter = _resSlots.begin(); resTypeIter != _resSlots.end(); ++resTypeIter) { ResourceSlots *slots = (*resTypeIter)._value; for (ResourceSlots::iterator slotIter = slots->begin(); slotIter != slots->end(); ++slotIter) { ResourceSlot *slot = &(*slotIter); if (slot->refCount <= 0 && slot->res) { _cacheDataSize -= slot->size; delete slot->res; slot->res = nullptr; slot->refCount = 0; _cacheCount--; } } } } } // End of namespace Made