/* 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 .
*
*/
/*
* 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)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::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