scummvm/engines/grim/patchr.cpp

249 lines
6.5 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "common/array.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/memstream.h"
#include "common/tokenizer.h"
#include "common/md5.h"
#include "common/file.h"
#include "engines/grim/patchr.h"
#include "engines/grim/debug.h"
namespace Grim {
const char *Patchr::InstructionS[8] = {"BEGIN", "END", "REPLACE", "INSERT", "DELETE", "FILL", "COPY", NULL};
void Patchr::loadPatch(Common::SeekableReadStream *patchStream) {
Common::String line, token;
struct Op op;
uint line_n = 0;
while (!patchStream->eos()) {
line = patchStream->readLine();
op.line_n = ++line_n;
line.trim();
Common::StringTokenizer patchTokens(line);
if (line.empty() || line[0] == '#')
continue;
//Extract the instruction
token = patchTokens.nextToken();
op.ist = INVALID;
for (uint i = 0; InstructionS[i] != NULL; ++i)
if (token.equalsIgnoreCase(InstructionS[i]))
op.ist = Instruction(i);
//Extract the arguments
op.args.clear();
while (!patchTokens.empty()) {
token = patchTokens.nextToken();
if (token.empty())
continue;
if (token[0] == '#')
break;
op.args.push_back(token);
}
_patch.push_back(op);
}
delete patchStream;
}
uint32 Patchr::calcIncSize(Common::Array<Op>::const_iterator start) {
uint32 incSize = 0;
for (Common::Array<Op>::const_iterator i = start; i->ist != END && i != _patch.end(); ++i)
if (i->ist == INSERT)
incSize += str2num(i->args[1]);
return incSize;
}
bool Patchr::patchFile(Common::SeekableReadStream *&file, Common::String &name) {
Common::Array<Op>::const_iterator line;
Common::String md5;
uint32 maxSize, fileSize;
uint32 offset, offset2, size;
byte fill;
//Compute the MD5 of the original file
md5 = computeStreamMD5AsString(*file, _kMd5size);
file->seek(0, SEEK_SET);
//Search a BEGIN statement with the right md5
for (line = _patch.begin(); line != _patch.end(); ++line)
if (line->ist == BEGIN)
if (md5.equalsIgnoreCase(line->args[0])) {
++line;
break;
}
if (line == _patch.end()) {
Debug::warning(Debug::Patchr, "No suitable patch for %s", name.c_str());
return false;
}
//Calc the maximum size of resulting file and read it
fileSize = file->size();
maxSize = fileSize + calcIncSize(line);
if (_err)
return false;
if (maxSize > _kMaxFileSize) {
Debug::warning(Debug::Patchr, "Requested patch makes the resulting file too big (> %u bytes)", _kMaxFileSize);
return false;
}
_data = new byte[maxSize];
file->read(_data, fileSize);
//Patch it!!
while (line->ist != END && line != _patch.end()) {
_curLine = *line;
switch (line->ist) {
case REPLACE:
if (line->args.size() < 2) {
err("Too few arguments");
return false;
}
offset = str2num(line->args[0]);
size = line->args.size() - 1;
if (offset + size > fileSize) {
err("out of bounds");
return false;
}
for (uint32 i = 0; i < size; ++i)
_data[offset + i] = byte(str2num(line->args[i + 1]));
break;
case INSERT:
if (line->args.size() < 2) {
err("Too few arguments");
return false;
}
offset = str2num(line->args[0]);
size = str2num(line->args[1]);
assert(fileSize + size <= maxSize);
if (offset > fileSize) {
err("out of bounds");
return false;
}
memmove(_data + offset + size, _data + offset, fileSize - offset);
fileSize += size;
break;
case DELETE:
if (line->args.size() < 2) {
err("Too few arguments");
return false;
}
offset = str2num(line->args[0]);
size = str2num(line->args[1]);
if (offset + size > fileSize) {
err("out of bounds");
return false;
}
memmove(_data + offset, _data + offset + size, fileSize - (offset + size));
fileSize -= size;
break;
case FILL:
if (line->args.size() < 3) {
err("Too few arguments");
return false;
}
offset = str2num(line->args[0]);
size = str2num(line->args[1]);
fill = byte(str2num(line->args[2]));
if (offset + size > fileSize) {
err("out of bounds");
return false;
}
memset(_data + offset, fill, size);
break;
case COPY:
if (line->args.size() < 3) {
err("Too few arguments");
return false;
}
offset = str2num(line->args[0]);
size = str2num(line->args[1]);
offset2 = str2num(line->args[2]);
if (offset + size > fileSize || offset2 + size > fileSize) {
err("out of bounds");
return false;
}
memmove(_data + offset, _data + offset2, size);
break;
case BEGIN:
err("misplaced instruction. Instructions block not closed by an END.");
break;
case INVALID:
Debug::warning(Debug::Patchr, "Invalid instruction at line %u", line->line_n);
_err = true;
break;
default:
Debug::error(Debug::Patchr, "Patchr: Internal error!");
break;
}
if (_err)
return false;
++line;
}
//During debug, dump the patched file
if (Debug::isChannelEnabled(Debug::Patchr)) {
Common::DumpFile dump;
if (dump.open(name)) {
dump.write(_data, fileSize);
dump.close();
} else
Debug::warning(Debug::Patchr,"Couldn't open file '%s' for writing", name.c_str());
}
//If the patch has correctly applied, return the updated file
delete file;
file = new Common::MemoryReadStream(_data, fileSize, DisposeAfterUse::YES);
_data = NULL;
return true;
}
uint32 Patchr::str2num(Common::String num) {
char *errpos;
uint32 val;
val = strtoul(num.c_str(), &errpos, 16);
if (num.c_str() == errpos)
err("Invalid number");
return val;
}
void Patchr::err(const char *s) {
Debug::warning(Debug::Patchr, "%s at line %u: %s", InstructionS[_curLine.ist], _curLine.line_n, s);
_err = true;
}
} // end of namespace Grim