289 lines
8.1 KiB
C++
289 lines
8.1 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/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This file is based on WME Lite.
|
|
* http://dead-code.org/redir.php?target=wmelite
|
|
* Copyright (c) 2011 Jan Nedoma
|
|
*/
|
|
|
|
#include "engines/wintermute/base/base_engine.h"
|
|
#include "engines/wintermute/base/file/base_package.h"
|
|
#include "engines/wintermute/base/file/base_file_entry.h"
|
|
#include "engines/wintermute/base/file/dcpackage.h"
|
|
#include "engines/wintermute/wintermute.h"
|
|
#include "common/file.h"
|
|
#include "common/stream.h"
|
|
#include "common/debug.h"
|
|
|
|
namespace Wintermute {
|
|
|
|
BasePackage::BasePackage() {
|
|
_name = "";
|
|
_cd = 0;
|
|
_priority = 0;
|
|
_boundToExe = false;
|
|
}
|
|
|
|
Common::SeekableReadStream *BasePackage::getFilePointer() {
|
|
Common::SeekableReadStream *stream = _fsnode.createReadStream();
|
|
|
|
return stream;
|
|
}
|
|
|
|
static bool findPackageSignature(Common::SeekableReadStream *f, uint32 *offset) {
|
|
byte buf[32768];
|
|
|
|
byte signature[8];
|
|
WRITE_LE_UINT32(signature + 0, PACKAGE_MAGIC_1);
|
|
WRITE_LE_UINT32(signature + 4, PACKAGE_MAGIC_2);
|
|
|
|
uint32 fileSize = (uint32)f->size();
|
|
uint32 startPos = 1024 * 1024;
|
|
uint32 bytesRead = startPos;
|
|
|
|
while (bytesRead < fileSize - 16) {
|
|
uint32 toRead = MIN<unsigned int>((unsigned int)32768, fileSize - bytesRead);
|
|
f->seek((int32)startPos, SEEK_SET);
|
|
uint32 actuallyRead = f->read(buf, toRead);
|
|
if (actuallyRead != toRead) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32 i = 0; i < toRead - 8; i++)
|
|
if (!memcmp(buf + i, signature, 8)) {
|
|
*offset = startPos + i;
|
|
return true;
|
|
}
|
|
|
|
bytesRead = bytesRead + toRead - 16;
|
|
startPos = startPos + toRead - 16;
|
|
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
void TPackageHeader::readFromStream(Common::ReadStream *stream) {
|
|
_magic1 = stream->readUint32LE();
|
|
_magic2 = stream->readUint32LE();
|
|
_packageVersion = stream->readUint32LE();
|
|
|
|
_gameVersion = stream->readUint32LE();
|
|
|
|
_priority = stream->readByte();
|
|
|
|
// HACK: reversion1 and reversion2 for Linux & Mac use some hacked Wintermute
|
|
// They provide "xlanguage_*.dcp" packages with 0x00 priority and change priority for a single package in runtime
|
|
// We already filter unwanted "xlanguage_*.dcp" packages at BaseFileManager::registerPackages()
|
|
// So, let's just raise the priority for all "xlanguage_*.dcp" here to the value of Windows version packages
|
|
if (_priority == 0 && BaseEngine::instance().getGameId().hasPrefix("reversion")) {
|
|
_priority = 0x02;
|
|
}
|
|
|
|
_cd = stream->readByte();
|
|
_masterIndex = stream->readByte();
|
|
stream->readByte(); // To align the next byte...
|
|
|
|
_creationTime = stream->readUint32LE();
|
|
|
|
stream->read(_desc, 100);
|
|
_numDirs = stream->readUint32LE();
|
|
}
|
|
|
|
PackageSet::PackageSet(Common::FSNode file, const Common::String &filename, bool searchSignature) {
|
|
uint32 absoluteOffset = 0;
|
|
_priority = 0;
|
|
bool boundToExe = false;
|
|
Common::SeekableReadStream *stream = file.createReadStream();
|
|
if (!stream) {
|
|
return;
|
|
}
|
|
if (searchSignature) {
|
|
uint32 offset;
|
|
if (!findPackageSignature(stream, &offset)) {
|
|
delete stream;
|
|
return;
|
|
} else {
|
|
stream->seek(offset, SEEK_SET);
|
|
absoluteOffset = offset;
|
|
boundToExe = true;
|
|
}
|
|
}
|
|
|
|
TPackageHeader hdr;
|
|
hdr.readFromStream(stream);
|
|
if (hdr._magic1 != PACKAGE_MAGIC_1 || hdr._magic2 != PACKAGE_MAGIC_2 || hdr._packageVersion > PACKAGE_VERSION) {
|
|
debugC(kWintermuteDebugFileAccess, " Invalid header in package file '%s'. Ignoring.", filename.c_str());
|
|
delete stream;
|
|
return;
|
|
}
|
|
|
|
if (hdr._packageVersion != PACKAGE_VERSION) {
|
|
debugC(kWintermuteDebugFileAccess, " Warning: package file '%s' is outdated.", filename.c_str());
|
|
}
|
|
_priority = hdr._priority;
|
|
_version = hdr._gameVersion;
|
|
|
|
// new in v2
|
|
if (hdr._packageVersion == PACKAGE_VERSION) {
|
|
uint32 dirOffset;
|
|
dirOffset = stream->readUint32LE();
|
|
dirOffset += absoluteOffset;
|
|
stream->seek(dirOffset, SEEK_SET);
|
|
}
|
|
assert(hdr._numDirs == 1);
|
|
for (uint32 i = 0; i < hdr._numDirs; i++) {
|
|
BasePackage *pkg = new BasePackage();
|
|
if (!pkg) {
|
|
return;
|
|
}
|
|
pkg->_fsnode = file;
|
|
|
|
pkg->_boundToExe = boundToExe;
|
|
|
|
// read package info
|
|
byte nameLength = stream->readByte();
|
|
char *pkgName = new char[nameLength];
|
|
stream->read(pkgName, nameLength);
|
|
pkg->_name = pkgName;
|
|
pkg->_cd = stream->readByte();
|
|
pkg->_priority = hdr._priority;
|
|
delete[] pkgName;
|
|
pkgName = nullptr;
|
|
|
|
if (!hdr._masterIndex) {
|
|
pkg->_cd = 0; // override CD to fixed disk
|
|
}
|
|
_packages.push_back(pkg);
|
|
|
|
// read file entries
|
|
uint32 numFiles = stream->readUint32LE();
|
|
|
|
for (uint32 j = 0; j < numFiles; j++) {
|
|
char *name;
|
|
uint32 offset, length, compLength, flags;/*, timeDate1, timeDate2;*/
|
|
|
|
nameLength = stream->readByte();
|
|
if (stream->eos()) {
|
|
debugC(kWintermuteDebugFileAccess, " Warning: end of package file '%s'.", filename.c_str());
|
|
break;
|
|
}
|
|
name = new char[nameLength];
|
|
stream->read(name, nameLength);
|
|
|
|
// v2 - xor name
|
|
if (hdr._packageVersion == PACKAGE_VERSION) {
|
|
for (int k = 0; k < nameLength; k++) {
|
|
((byte *)name)[k] ^= 'D';
|
|
}
|
|
}
|
|
debugC(kWintermuteDebugFileAccess, "Package contains %s", name);
|
|
|
|
Common::Path path(name, '\\');
|
|
|
|
delete[] name;
|
|
name = nullptr;
|
|
|
|
offset = stream->readUint32LE();
|
|
offset += absoluteOffset;
|
|
length = stream->readUint32LE();
|
|
compLength = stream->readUint32LE();
|
|
flags = stream->readUint32LE();
|
|
|
|
if (hdr._packageVersion == PACKAGE_VERSION) {
|
|
/* timeDate1 = */ stream->readUint32LE();
|
|
/* timeDate2 = */ stream->readUint32LE();
|
|
}
|
|
|
|
FilesMap::iterator _filesIter;
|
|
_filesIter = _files.find(path);
|
|
if (_filesIter == _files.end()) {
|
|
BaseFileEntry *fileEntry = new BaseFileEntry();
|
|
fileEntry->_package = pkg;
|
|
fileEntry->_offset = offset;
|
|
fileEntry->_length = length;
|
|
fileEntry->_compressedLength = compLength;
|
|
fileEntry->_flags = flags;
|
|
fileEntry->_filename = path;
|
|
|
|
_files[path] = Common::ArchiveMemberPtr(fileEntry);
|
|
} else {
|
|
// current package has higher priority than the registered
|
|
// TODO: This cast might be a bit ugly.
|
|
BaseFileEntry *filePtr = (BaseFileEntry *) &*(_filesIter->_value);
|
|
if (pkg->_priority > filePtr->_package->_priority) {
|
|
filePtr->_package = pkg;
|
|
filePtr->_offset = offset;
|
|
filePtr->_length = length;
|
|
filePtr->_compressedLength = compLength;
|
|
filePtr->_flags = flags;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
debugC(kWintermuteDebugFileAccess, " Registered %d files in %d package(s)", _files.size(), _packages.size());
|
|
|
|
delete stream;
|
|
}
|
|
|
|
PackageSet::~PackageSet() {
|
|
for (Common::Array<BasePackage *>::iterator it = _packages.begin(); it != _packages.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
_packages.clear();
|
|
}
|
|
|
|
bool PackageSet::hasFile(const Common::Path &path) const {
|
|
FilesMap::const_iterator it;
|
|
it = _files.find(path);
|
|
return (it != _files.end());
|
|
}
|
|
|
|
int PackageSet::listMembers(Common::ArchiveMemberList &list) const {
|
|
FilesMap::const_iterator it = _files.begin();
|
|
FilesMap::const_iterator end = _files.end();
|
|
int count = 0;
|
|
for (; it != end; ++it) {
|
|
const Common::ArchiveMemberPtr ptr(it->_value);
|
|
list.push_back(ptr);
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
const Common::ArchiveMemberPtr PackageSet::getMember(const Common::Path &path) const {
|
|
FilesMap::const_iterator it;
|
|
it = _files.find(path);
|
|
return Common::ArchiveMemberPtr(it->_value);
|
|
}
|
|
|
|
Common::SeekableReadStream *PackageSet::createReadStreamForMember(const Common::Path &path) const {
|
|
FilesMap::const_iterator it;
|
|
it = _files.find(path);
|
|
if (it != _files.end()) {
|
|
return it->_value->createReadStream();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // End of namespace Wintermute
|