Browse Source

c++-based patching, almost working

David Rose 16 years ago
parent
commit
3771d613ad

+ 2 - 0
direct/src/plugin/Sources.pp

@@ -48,6 +48,7 @@
     p3dObject.h p3dObject.I \
     p3dOsxSplashWindow.h p3dOsxSplashWindow.I \
     p3dPackage.h p3dPackage.I \
+    p3dPatchfileReader.h p3dPatchfileReader.I \
     p3dPatchFinder.h p3dPatchFinder.I \
     p3dPythonObject.h \
     p3dReferenceCount.h p3dReferenceCount.I \
@@ -82,6 +83,7 @@
     p3dObject.cxx \
     p3dOsxSplashWindow.cxx \
     p3dPackage.cxx \
+    p3dPatchfileReader.cxx \
     p3dPatchFinder.cxx \
     p3dPythonObject.cxx \
     p3dReferenceCount.cxx \

+ 27 - 4
direct/src/plugin/fileSpec.cxx

@@ -288,14 +288,37 @@ read_hash(const string &pathname) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FileSpec::compare_hash
+//     Function: FileSpec::read_hash_stream
 //       Access: Public
-//  Description: Returns true if this hash sorts before the other
-//               hash, false otherwise.
+//  Description: Reads the hash from the next 16 bytes on the
+//               indicated istream, in the same unusual order observed
+//               by Panda's HashVal::read_stream() method.
 ////////////////////////////////////////////////////////////////////
 bool FileSpec::
+read_hash_stream(istream &in) {
+  for (int i = 0; i < hash_size; i += 4) {
+    unsigned int a = in.get();
+    unsigned int b = in.get();
+    unsigned int c = in.get();
+    unsigned int d = in.get();
+    _hash[i + 0] = d;
+    _hash[i + 1] = c;
+    _hash[i + 2] = b;
+    _hash[i + 3] = a;
+  }
+
+  return !in.fail();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::compare_hash
+//       Access: Public
+//  Description: Returns < 0 if this hash sorts before the other
+//               hash, > 0 if it sorts after, 0 if they are the same.
+////////////////////////////////////////////////////////////////////
+int FileSpec::
 compare_hash(const FileSpec &other) const {
-  return memcmp(_hash, other._hash, hash_size) < 0;
+  return memcmp(_hash, other._hash, hash_size);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 1
direct/src/plugin/fileSpec.h

@@ -46,7 +46,8 @@ public:
   
   bool check_hash(const string &pathname) const;
   bool read_hash(const string &pathname);
-  bool compare_hash(const FileSpec &other) const;
+  bool read_hash_stream(istream &in);
+  int compare_hash(const FileSpec &other) const;
 
   void write(ostream &out) const;
   void output_hash(ostream &out) const;

+ 130 - 9
direct/src/plugin/p3dPackage.cxx

@@ -726,8 +726,8 @@ build_install_plans(TiXmlDocument *doc) {
     return;
   }
 
-  _install_plans.push_back(InstallPlan());
-  InstallPlan &plan = _install_plans.back();
+  _install_plans.push_front(InstallPlan());
+  InstallPlan &plan = _install_plans.front();
 
   bool needs_redownload = false;
   
@@ -745,7 +745,8 @@ build_install_plans(TiXmlDocument *doc) {
 
     // Uncompress the compressed archive to generate the uncompressed
     // archive.
-    step = new InstallStepUncompressFile(this, _compressed_archive, _uncompressed_archive);
+    step = new InstallStepUncompressFile
+      (this, _compressed_archive, _uncompressed_archive, true);
     plan.push_back(step);
   }
 
@@ -775,9 +776,40 @@ build_install_plans(TiXmlDocument *doc) {
       P3DPatchFinder patch_finder;
       P3DPatchFinder::Patchfiles chain;
       if (patch_finder.get_patch_chain_to_current(chain, doc, *on_disk_ptr)) {
-        cerr << "got patch chain of length " << chain.size() << "\n";
-      } else {
-        cerr << "No patch chain possible.\n";
+        nout << "Got patch chain of length " << chain.size() << "\n";
+
+        // OK, we can create a plan to download and apply the patches.
+        _install_plans.push_front(InstallPlan());
+        InstallPlan &plan = _install_plans.front();
+
+        P3DPatchFinder::Patchfiles::iterator pi;
+        for (pi = chain.begin(); pi != chain.end(); ++pi) {
+          P3DPatchFinder::Patchfile *patchfile = (*pi);
+
+          // Download the patchfile
+          step = new InstallStepDownloadFile(this, patchfile->_file);
+          plan.push_back(step);
+
+          // Uncompress it
+          FileSpec new_file = patchfile->_file;
+          string new_filename = new_file.get_filename();
+          size_t dot = new_filename.rfind('.');
+          assert(new_filename.substr(dot) == ".pz");
+          new_filename = new_filename.substr(0, dot);
+          new_file.set_filename(new_filename);
+          step = new InstallStepUncompressFile
+            (this, patchfile->_file, new_file, false);
+          plan.push_back(step);
+
+          // And apply it
+          FileSpec source_file = patchfile->_source_file;
+          FileSpec target_file = patchfile->_target_file;
+          source_file.set_filename(_uncompressed_archive.get_filename());
+          target_file.set_filename(_uncompressed_archive.get_filename());
+          step = new InstallStepApplyPatch
+            (this, new_file, source_file, target_file);
+          plan.push_back(step);
+        }
       }
     }
   }
@@ -818,6 +850,9 @@ follow_install_plans(bool download_finished) {
       _current_step_effort = step->get_effort();
 
       InstallToken token = step->do_step(download_finished);
+      nout << step << ":";
+      step->output(nout);
+      nout << " returned " << token << "\n";
       switch (token) {
       case IT_step_failed:
         // This plan has failed.
@@ -845,6 +880,7 @@ follow_install_plans(bool download_finished) {
     }
 
     // That plan failed.  Go on to the next plan.
+    nout << "Plan failed.\n";
     _install_plans.pop_front();
   }
 
@@ -1252,6 +1288,17 @@ do_step(bool download_finished) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepDownloadFile::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepDownloadFile::
+output(ostream &out) {
+  out << "InstallStepDownloadFile("  << _package->get_package_name()
+      << ", " << _file.get_filename() << ")";
+}
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::InstallStepUncompressFile::Constructor
@@ -1260,10 +1307,11 @@ do_step(bool download_finished) {
 ////////////////////////////////////////////////////////////////////
 P3DPackage::InstallStepUncompressFile::
 InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
-                          const FileSpec &target) :
+                          const FileSpec &target, bool verify_target) :
   InstallStep(package, target.get_size(), _uncompress_factor),
   _source(source),
-  _target(target)
+  _target(target),
+  _verify_target(verify_target)
 {
 }
 
@@ -1373,7 +1421,7 @@ do_step(bool download_finished) {
   source.close();
   target.close();
 
-  if (!_target.full_verify(_package->get_package_dir())) {
+  if (_verify_target && !_target.full_verify(_package->get_package_dir())) {
     nout << "after uncompressing " << target_pathname
          << ", failed hash check\n";
     return IT_step_failed;
@@ -1392,6 +1440,17 @@ do_step(bool download_finished) {
   return IT_step_complete;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepUncompressFile::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepUncompressFile::
+output(ostream &out) {
+  out << "InstallStepUncompressFile(" << _package->get_package_name()
+      << ", " << _source.get_filename() << ", " << _target.get_filename()
+      << ", " << _verify_target << ")";
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DPackage::InstallStepUnpackArchive::Constructor
@@ -1426,3 +1485,65 @@ do_step(bool download_finished) {
   return IT_step_complete;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepUnpackArchive::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepUnpackArchive::
+output(ostream &out) {
+  out << "InstallStepUnpackArchive(" << _package->get_package_name() << ")";
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepApplyPatch::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::InstallStepApplyPatch::
+InstallStepApplyPatch(P3DPackage *package, const FileSpec &patchfile,
+                      const FileSpec &source, const FileSpec &target) :
+  InstallStep(package, target.get_size(), _patch_factor),
+  _reader(package->get_package_dir(), patchfile, source, target)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepApplyPatch::do_step
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::InstallToken P3DPackage::InstallStepApplyPatch::
+do_step(bool download_finished) {
+  // Open the patchfile
+  if (!_reader.open_read()) {
+    _reader.close();
+    return IT_step_failed;
+  }
+
+  // Apply the patch.
+  while (_reader.step()) {
+    _bytes_done = _reader.get_bytes_written();
+    report_step_progress();
+  }
+
+  // Close and verify.
+  _reader.close();
+  if (!_reader.get_success()) {
+    nout << "Patching failed\n";
+    return IT_step_failed;
+  }
+
+  return IT_step_complete;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::InstallStepApplyPatch::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::InstallStepApplyPatch::
+output(ostream &out) {
+  out << "InstallStepApplyPatch(" << _package->get_package_name() << ")";
+}

+ 19 - 1
direct/src/plugin/p3dPackage.h

@@ -17,6 +17,7 @@
 
 #include "p3d_plugin_common.h"
 #include "p3dFileDownload.h"
+#include "p3dPatchfileReader.h"
 #include "fileSpec.h"
 #include "get_tinyxml.h"
 #include <deque>
@@ -119,6 +120,7 @@ private:
     virtual ~InstallStep();
 
     virtual InstallToken do_step(bool download_finished) = 0;
+    virtual void output(ostream &out) = 0;
 
     inline double get_effort() const;
     inline double get_progress() const;
@@ -136,6 +138,7 @@ private:
     virtual ~InstallStepDownloadFile();
 
     virtual InstallToken do_step(bool download_finished);
+    virtual void output(ostream &out);
 
     string _urlbase;
     string _pathname;
@@ -146,17 +149,32 @@ private:
   class InstallStepUncompressFile : public InstallStep {
   public:
     InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
-                              const FileSpec &target);
+                              const FileSpec &target, bool verify_target);
     virtual InstallToken do_step(bool download_finished);
+    virtual void output(ostream &out);
 
     FileSpec _source;
     FileSpec _target;
+    bool _verify_target;
   };
 
   class InstallStepUnpackArchive : public InstallStep {
   public:
     InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size);
     virtual InstallToken do_step(bool download_finished);
+    virtual void output(ostream &out);
+  };
+
+  class InstallStepApplyPatch : public InstallStep {
+  public:
+    InstallStepApplyPatch(P3DPackage *package,
+                          const FileSpec &patchfile,
+                          const FileSpec &source,
+                          const FileSpec &target);
+    virtual InstallToken do_step(bool download_finished);
+    virtual void output(ostream &out);
+
+    P3DPatchfileReader _reader;
   };
 
   typedef deque<InstallStep *> InstallPlan;

+ 1 - 1
direct/src/plugin/p3dPatchFinder.cxx

@@ -108,7 +108,7 @@ operator < (const PackageVersionKey &other) const {
   if (_host_url != other._host_url) {
     return _host_url < other._host_url;
   }
-  return _file.compare_hash(other._file);
+  return _file.compare_hash(other._file) < 0;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 88 - 0
direct/src/plugin/p3dPatchfileReader.I

@@ -0,0 +1,88 @@
+// Filename: p3dPatchfileReader.I
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::is_open
+//       Access: Public
+//  Description: Returns true if the patchfile is currently open,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+inline bool P3DPatchfileReader::
+is_open() const {
+  return _is_open;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::get_bytes_written
+//       Access: Public
+//  Description: Returns the number of bytes written to the output
+//               file so far during the patching process.
+////////////////////////////////////////////////////////////////////
+inline size_t P3DPatchfileReader::
+get_bytes_written() const {
+  return _bytes_written;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::get_success
+//       Access: Public
+//  Description: Returns true if the patching process has completed
+//               successfully, false if it has failed or has not yet
+//               completed.
+////////////////////////////////////////////////////////////////////
+inline bool P3DPatchfileReader::
+get_success() const {
+  return _success;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::read_uint16
+//       Access: Private
+//  Description: Extracts an unsigned short from the patchfile.
+////////////////////////////////////////////////////////////////////
+inline unsigned int P3DPatchfileReader::
+read_uint16() {
+  unsigned int a = _patch_in.get();
+  unsigned int b = _patch_in.get();
+  return (b << 8) | a;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::read_uint32
+//       Access: Private
+//  Description: Extracts an unsigned long from the patchfile.
+////////////////////////////////////////////////////////////////////
+inline unsigned int P3DPatchfileReader::
+read_uint32() {
+  unsigned int a = _patch_in.get();
+  unsigned int b = _patch_in.get();
+  unsigned int c = _patch_in.get();
+  unsigned int d = _patch_in.get();
+  return (d << 24) | (c << 16) | (b << 8) | a;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchfileReader::read_int32
+//       Access: Private
+//  Description: Extracts a signed long from the patchfile.
+////////////////////////////////////////////////////////////////////
+inline int P3DPatchfileReader::
+read_int32() {
+  unsigned int a = _patch_in.get();
+  unsigned int b = _patch_in.get();
+  unsigned int c = _patch_in.get();
+  int d = _patch_in.get();
+  return (d << 24) | (c << 16) | (b << 8) | a;
+}

+ 264 - 0
direct/src/plugin/p3dPatchfileReader.cxx

@@ -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);
+}

+ 76 - 0
direct/src/plugin/p3dPatchfileReader.h

@@ -0,0 +1,76 @@
+// Filename: p3dPatchfileReader.h
+// Created by:  drose (27Sep09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DPATCHFILEREADER_H
+#define P3DPATCHFILEREADER_H
+
+#include "p3d_plugin_common.h"
+#include "p3dInstanceManager.h"  // for openssl
+#include "fileSpec.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DPatchfileReader
+// Description : A read-only implementation of Panda's patchfile
+//               format, for applying patches.
+//
+//               This object assumes that the sourcefile has been
+//               already validated against its md5 hash, and does not
+//               validate it again.  It *does* verify that the md5
+//               hash in source and target match those read in the
+//               patchfile header; and it verifies the md5 hash on the
+//               target after completion.
+////////////////////////////////////////////////////////////////////
+class P3DPatchfileReader {
+public:
+  P3DPatchfileReader(const string &package_dir,
+                     const FileSpec &patchfile,
+                     const FileSpec &source,
+                     const FileSpec &target);
+  ~P3DPatchfileReader();
+
+  bool open_read();
+  inline bool is_open() const;
+
+  bool step();
+  inline size_t get_bytes_written() const; 
+  inline bool get_success() const;
+
+  void close();
+
+private:
+  bool copy_bytes(istream &in, size_t copy_byte_count);
+  inline unsigned int read_uint16();
+  inline unsigned int read_uint32();
+  inline int read_int32();
+
+private:
+  string _package_dir;
+  FileSpec _patchfile;
+  FileSpec _source;
+  FileSpec _target;
+
+  string _output_pathname;
+  ifstream _patch_in;
+  ifstream _source_in;
+  ofstream _target_out;
+
+  bool _is_open;
+  size_t _target_length;
+  size_t _bytes_written;
+  bool _success;
+};
+
+#include "p3dPatchfileReader.I"
+
+#endif

+ 1 - 0
direct/src/plugin/p3d_plugin_composite1.cxx

@@ -17,6 +17,7 @@
 #include "p3dObject.cxx"
 #include "p3dOsxSplashWindow.cxx"
 #include "p3dPackage.cxx"
+#include "p3dPatchfileReader.cxx"
 #include "p3dPatchFinder.cxx"
 #include "p3dPythonObject.cxx"
 #include "p3dReferenceCount.cxx"