mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
zlib is used when available and falls back on gzio. This allows performance improvements as our CRC32 and gzio implementations are slower than base zlib. As zlib is available when libpng is present, this is sensible to benefit from it.
388 lines
10 KiB
C++
388 lines
10 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 "common/substream.h"
|
|
#include "common/md5.h"
|
|
#include "common/file.h"
|
|
#include "common/compression/deflate.h"
|
|
#include "common/bufferedstream.h"
|
|
|
|
#include "engines/grim/patchr.h"
|
|
#include "engines/grim/debug.h"
|
|
|
|
namespace Grim {
|
|
|
|
class PatchedFile : public Common::SeekableReadStream {
|
|
public:
|
|
PatchedFile();
|
|
virtual ~PatchedFile();
|
|
|
|
bool load(Common::SeekableReadStream *file, const Common::String &patchName);
|
|
|
|
// Common::ReadStream implementation
|
|
bool eos() const override;
|
|
uint32 read(void *dataPtr, uint32 dataSize) override;
|
|
|
|
// Common::SeekableReadStream implementation
|
|
int64 pos() const override;
|
|
int64 size() const override;
|
|
bool seek(int64 offset, int whence = SEEK_SET) override;
|
|
|
|
private:
|
|
// Consts
|
|
static const uint32 _kDiffBufferSize, _kHeaderSize, _kMd5size;
|
|
static const uint16 _kVersionMajor, _kVersionMinor;
|
|
|
|
// Flags
|
|
enum Flags {
|
|
FLAG_MIX_DIFF_EXTRA = 1 << 0,
|
|
FLAG_COMPRESS_CTRL = 1 << 1
|
|
};
|
|
|
|
// Streams
|
|
Common::SeekableReadStream *_file;
|
|
Common::SeekableReadStream *_ctrl, *_diff, *_extra;
|
|
|
|
// Current instruction
|
|
uint32 _diffCopy, _extraCopy;
|
|
int32 _jump;
|
|
int32 _instrLeft;
|
|
bool readNextInst();
|
|
|
|
int32 _pos;
|
|
uint32 _flags, _newSize;
|
|
|
|
uint8 *_diffBuffer;
|
|
|
|
Common::String _patchName;
|
|
};
|
|
|
|
const uint16 PatchedFile::_kVersionMajor = 2;
|
|
const uint16 PatchedFile::_kVersionMinor = 0;
|
|
const uint32 PatchedFile::_kDiffBufferSize = 1024;
|
|
const uint32 PatchedFile::_kHeaderSize = 48;
|
|
const uint32 PatchedFile::_kMd5size = 5000;
|
|
|
|
PatchedFile::PatchedFile():
|
|
_file(nullptr), _ctrl(nullptr), _diff(nullptr), _extra(nullptr), _pos(0),
|
|
_instrLeft(0), _jump(0), _newSize(0), _flags(0), _extraCopy(0),
|
|
_diffCopy(0) {
|
|
_diffBuffer = new uint8[_kDiffBufferSize];
|
|
}
|
|
|
|
PatchedFile::~PatchedFile() {
|
|
delete[] _diffBuffer;
|
|
|
|
delete _file;
|
|
|
|
delete _ctrl;
|
|
delete _diff;
|
|
if (!(_flags & FLAG_MIX_DIFF_EXTRA))
|
|
delete _extra;
|
|
}
|
|
|
|
bool PatchedFile::load(Common::SeekableReadStream *file, const Common::String &patchName) {
|
|
uint8 md5_p[16], md5_f[16];
|
|
uint32 zctrllen, zdatalen, zextralen;
|
|
Common::File patch;
|
|
|
|
_patchName = patchName;
|
|
|
|
// Open the patch
|
|
if (!patch.open(_patchName)) {
|
|
error("Unable to open patchfile %s", _patchName.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Check for appropriate signature
|
|
if (patch.readUint32BE() != MKTAG('P','A','T','R')) {
|
|
error("%s patchfile is corrupted, wrong siganture", _patchName.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Check the version number
|
|
if (patch.readUint16LE() != _kVersionMajor || patch.readUint16LE() > _kVersionMinor) {
|
|
error("%s has a wrong version number (must be major = %d, minor <= %d)", _patchName.c_str(), _kVersionMajor, _kVersionMinor);
|
|
return false;
|
|
}
|
|
|
|
_flags = patch.readUint32LE();
|
|
|
|
// Check if the file to patch match
|
|
Common::computeStreamMD5(*file, md5_f, _kMd5size);
|
|
file->seek(0, SEEK_SET);
|
|
patch.read(md5_p, 16);
|
|
uint32 fileSize = patch.readUint32LE();
|
|
if (memcmp(md5_p, md5_f, 16) != 0 || (uint32)file->size() != fileSize) {
|
|
Debug::debug(Debug::Patchr, "%s targets a different file", _patchName.c_str());
|
|
if (Debug::isChannelEnabled(Debug::Patchr)) {
|
|
Common::String md5_ps, md5_fs;
|
|
for (int i = 0; i < 16; i++) {
|
|
md5_ps += Common::String::format("%02x", (int)md5_p[i]);
|
|
md5_fs += Common::String::format("%02x", (int)md5_f[i]);
|
|
}
|
|
Debug::debug(Debug::Patchr, "Patch target: size = %d, md5 = %s", fileSize, md5_ps.c_str());
|
|
Debug::debug(Debug::Patchr, "Actual file : size = %d, md5 = %s", (uint32)file->size(), md5_fs.c_str());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Read lengths from header
|
|
_newSize = patch.readUint32LE();
|
|
zctrllen = patch.readUint32LE();
|
|
zdatalen = patch.readUint32LE();
|
|
zextralen = patch.readUint32LE();
|
|
|
|
patch.close();
|
|
|
|
// Opens ctrl, diff and extra substreams
|
|
Common::File *tmp;
|
|
tmp = new Common::File;
|
|
tmp->open(_patchName);
|
|
_ctrl = new Common::SeekableSubReadStream(tmp, _kHeaderSize, _kHeaderSize + zctrllen, DisposeAfterUse::YES);
|
|
if (_flags & FLAG_COMPRESS_CTRL)
|
|
_ctrl = Common::wrapCompressedReadStream(_ctrl);
|
|
|
|
//ctrl stream sanity checks
|
|
if (_ctrl->size() % (3 * sizeof(uint32)) != 0) {
|
|
error("%s patchfile is corrupted", _patchName.c_str());
|
|
return false;
|
|
}
|
|
|
|
_instrLeft = _ctrl->size() / (3 * sizeof(uint32));
|
|
|
|
tmp = new Common::File;
|
|
tmp->open(_patchName);
|
|
_diff = new Common::SeekableSubReadStream(tmp, _kHeaderSize + zctrllen, _kHeaderSize + zctrllen + zdatalen, DisposeAfterUse::YES);
|
|
_diff = Common::wrapCompressedReadStream(_diff);
|
|
|
|
if (_flags & FLAG_MIX_DIFF_EXTRA)
|
|
_extra = _diff;
|
|
else {
|
|
tmp = new Common::File;
|
|
tmp->open(_patchName);
|
|
_extra = new Common::SeekableSubReadStream(tmp, _kHeaderSize + zctrllen + zdatalen, _kHeaderSize + zctrllen + zdatalen + zextralen, DisposeAfterUse::YES);
|
|
_extra = Common::wrapCompressedReadStream(_extra);
|
|
}
|
|
|
|
_file = file;
|
|
|
|
readNextInst();
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32 PatchedFile::read(void *dataPtr, uint32 dataSize) {
|
|
uint32 readSize, diffRead, toRead, rd;
|
|
byte *data = (byte*)dataPtr;
|
|
|
|
toRead = dataSize;
|
|
while (toRead > 0) {
|
|
// Read data from original file and apply the differences
|
|
if (_diffCopy > 0) {
|
|
readSize = MIN(toRead, _diffCopy);
|
|
rd = _file->read(data, readSize);
|
|
if (_file->err() || rd != readSize)
|
|
error("%s: Corrupted patchfile", _patchName.c_str());
|
|
|
|
toRead -= readSize;
|
|
_diffCopy -= readSize;
|
|
|
|
//Read data from diff as blocks of size _kDiffBufferSize,
|
|
// then xor original data with them in groups of 4 bytes
|
|
while (readSize > 0) {
|
|
diffRead = MIN(readSize, _kDiffBufferSize);
|
|
rd = _diff->read(_diffBuffer, diffRead);
|
|
if (_diff->err() || rd != diffRead)
|
|
error("%s: Corrupted patchfile", _patchName.c_str());
|
|
|
|
for (uint32 i = 0; i < diffRead / 4; ++i)
|
|
WRITE_UINT32((uint32 *)data + i, READ_UINT32((uint32 *)data + i) ^ READ_UINT32((uint32 *)_diffBuffer + i));
|
|
for (uint32 i = diffRead - diffRead % 4; i < diffRead; ++i)
|
|
data[i] ^= _diffBuffer[i];
|
|
|
|
readSize -= diffRead;
|
|
data += diffRead;
|
|
}
|
|
}
|
|
|
|
if (toRead == 0)
|
|
break;
|
|
|
|
// Read data from extra
|
|
if (_extraCopy > 0) {
|
|
readSize = MIN(toRead, _extraCopy);
|
|
rd = _extra->read(data, readSize);
|
|
if (_extra->err() || rd != readSize)
|
|
error("%s: Corrupted patchfile", _patchName.c_str());
|
|
|
|
data += readSize;
|
|
toRead -= readSize;
|
|
_extraCopy -= readSize;
|
|
}
|
|
|
|
// Jump and read next instructions
|
|
if (_diffCopy == 0 && _extraCopy == 0) {
|
|
if (_jump != 0)
|
|
_file->seek(_jump, SEEK_CUR);
|
|
|
|
//If there aren't new instructions, breaks here
|
|
if (!readNextInst())
|
|
break;
|
|
}
|
|
}
|
|
|
|
_pos += dataSize - toRead;
|
|
return (dataSize - toRead);
|
|
}
|
|
|
|
bool PatchedFile::readNextInst() {
|
|
if (_instrLeft == 0) {
|
|
_diffCopy = 0;
|
|
_extraCopy = 0;
|
|
_jump = 0;
|
|
return false;
|
|
}
|
|
|
|
_diffCopy = _ctrl->readUint32LE();
|
|
_extraCopy = _ctrl->readUint32LE();
|
|
_jump = _ctrl->readSint32LE();
|
|
|
|
//Sanity checks
|
|
if (_ctrl->err() ||
|
|
(int32(_diffCopy) > _file->size() - _file->pos()) ||
|
|
(int32(_diffCopy) > _diff->size() - _diff->pos()) ||
|
|
(int32(_extraCopy) > _extra->size() - _extra->pos()) ||
|
|
(_jump > _file->size() - _file->pos())) {
|
|
error("%s: Corrupted patchfile. istrleft = %d", _patchName.c_str(), _instrLeft);
|
|
}
|
|
|
|
--_instrLeft;
|
|
return true;
|
|
}
|
|
|
|
bool PatchedFile::eos() const {
|
|
if (_pos >= (int32)_newSize)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
int64 PatchedFile::pos() const {
|
|
return _pos;
|
|
}
|
|
|
|
int64 PatchedFile::size() const {
|
|
return _newSize;
|
|
}
|
|
|
|
bool PatchedFile::seek(int64 offset, int whence) {
|
|
int32 totJump, relOffset;
|
|
uint32 skipDiff, skipExtra, skipSize;
|
|
relOffset = 0;
|
|
skipDiff = 0;
|
|
skipExtra = 0;
|
|
totJump = 0;
|
|
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
relOffset = offset - pos();
|
|
break;
|
|
case SEEK_CUR:
|
|
relOffset = offset;
|
|
break;
|
|
case SEEK_END:
|
|
relOffset = (size() + offset) - pos();
|
|
break;
|
|
default:
|
|
error("%s: Invalid seek instruction", _patchName.c_str());
|
|
}
|
|
|
|
if (relOffset == 0)
|
|
return true;
|
|
|
|
if (relOffset < 0) {
|
|
Debug::debug(Debug::Patchr, "Seeking back to start %s", _patchName.c_str());
|
|
_file->seek(0, SEEK_SET);
|
|
_ctrl->seek(0, SEEK_SET);
|
|
_extra->seek(0, SEEK_SET);
|
|
_instrLeft = _ctrl->size() / (3 * sizeof(uint32));
|
|
readNextInst();
|
|
int p = pos() + relOffset;
|
|
_pos = 0;
|
|
return seek(p, SEEK_SET);
|
|
}
|
|
|
|
while (relOffset > 0) {
|
|
if (_diffCopy > 0) {
|
|
skipSize = MIN(_diffCopy, (uint32)relOffset);
|
|
_diffCopy -= skipSize;
|
|
relOffset -= skipSize;
|
|
skipDiff += skipSize;
|
|
totJump += skipSize;
|
|
}
|
|
if (relOffset == 0)
|
|
break;
|
|
|
|
if (_extraCopy > 0) {
|
|
skipSize = MIN(_extraCopy, (uint32)relOffset);
|
|
_extraCopy -= skipSize;
|
|
relOffset -= skipSize;
|
|
skipExtra += skipSize;
|
|
}
|
|
|
|
if (_diffCopy == 0 && _extraCopy == 0) {
|
|
totJump += _jump;
|
|
readNextInst();
|
|
}
|
|
}
|
|
_diff->seek(skipDiff, SEEK_CUR);
|
|
_extra->seek(skipExtra, SEEK_CUR);
|
|
_file->seek(totJump, SEEK_CUR);
|
|
|
|
return true;
|
|
}
|
|
|
|
Common::SeekableReadStream *wrapPatchedFile(Common::SeekableReadStream *rs, const Common::String &filename) {
|
|
if (!rs)
|
|
return nullptr;
|
|
|
|
Common::String patchfile = filename + ".patchr";
|
|
int i = 1;
|
|
while (SearchMan.hasFile(patchfile)) {
|
|
Debug::debug(Debug::Patchr, "Patch requested for %s (patch filename %s)", filename.c_str(), patchfile.c_str());
|
|
|
|
PatchedFile *pf = new PatchedFile;
|
|
if (pf->load(rs, patchfile)) {
|
|
rs = Common::wrapBufferedSeekableReadStream(pf, 1024, DisposeAfterUse::YES);
|
|
Debug::debug(Debug::Patchr, "Patch for %s successfully loaded", filename.c_str());
|
|
break;
|
|
}
|
|
|
|
delete pf;
|
|
patchfile = Common::String::format("%s_%d.patchr", filename.c_str(), i++);
|
|
}
|
|
|
|
return rs;
|
|
}
|
|
|
|
} // end of namespace Grim
|