open_toontown_panda3d/direct/src/plugin/p3dPatchfileReader.cxx

298 lines
9.0 KiB
C++

// Filename: p3dPatchfileReader.cxx
// Created by: drose (28Sep09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "p3dPatchfileReader.h"
#include "wstring_encode.h"
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPatchfileReader::
P3DPatchfileReader(const string &package_dir, const FileSpec &patchfile,
const FileSpec &source, const FileSpec &target) :
_package_dir(package_dir),
_patchfile(patchfile),
_source(source),
_target(target)
{
_is_open = false;
_bytes_written = 0;
_success = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPatchfileReader::
~P3DPatchfileReader() {
close();
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::open_read
// Access: Public
// Description: Opens the named patchfile for reading, reads the
// header, and validates the inputs. Returns true on
// success, false otherwise. If this returns false, you
// should immediately call close(), or let this object
// destruct.
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
open_read() {
close();
// Synthesize an output filename, in case the source and the target
// refer to the same filename.
_output_pathname = _target.get_pathname(_package_dir);
_output_pathname += ".tmp";
string patch_pathname = _patchfile.get_pathname(_package_dir);
_patch_in.clear();
#ifdef _WIN32
wstring patch_pathname_w;
if (string_to_wstring(patch_pathname_w, patch_pathname)) {
_patch_in.open(patch_pathname_w.c_str(), ios::in | ios::binary);
}
#else // _WIN32
_patch_in.open(patch_pathname.c_str(), ios::in | ios::binary);
#endif // _WIN32
string source_pathname = _source.get_pathname(_package_dir);
_source_in.clear();
#ifdef _WIN32
wstring source_pathname_w;
if (string_to_wstring(source_pathname_w, source_pathname)) {
_source_in.open(source_pathname_w.c_str(), ios::in | ios::binary);
}
#else // _WIN32
_source_in.open(source_pathname.c_str(), ios::in | ios::binary);
#endif // _WIN32
mkfile_complete(_output_pathname, nout);
_target_out.clear();
#ifdef _WIN32
wstring output_pathname_w;
if (string_to_wstring(output_pathname_w, _output_pathname)) {
_target_out.open(output_pathname_w.c_str(), ios::in | ios::binary);
}
#else // _WIN32
_target_out.open(_output_pathname.c_str(), ios::in | ios::binary);
#endif // _WIN32
_is_open = true;
// If any of those failed to open, we fail.
if (_patch_in.fail() || _source_in.fail() || _target_out.fail()) {
nout << "Couldn't open patchfile source, input, and/or target.\n";
return false;
}
// Read the patchfile header and validate it against the hashes we
// were given.
unsigned int magic_number = read_uint32();
if (magic_number != 0xfeebfaac) {
nout << "Not a valid patchfile: " << patch_pathname << "\n";
return false;
}
unsigned int version = read_uint16();
if (version != 2) {
// This code only knows about patchfile version 2. If the
// patchfile code is updated, we have to update this code
// accordingly.
nout << "Unsupported patchfile version: " << version << "\n";
return false;
}
size_t source_length = read_uint32();
if (source_length != _source.get_size()) {
nout << "Patchfile " << patch_pathname
<< " doesn't match source size.\n";
return false;
}
FileSpec validate;
validate.read_hash_stream(_patch_in);
if (_source.compare_hash(validate) != 0) {
nout << "Patchfile " << patch_pathname
<< " doesn't match source hash.\n";
return false;
}
_target_length = read_uint32();
if (_target_length != _target.get_size()) {
nout << "Patchfile " << patch_pathname
<< " doesn't match target size.\n";
return false;
}
validate.read_hash_stream(_patch_in);
if (_target.compare_hash(validate) != 0) {
nout << "Patchfile " << patch_pathname
<< " doesn't match target hash.\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::step
// Access: Public
// Description: Performs one incremental step of the patching
// operation. Returns true if the operation should
// continue and step() should be called again, false if
// the patching is done (either successfully, or due to
// failure).
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
step() {
assert(_is_open);
size_t add_length = read_uint16();
if (add_length != 0) {
// Add a number of bytes from the patchfile.
if (!copy_bytes(_patch_in, add_length)) {
nout << "Truncated patchfile.\n";
return false;
}
}
size_t copy_length = read_uint16();
if (copy_length != 0) {
// Copy a number of bytes from the original source.
int offset = read_int32();
_source_in.seekg(offset, ios::cur);
if (!copy_bytes(_source_in, copy_length)) {
nout << "Garbage in patchfile.\n";
return false;
}
}
assert(_bytes_written <= _target_length);
// When both counts reach 0, the patchfile is done.
if (add_length != 0 || copy_length != 0) {
// So, we've still got more to do.
return true;
}
if (_bytes_written != _target_length) {
nout << "Patchfile wrote truncated file.\n";
return false;
}
// Set the _success flag true, so close() will move the finished
// file into place.
_success = true;
close();
// Now validate the hash.
if (!_target.full_verify(_package_dir)) {
nout << "After patching, " << _target.get_filename()
<< " is still incorrect.\n";
_success = false;
return false;
}
// Successfully patched! Return false to indicate completion.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::close
// Access: Public
// Description: Closes the previously-opened files, and moves the
// output file into place. This also deletes the
// patchfile, assuming it will not be needed after it
// has been used.
////////////////////////////////////////////////////////////////////
void P3DPatchfileReader::
close() {
if (!_is_open) {
return;
}
_is_open = false;
_patch_in.close();
_source_in.close();
_target_out.close();
// Delete the patchfile.
string patch_pathname = _patchfile.get_pathname(_package_dir);
#ifdef _WIN32
// Windows can't delete a file if it's read-only.
chmod(patch_pathname.c_str(), 0644);
#endif
unlink(patch_pathname.c_str());
if (_success) {
// Move the output file onto the target file.
string target_pathname = _target.get_pathname(_package_dir);
#ifdef _WIN32
chmod(target_pathname.c_str(), 0644);
#endif
unlink(target_pathname.c_str());
rename(_output_pathname.c_str(), target_pathname.c_str());
} else {
// Failure; remove the output file.
#ifdef _WIN32
chmod(_output_pathname.c_str(), 0644);
#endif
unlink(_output_pathname.c_str());
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::copy_bytes
// Access: Private
// Description: Copies the indicated number of bytes from the
// indicated stream onto the output stream. Returns
// true on success, false if the input stream didn't
// have enough bytes.
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
copy_bytes(istream &in, size_t copy_byte_count) {
static const size_t buffer_size = 8192;
char buffer[buffer_size];
streamsize read_size = min(copy_byte_count, buffer_size);
in.read(buffer, read_size);
streamsize count = in.gcount();
while (count != 0) {
_target_out.write(buffer, count);
_bytes_written += (size_t)count;
if (_bytes_written > _target_length) {
nout << "Runaway patchfile.\n";
return false;
}
if (count != read_size) {
return false;
}
copy_byte_count -= (size_t)count;
count = 0;
if (copy_byte_count != 0) {
read_size = min(copy_byte_count, buffer_size);
in.read(buffer, read_size);
count = in.gcount();
}
}
return (copy_byte_count == 0);
}