ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

patchr.cpp

Go to the documentation of this file.
00001 /* ResidualVM - A 3D game interpreter
00002  *
00003  * ResidualVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "common/substream.h"
00024 #include "common/md5.h"
00025 #include "common/file.h"
00026 #include "common/zlib.h"
00027 #include "common/bufferedstream.h"
00028 
00029 #include "engines/grim/patchr.h"
00030 #include "engines/grim/debug.h"
00031 
00032 namespace Grim {
00033 
00034 class PatchedFile : public Common::SeekableReadStream {
00035 public:
00036     PatchedFile();
00037     virtual ~PatchedFile();
00038 
00039     bool load(Common::SeekableReadStream *file, const Common::String &patchName);
00040 
00041     // Common::ReadStream implementation
00042     virtual bool eos() const override;
00043     virtual uint32 read(void *dataPtr, uint32 dataSize) override;
00044 
00045     // Common::SeekableReadStream implementation
00046     virtual int32 pos() const override;
00047     virtual int32 size() const override;
00048     virtual bool seek(int32 offset, int whence = SEEK_SET) override;
00049 
00050 private:
00051     // Consts
00052     static const uint32 _kDiffBufferSize, _kHeaderSize, _kMd5size;
00053     static const uint16 _kVersionMajor, _kVersionMinor;
00054 
00055     //Flags
00056     enum Flags {
00057         FLAG_MIX_DIFF_EXTRA = 1 << 0,
00058         FLAG_COMPRESS_CTRL = 1 << 1
00059     };
00060 
00061     // Streams
00062     Common::SeekableReadStream *_file;
00063     Common::SeekableReadStream *_ctrl, *_diff, *_extra;
00064 
00065     // Current instruction
00066     uint32 _diffCopy, _extraCopy;
00067     int32 _jump;
00068     int32 _instrLeft;
00069     bool readNextInst();
00070 
00071     int32 _pos;
00072     uint32 _flags, _newSize;
00073 
00074     uint8 *_diffBuffer;
00075 
00076     Common::String _patchName;
00077 };
00078 
00079 const uint16 PatchedFile::_kVersionMajor = 2;
00080 const uint16 PatchedFile::_kVersionMinor = 0;
00081 const uint32 PatchedFile::_kDiffBufferSize = 1024;
00082 const uint32 PatchedFile::_kHeaderSize = 48;
00083 const uint32 PatchedFile::_kMd5size = 5000;
00084 
00085 PatchedFile::PatchedFile():
00086         _file(nullptr), _ctrl(nullptr), _diff(nullptr), _extra(nullptr), _pos(0),
00087         _instrLeft(0), _jump(0), _newSize(0), _flags(0), _extraCopy(0),
00088         _diffCopy(0) {
00089     _diffBuffer = new uint8[_kDiffBufferSize];
00090 }
00091 
00092 PatchedFile::~PatchedFile() {
00093     delete[] _diffBuffer;
00094 
00095     delete _file;
00096 
00097     delete _ctrl;
00098     delete _diff;
00099     if (!(_flags & FLAG_MIX_DIFF_EXTRA))
00100         delete _extra;
00101 }
00102 
00103 bool PatchedFile::load(Common::SeekableReadStream *file, const Common::String &patchName) {
00104     uint8 md5_p[16], md5_f[16];
00105     uint32 zctrllen, zdatalen, zextralen;
00106     Common::File patch;
00107 
00108     _patchName = patchName;
00109 
00110     // Open the patch
00111     if (!patch.open(_patchName)) {
00112         error("Unable to open patchfile %s", _patchName.c_str());
00113         return false;
00114     }
00115 
00116     // Check for appropriate signature
00117     if (patch.readUint32BE() != MKTAG('P','A','T','R')) {
00118         error("%s patchfile is corrupted, wrong siganture", _patchName.c_str());
00119         return false;
00120     }
00121 
00122     // Check the version number
00123     if (patch.readUint16LE() != _kVersionMajor || patch.readUint16LE() > _kVersionMinor) {
00124         error("%s has a wrong version number (must be major = %d, minor <= %d)", _patchName.c_str(), _kVersionMajor, _kVersionMinor);
00125         return false;
00126     }
00127 
00128     _flags = patch.readUint32LE();
00129 
00130     // Check if the file to patch match
00131     Common::computeStreamMD5(*file, md5_f, _kMd5size);
00132     file->seek(0, SEEK_SET);
00133     patch.read(md5_p, 16);
00134     uint32 fileSize = patch.readUint32LE();
00135     if (memcmp(md5_p, md5_f, 16) != 0 || (uint32)file->size() != fileSize) {
00136         Debug::debug(Debug::Patchr, "%s targets a different file", _patchName.c_str());
00137         if (Debug::isChannelEnabled(Debug::Patchr)) {
00138             Common::String md5_ps, md5_fs;
00139             for (int i = 0; i < 16; i++) {
00140                 md5_ps += Common::String::format("%02x", (int)md5_p[i]);
00141                 md5_fs += Common::String::format("%02x", (int)md5_f[i]);
00142             }
00143             Debug::debug(Debug::Patchr, "Patch target: size = %d, md5 = %s", fileSize, md5_ps.c_str());
00144             Debug::debug(Debug::Patchr, "Actual file : size = %d, md5 = %s", (uint32)file->size(), md5_fs.c_str());
00145         }
00146         return false;
00147     }
00148 
00149     // Read lengths from header
00150     _newSize = patch.readUint32LE();
00151     zctrllen = patch.readUint32LE();
00152     zdatalen = patch.readUint32LE();
00153     zextralen = patch.readUint32LE();
00154 
00155     patch.close();
00156 
00157     // Opens ctrl, diff and extra substreams
00158     Common::File *tmp;
00159     tmp = new Common::File;
00160     tmp->open(_patchName);
00161     _ctrl = new Common::SeekableSubReadStream(tmp, _kHeaderSize, _kHeaderSize + zctrllen, DisposeAfterUse::YES);
00162     if (_flags & FLAG_COMPRESS_CTRL)
00163         _ctrl = Common::wrapCompressedReadStream(_ctrl);
00164 
00165     //ctrl stream sanity checks
00166     if (_ctrl->size() % (3 * sizeof(uint32)) != 0) {
00167         error("%s patchfile is corrupted", _patchName.c_str());
00168         return false;
00169     }
00170 
00171     _instrLeft = _ctrl->size() / (3 * sizeof(uint32));
00172 
00173     tmp = new Common::File;
00174     tmp->open(_patchName);
00175     _diff = new Common::SeekableSubReadStream(tmp, _kHeaderSize + zctrllen, _kHeaderSize + zctrllen + zdatalen, DisposeAfterUse::YES);
00176     _diff = Common::wrapCompressedReadStream(_diff);
00177 
00178     if (_flags & FLAG_MIX_DIFF_EXTRA)
00179         _extra = _diff;
00180     else {
00181         tmp = new Common::File;
00182         tmp->open(_patchName);
00183         _extra = new Common::SeekableSubReadStream(tmp, _kHeaderSize + zctrllen + zdatalen, _kHeaderSize + zctrllen + zdatalen + zextralen, DisposeAfterUse::YES);
00184         _extra = Common::wrapCompressedReadStream(_extra);
00185     }
00186 
00187     _file = file;
00188 
00189     readNextInst();
00190 
00191     return true;
00192 }
00193 
00194 uint32 PatchedFile::read(void *dataPtr, uint32 dataSize) {
00195     uint32 readSize, diffRead, toRead, rd;
00196     byte *data = (byte*)dataPtr;
00197 
00198     toRead = dataSize;
00199     while (toRead > 0) {
00200         // Read data from original file and apply the differences
00201         if (_diffCopy > 0) {
00202             readSize = MIN(toRead, _diffCopy);
00203             rd = _file->read(data, readSize);
00204             if (_file->err() || rd != readSize)
00205                 error("%s: Corrupted patchfile", _patchName.c_str());
00206 
00207             toRead -= readSize;
00208             _diffCopy -= readSize;
00209 
00210             //Read data from diff as blocks of size _kDiffBufferSize,
00211             // then xor original data with them in groups of 4 bytes
00212             while (readSize > 0) {
00213                 diffRead = MIN(readSize, _kDiffBufferSize);
00214                 rd = _diff->read(_diffBuffer, diffRead);
00215                 if (_diff->err() || rd != diffRead)
00216                     error("%s: Corrupted patchfile", _patchName.c_str());
00217 
00218                 for (uint32 i = 0; i < diffRead / 4; ++i)
00219                     WRITE_UINT32((uint32 *)data + i, READ_UINT32((uint32 *)data + i) ^ READ_UINT32((uint32 *)_diffBuffer + i));
00220                 for (uint32 i = diffRead - diffRead % 4; i < diffRead; ++i)
00221                     data[i] ^= _diffBuffer[i];
00222                 
00223                 readSize -= diffRead;
00224                 data += diffRead;
00225             }
00226         }
00227 
00228         if (toRead == 0)
00229             break;
00230 
00231         // Read data from extra
00232         if (_extraCopy > 0) {
00233             readSize = MIN(toRead, _extraCopy);
00234             rd = _extra->read(data, readSize);
00235             if (_extra->err() || rd != readSize)
00236                 error("%s: Corrupted patchfile", _patchName.c_str());
00237 
00238             data += readSize;
00239             toRead -= readSize;
00240             _extraCopy -= readSize;
00241         }
00242 
00243         // Jump and read next instructions
00244         if (_diffCopy == 0 && _extraCopy == 0) {
00245             if (_jump != 0)
00246                 _file->seek(_jump, SEEK_CUR);
00247 
00248             //If there aren't new instructions, breaks here
00249             if (!readNextInst())
00250                 break;
00251         }
00252     }
00253 
00254     _pos += dataSize - toRead;
00255     return (dataSize - toRead);
00256 }
00257 
00258 bool PatchedFile::readNextInst() {
00259     if (_instrLeft == 0) {
00260         _diffCopy = 0;
00261         _extraCopy = 0;
00262         _jump = 0;
00263         return false;
00264     }
00265 
00266     _diffCopy = _ctrl->readUint32LE();
00267     _extraCopy = _ctrl->readUint32LE();
00268     _jump = _ctrl->readSint32LE();
00269 
00270     //Sanity checks
00271     if (_ctrl->err() ||
00272             (int32(_diffCopy) > _file->size() - _file->pos()) ||
00273             (int32(_diffCopy) > _diff->size() - _diff->pos()) ||
00274             (int32(_extraCopy) > _extra->size() - _extra->pos()) ||
00275             (_jump > _file->size() - _file->pos()))
00276         error("%s: Corrupted patchfile. istrleft = %d", _patchName.c_str(), _instrLeft);
00277 
00278     --_instrLeft;
00279     return true;
00280 }
00281 
00282 bool PatchedFile::eos() const {
00283     if (_pos >= (int32)_newSize)
00284         return true;
00285     else
00286         return false;
00287 }
00288 
00289 int32 PatchedFile::pos() const {
00290     return _pos;
00291 }
00292 
00293 int32 PatchedFile::size() const {
00294     return _newSize;
00295 }
00296 
00297 bool PatchedFile::seek(int32 offset, int whence) {
00298     int32 totJump, relOffset;
00299     uint32 skipDiff, skipExtra, skipSize;
00300     relOffset = 0;
00301     skipDiff = 0;
00302     skipExtra = 0;
00303     totJump = 0;
00304 
00305     switch (whence) {
00306         case SEEK_SET:
00307             relOffset = offset - pos();
00308             break;
00309         case SEEK_CUR:
00310             relOffset = offset;
00311             break;
00312         case SEEK_END:
00313             relOffset = (size() + offset) - pos();
00314             break;
00315         default:
00316             error("%s: Invalid seek instruction", _patchName.c_str());
00317     }
00318 
00319     if (relOffset == 0)
00320         return true;
00321 
00322     if (relOffset < 0) {
00323         Debug::debug(Debug::Patchr, "Seeking back to start %s", _patchName.c_str());
00324         _file->seek(0, SEEK_SET);
00325         _ctrl->seek(0, SEEK_SET);
00326         _extra->seek(0, SEEK_SET);
00327         _instrLeft = _ctrl->size() / (3 * sizeof(uint32));
00328         readNextInst();
00329         int p = pos() + relOffset;
00330         _pos = 0;
00331         return seek(p, SEEK_SET);
00332     }
00333 
00334     while (relOffset > 0) {
00335         if (_diffCopy > 0) {
00336             skipSize = MIN(_diffCopy, (uint32)relOffset);
00337             _diffCopy -= skipSize;
00338             relOffset -= skipSize;
00339             skipDiff += skipSize;
00340             totJump += skipSize;
00341         }
00342         if (relOffset == 0)
00343             break;
00344 
00345         if (_extraCopy > 0) {
00346             skipSize = MIN(_extraCopy, (uint32)relOffset);
00347             _extraCopy -= skipSize;
00348             relOffset -= skipSize;
00349             skipExtra += skipSize;
00350         }
00351 
00352         if (_diffCopy == 0 && _extraCopy == 0) {
00353             totJump += _jump;
00354             readNextInst();
00355         }
00356     }
00357     _diff->seek(skipDiff, SEEK_CUR);
00358     _extra->seek(skipExtra, SEEK_CUR);
00359     _file->seek(totJump, SEEK_CUR);
00360 
00361     return true;
00362 }
00363 
00364 Common::SeekableReadStream *wrapPatchedFile(Common::SeekableReadStream *rs, const Common::String &filename) {
00365     if (!rs)
00366         return nullptr;
00367 
00368     Common::String patchfile = filename + ".patchr";
00369     int i = 1;
00370     while (SearchMan.hasFile(patchfile)) {
00371         Debug::debug(Debug::Patchr, "Patch requested for %s (patch filename %s)", filename.c_str(), patchfile.c_str());
00372 
00373         PatchedFile *pf = new PatchedFile;
00374         if (pf->load(rs, patchfile)) {
00375             rs = Common::wrapBufferedSeekableReadStream(pf, 1024, DisposeAfterUse::YES);
00376             Debug::debug(Debug::Patchr, "Patch for %s successfully loaded", filename.c_str());
00377             break;
00378         }
00379 
00380         delete pf;
00381         patchfile = Common::String::format("%s_%d.patchr", filename.c_str(), i++);
00382     }
00383 
00384     return rs;
00385 }
00386 
00387 } // end of namespace Grim


Generated on Sat Oct 19 2019 05:01:05 for ResidualVM by doxygen 1.7.1
curved edge   curved edge