scummvm/engines/made/resource.cpp
Max Horn 0ce2ca4e00 COMMON: Replace MKID_BE by MKTAG
MKID_BE relied on unspecified behavior of the C++ compiler,
and as such was always a bit unsafe. The new MKTAG macro
is slightly less elegant, but does no longer depend on the
behavior of the compiler.
Inspired by FFmpeg, which has an almost identical macro.
2011-04-12 16:53:15 +02:00

585 lines
15 KiB
C++

/* 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.
*
* $URL$
* $Id$
*
*/
#include "common/endian.h"
#include "common/memstream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "made/resource.h"
#include "made/graphics.h"
#include "made/sound.h"
namespace Made {
/* Resource */
Resource::~Resource() {
}
/* PictureResource */
PictureResource::PictureResource() : _picture(NULL), _picturePalette(NULL) {
_hasPalette = false;
}
PictureResource::~PictureResource() {
if (_picture) {
_picture->free();
delete _picture;
_picture = 0;
}
delete[] _picturePalette;
_picturePalette = 0;
}
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, 1);
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, 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, 1);
decompressImage(source, *_picture, cmdOffs, pixelOffs, maskOffs, lineSize, cmdFlags, pixelFlags, maskFlags);
delete sourceS;
}
/* AnimationResource */
AnimationResource::AnimationResource() {
}
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, 1);
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(NULL) {
}
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 NULL;
}
/* FontResource */
FontResource::FontResource() : _data(NULL), _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 NULL;
}
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 NULL;
return _data + 1 + (c - 28) * (getHeight() + 1);
}
/* GenericResource */
GenericResource::GenericResource() : _data(NULL), _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;
}
ResourceReader::~ResourceReader() {
if (!_isV1) {
delete _fd;
} else {
delete _fdPics;
delete _fdSounds;
delete _fdMusic;
}
}
// V2
void ResourceReader::open(const char *filename) {
_fd = new Common::File();
_fd->open(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) {
blockFile->open(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<PictureResource>(kResFLEX, index);
}
AnimationResource *ResourceReader::getAnimation(int index) {
return createResource<AnimationResource>(kResANIM, index);
}
SoundResource *ResourceReader::getSound(int index) {
if (!_isV1)
return createResource<SoundResource>(kResSNDS, index);
else
return createResource<SoundResourceV1>(kResSNDS, index);
}
MenuResource *ResourceReader::getMenu(int index) {
return createResource<MenuResource>(kResMENU, index);
}
FontResource *ResourceReader::getFont(int index) {
return createResource<FontResource>(kResFONT, index);
}
GenericResource *ResourceReader::getXmidi(int index) {
return createResource<GenericResource>(kResXMID, index);
}
GenericResource *ResourceReader::getMidi(int index) {
return createResource<GenericResource>(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 NULL;
if (index >= 1 && index < slots->size()) {
return &(*slots)[index];
} else {
return NULL;
}
}
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 = NULL;
slot->refCount = 0;
_cacheCount--;
}
}
}
}
} // End of namespace Made