[Scummvm-git-logs] scummvm master -> b021957fb378811d8d59efa466b02e16f1d99fb0
sev-
noreply at scummvm.org
Mon Mar 13 19:17:41 UTC 2023
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
5b3fe1d916 COMMON: Error-out on an invalid clickteam compressed block
c33ce058f8 COMMON: Add createReadStreamForMemberNext for the ease of using patcher archives
6476e55aab COMMON: Allow memcaching archive to bypass caching part and return a stream
f513d6eee9 COMMON: Support patching clickteam installer and version used by PRCA
2f70d7dad0 SCUMM: Detect PRCA patch
b021957fb3 SCUMM: Support playing most RuSCUMM versions without patching manually
Commit: 5b3fe1d9163e7e35eb028a44af9d52737c4cccd3
https://github.com/scummvm/scummvm/commit/5b3fe1d9163e7e35eb028a44af9d52737c4cccd3
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
COMMON: Error-out on an invalid clickteam compressed block
Changed paths:
common/compression/gzio.cpp
diff --git a/common/compression/gzio.cpp b/common/compression/gzio.cpp
index c7857dc862e..d9d85c9fcbe 100644
--- a/common/compression/gzio.cpp
+++ b/common/compression/gzio.cpp
@@ -34,6 +34,7 @@
comments to that effect with your name and the date. Thank you.
*/
+#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/ptr.h"
@@ -881,6 +882,8 @@ GzioReadStream::get_new_block()
case 7:
_blockType = INFLATE_STORED;
break;
+ default:
+ error("Unsupported clickteam block type %d", (int)(b & 7));
}
DUMPBITS (3);
Commit: c33ce058f890982fffbafb42f8dc8ee04542c363
https://github.com/scummvm/scummvm/commit/c33ce058f890982fffbafb42f8dc8ee04542c363
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
COMMON: Add createReadStreamForMemberNext for the ease of using patcher archives
Changed paths:
common/archive.cpp
common/archive.h
diff --git a/common/archive.cpp b/common/archive.cpp
index 07400e4d1d0..48f754d7337 100644
--- a/common/archive.cpp
+++ b/common/archive.cpp
@@ -356,6 +356,24 @@ SeekableReadStream *SearchSet::createReadStreamForMember(const Path &path) const
return nullptr;
}
+SeekableReadStream *SearchSet::createReadStreamForMemberNext(const Path &path, const Archive *starting) const {
+ if (path.empty())
+ return nullptr;
+
+ ArchiveNodeList::const_iterator it = _list.begin();
+ for (; it != _list.end(); ++it)
+ if (it->_arc == starting) {
+ ++it;
+ break;
+ }
+ for (; it != _list.end(); ++it) {
+ SeekableReadStream *stream = it->_arc->createReadStreamForMember(path);
+ if (stream)
+ return stream;
+ }
+
+ return nullptr;
+}
SearchManager::SearchManager() {
clear(); // Force a reset
diff --git a/common/archive.h b/common/archive.h
index 1ed647353d8..089d0b7a218 100644
--- a/common/archive.h
+++ b/common/archive.h
@@ -143,6 +143,14 @@ public:
*/
virtual SeekableReadStream *createReadStreamForMember(const Path &path) const = 0;
+ /**
+ * For most archives: same as previous. For SearchSet see SearchSet
+ * documentation.
+ */
+ virtual SeekableReadStream *createReadStreamForMemberNext(const Path &path, const Archive *starting) const {
+ return createReadStreamForMember(path);
+ }
+
/**
* Dump all files from the archive to the given directory
*/
@@ -342,6 +350,11 @@ public:
*/
SeekableReadStream *createReadStreamForMember(const Path &path) const override;
+ /**
+ * Similar to above but exclude matches from archives before starting and starting itself.
+ */
+ SeekableReadStream *createReadStreamForMemberNext(const Path &path, const Archive *starting) const override;
+
/**
* Ignore clashes when adding directories. For more details, see the corresponding parameter
* in @ref FSDirectory documentation.
Commit: 6476e55aab6be73bdacb34eef4c80a5195b2d02c
https://github.com/scummvm/scummvm/commit/6476e55aab6be73bdacb34eef4c80a5195b2d02c
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
COMMON: Allow memcaching archive to bypass caching part and return a stream
It's useful for patchers if they can easily create a stream in some cases but
not others.
Changed paths:
common/archive.cpp
common/archive.h
diff --git a/common/archive.cpp b/common/archive.cpp
index 48f754d7337..62b7e277b9e 100644
--- a/common/archive.cpp
+++ b/common/archive.cpp
@@ -107,7 +107,10 @@ SeekableReadStream *MemcachingCaseInsensitiveArchive::createReadStreamForMember(
String translated = translatePath(path);
bool isNew = false;
if (!_cache.contains(translated)) {
- _cache[translated] = readContentsForPath(translated);
+ SharedArchiveContents readResult = readContentsForPath(translated);
+ if (readResult._bypass)
+ return readResult._bypass;
+ _cache[translated] = readResult;
isNew = true;
}
@@ -121,7 +124,10 @@ SeekableReadStream *MemcachingCaseInsensitiveArchive::createReadStreamForMember(
// Check whether the entry is still valid as WeakPtr might have expired.
if (!entry->makeStrong()) {
// If it's expired, recreate the entry.
- _cache[translated] = readContentsForPath(translated);
+ SharedArchiveContents readResult = readContentsForPath(translated);
+ if (readResult._bypass)
+ return readResult._bypass;
+ _cache[translated] = readResult;
entry = &_cache[translated];
isNew = true;
}
diff --git a/common/archive.h b/common/archive.h
index 089d0b7a218..462ac4ec279 100644
--- a/common/archive.h
+++ b/common/archive.h
@@ -169,10 +169,15 @@ class SharedArchiveContents {
public:
SharedArchiveContents(byte *contents, uint32 contentSize) :
_strongRef(contents, ArrayDeleter<byte>()), _weakRef(_strongRef),
- _contentSize(contentSize), _missingFile(false) {}
- SharedArchiveContents() : _strongRef(nullptr), _weakRef(nullptr), _contentSize(0), _missingFile(true) {}
+ _contentSize(contentSize), _missingFile(false), _bypass(nullptr) {}
+ SharedArchiveContents() : _strongRef(nullptr), _weakRef(nullptr), _contentSize(0), _missingFile(true), _bypass(nullptr) {}
+ static SharedArchiveContents bypass(SeekableReadStream *stream) {
+ return SharedArchiveContents(stream);
+ }
private:
+ SharedArchiveContents(SeekableReadStream *stream) : _strongRef(nullptr), _weakRef(nullptr), _contentSize(0), _missingFile(false), _bypass(stream) {}
+
bool isFileMissing() const { return _missingFile; }
SharedPtr<byte> getContents() const { return _strongRef; }
uint32 getSize() const { return _contentSize; }
@@ -197,6 +202,7 @@ private:
WeakPtr<byte> _weakRef;
uint32 _contentSize;
bool _missingFile;
+ SeekableReadStream *_bypass;
friend class MemcachingCaseInsensitiveArchive;
};
Commit: f513d6eee988220bf73ecbbcd21d366c28325bcd
https://github.com/scummvm/scummvm/commit/f513d6eee988220bf73ecbbcd21d366c28325bcd
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
COMMON: Support patching clickteam installer and version used by PRCA
Changed paths:
common/compression/clickteam.cpp
common/compression/clickteam.h
diff --git a/common/compression/clickteam.cpp b/common/compression/clickteam.cpp
index c06f3b4f883..934dcb5025a 100644
--- a/common/compression/clickteam.cpp
+++ b/common/compression/clickteam.cpp
@@ -27,48 +27,131 @@
#include "common/substream.h"
#include "common/memstream.h"
-#define STUB_SIZE 0x16000
#define FLAG_COMPRESSED 1
namespace Common {
ClickteamInstaller::ClickteamFileDescriptor::ClickteamFileDescriptor(const ClickteamTag& contentsTag, uint32 off)
- : _fileDataOffset(0), _fileDescriptorOffset(0), _compressedSize(0), _uncompressedSize(0) {
- uint32 stringsOffset = 36;
- byte *tag = contentsTag._contents + off;
- uint32 lmax = contentsTag._size - off;
- if (lmax < 0x24)
- return;
- uint16 ls = READ_LE_UINT32(tag), l;
- if (ls < 0x24)
- return;
-
- l = MIN((uint32)ls, lmax);
-
- uint16 flags = READ_LE_UINT32(tag+22);
- if (flags & 6)
- stringsOffset += 0x12;
- if (flags & 8)
- stringsOffset += 0x18;
- if (stringsOffset >= l) {
- return;
+ : _fileDataOffset(0), _fileDescriptorOffset(0), _compressedSize(0), _uncompressedSize(0), _isReferenceMissing(false) {
+ switch (contentsTag._tagId) {
+ case (uint16)ClickteamTagId::FILE_LIST: {
+ uint32 stringsOffset = 36;
+ byte *tag = contentsTag._contents + off;
+ uint32 lmax = contentsTag._size - off;
+ if (lmax < 0x24)
+ return;
+ uint32 ls = READ_LE_UINT32(tag), l;
+ if (ls < 0x24)
+ return;
+
+ l = MIN((uint32)ls, lmax);
+
+ uint16 flags = READ_LE_UINT32(tag+22);
+ if (flags & 6)
+ stringsOffset += 0x12;
+ if (flags & 8)
+ stringsOffset += 0x18;
+ if (stringsOffset >= l) {
+ return;
+ }
+ _fileDataOffset = READ_LE_UINT32(tag + 6);
+ _compressedSize = READ_LE_UINT32(tag + 10);
+ _uncompressedSize = READ_LE_UINT32(tag + 14);
+ _expectedCRC = READ_LE_UINT32(tag + 18);
+ char *strings = (char *)tag + stringsOffset;
+ char *p;
+ for (p = strings; p < (char*)tag + lmax && *p; p++);
+ _fileName = Common::String(strings, p - strings);
+ _fileDescriptorOffset = off;
+ _supported = true;
+ _isPatchFile = false;
+ _crcIsXorred = true;
+ break;
+ }
+ case (uint16)ClickteamTagId::FILE_PATCHING_LIST: {
+ uint32 stringsOffset = 0x36;
+ byte *tag = contentsTag._contents + off;
+ uint32 lmax = contentsTag._size - off;
+ if (lmax < 33)
+ return;
+ uint32 ls = READ_LE_UINT32(tag);
+ if (ls < 33)
+ return;
+
+ byte type = tag[7];
+ if (type != 0) {
+ _supported = false;
+ _fileName = "";
+ _fileDataOffset = 0;
+ _fileDescriptorOffset = off;
+ _compressedSize = 0;
+ _uncompressedSize = 0;
+ _expectedCRC = 0;
+ _isPatchFile = false;
+ _crcIsXorred = false;
+ return;
+ }
+
+ // Layout:
+ // 0-3: tag size
+ // 4-6: ???
+ // 7: operation
+ // 8: file type
+ // 9: ???
+ // a-d: uncompressed size
+ // e-11: unxorred uncompressed CRC
+ // 12-15: number of original files entries
+ // 16-17: pointer to original files entries
+ // 18-1d: ???
+ // 1e-36: 3 blocks of 8 bytes relating to some timestamps..
+
+ // Original files entries. Array of:
+ // 0-3: original CRC
+ // 4-7: file size before patching
+ // 8-b: patch data offset
+ // c-f: patch size
+
+ _expectedCRC = READ_LE_UINT32(tag + 0xe);
+ int numPatchEntries = READ_LE_UINT16(tag + 0x12);
+ byte *blockb = tag + READ_LE_UINT16(tag + 0x16);
+
+ _uncompressedSize = READ_LE_UINT32(tag + 0xa);
+ char *strings = (char *)tag + stringsOffset;
+ char *p;
+ for (p = strings; p < (char*)tag + lmax && *p; p++);
+ _fileName = Common::String(strings, p - strings);
+ _fileDescriptorOffset = off;
+ _compressedSize = 0;
+ _fileDataOffset = 0;
+ _supported = true;
+ _isPatchFile = !(tag[8] & 2);
+
+ if (!_isPatchFile && numPatchEntries > 0) {
+ _compressedSize = READ_LE_UINT32(blockb + 0xc);
+ _fileDataOffset = READ_LE_UINT32(blockb + 0x8);
+ }
+
+ _patchEntries.resize(numPatchEntries);
+
+ for (int i = 0; i < numPatchEntries; i++) {
+ _patchEntries[i]._originalCRC = READ_LE_UINT32(blockb + 0x10 * i);
+ _patchEntries[i]._originalSize = READ_LE_UINT32(blockb + 0x10 * i + 4);
+ _patchEntries[i]._patchDataOffset = READ_LE_UINT32(blockb + 0x10 * i + 8);
+ _patchEntries[i]._patchSize = READ_LE_UINT32(blockb + 0x10 * i + 12);
+ }
+
+ _crcIsXorred = false;
+ break;
+ }
}
- _fileDataOffset = READ_LE_UINT32(tag + 6);
- _compressedSize = READ_LE_UINT32(tag + 10);
- _uncompressedSize = READ_LE_UINT32(tag + 14);
- _expectedCRC = READ_LE_UINT32(tag + 18);
- char *strings = (char *)tag + stringsOffset;
- char *p;
- for (p = strings; p < (char*)tag + lmax && *p; p++);
- _fileName = Common::String(strings, p - strings);
- _fileDescriptorOffset = off;
}
ClickteamInstaller::ClickteamTag* ClickteamInstaller::getTag(ClickteamTagId tagId) const {
return _tags.getValOrDefault((uint16) tagId).get();
}
-static uint32 computeCRC(byte *buf, uint32 sz, uint32 previous) {
+namespace {
+uint32 computeCRC(byte *buf, uint32 sz, uint32 previous) {
uint32 cur = previous;
byte *ptr = buf;
uint32 i;
@@ -83,47 +166,137 @@ static uint32 computeCRC(byte *buf, uint32 sz, uint32 previous) {
}
bool checkStubAndComputeCRC1(Common::SeekableReadStream *stream, uint32 &crc) {
- if (stream->size() <= STUB_SIZE) {
+ static const byte BLOCK1_MAGIC_START[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x53 };
+ static const byte BLOCK1_MAGIC_END[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x45 };
+ static const byte STUB_SIZE_MAGIC[] = { 0x77, 0x77, 0x67, 0x54, 0x29, 0x48 };
+ static const uint32 MAX_SEARCH_RANGE = 0x16000; // So far, if needed increase
+ uint32 blockSearchRange = MIN<uint32>(MAX_SEARCH_RANGE, stream->size());
+
+ if (blockSearchRange <= sizeof(STUB_SIZE_MAGIC) + 4 + sizeof(BLOCK1_MAGIC_START) + sizeof(BLOCK1_MAGIC_END)) {
return false;
}
- byte *stub = new byte[STUB_SIZE];
- static const byte BLOCK1_MAGIC_START[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x53 };
- static const byte BLOCK1_MAGIC_END[] = { 0x77, 0x77, 0x49, 0x4e, 0x53, 0x45 };
+ byte *stub = new byte[blockSearchRange];
stream->seek(0);
- stream->read(stub, STUB_SIZE);
+ stream->read(stub, blockSearchRange);
+ byte *block1start = nullptr;
+ byte *block1end = nullptr;
+ byte *stubSizePtr = nullptr;
byte *ptr;
- for (ptr = stub; ptr < stub + STUB_SIZE - sizeof(BLOCK1_MAGIC_START); ptr++) {
+ for (ptr = stub; ptr < stub + blockSearchRange - sizeof(STUB_SIZE_MAGIC) - 3; ptr++) {
+ if (memcmp(ptr, STUB_SIZE_MAGIC, sizeof(STUB_SIZE_MAGIC)) == 0)
+ stubSizePtr = ptr;
+ if (block1start && memcmp(ptr, BLOCK1_MAGIC_END, sizeof(BLOCK1_MAGIC_END)) == 0)
+ block1end = ptr;
if (memcmp(ptr, BLOCK1_MAGIC_START, sizeof(BLOCK1_MAGIC_START)) == 0)
+ block1start = ptr;
+ if (block1start && block1end && stubSizePtr)
break;
}
- if (ptr == stub + STUB_SIZE - sizeof(BLOCK1_MAGIC_START)) {
+ if (!block1start || !block1end || !stubSizePtr) {
delete[] stub;
return false;
}
- byte *block1start = ptr;
- ptr += sizeof(BLOCK1_MAGIC_START);
+ uint32 stubSize = READ_LE_UINT32(stubSizePtr + sizeof(STUB_SIZE_MAGIC));
+ crc = computeCRC(block1start, block1end - block1start, 0);
+ delete[] stub;
- for (; ptr < stub + STUB_SIZE - sizeof(BLOCK1_MAGIC_END); ptr++) {
- if (memcmp(ptr, BLOCK1_MAGIC_END, sizeof(BLOCK1_MAGIC_END)) == 0)
- break;
+ stream->seek(stubSize);
+ return true;
+}
+
+int32 signExtendAndOffset(uint32 val, int bit, uint32 offset) {
+ if (val & (1 << bit)) {
+ return (val | (0xffffffff << bit)) - offset;
}
+ return val + offset;
+}
- if (ptr == stub + STUB_SIZE - sizeof(BLOCK1_MAGIC_END)) {
- delete[] stub;
- return false;
+void applyClickteamPatch(Common::WriteStream *outStream, Common::SeekableReadStream *refStream,
+ Common::SeekableReadStream *patchStream, Common::SeekableReadStream *literalsStream) {
+ uint32 referenceBaseOffset = 0;
+
+ while (!patchStream->eos() && !outStream->err()) {
+ uint32 referenceReadSize = 0;
+ byte patchByte = patchStream->readByte();
+ if (patchByte & 0x80) {
+ uint32 litteralReadSize = (patchByte >> 5) & 3;
+ if (litteralReadSize == 0)
+ litteralReadSize = ((patchByte & 0x1f) + 1);
+ else
+ referenceReadSize = (patchByte & 0x1f) + 2;
+ byte *buf = new byte[litteralReadSize]; // optimize this
+ literalsStream->read(buf, litteralReadSize);
+ outStream->write(buf,litteralReadSize);
+ delete[] buf;
+ } else if (patchByte == 0)
+ referenceReadSize = patchStream->readUint16LE() + 0x81;
+ else
+ referenceReadSize = patchByte + 1;
+ if (referenceReadSize != 0) {
+ int referenceOffsetDelta;
+ patchByte = patchStream->readByte();
+ if (patchByte & 0x80) {
+ if ((patchByte & 0x40) == 0)
+ referenceOffsetDelta = signExtendAndOffset(patchByte & 0x3f, 5, 0);
+ else {
+ referenceOffsetDelta = ((patchByte & 0x3f) << 16) | patchStream->readUint16BE();
+ if (referenceOffsetDelta == 0x100000)
+ referenceOffsetDelta = patchStream->readSint32BE();
+ else
+ referenceOffsetDelta = signExtendAndOffset(referenceOffsetDelta, 21, 0x4020);
+ }
+ } else
+ referenceOffsetDelta = signExtendAndOffset(((patchByte & 0x7f) << 8) | patchStream->readByte(), 14, 0x20);
+ uint32 referenseOffset = referenceOffsetDelta + referenceBaseOffset;
+ byte *buf = new byte[referenceReadSize]; // optimize this
+ if (referenseOffset < refStream->size()) {
+ refStream->seek(referenseOffset);
+ refStream->read(buf, referenceReadSize);
+ } else {
+ memset(buf, 0, referenceReadSize);
+ }
+ // TODO: Handle zero-out blocks. We never encountered any so far
+ outStream->write(buf, referenceReadSize);
+ delete[] buf;
+ referenceBaseOffset += referenceOffsetDelta + referenceReadSize;
+ }
}
+}
- byte *block1end = ptr;
- crc = computeCRC(block1start, block1end - block1start, 0);
- delete[] stub;
+bool readBlockHeader(Common::SeekableReadStream *stream, uint32 &compressedSize, uint32 &uncompressedSize, bool &isCompressed) {
+ byte codec = stream->readByte();
+ if (codec > 7) {
+ warning("Unknown block codec %d", codec);
+ return false;
+ }
+ if (codec & 2)
+ uncompressedSize = stream->readUint32LE();
+ else
+ uncompressedSize = stream->readUint16LE();
+ switch (codec & 5) {
+ case 5:
+ compressedSize = stream->readUint32LE();
+ break;
+ case 1:
+ compressedSize = stream->readUint16LE();
+ break;
+ case 0:
+ compressedSize = uncompressedSize;
+ break;
+ default:
+ warning("Unknown block codec %d", codec);
+ return false;
+ }
+ isCompressed = codec & 1;
return true;
}
+} // end of anonymous namespace
struct TagHead {
uint16 id;
@@ -131,7 +304,50 @@ struct TagHead {
uint32 compressedLen;
};
+int ClickteamInstaller::findPatchIdx(const ClickteamFileDescriptor &desc, Common::SeekableReadStream *refStream,
+ const Common::String &fileName,
+ uint32 crcXor, bool doWarn) {
+ bool hasMatching = refStream->size() == desc._uncompressedSize; // Maybe already patched?
+ for (uint i = 0; !hasMatching && i < desc._patchEntries.size(); i++)
+ if (desc._patchEntries[i]._originalSize == refStream->size()) {
+ hasMatching = true;
+ break;
+ }
+ if (!hasMatching) {
+ if (doWarn)
+ warning("Couldn't find matching patch entry for file %s size %d", fileName.c_str(), (int)refStream->size());
+ return -1;
+ }
+ uint32 crcOriginal = 0;
+ {
+ byte buf[0x1000]; // Must be divisible by 4
+ while (!refStream->eos()) {
+ uint32 actual = refStream->read(buf, sizeof(buf));
+ crcOriginal = computeCRC(buf, actual, crcOriginal);
+ }
+ }
+ int patchDescIdx = -1;
+ for (uint i = 0; i < desc._patchEntries.size(); i++)
+ if (desc._patchEntries[i]._originalSize == refStream->size() && desc._patchEntries[i]._originalCRC == (crcOriginal ^ crcXor)) {
+ patchDescIdx = i;
+ break;
+ }
+ // Maybe already patched if nothing else is found?
+ if (patchDescIdx == -1 && refStream->size() == desc._uncompressedSize && crcOriginal == desc._expectedCRC)
+ return -2;
+ if (patchDescIdx < 0 && doWarn) {
+ warning("Couldn't find matching patch entry for file %s size %d and CRC 0x%x", fileName.c_str(), (int)refStream->size(), crcOriginal);
+ }
+ return patchDescIdx;
+}
+
+
ClickteamInstaller* ClickteamInstaller::open(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose) {
+ return openPatch(stream, false, true, nullptr, dispose);
+}
+
+ClickteamInstaller* ClickteamInstaller::openPatch(Common::SeekableReadStream *stream, bool verifyOriginal, bool verifyAllowSkip,
+ Common::Archive *reference, DisposeAfterUse::Flag dispose) {
Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> files;
HashMap<uint16, Common::SharedPtr<ClickteamTag>> tags;
uint32 crc_xor;
@@ -169,12 +385,12 @@ ClickteamInstaller* ClickteamInstaller::open(Common::SeekableReadStream *stream,
uint32 uncompressedPayloadLen = READ_LE_UINT32(compressedPayload);
byte *uncompressedPayload = new byte[uncompressedPayloadLen];
int32 ret = Common::GzioReadStream::clickteamDecompress(uncompressedPayload,
- uncompressedPayloadLen,
- compressedPayload + 4,
- compressedPayloadLen - 4);
+ uncompressedPayloadLen,
+ compressedPayload + 4,
+ compressedPayloadLen - 4);
delete[] compressedPayload;
if (ret < 0) {
- debug ("Decompression error");
+ warning("Decompression error for tag 0x%04x", tagId);
continue;
}
tag = new ClickteamTag(tagId, uncompressedPayload, uncompressedPayloadLen);
@@ -183,18 +399,23 @@ ClickteamInstaller* ClickteamInstaller::open(Common::SeekableReadStream *stream,
}
tags[tagId].reset(tag);
switch (tag->_tagId) {
- case (uint16) ClickteamTagId::FILE_LIST: {
+ case (uint16) ClickteamTagId::FILE_LIST:
+ case (uint16) ClickteamTagId::FILE_PATCHING_LIST: {
if (tag->_size < 4) {
return nullptr;
}
uint32 count = READ_LE_UINT32(tag->_contents);
uint32 off = 4;
for (unsigned i = 0; i < count && off + 0x24 < tag->_size; i++) {
- uint16 l = READ_LE_UINT16(tag->_contents + off);
- if (l < 0x24)
+ uint32 l = READ_LE_UINT32(tag->_contents + off);
+ if (l < 33)
break;
ClickteamFileDescriptor desc(*tag, off);
- files[desc._fileName] = desc;
+ if (desc._supported) {
+ // Prefer non-patches
+ if (!desc._isPatchFile || ! files.contains(desc._fileName))
+ files[desc._fileName] = desc;
+ }
off += l;
}
break;
@@ -213,7 +434,34 @@ ClickteamInstaller* ClickteamInstaller::open(Common::SeekableReadStream *stream,
if (block3_offset <= 0 || block3_len <= 0)
return nullptr;
- return new ClickteamInstaller(files, tags, crc_xor, block3_offset, block3_len, stream, dispose);
+ if (verifyOriginal && reference) {
+ for (Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo>::iterator i = files.begin(), end = files.end();
+ i != end; ++i) {
+ if (i->_value._isPatchFile) {
+ Common::ScopedPtr<Common::SeekableReadStream> refStream(reference->createReadStreamForMember(Common::Path(i->_key, '\\')));
+ if (!refStream) {
+ if (verifyAllowSkip) {
+ i->_value._isReferenceMissing = true;
+ continue;
+ }
+ return nullptr;
+ }
+ if (findPatchIdx(i->_value, refStream.get(), i->_key, crc_xor, false) == -1)
+ return nullptr;
+ }
+ }
+ }
+
+ if (!reference) {
+ for (Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo>::iterator i = files.begin(), end = files.end();
+ i != end; ++i) {
+ if (i->_value._isPatchFile) {
+ i->_value._isReferenceMissing = true;
+ }
+ }
+ }
+
+ return new ClickteamInstaller(files, tags, crc_xor, block3_offset, block3_len, stream, reference, dispose);
}
bool ClickteamInstaller::hasFile(const Path &path) const {
@@ -225,8 +473,10 @@ int ClickteamInstaller::listMembers(ArchiveMemberList &list) const {
for (Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo>::const_iterator i = _files.begin(), end = _files.end();
i != end; ++i) {
- list.push_back(ArchiveMemberList::value_type(new GenericArchiveMember(i->_key, this)));
- ++members;
+ if (!i->_value._isReferenceMissing) {
+ list.push_back(ArchiveMemberList::value_type(new GenericArchiveMember(i->_key, this)));
+ ++members;
+ }
}
return members;
@@ -234,43 +484,131 @@ int ClickteamInstaller::listMembers(ArchiveMemberList &list) const {
const ArchiveMemberPtr ClickteamInstaller::getMember(const Path &path) const {
Common::String translated = translatePath(path);
- if (!_files.contains(translated))
+ ClickteamFileDescriptor el;
+ if (!_files.tryGetVal(translated, el))
return nullptr;
- return Common::SharedPtr<Common::ArchiveMember>(new GenericArchiveMember(_files.getVal(translated)._fileName, this));
+ if (el._isReferenceMissing)
+ return nullptr;
+
+ return Common::SharedPtr<Common::ArchiveMember>(new GenericArchiveMember(el._fileName, this));
}
Common::SharedArchiveContents ClickteamInstaller::readContentsForPath(const Common::String& translated) const {
- if (!_files.contains(translated))
- return Common::SharedArchiveContents();
- ClickteamFileDescriptor desc = _files.getVal(translated);
- Common::SeekableReadStream *subStream = new Common::SeekableSubReadStream(_stream.get(), _block3Offset + desc._fileDataOffset,
- _block3Offset + desc._fileDataOffset + desc._compressedSize);
- if (!subStream) {
- debug("Decompression error");
+ ClickteamFileDescriptor desc;
+ byte *uncompressedBuffer = nullptr;
+
+ if (!_files.tryGetVal(translated, desc))
return Common::SharedArchiveContents();
- }
- Common::ScopedPtr<Common::SeekableReadStream> uncStream(GzioReadStream::openClickteam(subStream, desc._uncompressedSize, DisposeAfterUse::YES));
- if (!uncStream) {
- debug("Decompression error");
+ if (desc._isReferenceMissing)
return Common::SharedArchiveContents();
- }
- byte *uncompressedBuffer = new byte[desc._uncompressedSize];
+ if (desc._isPatchFile) {
+ Common::ScopedPtr<Common::SeekableReadStream> refStream(_reference->createReadStreamForMemberNext(Common::Path(translated, '\\'), this));
+ if (!refStream) {
+ warning("Couldn't open reference file for %s. Skipping", translated.c_str());
+ return Common::SharedArchiveContents();
+ }
+ int patchDescIdx = findPatchIdx(desc, refStream.get(), translated, _crcXor, true);
+ if (patchDescIdx == -1 || patchDescIdx < -2)
+ return Common::SharedArchiveContents();
- int64 ret = uncStream->read(uncompressedBuffer, desc._uncompressedSize);
- if (ret < 0 || ret < desc._uncompressedSize) {
- debug ("Decompression error");
- delete[] uncompressedBuffer;
- return Common::SharedArchiveContents();
+ refStream->seek(0);
+
+ // Already patched
+ if (patchDescIdx == -2) {
+ return Common::SharedArchiveContents::bypass(refStream.release());
+ }
+
+ uint32 patchDataOffset = _block3Offset + desc._patchEntries[patchDescIdx]._patchDataOffset;
+ _stream->seek(patchDataOffset);
+ uint32 patchCompressedSize, patchUncompressedSize;
+ bool patchIsCompressed;
+ if (!readBlockHeader(_stream.get(), patchCompressedSize, patchUncompressedSize, patchIsCompressed))
+ return Common::SharedArchiveContents();
+
+ uint32 patchStart = _stream->pos();
+ _stream->skip(patchCompressedSize);
+
+ uint32 literalsCompressedSize, literalsUncompressedSize;
+ bool literalsIsCompressed;
+ if (!readBlockHeader(_stream.get(), literalsCompressedSize, literalsUncompressedSize, literalsIsCompressed)) {
+ return Common::SharedArchiveContents();
+ }
+ uint32 literalsStart = _stream->pos();
+
+ Common::ScopedPtr<Common::SeekableReadStream> uncompressedPatchStream, uncompressedLiteralsStream;
+
+ if (patchIsCompressed) {
+ Common::SeekableReadStream *compressedPatchStream = new Common::SafeSeekableSubReadStream(
+ _stream.get(), patchStart, patchStart + patchCompressedSize);
+ if (!compressedPatchStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ uncompressedPatchStream.reset(GzioReadStream::openClickteam(compressedPatchStream, patchUncompressedSize, DisposeAfterUse::YES));
+ } else {
+ uncompressedPatchStream.reset(new Common::SafeSeekableSubReadStream(
+ _stream.get(), patchStart, patchStart + patchCompressedSize));
+
+ }
+ if (!uncompressedPatchStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ if (literalsIsCompressed) {
+ Common::SeekableReadStream *compressedLiteralsStream = new Common::SafeSeekableSubReadStream(
+ _stream.get(), literalsStart, literalsStart + literalsCompressedSize);
+ if (!compressedLiteralsStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ uncompressedLiteralsStream.reset(GzioReadStream::openClickteam(compressedLiteralsStream, literalsUncompressedSize, DisposeAfterUse::YES));
+ } else {
+ uncompressedLiteralsStream.reset(new Common::SafeSeekableSubReadStream(
+ _stream.get(), literalsStart, literalsStart + literalsCompressedSize));
+ }
+ if (!uncompressedLiteralsStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ uncompressedBuffer = new byte[desc._uncompressedSize];
+ Common::MemoryWriteStream outStream(uncompressedBuffer, desc._uncompressedSize);
+ applyClickteamPatch(&outStream, refStream.get(), uncompressedPatchStream.get(), uncompressedLiteralsStream.get());
+ } else {
+ Common::SeekableReadStream *subStream = new Common::SeekableSubReadStream(_stream.get(), _block3Offset + desc._fileDataOffset,
+ _block3Offset + desc._fileDataOffset + desc._compressedSize);
+ if (!subStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ Common::ScopedPtr<Common::SeekableReadStream> uncStream(GzioReadStream::openClickteam(subStream, desc._uncompressedSize, DisposeAfterUse::YES));
+ if (!uncStream) {
+ warning("Decompression error");
+ return Common::SharedArchiveContents();
+ }
+
+ uncompressedBuffer = new byte[desc._uncompressedSize];
+
+ int64 ret = uncStream->read(uncompressedBuffer, desc._uncompressedSize);
+ if (ret < 0 || ret < desc._uncompressedSize) {
+ warning ("Decompression error");
+ delete[] uncompressedBuffer;
+ return Common::SharedArchiveContents();
+ }
}
if (desc._expectedCRC != 0 || !desc._fileName.equalsIgnoreCase("Uninstal.exe")) {
- uint32 expectedCrc = desc._expectedCRC ^ _crcXor;
+ uint32 expectedCrc = desc._crcIsXorred ? desc._expectedCRC ^ _crcXor : desc._expectedCRC;
uint32 actualCrc = computeCRC(uncompressedBuffer, desc._uncompressedSize, 0);
if (actualCrc != expectedCrc) {
- debug("CRC mismatch for %s: expected=%08x (obfuscated %08x), actual=%08x", desc._fileName.c_str(), expectedCrc, desc._expectedCRC, actualCrc);
+ warning("CRC mismatch for %s: expected=%08x (obfuscated %08x), actual=%08x (back %08x)", desc._fileName.c_str(), expectedCrc, desc._expectedCRC, actualCrc, actualCrc ^ _crcXor);
delete[] uncompressedBuffer;
return Common::SharedArchiveContents();
}
diff --git a/common/compression/clickteam.h b/common/compression/clickteam.h
index 0eecfe1a007..bd5ef41091b 100644
--- a/common/compression/clickteam.h
+++ b/common/compression/clickteam.h
@@ -35,7 +35,8 @@ public:
BANNER_IMAGE = 0x1235,
FILE_LIST = 0x123a,
STRINGS = 0x123e,
- UNINSTALLER = 0x123f
+ UNINSTALLER = 0x123f,
+ FILE_PATCHING_LIST = 0x1242,
};
class ClickteamTag : Common::NonCopyable {
@@ -53,16 +54,24 @@ public:
}
};
- bool hasFile(const Path &path) const override;
+ bool hasFile(const Common::Path &path) const override;
int listMembers(Common::ArchiveMemberList&) const override;
- const ArchiveMemberPtr getMember(const Path &path) const override;
+ const ArchiveMemberPtr getMember(const Common::Path &path) const override;
Common::SharedArchiveContents readContentsForPath(const Common::String& translated) const override;
ClickteamTag* getTag(ClickteamTagId tagId) const;
+ static ClickteamInstaller* openPatch(Common::SeekableReadStream *stream, bool verifyOriginal = true, bool verifyAllowSkip = true,
+ Common::Archive *reference = &SearchMan, DisposeAfterUse::Flag dispose = DisposeAfterUse::NO);
static ClickteamInstaller* open(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose = DisposeAfterUse::NO);
private:
+ struct ClickteamPatchDescriptor {
+ uint32 _originalCRC;
+ uint32 _originalSize;
+ uint32 _patchDataOffset;
+ uint32 _patchSize;
+ };
class ClickteamFileDescriptor {
private:
Common::String _fileName;
@@ -74,6 +83,12 @@ private:
uint32 _compressedSize;
uint32 _uncompressedSize;
uint32 _expectedCRC;
+ bool _supported;
+ bool _isPatchFile;
+ bool _crcIsXorred;
+ bool _isReferenceMissing;
+ uint16 _field1c;
+ Common::Array<ClickteamPatchDescriptor> _patchEntries;
ClickteamFileDescriptor(const ClickteamTag& contentsTag, uint32 off);
friend class ClickteamInstaller;
@@ -84,14 +99,20 @@ private:
ClickteamInstaller(Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> files,
Common::HashMap<uint16, Common::SharedPtr<ClickteamTag>> tags,
- uint32 crcXor, uint32 block3Offset, uint32 block3Size, Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose)
- : _files(files), _tags(tags), _crcXor(crcXor), _block3Offset(block3Offset), /*_block3Size(block3Size), */_stream(stream, dispose) {
+ uint32 crcXor, uint32 block3Offset, uint32 block3Size, Common::SeekableReadStream *stream,
+ Common::Archive *reference,
+ DisposeAfterUse::Flag dispose)
+ : _files(files), _tags(tags), _crcXor(crcXor), _block3Offset(block3Offset), /*_block3Size(block3Size), */_stream(stream, dispose),
+ _reference(reference) {
}
+ static int findPatchIdx(const ClickteamFileDescriptor &desc, Common::SeekableReadStream *refStream, const Common::String &fileName,
+ uint32 crcXor, bool doWarn);
Common::HashMap<Common::String, ClickteamFileDescriptor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _files;
Common::HashMap<uint16, Common::SharedPtr<ClickteamTag>> _tags;
Common::DisposablePtr<Common::SeekableReadStream> _stream;
uint32 _crcXor, _block3Offset/*, _block3Size*/;
+ Common::Archive *_reference;
};
}
#endif
Commit: 2f70d7dad0eca332b0baacc8a9c47a7150b9d47e
https://github.com/scummvm/scummvm/commit/2f70d7dad0eca332b0baacc8a9c47a7150b9d47e
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
SCUMM: Detect PRCA patch
Changed paths:
engines/scumm/detection.h
engines/scumm/detection_internal.h
engines/scumm/detection_tables.h
diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index 5ccde31d359..e4fb7ab5ebf 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -247,6 +247,11 @@ enum ScummGameId {
GID_HECUP // CUP demos
};
+struct RuScummPatcher {
+ ScummGameId gameid;
+ const char *variant;
+ const char *patcherName;
+};
} // End of namespace Scumm
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index 8dc5a5f610f..f7e6bb76fba 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -24,6 +24,7 @@
#include "common/debug.h"
#include "common/md5.h"
+#include "common/punycode.h"
#include "gui/error.h"
@@ -210,7 +211,7 @@ static bool detectSpeech(const Common::FSList &fslist, const GameSettings *gs) {
}
// The following function tries to detect the language.
-static Common::Language detectLanguage(const Common::FSList &fslist, byte id, Common::Language originalLanguage = Common::UNK_LANG) {
+static Common::Language detectLanguage(const Common::FSList &fslist, byte id, const char *variant, Common::Language originalLanguage = Common::UNK_LANG) {
// First try to detect Chinese translation.
Common::FSNode fontFile;
@@ -219,6 +220,15 @@ static Common::Language detectLanguage(const Common::FSList &fslist, byte id, Co
return Common::ZH_CHN;
}
+ for (uint i = 0; ruScummPatcherTable[i].patcherName; i++) {
+ Common::FSNode patchFile;
+ if (ruScummPatcherTable[i].gameid == id && (variant == nullptr || strcmp(variant, ruScummPatcherTable[i].variant) == 0)
+ && searchFSNode(fslist, Common::punycode_decode(ruScummPatcherTable[i].patcherName), patchFile)) {
+ debugC(0, kDebugGlobalDetection, "Russian detected");
+ return Common::RU_RUS;
+ }
+ }
+
if (id != GID_CMI && id != GID_DIG) {
// Detect Korean fan translated games
Common::FSNode langFile;
@@ -385,7 +395,7 @@ static void computeGameSettingsFromMD5(const Common::FSList &fslist, const GameF
// HACK: Try to detect languages for translated games.
if (dr.language == UNK_LANG || dr.language == Common::EN_ANY) {
- dr.language = detectLanguage(fslist, dr.game.id, dr.language);
+ dr.language = detectLanguage(fslist, dr.game.id, g->variant, dr.language);
}
// HACK: Detect between 68k and PPC versions.
@@ -572,7 +582,7 @@ static void detectGames(const Common::FSList &fslist, Common::List<DetectorResul
}
// HACK: Perhaps it is some modified translation?
- dr.language = detectLanguage(fslist, g->id);
+ dr.language = detectLanguage(fslist, g->id, g->variant);
// Detect if there are speech files in this unknown game.
if (detectSpeech(fslist, g)) {
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 2e7fa664e12..6a603509775 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -904,6 +904,20 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ NULL, NULL, kGenUnchanged, UNK_LANG, UNK, 0 }
};
+static const RuScummPatcher ruScummPatcherTable[] = {
+ // cmi_rus_1.1b.exe is InstallShield
+ { GID_MANIAC, "V2", "mm_ega_rus_1.0.exe" },
+ { GID_MANIAC, "V2", "mm_ega_rus_1.1.exe" },
+ { GID_INDY3, "VGA", "indy3vga_rus_1.01_final.exe" },
+ { GID_INDY4, "", "indy_atlantis_rus_v1.0_cd.exe" }, // CD
+ { GID_LOOM, "EGA", "loom_ega_1.0_rus.exe" },
+ { GID_LOOM, "EGA", "loom_ega_1.1_rus.exe" },
+ { GID_LOOM, "FM-TOWNS", "loom_fm_1.0_rus.exe" },
+ { GID_ZAK, "V2", "zak_ega_rus_1.0.exe" },
+ { GID_TENTACLE, "", "xn-- . -. .1.0.exe-vtlacb4b3avgf1ea6cs7c2brf8abfez6ajun7b8d6byezgg" }, // CD, "Ð´ÐµÐ½Ñ ÑÑпалÑÑа. паÑÑеÑ-ÑÑÑиÑикаÑоÑ. веÑ.1.0.exe"
+ { GID_CMI, NULL, NULL }
+};
+
} // End of namespace Scumm
#endif
Commit: b021957fb378811d8d59efa466b02e16f1d99fb0
https://github.com/scummvm/scummvm/commit/b021957fb378811d8d59efa466b02e16f1d99fb0
Author: Vladimir Serbinenko (phcoder at gmail.com)
Date: 2023-03-13T20:17:33+01:00
Commit Message:
SCUMM: Support playing most RuSCUMM versions without patching manually
Changed paths:
engines/scumm/scumm.cpp
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 5afe9e21b6f..7ef252471c6 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -20,6 +20,7 @@
*/
#include "common/config-manager.h"
+#include "common/compression/clickteam.h"
#include "common/debug-channels.h"
#include "common/macresman.h"
#include "common/md5.h"
@@ -40,6 +41,7 @@
#include "scumm/charset.h"
#include "scumm/costume.h"
#include "scumm/debugger.h"
+#include "scumm/detection_tables.h"
#include "scumm/dialogs.h"
#include "scumm/file.h"
#include "scumm/file_nes.h"
@@ -888,6 +890,21 @@ Common::Error ScummEngine::init() {
const Common::FSNode gameDataDir(ConfMan.get("path"));
+ for (uint i = 0; ruScummPatcherTable[i].patcherName; i++) {
+ if (ruScummPatcherTable[i].gameid == _game.id && (_game.variant == nullptr || strcmp(_game.variant, ruScummPatcherTable[i].variant) == 0)) {
+ Common::File *f = new Common::File();
+ if (f->open(ruScummPatcherTable[i].patcherName)) {
+ Common::Archive *patcher = Common::ClickteamInstaller::openPatch(f, true, true, &SearchMan, DisposeAfterUse::YES);
+ if (patcher) {
+ SearchMan.add("ruscumm", patcher, 3);
+ break;
+ }
+ }
+ delete f;
+ }
+ }
+
+
ConfMan.registerDefault("original_gui", true);
if (ConfMan.hasKey("original_gui", _targetName)) {
_useOriginalGUI = ConfMan.getBool("original_gui");
More information about the Scummvm-git-logs
mailing list