scummvm/engines/myst3/archive.cpp
2023-07-08 22:02:00 +03:00

303 lines
8.2 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/myst3/archive.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/substream.h"
namespace Myst3 {
void Archive::decryptHeader(Common::SeekableReadStream &inStream, Common::WriteStream &outStream) {
static const uint32 addKey = 0x3C6EF35F;
static const uint32 multKey = 0x0019660D;
inStream.seek(0);
uint32 size = inStream.readUint32LE();
bool encrypted = size > 1000000;
inStream.seek(0);
if (encrypted) {
uint32 decryptedSize = size ^ addKey;
uint32 currentKey = 0;
for (uint i = 0; i < decryptedSize; i++) {
currentKey += addKey;
outStream.writeUint32LE(inStream.readUint32LE() ^ currentKey);
currentKey *= multKey;
}
} else {
for (uint i = 0; i < size; i++) {
outStream.writeUint32LE(inStream.readUint32LE());
}
}
}
static Common::String readFixedString(Common::ReadStream &stream, uint32 length) {
Common::String value;
for (uint i = 0; i < length; i++) {
value += stream.readByte();
}
return value;
}
static uint32 readUint24(Common::ReadStream &stream) {
uint32 value = stream.readUint16LE();
value |= stream.readByte() << 16;
return value;
}
Archive::DirectorySubEntry Archive::readSubEntry(Common::ReadStream &stream) {
DirectorySubEntry subEntry;
subEntry.offset = stream.readUint32LE();
subEntry.size = stream.readUint32LE();
uint16 metadataSize = stream.readUint16LE();
subEntry.face = stream.readByte();
subEntry.type = static_cast<ResourceType>(stream.readByte());
subEntry.metadata.resize(metadataSize);
for (uint i = 0; i < metadataSize; i++) {
subEntry.metadata[i] = stream.readUint32LE();
}
return subEntry;
}
Archive::DirectoryEntry Archive::readEntry(Common::ReadStream &stream) {
DirectoryEntry entry;
if (_roomName.empty()) {
entry.roomName = readFixedString(stream, 4);
} else {
entry.roomName = _roomName;
}
entry.index = readUint24(stream);
byte subItemCount = stream.readByte();
entry.subentries.resize(subItemCount);
for (uint i = 0; i < subItemCount; i++) {
entry.subentries[i] = readSubEntry(stream);
}
return entry;
}
void Archive::readDirectory() {
Common::MemoryWriteStreamDynamic buf(DisposeAfterUse::YES);
decryptHeader(_file, buf);
Common::MemoryReadStream directory(buf.getData(), buf.size());
_directorySize = directory.readUint32LE();
while (directory.pos() + 4 < directory.size()) {
_directory.push_back(readEntry(directory));
}
}
void Archive::visit(ArchiveVisitor &visitor) {
visitor.visitArchive(*this);
for (uint i = 0; i < _directory.size(); i++) {
visitor.visitDirectoryEntry(_directory[i]);
for (uint j = 0; j < _directory[i].subentries.size(); j++) {
visitor.visitDirectorySubEntry(_directory[i].subentries[j]);
}
}
}
Common::SeekableReadStream *Archive::dumpToMemory(uint32 offset, uint32 size) {
_file.seek(offset);
return _file.readStream(size);
}
uint32 Archive::copyTo(uint32 offset, uint32 size, Common::WriteStream &out) {
Common::SeekableSubReadStream subStream(&_file, offset, offset + size);
subStream.seek(0);
return out.writeStream(&subStream);
}
const Archive::DirectoryEntry *Archive::getEntry(const Common::String &room, uint32 index) const {
for (uint i = 0; i < _directory.size(); i++) {
const DirectoryEntry &entry = _directory[i];
if (entry.index == index && entry.roomName == room) {
return &entry;
}
}
return nullptr;
}
ResourceDescription Archive::getDescription(const Common::String &room, uint32 index, uint16 face,
ResourceType type) {
const DirectoryEntry *entry = getEntry(room, index);
if (!entry) {
return ResourceDescription();
}
for (uint i = 0; i < entry->subentries.size(); i++) {
const DirectorySubEntry &subentry = entry->subentries[i];
if (subentry.face == face && subentry.type == type) {
return ResourceDescription(this, subentry);
}
}
return ResourceDescription();
}
ResourceDescriptionArray Archive::listFilesMatching(const Common::String &room, uint32 index, uint16 face,
ResourceType type) {
const DirectoryEntry *entry = getEntry(room, index);
if (!entry) {
return ResourceDescriptionArray();
}
ResourceDescriptionArray list;
for (uint i = 0; i < entry->subentries.size(); i++) {
const DirectorySubEntry &subentry = entry->subentries[i];
if (subentry.face == face && subentry.type == type) {
list.push_back(ResourceDescription(this, subentry));
}
}
return list;
}
bool Archive::open(const char *fileName, const char *room) {
// If the room name is not provided, it is assumed that
// we are opening a multi-room archive
if (room) {
_roomName = room;
}
if (_file.open(fileName)) {
readDirectory();
return true;
}
return false;
}
void Archive::close() {
_directorySize = 0;
_roomName.clear();
_directory.clear();
_file.close();
}
ResourceDescription::ResourceDescription() :
_archive(nullptr),
_subentry(nullptr) {
}
ResourceDescription::ResourceDescription(Archive *archive, const Archive::DirectorySubEntry &subentry) :
_archive(archive),
_subentry(&subentry) {
}
Common::SeekableReadStream *ResourceDescription::getData() const {
return _archive->dumpToMemory(_subentry->offset, _subentry->size);
}
ResourceDescription::SpotItemData ResourceDescription::getSpotItemData() const {
assert(_subentry->type == Archive::kSpotItem || _subentry->type == Archive::kLocalizedSpotItem);
SpotItemData spotItemData;
spotItemData.u = _subentry->metadata[0];
spotItemData.v = _subentry->metadata[1];
return spotItemData;
}
ResourceDescription::VideoData ResourceDescription::getVideoData() const {
VideoData videoData;
if (_subentry->type == Archive::kMovie || _subentry->type == Archive::kMultitrackMovie) {
videoData.v1.setValue(0, static_cast<int32>(_subentry->metadata[0]) * 0.000001f);
videoData.v1.setValue(1, static_cast<int32>(_subentry->metadata[1]) * 0.000001f);
videoData.v1.setValue(2, static_cast<int32>(_subentry->metadata[2]) * 0.000001f);
videoData.v2.setValue(0, static_cast<int32>(_subentry->metadata[3]) * 0.000001f);
videoData.v2.setValue(1, static_cast<int32>(_subentry->metadata[4]) * 0.000001f);
videoData.v2.setValue(2, static_cast<int32>(_subentry->metadata[5]) * 0.000001f);
videoData.u = static_cast<int32>(_subentry->metadata[6]);
videoData.v = static_cast<int32>(_subentry->metadata[7]);
videoData.width = static_cast<int32>(_subentry->metadata[8]);
videoData.height = static_cast<int32>(_subentry->metadata[9]);
}
return videoData;
}
uint32 ResourceDescription::getMiscData(uint index) const {
assert(_subentry->type == Archive::kNumMetadata || _subentry->type == Archive::kTextMetadata);
if (index == 0) {
return _subentry->offset;
} else if (index == 1) {
return _subentry->size;
} else {
return _subentry->metadata[index - 2];
}
}
Common::String ResourceDescription::getTextData(uint index) const {
assert(_subentry->type == Archive::kTextMetadata);
uint8 key = 35;
uint8 cnt = 0;
uint8 decrypted[89];
memset(decrypted, 0, sizeof(decrypted));
uint8 *out = &decrypted[0];
while (cnt / 4u < (_subentry->metadata.size() + 2) && cnt < 89) {
// XORed text stored in little endian 32 bit words
*out++ = (getMiscData(cnt / 4) >> (8 * (3 - (cnt % 4)))) ^ key++;
cnt++;
}
// decrypted contains a null separated string array
// extract the wanted one
cnt = 0;
int i = 0;
while (cnt < index) {
if (!decrypted[i])
cnt++;
i++;
}
Common::String text((const char *)&decrypted[i]);
return text;
}
ArchiveVisitor::~ArchiveVisitor() {
}
} // End of namespace Myst3