Initial commit
This commit is contained in:
387
engines/grim/patchr.cpp
Normal file
387
engines/grim/patchr.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
/* 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::Path &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::Path _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::Path &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.toString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for appropriate signature
|
||||
if (patch.readUint32BE() != MKTAG('P','A','T','R')) {
|
||||
error("%s patchfile is corrupted, wrong siganture", _patchName.toString().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.toString().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.toString().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.toString().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.toString().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.toString().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.toString().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.toString().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.toString().c_str());
|
||||
}
|
||||
|
||||
if (relOffset == 0)
|
||||
return true;
|
||||
|
||||
if (relOffset < 0) {
|
||||
Debug::debug(Debug::Patchr, "Seeking back to start %s", _patchName.toString().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::Path &filename) {
|
||||
if (!rs)
|
||||
return nullptr;
|
||||
|
||||
Common::Path patchfile = filename.append(".patchr");
|
||||
int i = 1;
|
||||
while (SearchMan.hasFile(patchfile)) {
|
||||
Debug::debug(Debug::Patchr, "Patch requested for %s (patch filename %s)", filename.toString().c_str(), patchfile.toString().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.toString().c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
delete pf;
|
||||
patchfile = filename.append(Common::String::format("_%d.patchr", i++));
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
} // end of namespace Grim
|
||||
Reference in New Issue
Block a user