mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-22 01:57:16 +00:00
450 lines
12 KiB
C++
450 lines
12 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 "director/resource.h"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/macresman.h"
|
|
|
|
namespace Director {
|
|
|
|
// Base Archive code
|
|
|
|
Archive::Archive() {
|
|
_stream = 0;
|
|
_isBigEndian = true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
_fileName = fileName;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Archive::close() {
|
|
_types.clear();
|
|
|
|
if (_stream)
|
|
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::SeekableSubReadStreamEndian *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::SeekableSubReadStreamEndian(_stream, res.offset, res.offset + res.size, _isBigEndian, DisposeAfterUse::NO);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
uint32 Archive::convertTagToUppercase(uint32 tag) {
|
|
uint32 newTag = toupper(tag >> 24) << 24;
|
|
newTag |= toupper((tag >> 16) & 0xFF) << 16;
|
|
newTag |= toupper((tag >> 8) & 0xFF) << 8;
|
|
|
|
return newTag | toupper(tag & 0xFF);
|
|
}
|
|
|
|
// Mac Archive code
|
|
|
|
MacArchive::MacArchive() : Archive(), _resFork(0) {
|
|
}
|
|
|
|
MacArchive::~MacArchive() {
|
|
delete _resFork;
|
|
}
|
|
|
|
void MacArchive::close() {
|
|
Archive::close();
|
|
delete _resFork;
|
|
_resFork = 0;
|
|
}
|
|
|
|
bool MacArchive::openFile(const Common::String &fileName) {
|
|
close();
|
|
|
|
_resFork = new Common::MacResManager();
|
|
|
|
if (!_resFork->open(fileName) || !_resFork->hasResFork()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_fileName = _resFork->getBaseFileName();
|
|
if (_fileName.hasSuffix(".bin")) {
|
|
for (int i = 0; i < 4; i++)
|
|
_fileName.deleteLastChar();
|
|
}
|
|
|
|
Common::MacResTagArray tagArray = _resFork->getResTagArray();
|
|
|
|
for (uint32 i = 0; i < tagArray.size(); i++) {
|
|
ResourceMap &resMap = _types[tagArray[i]];
|
|
Common::MacResIDArray idArray = _resFork->getResIDArray(tagArray[i]);
|
|
|
|
for (uint32 j = 0; j < idArray.size(); j++) {
|
|
Resource &res = resMap[idArray[j]];
|
|
|
|
res.offset = res.size = 0; // unused
|
|
res.name = _resFork->getResName(tagArray[i], idArray[j]);
|
|
debug(3, "Found MacArchive resource '%s' %d: %s", tag2str(tagArray[i]), idArray[j], res.name.c_str());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MacArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
|
|
// TODO: Add support for this (v4 Windows games)
|
|
return false;
|
|
}
|
|
|
|
Common::SeekableSubReadStreamEndian *MacArchive::getResource(uint32 tag, uint16 id) {
|
|
assert(_resFork);
|
|
Common::SeekableReadStream *stream = _resFork->getResource(tag, id);
|
|
return new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), true, DisposeAfterUse::NO);
|
|
}
|
|
|
|
// RIFF Archive code
|
|
|
|
bool RIFFArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
|
|
close();
|
|
|
|
stream->seek(startOffset);
|
|
|
|
if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('R', 'I', 'F', 'F'))
|
|
return false;
|
|
|
|
stream->readUint32LE(); // size
|
|
|
|
if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('R', 'M', 'M', 'P'))
|
|
return false;
|
|
|
|
if (convertTagToUppercase(stream->readUint32BE()) != MKTAG('C', 'F', 'T', 'C'))
|
|
return false;
|
|
|
|
uint32 cftcSize = stream->readUint32LE();
|
|
uint32 startPos = stream->pos();
|
|
stream->readUint32LE(); // unknown (always 0?)
|
|
|
|
while ((uint32)stream->pos() < startPos + cftcSize) {
|
|
uint32 tag = convertTagToUppercase(stream->readUint32BE());
|
|
|
|
uint32 size = stream->readUint32LE();
|
|
uint32 id = stream->readUint32LE();
|
|
uint32 offset = stream->readUint32LE();
|
|
|
|
if (tag == 0)
|
|
break;
|
|
|
|
uint16 startResPos = stream->pos();
|
|
stream->seek(offset + 12);
|
|
|
|
Common::String name = "";
|
|
byte nameSize = stream->readByte();
|
|
|
|
if (nameSize) {
|
|
for (uint8 i = 0; i < nameSize; i++) {
|
|
name += stream->readByte();
|
|
}
|
|
}
|
|
|
|
stream->seek(startResPos);
|
|
|
|
debug(3, "Found RIFF resource '%s' %d: %d @ 0x%08x", tag2str(tag), id, size, offset);
|
|
|
|
ResourceMap &resMap = _types[tag];
|
|
Resource &res = resMap[id];
|
|
res.offset = offset;
|
|
res.size = size;
|
|
res.name = name;
|
|
}
|
|
|
|
_stream = stream;
|
|
return true;
|
|
}
|
|
|
|
Common::SeekableSubReadStreamEndian *RIFFArchive::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];
|
|
|
|
// Adjust to skip the resource header
|
|
uint32 offset = res.offset + 12;
|
|
uint32 size = res.size - 4;
|
|
// Skip the Pascal string
|
|
_stream->seek(offset);
|
|
byte stringSize = _stream->readByte(); // 1 for this byte
|
|
|
|
offset += stringSize + 1;
|
|
size -= stringSize + 1;
|
|
|
|
// Align to nearest word boundary
|
|
if (offset & 1) {
|
|
offset++;
|
|
size--;
|
|
}
|
|
|
|
return new Common::SeekableSubReadStreamEndian(_stream, offset, offset + size, true, DisposeAfterUse::NO);
|
|
}
|
|
|
|
// RIFX Archive code
|
|
|
|
bool RIFXArchive::openStream(Common::SeekableReadStream *stream, uint32 startOffset) {
|
|
close();
|
|
|
|
stream->seek(startOffset);
|
|
|
|
uint32 headerTag = stream->readUint32BE();
|
|
|
|
if (headerTag == MKTAG('R', 'I', 'F', 'X'))
|
|
_isBigEndian = true;
|
|
else if (SWAP_BYTES_32(headerTag) == MKTAG('R', 'I', 'F', 'X'))
|
|
_isBigEndian = false;
|
|
else
|
|
return false;
|
|
|
|
Common::SeekableSubReadStreamEndian subStream(stream, startOffset + 4, stream->size(), _isBigEndian, DisposeAfterUse::NO);
|
|
|
|
subStream.readUint32(); // size
|
|
|
|
uint32 rifxType = subStream.readUint32();
|
|
|
|
if (rifxType != MKTAG('M', 'V', '9', '3') && rifxType != MKTAG('A', 'P', 'P', 'L'))
|
|
return false;
|
|
|
|
if (subStream.readUint32() != MKTAG('i', 'm', 'a', 'p'))
|
|
return false;
|
|
|
|
subStream.readUint32(); // imap length
|
|
subStream.readUint32(); // unknown
|
|
uint32 mmapOffset = subStream.readUint32() - startOffset - 4;
|
|
|
|
subStream.seek(mmapOffset);
|
|
|
|
if (subStream.readUint32() != MKTAG('m', 'm', 'a', 'p'))
|
|
return false;
|
|
|
|
subStream.readUint32(); // mmap length
|
|
subStream.readUint16(); // unknown
|
|
subStream.readUint16(); // unknown
|
|
subStream.readUint32(); // resCount + empty entries
|
|
uint32 resCount = subStream.readUint32();
|
|
subStream.skip(8); // all 0xFF
|
|
subStream.readUint32(); // unknown
|
|
|
|
Common::Array<Resource> resources;
|
|
|
|
// Need to look for these two resources
|
|
const Resource *keyRes = 0;
|
|
const Resource *casRes = 0;
|
|
|
|
for (uint32 i = 0; i < resCount; i++) {
|
|
uint32 tag = subStream.readUint32();
|
|
uint32 size = subStream.readUint32();
|
|
uint32 offset = subStream.readUint32();
|
|
/*uint16 flags = */ subStream.readUint16();
|
|
/*uint16 unk1 = */ subStream.readUint16();
|
|
/*uint32 unk2 = */ subStream.readUint32();
|
|
|
|
debug(3, "Found RIFX resource index %d: '%s', %d @ 0x%08x", i, tag2str(tag), size, offset);
|
|
|
|
Resource res;
|
|
res.offset = offset;
|
|
res.size = size;
|
|
resources.push_back(res);
|
|
|
|
// APPL is a special case; it has an embedded "normal" archive
|
|
if (rifxType == MKTAG('A', 'P', 'P', 'L') && tag == MKTAG('F', 'i', 'l', 'e'))
|
|
return openStream(stream, offset);
|
|
|
|
// Looking for two types here
|
|
if (tag == MKTAG('K', 'E', 'Y', '*'))
|
|
keyRes = &resources[resources.size() - 1];
|
|
else if (tag == MKTAG('C', 'A', 'S', '*'))
|
|
casRes = &resources[resources.size() - 1];
|
|
}
|
|
|
|
// We need to have found the 'File' resource already
|
|
if (rifxType == MKTAG('A', 'P', 'P', 'L')) {
|
|
warning("No 'File' resource present in APPL archive");
|
|
return false;
|
|
}
|
|
|
|
// A KEY* must be present
|
|
if (!keyRes) {
|
|
warning("No 'KEY*' resource present");
|
|
return false;
|
|
}
|
|
|
|
// Parse the CAS*, if present
|
|
Common::Array<uint32> casEntries;
|
|
if (casRes) {
|
|
Common::SeekableSubReadStreamEndian casStream(stream, casRes->offset + 8, casRes->offset + 8 + casRes->size, _isBigEndian, DisposeAfterUse::NO);
|
|
casEntries.resize(casRes->size / 4);
|
|
|
|
for (uint32 i = 0; i < casEntries.size(); i++)
|
|
casEntries[i] = casStream.readUint32();
|
|
}
|
|
|
|
// Parse the KEY*
|
|
Common::SeekableSubReadStreamEndian keyStream(stream, keyRes->offset + 8, keyRes->offset + 8 + keyRes->size, _isBigEndian, DisposeAfterUse::NO);
|
|
/*uint16 unk1 = */ keyStream.readUint16();
|
|
/*uint16 unk2 = */ keyStream.readUint16();
|
|
/*uint32 unk3 = */ keyStream.readUint32();
|
|
uint32 keyCount = keyStream.readUint32();
|
|
|
|
for (uint32 i = 0; i < keyCount; i++) {
|
|
uint32 index = keyStream.readUint32();
|
|
uint32 id = keyStream.readUint32();
|
|
uint32 resTag = keyStream.readUint32();
|
|
|
|
// Handle CAS*/CASt nonsense
|
|
if (resTag == MKTAG('C', 'A', 'S', 't')) {
|
|
for (uint32 j = 0; j < casEntries.size(); j++) {
|
|
if (casEntries[j] == index) {
|
|
id += j + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Resource &res = resources[index];
|
|
debug(3, "Found RIFX resource: '%s' 0x%04x, %d @ 0x%08x", tag2str(resTag), id, res.size, res.offset);
|
|
_types[resTag][id] = res;
|
|
}
|
|
|
|
_stream = stream;
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace Director
|