820 lines
26 KiB
C++
820 lines
26 KiB
C++
// Filename: patchfile.cxx
|
|
// Created by: darren, mike (09Jan97)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://www.panda3d.org/license.txt .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d@yahoogroups.com .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "config_express.h"
|
|
#include "error_utils.h"
|
|
#include <stdio.h> // for tempnam
|
|
|
|
#include "patchfile.h"
|
|
#include "crypto_utils.h" // MD5 stuff
|
|
|
|
// PROFILING ///////////////////////////////////////////////////////
|
|
//#define PROFILE_PATCH_BUILD
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
|
|
#include "clockObject.h"
|
|
ClockObject *globalClock = ClockObject::get_global_clock();
|
|
|
|
#define GET_PROFILE_TIME() globalClock->get_real_time()
|
|
#define START_PROFILE(var) double var = GET_PROFILE_TIME()
|
|
#define END_PROFILE(startTime, name) \
|
|
cout << name << " took " << (GET_PROFILE_TIME() - (startTime)) << " seconds" << endl
|
|
|
|
#else
|
|
#define START_PROFILE(var)
|
|
#define END_PROFILE(startTime, name)
|
|
#endif
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
// this actually slows things down...
|
|
//#define USE_MD5_FOR_HASHTABLE_INDEX_VALUES
|
|
|
|
// Patch File Format ///////////////////////////////////////////////
|
|
///// IF THIS CHANGES, UPDATE installerApplyPatch.cxx IN THE INSTALLER
|
|
////////////////////////////////////////////////////////////////////
|
|
// [ HEADER ]
|
|
// 4 bytes 0xfeebfaab ("magic number")
|
|
// 4 bytes length of resulting patched file
|
|
// 16 bytes MD5 of resultant patched file
|
|
const int _header_length = sizeof(PN_uint32) + sizeof(PN_uint32) + (4*sizeof(PN_uint32));
|
|
//
|
|
// [ ADD/COPY pairs; repeated N times ]
|
|
// 2 bytes AL = ADD length
|
|
// AL bytes bytes to add
|
|
// 2 bytes CL = COPY length
|
|
// 4 bytes offset of data to copy from original file, if CL != 0
|
|
//
|
|
// [ TERMINATOR ]
|
|
// 2 bytes zero-length ADD
|
|
// 2 bytes zero-length COPY
|
|
////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Defines
|
|
////////////////////////////////////////////////////////////////////
|
|
const PN_uint32 Patchfile::_magic_number = 0xfeebfaab;
|
|
|
|
const PN_uint32 Patchfile::_HASHTABLESIZE = PN_uint32(1) << 16;
|
|
const PN_uint32 Patchfile::_DEFAULT_FOOTPRINT_LENGTH = 9; // this produced the smallest patch file for libpanda.dll when tested, 12/20/2000
|
|
const PN_uint32 Patchfile::_NULL_VALUE = PN_uint32(0) - 1;
|
|
const PN_uint32 Patchfile::_MAX_RUN_LENGTH = (PN_uint32(1) << 16) - 1;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
Patchfile::
|
|
Patchfile(void) {
|
|
PT(Buffer) buffer = new Buffer(patchfile_buffer_size);
|
|
init(buffer);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
Patchfile::
|
|
Patchfile(PT(Buffer) buffer) {
|
|
init(buffer);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::init
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
init(PT(Buffer) buffer) {
|
|
_initiated = false;
|
|
nassertv(!buffer.is_null());
|
|
_buffer = buffer;
|
|
|
|
reset_footprint_length();
|
|
|
|
_datagram.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
Patchfile::
|
|
~Patchfile(void) {
|
|
if (true == _initiated)
|
|
cleanup();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::cleanup
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
cleanup(void) {
|
|
if (false == _initiated) {
|
|
express_cat.error()
|
|
<< "Patchfile::cleanup() - Patching has not been initiated"
|
|
<< endl;
|
|
return;
|
|
}
|
|
|
|
// close files
|
|
_origfile_stream.close();
|
|
_patch_stream.close();
|
|
_write_stream.close();
|
|
|
|
_initiated = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
///// PATCH FILE APPLY MEMBER FUNCTIONS
|
|
/////
|
|
////////////////////
|
|
///// NOTE: this patch-application functionality unfortunately has to be
|
|
///// duplicated in the Installer. It is contained in the file
|
|
///// installerApplyPatch.cxx
|
|
///// PLEASE MAKE SURE THAT THAT FILE GETS UPDATED IF ANY OF THIS
|
|
///// LOGIC CHANGES! (i.e. if the patch file format changes)
|
|
////////////////////
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::initiate
|
|
// Access: Public
|
|
// Description: Set up to apply the patch to the file (original
|
|
// file and patch are destroyed in the process).
|
|
////////////////////////////////////////////////////////////////////
|
|
int Patchfile::
|
|
initiate(Filename &patch_file, Filename &file) {
|
|
if (true == _initiated) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - Patching has already been initiated"
|
|
<< endl;
|
|
return EU_error_abort;
|
|
}
|
|
|
|
// Open the patch file for read
|
|
_patch_file = patch_file;
|
|
_patch_file.set_binary();
|
|
if (!_patch_file.open_read(_patch_stream)) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - Failed to open file: " << _patch_file << endl;
|
|
return get_write_error();
|
|
}
|
|
|
|
// Open the original file for read
|
|
_orig_file = file;
|
|
_orig_file.set_binary();
|
|
if (!_orig_file.open_read(_origfile_stream)) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - Failed to open file: " << _orig_file << endl;
|
|
return get_write_error();
|
|
}
|
|
|
|
// Open the temp file for write
|
|
{
|
|
char *tempfilename = _tempnam(".", "pf");
|
|
if (NULL == tempfilename) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - Failed to create temp file name, using default" << endl;
|
|
_temp_file = "patcher_temp_file";
|
|
} else {
|
|
//cout << tempfilename << endl;
|
|
_temp_file = tempfilename;
|
|
free(tempfilename);
|
|
}
|
|
}
|
|
_temp_file.set_binary();
|
|
if (!_temp_file.open_write(_write_stream)) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - Failed to open file: " << _temp_file << endl;
|
|
return get_write_error();
|
|
}
|
|
|
|
/////////////
|
|
// read header, make sure the patch file is valid
|
|
|
|
// check the magic number
|
|
nassertr(_buffer->get_length() >= _header_length, false);
|
|
_patch_stream.read(_buffer->_buffer, _header_length);
|
|
_datagram.clear();
|
|
_datagram.append_data(_buffer->_buffer, _header_length);
|
|
DatagramIterator di(_datagram);
|
|
PN_uint32 magic_number = di.get_uint32();
|
|
if (magic_number != _magic_number) {
|
|
express_cat.error()
|
|
<< "Patchfile::initiate() - invalid patch file: " << _patch_file << endl;
|
|
return EU_error_file_invalid;
|
|
}
|
|
|
|
// get the length of the patched result file
|
|
_result_file_length = di.get_uint32();
|
|
|
|
// get the MD5 of the resultant patched file
|
|
_MD5_ofResult.set_value(0, di.get_uint32());
|
|
_MD5_ofResult.set_value(1, di.get_uint32());
|
|
_MD5_ofResult.set_value(2, di.get_uint32());
|
|
_MD5_ofResult.set_value(3, di.get_uint32());
|
|
|
|
express_cat.debug()
|
|
<< "Patchfile::initiate() - valid patchfile" << endl;
|
|
/*
|
|
express_cat.debug()
|
|
<< "Patchfile::initiate() - valid patchfile for file: " << name << endl;
|
|
*/
|
|
|
|
_total_bytes_processed = 0;
|
|
|
|
_initiated = true;
|
|
|
|
return EU_success;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::run
|
|
// Access: Public
|
|
// Description: Perform one buffer's worth of patching
|
|
////////////////////////////////////////////////////////////////////
|
|
int Patchfile::
|
|
run(void) {
|
|
// Now patch the file using the given buffer
|
|
int buflen;
|
|
int bytes_read;
|
|
PN_uint16 ADD_length;
|
|
PN_uint16 COPY_length;
|
|
PN_uint32 COPY_offset;
|
|
|
|
if (_initiated == false) {
|
|
express_cat.error()
|
|
<< "Patchfile::run() - Patching has not been initiated"
|
|
<< endl;
|
|
return EU_error_abort;
|
|
}
|
|
|
|
buflen = _buffer->get_length();
|
|
bytes_read = 0;
|
|
|
|
while (bytes_read < buflen) {
|
|
///////////
|
|
// read # of ADD bytes
|
|
nassertr(_buffer->get_length() >= (int)sizeof(ADD_length), false);
|
|
_patch_stream.read(_buffer->_buffer, sizeof(ADD_length));
|
|
_datagram.clear();
|
|
_datagram.append_data(_buffer->_buffer, sizeof(ADD_length));
|
|
DatagramIterator di(_datagram);
|
|
ADD_length = di.get_uint16();
|
|
|
|
bytes_read += (int)ADD_length;
|
|
_total_bytes_processed += (int)ADD_length;
|
|
|
|
// if there are bytes to add, read them from patch file and write them to output
|
|
if (0 != ADD_length) {
|
|
PN_uint32 bytes_left = (PN_uint32)ADD_length;
|
|
|
|
//cout << "ADD: " << ADD_length << endl;
|
|
|
|
while (bytes_left > 0) {
|
|
PN_uint32 bytes_this_time = (PN_uint32) min(bytes_left, (PN_uint32) buflen);
|
|
_patch_stream.read(_buffer->_buffer, bytes_this_time);
|
|
_write_stream.write(_buffer->_buffer, bytes_this_time);
|
|
bytes_left -= bytes_this_time;
|
|
}
|
|
}
|
|
|
|
///////////
|
|
// read # of COPY bytes
|
|
nassertr(_buffer->get_length() >= (int)sizeof(COPY_length), false);
|
|
_patch_stream.read(_buffer->_buffer, sizeof(COPY_length));
|
|
_datagram.clear();
|
|
_datagram.append_data(_buffer->_buffer, sizeof(COPY_length));
|
|
DatagramIterator di2(_datagram);
|
|
COPY_length = di2.get_uint16();
|
|
|
|
bytes_read += (int)COPY_length;
|
|
_total_bytes_processed += (int)COPY_length;
|
|
|
|
// if there are bytes to copy, read them from original file and write them to output
|
|
if (0 != COPY_length) {
|
|
// read copy offset
|
|
nassertr(_buffer->get_length() >= (int)sizeof(COPY_offset), false);
|
|
_patch_stream.read(_buffer->_buffer, sizeof(COPY_offset));
|
|
_datagram.clear();
|
|
_datagram.append_data(_buffer->_buffer, sizeof(COPY_offset));
|
|
DatagramIterator di(_datagram);
|
|
COPY_offset = di.get_uint32();
|
|
|
|
//cout << "COPY: " << COPY_length << " bytes at offset " << COPY_offset << endl;
|
|
|
|
// seek to the offset
|
|
_origfile_stream.seekg(COPY_offset, ios::beg);
|
|
|
|
// read the copy bytes from original file and write them to output
|
|
PN_uint32 bytes_left = (PN_uint32)COPY_length;
|
|
|
|
while (bytes_left > 0) {
|
|
PN_uint32 bytes_this_time = (PN_uint32) min(bytes_left, (PN_uint32) buflen);
|
|
_origfile_stream.read(_buffer->_buffer, bytes_this_time);
|
|
_write_stream.write(_buffer->_buffer, bytes_this_time);
|
|
bytes_left -= bytes_this_time;
|
|
}
|
|
}
|
|
|
|
// if we got a pair of zero-length ADD and COPY blocks, we're done
|
|
if ((0 == ADD_length) && (0 == COPY_length)) {
|
|
cleanup();
|
|
|
|
//cout << _result_file_length << " " << _total_bytes_processed << endl;
|
|
|
|
// check the MD5 from the patch file against the newly patched file
|
|
{
|
|
HashVal MD5_actual;
|
|
md5_a_file(_temp_file, MD5_actual);
|
|
if (memcmp((const void*)&_MD5_ofResult, (const void*)&MD5_actual, sizeof(HashVal))) {
|
|
// delete the temp file and the patch file
|
|
_temp_file.unlink();
|
|
_patch_file.unlink();
|
|
// return "invalid checksum"
|
|
return EU_error_invalid_checksum;
|
|
}
|
|
}
|
|
|
|
// delete the patch file and the original file
|
|
_patch_file.unlink();
|
|
_orig_file.unlink();
|
|
|
|
// rename the temp file to the original file name
|
|
if (!_temp_file.rename_to(_orig_file)) {
|
|
express_cat.error()
|
|
<< "Patchfile::run() failed to rename temp file to: " << _orig_file
|
|
<< endl;
|
|
return EU_error_write_file_rename;
|
|
}
|
|
|
|
return EU_success;
|
|
}
|
|
}
|
|
|
|
return EU_ok;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::apply
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Patchfile::
|
|
apply(Filename &patch_file, Filename &file) {
|
|
int ret = initiate(patch_file, file);
|
|
if (ret < 0)
|
|
return false;
|
|
for (;;) {
|
|
ret = run();
|
|
if (ret == EU_success)
|
|
return true;
|
|
if (ret < 0)
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
///// PATCH FILE BUILDING MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::calc_hash
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_uint16 Patchfile::
|
|
calc_hash(const char *buffer) {
|
|
#ifdef USE_MD5_FOR_HASHTABLE_INDEX_VALUES
|
|
HashVal hash;
|
|
|
|
md5_a_buffer((const unsigned char*)buffer, (int)_footprint_length, hash);
|
|
|
|
//cout << PN_uint16(hash.get_value(0)) << " ";
|
|
|
|
return PN_uint16(hash.get_value(0));
|
|
#else
|
|
PN_uint16 hash_value = 0;
|
|
|
|
for(int i = 0; i < (int)_footprint_length; i++) {
|
|
// this is probably not such a good hash. to be replaced
|
|
/// --> TRIED MD5, was not worth it for the execution-time hit on 800Mhz PC
|
|
hash_value ^= (*buffer) << (i % 8);
|
|
buffer++;
|
|
}
|
|
|
|
//cout << hash_value << " ";
|
|
|
|
return hash_value;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::build_hash_link_tables
|
|
// Access: Private
|
|
// Description:
|
|
// The hash and link tables allow for a quick, linear
|
|
// search of all locations in the file that begin with
|
|
// a particular sequence of bytes, or "footprint."
|
|
//
|
|
// The hash table is a table of offsets into the file,
|
|
// with one entry for every possible footprint hash
|
|
// value. For a hash of a footprint, the entry at the
|
|
// offset of the hash value provides an initial location
|
|
// in the file that has a matching footprint.
|
|
//
|
|
// The link table is a large linked list of file offsets,
|
|
// with one entry for every byte in the file. Each offset
|
|
// in the link table will point to another offset that
|
|
// has the same footprint at the corresponding offset in the
|
|
// actual file. Starting with an offset taken from the hash
|
|
// table, one can rapidly produce a list of offsets that
|
|
// all have the same footprint.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
build_hash_link_tables(const char *buffer_orig, PN_uint32 length_orig,
|
|
PN_uint32 *hash_table, PN_uint32 *link_table) {
|
|
|
|
PN_uint32 i;
|
|
|
|
START_PROFILE(clearTables);
|
|
|
|
// clear hash table
|
|
for(i = 0; i < _HASHTABLESIZE; i++) {
|
|
hash_table[i] = _NULL_VALUE;
|
|
}
|
|
|
|
// clear link table
|
|
for(i = 0; i < length_orig; i++) {
|
|
link_table[i] = _NULL_VALUE;
|
|
}
|
|
|
|
END_PROFILE(clearTables, "clearing hash and link tables");
|
|
|
|
if(length_orig < _footprint_length) return;
|
|
|
|
START_PROFILE(hashingFootprints);
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
double hashCalc = 0.0;
|
|
double linkSearch = 0.0;
|
|
#endif
|
|
|
|
// run through original file and hash each footprint
|
|
for(i = 0; i < (length_orig - _footprint_length); i++) {
|
|
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
double t = GET_PROFILE_TIME();
|
|
#endif
|
|
|
|
PN_uint16 hash_value = calc_hash(&buffer_orig[i]);
|
|
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
hashCalc += GET_PROFILE_TIME() - t;
|
|
#endif
|
|
|
|
// we must now store this file index in the hash table
|
|
// at the offset of the hash value
|
|
|
|
// to account for multiple file offsets with identical
|
|
// hash values, there is a link table with an entry for
|
|
// every footprint in the file. We create linked lists
|
|
// of offsets in the link table.
|
|
|
|
// first, set the value in the link table for the current
|
|
// offset to whatever the current list head is (the
|
|
// value in the hash table) (note that this only works
|
|
// because the hash and link tables both use
|
|
// _NULL_VALUE to indicate a null index)
|
|
link_table[i] = hash_table[hash_value];
|
|
|
|
// set the new list head; store the current offset in the
|
|
// hash table at the offset of the footprint's hash value
|
|
hash_table[hash_value] = i;
|
|
|
|
/*
|
|
if (_NULL_VALUE == hash_table[hash_value]) {
|
|
// hash entry is empty, store this offset
|
|
hash_table[hash_value] = i;
|
|
} else {
|
|
// hash entry is taken, go to the link table
|
|
PN_uint32 link_offset = hash_table[hash_value];
|
|
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
double t = GET_PROFILE_TIME();
|
|
#endif
|
|
while (_NULL_VALUE != link_table[link_offset]) {
|
|
link_offset = link_table[link_offset];
|
|
}
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
linkSearch += GET_PROFILE_TIME() - t;
|
|
#endif
|
|
|
|
link_table[link_offset] = i;
|
|
}
|
|
*/
|
|
}
|
|
|
|
#ifdef PROFILE_PATCH_BUILD
|
|
cout << "calculating footprint hashes took " << hashCalc << " seconds" << endl;
|
|
cout << "traversing the link table took " << linkSearch << " seconds" << endl;
|
|
#endif
|
|
|
|
END_PROFILE(hashingFootprints, "hashing footprints");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::calc_match_length
|
|
// Access: Private
|
|
// Description:
|
|
// This function calculates the length of a match between
|
|
// two strings of bytes
|
|
////////////////////////////////////////////////////////////////////
|
|
PN_uint32 Patchfile::
|
|
calc_match_length(const char* buf1, const char* buf2, PN_uint32 max_length) {
|
|
PN_uint32 length = 0;
|
|
while ((length < max_length) && (*buf1 == *buf2)) {
|
|
buf1++, buf2++, length++;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::find_longest_match
|
|
// Access: Private
|
|
// Description:
|
|
// This function will find the longest string in the
|
|
// original file that matches a string in the new file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
find_longest_match(PN_uint32 new_pos, PN_uint32 ©_offset, PN_uint16 ©_length,
|
|
PN_uint32 *hash_table, PN_uint32 *link_table, const char* buffer_orig,
|
|
PN_uint32 length_orig, const char* buffer_new, PN_uint32 length_new) {
|
|
|
|
// set length to a safe value
|
|
copy_length = 0;
|
|
|
|
// get offset of matching string (in orig file) from hash table
|
|
PN_uint16 hash_value = calc_hash(&buffer_new[new_pos]);
|
|
|
|
// if no match, bail
|
|
if (_NULL_VALUE == hash_table[hash_value])
|
|
return;
|
|
|
|
copy_offset = hash_table[hash_value];
|
|
|
|
// calc match length
|
|
copy_length = (PN_uint16)calc_match_length(&buffer_new[new_pos], &buffer_orig[copy_offset],
|
|
min(min((length_new - new_pos),(length_orig - copy_offset)), _MAX_RUN_LENGTH));
|
|
|
|
// run through link table, see if we find any longer matches
|
|
PN_uint32 match_offset;
|
|
PN_uint16 match_length;
|
|
match_offset = link_table[copy_offset];
|
|
|
|
while (match_offset != _NULL_VALUE) {
|
|
match_length = (PN_uint16)calc_match_length(&buffer_new[new_pos], &buffer_orig[match_offset],
|
|
min(min((length_new - new_pos),(length_orig - match_offset)), _MAX_RUN_LENGTH));
|
|
|
|
// have we found a longer match?
|
|
if (match_length > copy_length) {
|
|
copy_offset = match_offset;
|
|
copy_length = match_length;
|
|
}
|
|
|
|
// traverse the link table
|
|
match_offset = link_table[match_offset];
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::emit_ADD
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
emit_ADD(ofstream &write_stream, PN_uint16 length, const char* buffer) {
|
|
//cout << "ADD: " << length << " bytes" << endl;
|
|
|
|
// write ADD length
|
|
_datagram.clear();
|
|
_datagram.add_uint16(length);
|
|
string msg = _datagram.get_message();
|
|
write_stream.write((char *)msg.data(), msg.length());
|
|
|
|
// if there are bytes to add, add them
|
|
if (length > 0) {
|
|
_datagram.clear();
|
|
_datagram.append_data(buffer, length);
|
|
string msg = _datagram.get_message();
|
|
write_stream.write((char *)msg.data(), msg.length());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::emit_COPY
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void Patchfile::
|
|
emit_COPY(ofstream &write_stream, PN_uint16 length, PN_uint32 offset) {
|
|
//cout << "COPY: " << length << " bytes at offset " << offset << endl;
|
|
|
|
// write COPY length
|
|
_datagram.clear();
|
|
_datagram.add_uint16(length);
|
|
string msg = _datagram.get_message();
|
|
write_stream.write((char *)msg.data(), msg.length());
|
|
|
|
if(length > 0) {
|
|
// write COPY offset
|
|
_datagram.clear();
|
|
_datagram.add_uint32(offset);
|
|
string msg2 = _datagram.get_message();
|
|
write_stream.write((char *)msg2.data(), msg2.length());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Patchfile::build
|
|
// Access: Public
|
|
// Description:
|
|
// This implementation uses the "greedy differencing
|
|
// algorithm" described in the masters thesis
|
|
// "Differential Compression: A Generalized Solution
|
|
// for Binary Files" by Randal C. Burns (p.13).
|
|
// For an original file of size M and a new file of
|
|
// size N, this algorithm is O(M) in space and O(M*N)
|
|
// in time.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Patchfile::
|
|
build(Filename &file_orig, Filename &file_new) {
|
|
Filename patch_name;
|
|
patch_name.set_binary();
|
|
|
|
START_PROFILE(overall);
|
|
|
|
START_PROFILE(readFiles);
|
|
|
|
// Open the original file for read
|
|
ifstream stream_orig;
|
|
file_orig.set_binary();
|
|
if (!file_orig.open_read(stream_orig)) {
|
|
express_cat.error()
|
|
<< "Patchfile::build() - Failed to open file: " << file_orig << endl;
|
|
return false;
|
|
}
|
|
|
|
// Open the new file for read
|
|
ifstream stream_new;
|
|
file_new.set_binary();
|
|
if (!file_new.open_read(stream_new)) {
|
|
express_cat.error()
|
|
<< "Patchfile::build() - Failed to open file: " << file_new << endl;
|
|
return false;
|
|
}
|
|
|
|
// Open patch file for write
|
|
ofstream write_stream;
|
|
patch_name = file_orig.get_fullpath() + ".pch";
|
|
if (!patch_name.open_write(write_stream)) {
|
|
express_cat.error()
|
|
<< "Patchfile::build() - Failed to open file: " << patch_name << endl;
|
|
return false;
|
|
}
|
|
|
|
// read in original file
|
|
stream_orig.seekg(0, ios::end);
|
|
int length_orig = stream_orig.tellg();
|
|
char *buffer_orig = new char[length_orig];
|
|
stream_orig.seekg(0, ios::beg);
|
|
stream_orig.read(buffer_orig, length_orig);
|
|
|
|
// read in new file
|
|
stream_new.seekg(0, ios::end);
|
|
int length_new = stream_new.tellg();
|
|
char *buffer_new = new char[length_new];
|
|
stream_new.seekg(0, ios::beg);
|
|
stream_new.read(buffer_new, length_new);
|
|
|
|
// close the original and new files (we have em in memory)
|
|
stream_orig.close();
|
|
stream_new.close();
|
|
|
|
END_PROFILE(readFiles, "reading files");
|
|
|
|
START_PROFILE(allocTables);
|
|
|
|
// allocate hash/link tables
|
|
PN_uint32* hash_table = new PN_uint32[_HASHTABLESIZE];
|
|
PN_uint32* link_table = new PN_uint32[length_orig];
|
|
|
|
END_PROFILE(allocTables, "allocating hash and link tables");
|
|
|
|
START_PROFILE(buildTables);
|
|
|
|
// build hash and link tables for original file
|
|
build_hash_link_tables(buffer_orig, length_orig, hash_table, link_table);
|
|
|
|
END_PROFILE(buildTables, "building hash and link tables");
|
|
|
|
// prepare to write the patch file header
|
|
|
|
START_PROFILE(writeHeader);
|
|
|
|
// write the patch file header
|
|
_datagram.clear();
|
|
_datagram.add_uint32(_magic_number);
|
|
_datagram.add_uint32(length_new);
|
|
{
|
|
// calc MD5 of resultant patched file
|
|
HashVal md5New;
|
|
md5_a_buffer((const unsigned char*)buffer_new, (int)length_new, md5New);
|
|
// add it to the header
|
|
_datagram.add_uint32(md5New.get_value(0));
|
|
_datagram.add_uint32(md5New.get_value(1));
|
|
_datagram.add_uint32(md5New.get_value(2));
|
|
_datagram.add_uint32(md5New.get_value(3));
|
|
}
|
|
string msg = _datagram.get_message();
|
|
write_stream.write((char *)msg.data(), msg.length());
|
|
|
|
END_PROFILE(writeHeader, "writing patch file header");
|
|
|
|
// run through new file
|
|
START_PROFILE(buildPatchfile);
|
|
|
|
PN_uint32 new_pos = 0;
|
|
PN_uint32 ADD_offset = new_pos; // this is the offset for the start of ADD operations
|
|
|
|
if(((PN_uint32) length_new) >= _footprint_length)
|
|
{
|
|
while (new_pos < (length_new - _footprint_length)) {
|
|
|
|
// find best match for current position
|
|
PN_uint32 COPY_offset;
|
|
PN_uint16 COPY_length;
|
|
|
|
find_longest_match(new_pos, COPY_offset, COPY_length, hash_table, link_table,
|
|
buffer_orig, length_orig, buffer_new, length_new);
|
|
|
|
// if no match or match not longer than footprint length, skip to next byte
|
|
if (COPY_length < _footprint_length) {
|
|
// go to next byte
|
|
new_pos++;
|
|
} else {
|
|
// emit ADD for all skipped bytes
|
|
emit_ADD(write_stream, new_pos - ADD_offset, &buffer_new[ADD_offset]);
|
|
|
|
// emit COPY for matching string
|
|
emit_COPY(write_stream, COPY_length, COPY_offset);
|
|
|
|
// skip past match in new_file
|
|
new_pos += (PN_uint32)COPY_length;
|
|
ADD_offset = new_pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
// are there still more bytes left in the new file?
|
|
if ((int)ADD_offset != length_new) {
|
|
// emit ADD for all remaining bytes
|
|
emit_ADD(write_stream, length_new - ADD_offset, &buffer_new[ADD_offset]);
|
|
|
|
// write null COPY
|
|
emit_COPY(write_stream, 0, 0);
|
|
}
|
|
|
|
END_PROFILE(buildPatchfile, "building patch file");
|
|
|
|
// write terminator (null ADD, null COPY)
|
|
emit_ADD(write_stream, 0, NULL);
|
|
emit_COPY(write_stream, 0, 0);
|
|
|
|
END_PROFILE(overall, "total patch building operation");
|
|
|
|
return true;
|
|
}
|
|
|