|
|
@@ -0,0 +1,264 @@
|
|
|
+// 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"
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// 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();
|
|
|
+ _patch_in.open(patch_pathname.c_str(), ios::in | ios::binary);
|
|
|
+
|
|
|
+ string source_pathname = _source.get_pathname(_package_dir);
|
|
|
+ _source_in.clear();
|
|
|
+ _source_in.open(source_pathname.c_str(), ios::in | ios::binary);
|
|
|
+
|
|
|
+ mkfile_complete(_output_pathname, nout);
|
|
|
+ _target_out.clear();
|
|
|
+ _target_out.open(_output_pathname.c_str(), ios::out | ios::binary);
|
|
|
+
|
|
|
+ _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.
|
|
|
+ ssize_t 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.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+void P3DPatchfileReader::
|
|
|
+close() {
|
|
|
+ if (!_is_open) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _patch_in.close();
|
|
|
+ _source_in.close();
|
|
|
+ _target_out.close();
|
|
|
+
|
|
|
+ if (_success) {
|
|
|
+ // Move the output file onto the target file.
|
|
|
+ string target_pathname = _target.get_pathname(_package_dir);
|
|
|
+#ifdef _WIN32
|
|
|
+ // Windows can't delete a file if it's read-only.
|
|
|
+ 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];
|
|
|
+
|
|
|
+ size_t read_size = min(copy_byte_count, buffer_size);
|
|
|
+ in.read(buffer, read_size);
|
|
|
+ size_t count = in.gcount();
|
|
|
+ while (count != 0) {
|
|
|
+ _target_out.write(buffer, count);
|
|
|
+ _bytes_written += count;
|
|
|
+ if (_bytes_written > _target_length) {
|
|
|
+ nout << "Runaway patchfile.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (count != read_size) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ copy_byte_count -= 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);
|
|
|
+}
|