scummvm/engines/composer/resource.cpp

404 lines
10 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.
*
*/
#include "composer/resource.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/util.h"
#include "common/textconsole.h"
namespace Composer {
// Base Archive code
// (copied from clone2727's mohawk code)
Archive::Archive() {
_stream = 0;
}
Archive::~Archive() {
close();
}
bool Archive::openFile(const Common::String &fileName) {
Common::File *file = new Common::File();
if (!file->open(fileName)) {
delete file;
return false;
}
if (!openStream(file)) {
close();
return false;
}
return true;
}
void Archive::close() {
_types.clear();
delete _stream; _stream = 0;
}
bool Archive::hasResource(uint32 tag, uint16 id) const {
if (!_types.contains(tag))
return false;
return _types[tag].contains(id);
}
bool Archive::hasResource(uint32 tag, const Common::String &resName) const {
if (!_types.contains(tag) || resName.empty())
return false;
const ResourceMap &resMap = _types[tag];
for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
if (it->_value.name.matchString(resName))
return true;
return false;
}
Common::SeekableReadStream *Archive::getResource(uint32 tag, uint16 id) {
if (!_types.contains(tag))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const ResourceMap &resMap = _types[tag];
if (!resMap.contains(id))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const Resource &res = resMap[id];
return new Common::SeekableSubReadStream(_stream, res.offset, res.offset + res.size);
}
uint32 Archive::getResourceFlags(uint32 tag, uint16 id) const {
if (!_types.contains(tag))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const ResourceMap &resMap = _types[tag];
if (!resMap.contains(id))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const Resource &res = resMap[id];
return res.flags;
}
uint32 Archive::getOffset(uint32 tag, uint16 id) const {
if (!_types.contains(tag))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const ResourceMap &resMap = _types[tag];
if (!resMap.contains(id))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
return resMap[id].offset;
}
uint16 Archive::findResourceID(uint32 tag, const Common::String &resName) const {
if (!_types.contains(tag) || resName.empty())
return 0xFFFF;
const ResourceMap &resMap = _types[tag];
for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
if (it->_value.name.matchString(resName))
return it->_key;
return 0xFFFF;
}
Common::String Archive::getName(uint32 tag, uint16 id) const {
if (!_types.contains(tag))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const ResourceMap &resMap = _types[tag];
if (!resMap.contains(id))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
return resMap[id].name;
}
Common::Array<uint32> Archive::getResourceTypeList() const {
Common::Array<uint32> typeList;
for (TypeMap::const_iterator it = _types.begin(); it != _types.end(); it++)
typeList.push_back(it->_key);
return typeList;
}
Common::Array<uint16> Archive::getResourceIDList(uint32 type) const {
Common::Array<uint16> idList;
if (!_types.contains(type))
return idList;
const ResourceMap &resMap = _types[type];
for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++)
idList.push_back(it->_key);
return idList;
}
// Composer Archive code
bool ComposerArchive::openStream(Common::SeekableReadStream *stream) {
// Make sure no other file is open...
close();
bool newStyle = false;
uint32 headerSize = stream->readUint32LE();
if (headerSize == SWAP_CONSTANT_32(ID_LBRC)) {
// new-style file
newStyle = true;
headerSize = stream->readUint32LE();
uint32 zeros = stream->readUint32LE();
if (zeros != 0)
error("invalid LBRC header (%d instead of zeros)", zeros);
}
uint16 numResourceTypes = stream->readUint16LE();
if (newStyle) {
uint16 unknown = stream->readUint16LE();
debug(4, "skipping unknown %04x", unknown);
}
debug(4, "Reading LBRC resource table with %d entries", numResourceTypes);
for (uint i = 0; i < numResourceTypes; i++) {
uint32 tag = stream->readUint32BE();
uint32 tableOffset = stream->readUint32LE();
debug(4, "Type '%s' at offset %d", tag2str(tag), tableOffset);
// starting from the start of the resource table, which differs
// according to whether we have the 10 extra bytes for newStyle
if (newStyle)
tableOffset += 16;
else
tableOffset += 6;
ResourceMap &resMap = _types[tag];
uint32 oldPos = stream->pos();
stream->seek(tableOffset);
while (true) {
if (stream->eos())
error("LBRC file ran out of stream");
uint32 offset, size, id, flags;
if (newStyle) {
offset = stream->readUint32LE();
if (!offset)
break;
size = stream->readUint32LE();
id = stream->readUint16LE();
flags = stream->readUint16LE(); // set to 1 for preload, otherwise no preload
/*uint32 junk = */ stream->readUint32LE();
} else {
id = stream->readUint16LE();
if (!id)
break;
offset = stream->readUint32LE();
offset += headerSize;
size = stream->readUint32LE();
flags = stream->readUint16LE(); // FIXME
}
Resource &res = resMap[id];
res.offset = offset;
res.size = size;
res.flags = flags;
debug(4, "Id %d, offset %d, size %d, flags %08x", id, offset, size, flags);
}
stream->seek(oldPos);
}
_stream = stream;
return true;
}
Pipe::Pipe(Common::SeekableReadStream *stream) {
_offset = 0;
_stream = stream;
_anim = NULL;
}
Pipe::~Pipe() {
}
void Pipe::nextFrame() {
if (_offset == (uint)_stream->size())
return;
_stream->seek(_offset, SEEK_SET);
uint32 tagCount = _stream->readUint32LE();
_offset += 4;
for (uint i = 0; i < tagCount; i++) {
uint32 tag = _stream->readUint32BE();
uint32 count = _stream->readUint32LE();
_offset += 8;
ResourceMap &resMap = _types[tag];
_offset += (12 * count);
for (uint j = 0; j < count; j++) {
uint32 offset = _stream->readUint32LE();
uint32 size = _stream->readUint32LE();
uint16 id = _stream->readUint16LE();
uint32 unknown = _stream->readUint16LE(); // frame id?
debug(9, "pipe: %s/%d: offset %d, size %d, unknown %d", tag2str(tag), id, offset, size, unknown);
PipeResourceEntry entry;
entry.size = size;
entry.offset = _offset;
resMap[id].entries.push_back(entry);
_offset += size;
}
_stream->seek(_offset, SEEK_SET);
}
}
bool Pipe::hasResource(uint32 tag, uint16 id) const {
if (!_types.contains(tag))
return false;
return _types[tag].contains(id);
}
Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffering) {
if (!_types.contains(tag))
error("Pipe does not contain '%s' %04x", tag2str(tag), id);
const ResourceMap &resMap = _types[tag];
if (!resMap.contains(id))
error("Archive does not contain '%s' %04x", tag2str(tag), id);
const PipeResource &res = resMap[id];
if (res.entries.size() == 1) {
Common::SeekableReadStream *stream = new Common::SeekableSubReadStream(_stream,
res.entries[0].offset, res.entries[0].offset + res.entries[0].size);
if (buffering)
_types[tag].erase(id);
return stream;
}
// If there are multiple entries in the pipe, we have to concaternate them together.
uint32 size = 0;
for (uint i = 0; i < res.entries.size(); i++)
size += res.entries[i].size;
byte *buffer = (byte *)malloc(size);
uint32 offset = 0;
for (uint i = 0; i < res.entries.size(); i++) {
_stream->seek(res.entries[i].offset, SEEK_SET);
_stream->read(buffer + offset, res.entries[i].size);
offset += res.entries[i].size;
}
if (buffering)
_types[tag].erase(id);
return new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES);
}
OldPipe::OldPipe(Common::SeekableReadStream *stream) : Pipe(stream), _currFrame(0) {
uint32 tag = _stream->readUint32BE();
if (tag != ID_PIPE)
error("invalid tag for pipe (%08x)", tag);
_numFrames = _stream->readUint32LE();
uint16 scriptCount = _stream->readUint16LE();
_scripts.reserve(scriptCount);
for (uint i = 0; i < scriptCount; i++)
_scripts.push_back(_stream->readUint16LE());
_offset = _stream->pos();
}
void OldPipe::nextFrame() {
if (_currFrame >= _numFrames)
return;
_stream->seek(_offset, SEEK_SET);
uint32 tag = _stream->readUint32BE();
if (tag != ID_FRME)
error("invalid tag for pipe (%08x)", tag);
uint16 spriteCount = _stream->readUint16LE();
uint32 spriteSize = _stream->readUint32LE();
uint32 audioSize = _stream->readUint32LE();
Common::Array<uint16> spriteIds;
Common::Array<PipeResourceEntry> spriteEntries;
for (uint i = 0; i < spriteCount; i++) {
spriteIds.push_back(_stream->readUint16LE());
PipeResourceEntry entry;
entry.size = _stream->readUint32LE();
entry.offset = _stream->readUint32LE();
spriteEntries.push_back(entry);
}
uint32 spriteDataOffset = _stream->pos();
_stream->skip(spriteSize);
ResourceMap &spriteResMap = _types[ID_BMAP];
spriteResMap.clear();
for (uint i = 0; i < spriteIds.size(); i++) {
PipeResourceEntry &entry = spriteEntries[i];
entry.offset += spriteDataOffset;
spriteResMap[spriteIds[i]].entries.push_back(entry);
}
ResourceMap &audioResMap = _types[ID_WAVE];
audioResMap.clear();
if (audioSize > 0) {
PipeResourceEntry entry;
entry.size = audioSize;
entry.offset = _stream->pos();
// we use 0xffff for per-frame pipe audio
audioResMap[0xffff].entries.push_back(entry);
_stream->skip(audioSize);
}
_offset = _stream->pos();
_currFrame++;
}
} // End of namespace Composer