Files
scummvm/engines/wintermute/base/file/base_package.cpp
T
lolbot-iichan 3c1e883ff0 WINTERMUTE: Add lots of entries to detection table and refactor handling of localization packages
* WINTERMUTE: Add detection for unsupported "Basis Octavus" game

This game is not supported by ScummVM, because it's 2.5D. However, there
are already other 2.5D games in detection table and the intent is to add
all known games, and support 2.5D in ResidualVM later.

* WINTERMUTE: Add detection for Alimardan dilogy

"Alimardan's Mischief" and "Alimardan Meets Merlin" are games by Iranian
developer Resana Shokooh Kavir (http://rskgd.com/)
It seems that Persian versions also exist, I'll surely look at it later:
https://www.digikala.com/product/dkp-75901

There were 2 steam releases for alimardan1:
https://steamdb.info/depot/694421/manifests/

There was only one steam release of alimardan2:
https://steamdb.info/depot/694431/manifests/

* WINTERMUTE: Add detection for Oknytt 1.12

Oknytt had 2 steam releases (1.12 and 1.13):
https://steamdb.info/depot/286321/manifests/
I have downloaded 1.12 and added detection.

* WINTERMUTE: Add empty strings between detection table lines

* WINTERMUTE: Add detection for Steam versions of reversion1 for Windows

Source: https://steamdb.info/depot/270572/manifests/

* WINTERMUTE: Add detection for Steam version of reversion1 for Linux

Source: https://steamdb.info/depot/270571/manifests/

* WINTERMUTE: Fix localization loading for Mac and Linux

Mac and Linux builds of reversion1 & reversion2 does not have any
"languages" folders.
So, we need to filter those files according to filenames too.

Once there is such filtering, there is no need for this overspecific
hack for revision1's xlanguage_pt.dcp.

* WINTERMUTE: Add detection for Steam versions of reversion2 for Windows

Source: https://steamdb.info/depot/281061/manifests/

* WINTERMUTE: Language packages priority for Linux & Mac

* WINTERMUTE: Add detection for Steam versions of reversion2 for Linux

Source: https://steamdb.info/depot/281062/manifests/

* WINTERMUTE: Add detection for Steam versions of reversion2 for Mac

Source: https://steamdb.info/depot/281063/manifests/

* WINTERMUTE: Add detection for Steam versions of reversion1 for Mac

Source: https://steamdb.info/depot/270573/manifests/

* WINTERMUTE: Add detection for old Desura versions of reversion2

Source: https://bugs.scummvm.org/ticket/6564

* WINTERMUTE: Add detection for Spanish versions of reversion1&2

As was shown by windlepoons, xlanguage_* contains additional translation
package, while data.dcp contains original translation (which is
Spanish).

So, even without owning those old releases of reversion1&2, I can wrote
detection tables for them by not looking for any "xlanguage_*.dcp" file.
However, we shouldn't use WME_ENTRY1s for Spanish, because advanced
detector say "new best match, removing all previous candidates" once it
hit a WME_ENTRY2s. Instead, let's just look for data.dcp twice to give
user a choice of several WME_ENTRY2s. Lor Linux, it's WME_ENTRY3s.

* WINTERMUTE: Filter language packages ignoring folder name

There are 4 types of multilang games:
1. Each installation contain a data.dcp package (same for all languages)
+ an additional language package. Other localization packages are not
installed. Those games are: "Helga Deep In Trouble", "Dirty Split",
"Looky".

2. Each installation contain a data.dcp package (same for all languages)
+ a folder with all localization packages + a single "selected" language
package copied to main folder.  "xlanguage_" prefix is usual for
reversion series. Other games usually use "english.dcp", "spanich.dcp",
"czech.dcp", etc.
Those are: "Five Lethal Demons", "Five Magical Amulets", "Dead City",
"Oknytt", "Reversion: The Escape", "Reversion: The Meeting".

3. All localizations are inside data.dcp all-together. Those are: "One
Hellavu Day", "The White Chamber" and "James Peris: No License Nor
Control".

4. Different data.dcp build for each language. All other known games are
here.

This code covers all known filenames for localization packages and
provide several guessworks for ignoring all irrelevant localization
packages.

* WINTERMUTE: Add localization detection for Five Lethal Demons

* WINTERMUTE: Tweak detection for Dead City (Czech)

"english.dcp" is not required to play Dead City in Czech language, so we
don't need to detect it

* WINTERMUTE: Add detection table for Looky in English

I got English version here:
https://bullshit-softworx.itch.io/looky-adventure

* WINTERMUTE: Add detection for Czech versions of Helga

"english.dcp" happen to be optional
2019-06-25 13:28:03 +03:00

288 lines
8.5 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.
*
*/
/*
* 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 | kWintermuteDebugLog, " Invalid header in package file '%s'. Ignoring.", filename.c_str());
delete stream;
return;
}
if (hdr._packageVersion != PACKAGE_VERSION) {
debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, " Warning: package file '%s' is outdated.", filename.c_str());
}
_priority = hdr._priority;
// 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();
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::String upcName = name;
upcName.toUppercase();
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();
}
_filesIter = _files.find(upcName);
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 = upcName;
_files[upcName] = 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::String &name) const {
Common::String upcName = name;
upcName.toUppercase();
Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator it;
it = _files.find(upcName.c_str());
return (it != _files.end());
}
int PackageSet::listMembers(Common::ArchiveMemberList &list) const {
Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator it = _files.begin();
Common::HashMap<Common::String, Common::ArchiveMemberPtr>::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::String &name) const {
Common::String upcName = name;
upcName.toUppercase();
Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator it;
it = _files.find(upcName.c_str());
return Common::ArchiveMemberPtr(it->_value);
}
Common::SeekableReadStream *PackageSet::createReadStreamForMember(const Common::String &name) const {
Common::String upcName = name;
upcName.toUppercase();
Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator it;
it = _files.find(upcName.c_str());
if (it != _files.end()) {
return it->_value->createReadStream();
}
return nullptr;
}
} // End of namespace Wintermute