mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-05 17:20:30 +00:00
303 lines
8.2 KiB
C++
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
|