2051 lines
70 KiB
C++
2051 lines
70 KiB
C++
// Filename: multifile.cxx
|
|
// Created by: mike (09Jan97)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "multifile.h"
|
|
|
|
#include "config_express.h"
|
|
#include "streamWriter.h"
|
|
#include "streamReader.h"
|
|
#include "datagram.h"
|
|
#include "zStream.h"
|
|
#include "encryptStream.h"
|
|
|
|
#include <algorithm>
|
|
|
|
// This sequence of bytes begins each Multifile to identify it as a
|
|
// Multifile.
|
|
const char Multifile::_header[] = "pmf\0\n\r";
|
|
const size_t Multifile::_header_size = 6;
|
|
|
|
// These numbers identify the version of the Multifile. Generally, a
|
|
// change in the major version is intolerable; while a Multifile with
|
|
// an older minor version may still be read.
|
|
const int Multifile::_current_major_ver = 1;
|
|
|
|
const int Multifile::_current_minor_ver = 1;
|
|
// Bumped to version 1.1 on 6/8/06 to add timestamps.
|
|
|
|
// To confirm that the supplied password matches, we write the
|
|
// Mutifile magic header at the beginning of the encrypted stream.
|
|
// I suppose this does compromise the encryption security a tiny
|
|
// bit by making it easy for crackers to validate that a
|
|
// particular password guess matches or doesn't match, but the
|
|
// encryption algorithm doesn't depend on this being difficult
|
|
// anyway.
|
|
const char Multifile::_encrypt_header[] = "crypty";
|
|
const size_t Multifile::_encrypt_header_size = 6;
|
|
|
|
|
|
|
|
//
|
|
// A Multifile consists of the following elements:
|
|
//
|
|
// (1) A header. This is always the first n bytes of the Multifile,
|
|
// and contains a magic number to identify the file, as well as
|
|
// version numbers and any file-specific parameters.
|
|
//
|
|
// char[6] The string Multifile::_header, a magic number.
|
|
// int16 The file's major version number
|
|
// int16 The file's minor version number
|
|
// uint32 Scale factor. This scales all address references within
|
|
// the file. Normally 1, this may be set larger to
|
|
// support Multifiles larger than 4GB.
|
|
// uint32 An overall modification timestamp for the entire multifile.
|
|
|
|
//
|
|
// (2) Zero or more index entries, one for each subfile within the
|
|
// Multifile. These entries are of variable length. The first one of
|
|
// these immediately follows the header, and the first word of each
|
|
// index entry contains the address of the next index entry. A zero
|
|
// "next" address marks the end of the chain. These may appear at any
|
|
// point within the Multifile; they do not necessarily appear in
|
|
// sequential order at the beginning of the file (although they will
|
|
// after the file has been "packed").
|
|
//
|
|
// uint32 The address of the next entry. 0 to mark the end.
|
|
// uint32 The address of this subfile's data record.
|
|
// uint32 The length in bytes of this subfile's data record.
|
|
// uint16 The Subfile::_flags member.
|
|
// [uint32] The original, uncompressed and unencrypted length of the
|
|
// subfile, if it is compressed or encrypted. This field
|
|
// is only present if one or both of the SF_compressed
|
|
// or SF_encrypted bits are set in _flags.
|
|
// uint32 A modification timestamp for the subfile.
|
|
// uint16 The length in bytes of the subfile's name.
|
|
// char[n] The subfile's name.
|
|
//
|
|
// (3) Zero or more data entries, one for each subfile. These may
|
|
// appear at any point within the Multifile; they do not necessarily
|
|
// follow each index entry, nor are they necessarily all grouped
|
|
// together at the end (although they will be all grouped together at
|
|
// the end after the file has been "packed"). These are just blocks
|
|
// of literal data.
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Constructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
Multifile::
|
|
Multifile() :
|
|
_read_filew(_read_file),
|
|
_read_write_filew(_read_write_file)
|
|
{
|
|
ConfigVariableInt multifile_encryption_iteration_count
|
|
("multifile-encryption-iteration-count", 0,
|
|
PRC_DESC("This is a special value of encryption-iteration-count used to encrypt "
|
|
"subfiles within a multifile. It has a default value of 0 (just one "
|
|
"application), on the assumption that the files from a multifile must "
|
|
"be loaded quickly, without paying the cost of an expensive hash on "
|
|
"each subfile in order to decrypt it."));
|
|
|
|
_read = (IStreamWrapper *)NULL;
|
|
_write = (ostream *)NULL;
|
|
_owns_stream = false;
|
|
_next_index = 0;
|
|
_last_index = 0;
|
|
_needs_repack = false;
|
|
_timestamp = 0;
|
|
_timestamp_dirty = false;
|
|
_record_timestamp = true;
|
|
_scale_factor = 1;
|
|
_new_scale_factor = 1;
|
|
_encryption_flag = false;
|
|
_encryption_iteration_count = multifile_encryption_iteration_count;
|
|
_file_major_ver = 0;
|
|
_file_minor_ver = 0;
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
// Get these values from the config file via an EncryptStreamBuf.
|
|
EncryptStreamBuf tbuf;
|
|
_encryption_algorithm = tbuf.get_algorithm();
|
|
_encryption_key_length = tbuf.get_key_length();
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Destructor
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
Multifile::
|
|
~Multifile() {
|
|
close();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Copy Constructor
|
|
// Access: Private
|
|
// Description: Don't try to copy Multifiles.
|
|
////////////////////////////////////////////////////////////////////
|
|
Multifile::
|
|
Multifile(const Multifile ©) :
|
|
_read_filew(_read_file),
|
|
_read_write_filew(_read_write_file)
|
|
{
|
|
nassertv(false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Copy Assignment Operator
|
|
// Access: Private
|
|
// Description: Don't try to copy Multifiles.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
operator = (const Multifile ©) {
|
|
nassertv(false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_read
|
|
// Access: Published
|
|
// Description: Opens the named Multifile on disk for reading. The
|
|
// Multifile index is read in, and the list of subfiles
|
|
// becomes available; individual subfiles may then be
|
|
// extracted or read, but the list of subfiles may not
|
|
// be modified.
|
|
//
|
|
// Also see the version of open_read() which accepts an
|
|
// istream. Returns true on success, false on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_read(const Filename &multifile_name) {
|
|
close();
|
|
Filename fname = multifile_name;
|
|
fname.set_binary();
|
|
if (!fname.open_read(_read_file)) {
|
|
return false;
|
|
}
|
|
_timestamp = fname.get_timestamp();
|
|
_timestamp_dirty = true;
|
|
_read = &_read_filew;
|
|
_multifile_name = multifile_name;
|
|
return read_index();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_read
|
|
// Access: Public
|
|
// Description: Opens an anonymous Multifile for reading using an
|
|
// istream. There must be seek functionality via
|
|
// seekg() and tellg() on the istream.
|
|
//
|
|
// If owns_pointer is true, then the Multifile assumes
|
|
// ownership of the stream pointer and will delete it
|
|
// when the multifile is closed, including if this
|
|
// function returns false.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_read(IStreamWrapper *multifile_stream, bool owns_pointer) {
|
|
close();
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
_read = multifile_stream;
|
|
_owns_stream = owns_pointer;
|
|
return read_index();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_write
|
|
// Access: Published
|
|
// Description: Opens the named Multifile on disk for writing. If
|
|
// there already exists a file by that name, it is
|
|
// truncated. The Multifile is then prepared for
|
|
// accepting a brand new set of subfiles, which will be
|
|
// written to the indicated filename. Individual
|
|
// subfiles may not be extracted or read.
|
|
//
|
|
// Also see the version of open_write() which accepts an
|
|
// ostream. Returns true on success, false on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_write(const Filename &multifile_name) {
|
|
close();
|
|
Filename fname = multifile_name;
|
|
fname.set_binary();
|
|
if (!fname.open_write(_write_file, true)) {
|
|
return false;
|
|
}
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
_write = &_write_file;
|
|
_multifile_name = multifile_name;
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_write
|
|
// Access: Public
|
|
// Description: Opens an anonymous Multifile for writing using an
|
|
// ostream. There must be seek functionality via
|
|
// seekp() and tellp() on the pstream.
|
|
//
|
|
// If owns_pointer is true, then the Multifile assumes
|
|
// ownership of the stream pointer and will delete it
|
|
// when the multifile is closed, including if this
|
|
// function returns false.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_write(ostream *multifile_stream, bool owns_pointer) {
|
|
close();
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
_write = multifile_stream;
|
|
_owns_stream = owns_pointer;
|
|
_write->seekp(0, ios::beg);
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_read_write
|
|
// Access: Published
|
|
// Description: Opens the named Multifile on disk for reading and
|
|
// writing. If there already exists a file by that
|
|
// name, its index is read. Subfiles may be added or
|
|
// removed, and the resulting changes will be written to
|
|
// the named file.
|
|
//
|
|
// Also see the version of open_read_write() which
|
|
// accepts an iostream. Returns true on success, false
|
|
// on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_read_write(const Filename &multifile_name) {
|
|
close();
|
|
Filename fname = multifile_name;
|
|
fname.set_binary();
|
|
bool exists = fname.exists();
|
|
if (!fname.open_read_write(_read_write_file)) {
|
|
return false;
|
|
}
|
|
if (exists) {
|
|
_timestamp = fname.get_timestamp();
|
|
} else {
|
|
_timestamp = time(NULL);
|
|
}
|
|
_timestamp_dirty = true;
|
|
_read = &_read_write_filew;
|
|
_write = &_read_write_file;
|
|
_multifile_name = multifile_name;
|
|
|
|
if (exists) {
|
|
return read_index();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_read_write
|
|
// Access: Public
|
|
// Description: Opens an anonymous Multifile for reading and writing
|
|
// using an iostream. There must be seek functionality
|
|
// via seekg()/seekp() and tellg()/tellp() on the
|
|
// iostream.
|
|
//
|
|
// If owns_pointer is true, then the Multifile assumes
|
|
// ownership of the stream pointer and will delete it
|
|
// when the multifile is closed, including if this
|
|
// function returns false.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
open_read_write(iostream *multifile_stream, bool owns_pointer) {
|
|
close();
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
|
|
// We don't support locking when opening a file in read-write mode,
|
|
// because we don't bother with locking on write. But we need to
|
|
// have an IStreamWrapper to assign to the _read member, so we
|
|
// create one on-the-fly here.
|
|
_read = new StreamWrapper(multifile_stream, owns_pointer);
|
|
_write = multifile_stream;
|
|
_owns_stream = true; // Because we own the StreamWrapper, above.
|
|
_write->seekp(0, ios::beg);
|
|
|
|
// Check whether the read stream is empty.
|
|
multifile_stream->seekg(0, ios::end);
|
|
if (multifile_stream->tellg() == (streampos)0) {
|
|
// The read stream is empty, which is always valid.
|
|
return true;
|
|
}
|
|
|
|
// The read stream is not empty, so we'd better have a valid
|
|
// Multifile.
|
|
return read_index();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::close
|
|
// Access: Published
|
|
// Description: Closes the Multifile if it is open. All changes are
|
|
// flushed to disk, and the file becomes invalid for
|
|
// further operations until the next call to open().
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
close() {
|
|
if (_new_scale_factor != _scale_factor) {
|
|
// If we have changed the scale factor recently, we need to force
|
|
// a repack.
|
|
repack();
|
|
} else {
|
|
flush();
|
|
}
|
|
|
|
if (_owns_stream) {
|
|
// We prefer to delete the IStreamWrapper over the ostream, if
|
|
// possible.
|
|
if (_read != (IStreamWrapper *)NULL) {
|
|
delete _read;
|
|
} else if (_write != (ostream *)NULL) {
|
|
delete _write;
|
|
}
|
|
}
|
|
|
|
_read = (IStreamWrapper *)NULL;
|
|
_write = (ostream *)NULL;
|
|
_owns_stream = false;
|
|
_next_index = 0;
|
|
_last_index = 0;
|
|
_needs_repack = false;
|
|
_timestamp = 0;
|
|
_timestamp_dirty = false;
|
|
_scale_factor = 1;
|
|
_new_scale_factor = 1;
|
|
_encryption_flag = false;
|
|
_file_major_ver = 0;
|
|
_file_minor_ver = 0;
|
|
|
|
_read_file.close();
|
|
_write_file.close();
|
|
_read_write_file.close();
|
|
_multifile_name = Filename();
|
|
|
|
clear_subfiles();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::set_scale_factor
|
|
// Access: Published
|
|
// Description: Changes the internal scale factor for this Multifile.
|
|
//
|
|
// This is normally 1, but it may be set to any
|
|
// arbitrary value (greater than zero) to support
|
|
// Multifile archives that exceed 4GB, if necessary.
|
|
// (Individual subfiles may still not exceed 4GB.)
|
|
//
|
|
// All addresses within the file are rounded up to the
|
|
// next multiple of _scale_factor, and zeros are written
|
|
// to the file to fill the resulting gaps. Then the
|
|
// address is divided by _scale_factor and written out
|
|
// as a 32-bit integer. Thus, setting a scale factor of
|
|
// 2 supports up to 8GB files, 3 supports 12GB files,
|
|
// etc.
|
|
//
|
|
// Calling this function on an already-existing
|
|
// Multifile will have no immediate effect until a
|
|
// future call to repack() or close() (or until the
|
|
// Multifile is destructed).
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
set_scale_factor(size_t scale_factor) {
|
|
nassertv(is_write_valid());
|
|
nassertv(scale_factor != (size_t)0);
|
|
|
|
if (_next_index == (streampos)0) {
|
|
// If it's a brand new Multifile, we can go ahead and set it
|
|
// immediately.
|
|
_scale_factor = scale_factor;
|
|
} else {
|
|
// Otherwise, we'd better have read access so we can repack it
|
|
// later.
|
|
nassertv(is_read_valid());
|
|
}
|
|
|
|
// Setting the _new_scale_factor different from the _scale_factor
|
|
// will force a repack operation on close.
|
|
_new_scale_factor = scale_factor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::add_subfile
|
|
// Access: Published
|
|
// Description: Adds a file on disk as a subfile to the Multifile.
|
|
// The file named by filename will be read and added to
|
|
// the Multifile at the next call to flush(). If there
|
|
// already exists a subfile with the indicated name, it
|
|
// is replaced without examining its contents (but see
|
|
// also update_subfile).
|
|
//
|
|
// Returns the subfile name on success (it might have
|
|
// been modified slightly), or empty string on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
string Multifile::
|
|
add_subfile(const string &subfile_name, const Filename &filename,
|
|
int compression_level) {
|
|
nassertr(is_write_valid(), string());
|
|
|
|
if (!filename.exists()) {
|
|
return string();
|
|
}
|
|
string name = standardize_subfile_name(subfile_name);
|
|
if (!name.empty()) {
|
|
Subfile *subfile = new Subfile;
|
|
subfile->_name = name;
|
|
subfile->_source_filename = filename;
|
|
subfile->_source_filename.set_binary();
|
|
|
|
add_new_subfile(subfile, compression_level);
|
|
}
|
|
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
|
|
return name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::add_subfile
|
|
// Access: Public
|
|
// Description: Adds a file from a stream as a subfile to the Multifile.
|
|
// The indicated istream will be read and its contents
|
|
// added to the Multifile at the next call to flush().
|
|
//
|
|
// Note that the istream must remain untouched and
|
|
// unused by any other code until flush() is called. At
|
|
// that time, the Multifile will read the entire
|
|
// contents of the istream from the current file
|
|
// position to the end of the file. Subsequently, the
|
|
// Multifile will *not* close or delete the istream. It
|
|
// is the caller's responsibility to ensure that the
|
|
// istream pointer does not destruct during the lifetime
|
|
// of the Multifile.
|
|
//
|
|
// Returns the subfile name on success (it might have
|
|
// been modified slightly), or empty string on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
string Multifile::
|
|
add_subfile(const string &subfile_name, istream *subfile_data,
|
|
int compression_level) {
|
|
nassertr(is_write_valid(), string());
|
|
|
|
string name = standardize_subfile_name(subfile_name);
|
|
if (!name.empty()) {
|
|
Subfile *subfile = new Subfile;
|
|
subfile->_name = name;
|
|
subfile->_source = subfile_data;
|
|
add_new_subfile(subfile, compression_level);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::update_subfile
|
|
// Access: Published
|
|
// Description: Adds a file on disk to the subfile. If a subfile
|
|
// already exists with the same name, its contents are
|
|
// compared byte-for-byte to the disk file, and it is
|
|
// replaced only if it is different; otherwise, the
|
|
// multifile is left unchanged.
|
|
////////////////////////////////////////////////////////////////////
|
|
string Multifile::
|
|
update_subfile(const string &subfile_name, const Filename &filename,
|
|
int compression_level) {
|
|
nassertr(is_write_valid(), string());
|
|
|
|
if (!filename.exists()) {
|
|
return string();
|
|
}
|
|
string name = standardize_subfile_name(subfile_name);
|
|
if (!name.empty()) {
|
|
int index = find_subfile(name);
|
|
if (index >= 0) {
|
|
// The subfile already exists; compare it to the source file.
|
|
if (compare_subfile(index, filename)) {
|
|
// The files are identical; do nothing.
|
|
return name;
|
|
}
|
|
}
|
|
|
|
// The subfile does not already exist or it is different from the
|
|
// source file. Add the new source file.
|
|
Subfile *subfile = new Subfile;
|
|
subfile->_name = name;
|
|
subfile->_source_filename = filename;
|
|
subfile->_source_filename.set_binary();
|
|
|
|
add_new_subfile(subfile, compression_level);
|
|
}
|
|
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
|
|
return name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::flush
|
|
// Access: Published
|
|
// Description: Writes all contents of the Multifile to disk. Until
|
|
// flush() is called, add_subfile() and remove_subfile()
|
|
// do not actually do anything to disk. At this point,
|
|
// all of the recently-added subfiles are read and their
|
|
// contents are added to the end of the Multifile, and
|
|
// the recently-removed subfiles are marked gone from
|
|
// the Multifile.
|
|
//
|
|
// This may result in a suboptimal index. To guarantee
|
|
// that the index is written at the beginning of the
|
|
// file, call repack() instead of flush().
|
|
//
|
|
// It is not necessary to call flush() explicitly unless
|
|
// you are concerned about reading the recently-added
|
|
// subfiles immediately.
|
|
//
|
|
// Returns true on success, false on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
flush() {
|
|
if (!is_write_valid()) {
|
|
return false;
|
|
}
|
|
|
|
if (_next_index == (streampos)0) {
|
|
// If we don't have an index yet, we don't have a header. Write
|
|
// the header.
|
|
if (!write_header()) {
|
|
return false;
|
|
}
|
|
|
|
} else {
|
|
if (_file_minor_ver != _current_minor_ver) {
|
|
// If we *do* have an index already, but this is an old version
|
|
// multifile, we have to completely rewrite it anyway.
|
|
return repack();
|
|
}
|
|
}
|
|
|
|
nassertr(_write != (ostream *)NULL, false);
|
|
|
|
// First, mark out all of the removed subfiles.
|
|
PendingSubfiles::iterator pi;
|
|
for (pi = _removed_subfiles.begin(); pi != _removed_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
subfile->rewrite_index_flags(*_write);
|
|
delete subfile;
|
|
}
|
|
_removed_subfiles.clear();
|
|
|
|
bool wrote_ok = true;
|
|
|
|
if (!_new_subfiles.empty()) {
|
|
// Add a few more files to the end. We always add subfiles at the
|
|
// end of the multifile, so go there first.
|
|
sort(_new_subfiles.begin(), _new_subfiles.end(), IndirectLess<Subfile>());
|
|
if (_last_index != (streampos)0) {
|
|
_write->seekp(0, ios::end);
|
|
if (_write->fail()) {
|
|
express_cat.info()
|
|
<< "Unable to seek Multifile " << _multifile_name << ".\n";
|
|
return false;
|
|
}
|
|
_next_index = _write->tellp();
|
|
_next_index = pad_to_streampos(_next_index);
|
|
|
|
// And update the forward link from the last_index to point to
|
|
// this new index location.
|
|
_write->seekp(_last_index);
|
|
StreamWriter writer(_write, false);
|
|
writer.add_uint32(streampos_to_word(_next_index));
|
|
}
|
|
|
|
_write->seekp(_next_index);
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
|
|
// Ok, here we are at the end of the file. Write out the
|
|
// recently-added subfiles here. First, count up the index size.
|
|
for (pi = _new_subfiles.begin(); pi != _new_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
_last_index = _next_index;
|
|
_next_index = subfile->write_index(*_write, _next_index, this);
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
_next_index = pad_to_streampos(_next_index);
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
}
|
|
|
|
// Now we're at the end of the index. Write a 0 here to mark the
|
|
// end.
|
|
StreamWriter writer(_write, false);
|
|
writer.add_uint32(0);
|
|
_next_index += 4;
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
_next_index = pad_to_streampos(_next_index);
|
|
|
|
// All right, now write out each subfile's data.
|
|
for (pi = _new_subfiles.begin(); pi != _new_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
|
|
if (_read != (IStreamWrapper *)NULL) {
|
|
_read->acquire();
|
|
_next_index = subfile->write_data(*_write, _read->get_istream(),
|
|
_next_index, this);
|
|
_read->release();
|
|
|
|
} else {
|
|
_next_index = subfile->write_data(*_write, NULL, _next_index, this);
|
|
}
|
|
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
_next_index = pad_to_streampos(_next_index);
|
|
if (subfile->is_data_invalid()) {
|
|
wrote_ok = false;
|
|
}
|
|
nassertr(_next_index == _write->tellp(), false);
|
|
}
|
|
|
|
// Now go back and fill in the proper addresses for the data start.
|
|
// We didn't do it in the first pass, because we don't really want
|
|
// to keep all those file handles open, and so we didn't have to
|
|
// determine each file's length ahead of time.
|
|
for (pi = _new_subfiles.begin(); pi != _new_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
subfile->rewrite_index_data_start(*_write, this);
|
|
}
|
|
|
|
_new_subfiles.clear();
|
|
}
|
|
|
|
// Also update the overall timestamp.
|
|
if (_timestamp_dirty) {
|
|
nassertr(!_write->fail(), false);
|
|
static const size_t timestamp_pos = _header_size + 2 + 2 + 4;
|
|
_write->seekp(timestamp_pos);
|
|
nassertr(!_write->fail(), false);
|
|
|
|
StreamWriter writer(*_write);
|
|
if (_record_timestamp) {
|
|
writer.add_uint32(_timestamp);
|
|
} else {
|
|
writer.add_uint32(0);
|
|
}
|
|
_timestamp_dirty = false;
|
|
}
|
|
|
|
_write->flush();
|
|
if (!wrote_ok || _write->fail()) {
|
|
express_cat.info()
|
|
<< "Unable to update Multifile " << _multifile_name << ".\n";
|
|
close();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::repack
|
|
// Access: Published
|
|
// Description: Forces a complete rewrite of the Multifile and all of
|
|
// its contents, so that its index will appear at the
|
|
// beginning of the file with all of the subfiles listed
|
|
// in alphabetical order. This is considered optimal
|
|
// for reading, and is the standard configuration; but
|
|
// it is not essential to do this.
|
|
//
|
|
// It is only valid to call this if the Multifile was
|
|
// opened using open_read_write() and an explicit
|
|
// filename, rather than an iostream. Also, we must
|
|
// have write permission to the directory containing the
|
|
// Multifile.
|
|
//
|
|
// Returns true on success, false on failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
repack() {
|
|
if (_next_index == (streampos)0) {
|
|
// If the Multifile hasn't yet been written, this is really just a
|
|
// flush operation.
|
|
_needs_repack = false;
|
|
return flush();
|
|
}
|
|
|
|
nassertr(is_write_valid() && is_read_valid(), false);
|
|
nassertr(!_multifile_name.empty(), false);
|
|
|
|
// First, we open a temporary filename to copy the Multifile to.
|
|
Filename dirname = _multifile_name.get_dirname();
|
|
if (dirname.empty()) {
|
|
dirname = ".";
|
|
}
|
|
Filename temp_filename = Filename::temporary(dirname, "mftemp");
|
|
temp_filename.set_binary();
|
|
pofstream temp;
|
|
if (!temp_filename.open_write(temp)) {
|
|
express_cat.info()
|
|
<< "Unable to open temporary file " << temp_filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
// Now we scrub our internal structures so it looks like we're a
|
|
// brand new Multifile.
|
|
PendingSubfiles::iterator pi;
|
|
for (pi = _removed_subfiles.begin(); pi != _removed_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
delete subfile;
|
|
}
|
|
_removed_subfiles.clear();
|
|
_new_subfiles.clear();
|
|
copy(_subfiles.begin(), _subfiles.end(), back_inserter(_new_subfiles));
|
|
_next_index = 0;
|
|
_last_index = 0;
|
|
_scale_factor = _new_scale_factor;
|
|
|
|
// And we write our contents to our new temporary file.
|
|
_write = &temp;
|
|
if (!flush()) {
|
|
temp.close();
|
|
temp_filename.unlink();
|
|
return false;
|
|
}
|
|
|
|
// Now close everything, and move the temporary file back over our
|
|
// original file.
|
|
Filename orig_name = _multifile_name;
|
|
temp.close();
|
|
close();
|
|
orig_name.unlink();
|
|
if (!temp_filename.rename_to(orig_name)) {
|
|
express_cat.info()
|
|
<< "Unable to rename temporary file " << temp_filename << " to "
|
|
<< orig_name << ".\n";
|
|
return false;
|
|
}
|
|
|
|
if (!open_read_write(orig_name)) {
|
|
express_cat.info()
|
|
<< "Unable to read newly repacked " << _multifile_name
|
|
<< ".\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_num_subfiles
|
|
// Access: Published
|
|
// Description: Returns the number of subfiles within the Multifile.
|
|
// The subfiles may be accessed in alphabetical order by
|
|
// iterating through [0 .. get_num_subfiles()).
|
|
////////////////////////////////////////////////////////////////////
|
|
int Multifile::
|
|
get_num_subfiles() const {
|
|
return _subfiles.size();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::find_subfile
|
|
// Access: Published
|
|
// Description: Returns the index of the subfile with the indicated
|
|
// name, or -1 if the named subfile is not within the
|
|
// Multifile.
|
|
////////////////////////////////////////////////////////////////////
|
|
int Multifile::
|
|
find_subfile(const string &subfile_name) const {
|
|
Subfile find_subfile;
|
|
find_subfile._name = standardize_subfile_name(subfile_name);
|
|
Subfiles::const_iterator fi;
|
|
fi = _subfiles.find(&find_subfile);
|
|
if (fi == _subfiles.end()) {
|
|
// Not present.
|
|
return -1;
|
|
}
|
|
return (fi - _subfiles.begin());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::has_directory
|
|
// Access: Published
|
|
// Description: Returns true if the indicated subfile name is the
|
|
// directory prefix to one or more files within the
|
|
// Multifile. That is, the Multifile contains at least
|
|
// one file named "subfile_name/...".
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
has_directory(const string &subfile_name) const {
|
|
string prefix = subfile_name;
|
|
if (!prefix.empty()) {
|
|
prefix += '/';
|
|
}
|
|
Subfile find_subfile;
|
|
find_subfile._name = prefix;
|
|
Subfiles::const_iterator fi;
|
|
fi = _subfiles.upper_bound(&find_subfile);
|
|
if (fi == _subfiles.end()) {
|
|
// Not present.
|
|
return false;
|
|
}
|
|
|
|
// At least one subfile exists whose name sorts after prefix. If it
|
|
// contains prefix as the initial substring, then we have a match.
|
|
Subfile *subfile = (*fi);
|
|
return (subfile->_name.length() > prefix.length() &&
|
|
subfile->_name.substr(0, prefix.length()) == prefix);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::scan_directory
|
|
// Access: Published
|
|
// Description: Considers subfile_name to be the name of a
|
|
// subdirectory within the Multifile, but not a file
|
|
// itself; fills the given vector up with the sorted list
|
|
// of subdirectories or files within the named
|
|
// directory.
|
|
//
|
|
// Note that directories do not exist explicitly within
|
|
// a Multifile; this just checks for the existence of
|
|
// files with the given initial prefix.
|
|
//
|
|
// Returns true if successful, false otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
scan_directory(vector_string &contents, const string &subfile_name) const {
|
|
string prefix = subfile_name;
|
|
if (!prefix.empty()) {
|
|
prefix += '/';
|
|
}
|
|
Subfile find_subfile;
|
|
find_subfile._name = prefix;
|
|
Subfiles::const_iterator fi;
|
|
fi = _subfiles.upper_bound(&find_subfile);
|
|
|
|
string previous = "";
|
|
while (fi != _subfiles.end()) {
|
|
Subfile *subfile = (*fi);
|
|
if (!(subfile->_name.length() > prefix.length() &&
|
|
subfile->_name.substr(0, prefix.length()) == prefix)) {
|
|
// We've reached the end of the list of subfiles beneath the
|
|
// indicated directory prefix.
|
|
return true;
|
|
}
|
|
|
|
size_t slash = subfile->_name.find('/', prefix.length());
|
|
string basename = subfile->_name.substr(prefix.length(), slash - prefix.length());
|
|
if (basename != previous) {
|
|
contents.push_back(basename);
|
|
previous = basename;
|
|
}
|
|
++fi;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::remove_subfile
|
|
// Access: Published
|
|
// Description: Removes the nth subfile from the Multifile. This
|
|
// will cause all subsequent index numbers to decrease
|
|
// by one. The file will not actually be removed from
|
|
// the disk until the next call to flush().
|
|
//
|
|
// Note that this does not actually remove the data from
|
|
// the indicated subfile; it simply removes it from the
|
|
// index. The Multifile will not be reduced in size
|
|
// after this operation, until the next call to
|
|
// repack().
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
remove_subfile(int index) {
|
|
nassertv(is_write_valid());
|
|
nassertv(index >= 0 && index < (int)_subfiles.size());
|
|
Subfile *subfile = _subfiles[index];
|
|
subfile->_flags |= SF_deleted;
|
|
_removed_subfiles.push_back(subfile);
|
|
_subfiles.erase(_subfiles.begin() + index);
|
|
|
|
_timestamp = time(NULL);
|
|
_timestamp_dirty = true;
|
|
|
|
_needs_repack = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_subfile_name
|
|
// Access: Published
|
|
// Description: Returns the name of the nth subfile.
|
|
////////////////////////////////////////////////////////////////////
|
|
const string &Multifile::
|
|
get_subfile_name(int index) const {
|
|
#ifndef NDEBUG
|
|
static string empty_string;
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), empty_string);
|
|
#endif
|
|
return _subfiles[index]->_name;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_subfile_length
|
|
// Access: Published
|
|
// Description: Returns the uncompressed data length of the nth
|
|
// subfile. This might return 0 if the subfile has
|
|
// recently been added and flush() has not yet been
|
|
// called.
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t Multifile::
|
|
get_subfile_length(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
return _subfiles[index]->_uncompressed_length;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_subfile_timestamp
|
|
// Access: Published
|
|
// Description: Returns the modification time of the nth
|
|
// subfile. If this is called on an older .mf file,
|
|
// which did not store individual timestamps in the file
|
|
// (or if get_record_timestamp() is false), this will
|
|
// return the modification time of the overall
|
|
// multifile.
|
|
////////////////////////////////////////////////////////////////////
|
|
time_t Multifile::
|
|
get_subfile_timestamp(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
if (!get_record_timestamp()) {
|
|
return get_timestamp();
|
|
} else {
|
|
return _subfiles[index]->_timestamp;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::is_subfile_compressed
|
|
// Access: Published
|
|
// Description: Returns true if the indicated subfile has been
|
|
// compressed when stored within the archive, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
is_subfile_compressed(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
return (_subfiles[index]->_flags & SF_compressed) != 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::is_subfile_encrypted
|
|
// Access: Published
|
|
// Description: Returns true if the indicated subfile has been
|
|
// encrypted when stored within the archive, false
|
|
// otherwise.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
is_subfile_encrypted(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
return (_subfiles[index]->_flags & SF_encrypted) != 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_index_end
|
|
// Access: Published
|
|
// Description: Returns the first byte that is guaranteed to follow
|
|
// any index byte already written to disk in the
|
|
// Multifile.
|
|
//
|
|
// This number is largely meaningless in many cases, but
|
|
// if needs_repack() is false, and the file is flushed,
|
|
// this will indicate the number of bytes in the header
|
|
// + index. Everything at this byte position and later
|
|
// will be actual data.
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::
|
|
get_index_end() const {
|
|
return normalize_streampos(_next_index + (streampos)4);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_subfile_internal_start
|
|
// Access: Published
|
|
// Description: Returns the starting byte position within the
|
|
// Multifile at which the indicated subfile begins.
|
|
// This may be used, with get_subfile_internal_length(),
|
|
// for low-level access to the subfile, but usually it
|
|
// is better to use open_read_subfile() instead (which
|
|
// automatically decrypts and/or uncompresses the
|
|
// subfile data).
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::
|
|
get_subfile_internal_start(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
return _subfiles[index]->_data_start;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::get_subfile_internal_length
|
|
// Access: Published
|
|
// Description: Returns the number of bytes the indicated subfile
|
|
// consumes within the archive. For compressed
|
|
// subfiles, this will generally be smaller than
|
|
// get_subfile_length(); for encrypted (but
|
|
// noncompressed) subfiles, it may be slightly
|
|
// different, for noncompressed and nonencrypted
|
|
// subfiles, it will be equal.
|
|
////////////////////////////////////////////////////////////////////
|
|
size_t Multifile::
|
|
get_subfile_internal_length(int index) const {
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), 0);
|
|
return _subfiles[index]->_data_length;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::open_read_subfile
|
|
// Access: Published
|
|
// Description: Returns an istream that may be used to read the
|
|
// indicated subfile. You may seek() within this
|
|
// istream to your heart's content; even though it will
|
|
// be a reference to the already-opened pfstream of the
|
|
// Multifile itself, byte 0 appears to be the beginning
|
|
// of the subfile and EOF appears to be the end of the
|
|
// subfile.
|
|
//
|
|
// The returned istream will have been allocated via
|
|
// new; you should pass the pointer to
|
|
// close_read_subfile() when you are finished with it to
|
|
// delete it and release its resources.
|
|
//
|
|
// Any future calls to repack() or close() (or the
|
|
// Multifile destructor) will invalidate all currently
|
|
// open subfile pointers.
|
|
//
|
|
// The return value will be NULL if the stream cannot be
|
|
// opened for some reason.
|
|
////////////////////////////////////////////////////////////////////
|
|
istream *Multifile::
|
|
open_read_subfile(int index) {
|
|
nassertr(is_read_valid(), NULL);
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), NULL);
|
|
Subfile *subfile = _subfiles[index];
|
|
|
|
if (subfile->_source != (istream *)NULL ||
|
|
!subfile->_source_filename.empty()) {
|
|
// The subfile has not yet been copied into the physical
|
|
// Multifile. Force a flush operation to incorporate it.
|
|
flush();
|
|
|
|
// That shouldn't change the subfile index or delete the subfile
|
|
// pointer.
|
|
nassertr(subfile == _subfiles[index], NULL);
|
|
}
|
|
|
|
// Return an ISubStream object that references into the open
|
|
// Multifile istream.
|
|
nassertr(subfile->_data_start != (streampos)0, NULL);
|
|
istream *stream =
|
|
new ISubStream(_read, subfile->_data_start,
|
|
subfile->_data_start + (streampos)subfile->_data_length);
|
|
|
|
if ((subfile->_flags & SF_encrypted) != 0) {
|
|
#ifndef HAVE_OPENSSL
|
|
express_cat.error()
|
|
<< "OpenSSL not compiled in; cannot read encrypted multifiles.\n";
|
|
delete stream;
|
|
return NULL;
|
|
#else // HAVE_OPENSSL
|
|
// The subfile is encrypted. So actually, return an
|
|
// IDecryptStream that wraps around the ISubStream.
|
|
IDecryptStream *wrapper =
|
|
new IDecryptStream(stream, true, _encryption_password);
|
|
stream = wrapper;
|
|
|
|
// Validate the password by confirming that the encryption header
|
|
// matches.
|
|
char this_header[_encrypt_header_size];
|
|
stream->read(this_header, _encrypt_header_size);
|
|
if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size ||
|
|
memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) {
|
|
express_cat.error()
|
|
<< "Unable to decrypt subfile " << subfile->_name << ".\n";
|
|
delete stream;
|
|
return NULL;
|
|
}
|
|
#endif // HAVE_OPENSSL
|
|
}
|
|
|
|
if ((subfile->_flags & SF_compressed) != 0) {
|
|
#ifndef HAVE_ZLIB
|
|
express_cat.error()
|
|
<< "zlib not compiled in; cannot read compressed multifiles.\n";
|
|
delete stream;
|
|
return NULL;
|
|
#else // HAVE_ZLIB
|
|
// Oops, the subfile is compressed. So actually, return an
|
|
// IDecompressStream that wraps around the ISubStream.
|
|
IDecompressStream *wrapper = new IDecompressStream(stream, true);
|
|
stream = wrapper;
|
|
#endif // HAVE_ZLIB
|
|
}
|
|
|
|
if (stream->fail()) {
|
|
// Hmm, some inexplicable problem.
|
|
delete stream;
|
|
return NULL;
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::close_read_subfile
|
|
// Access: Published, Static
|
|
// Description: Closes a file opened by a previous call to
|
|
// open_read_subfile(). This really just deletes the
|
|
// istream pointer, but it is recommended to use this
|
|
// interface instead of deleting it explicitly, to help
|
|
// work around compiler issues.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
close_read_subfile(istream *stream) {
|
|
if (stream != (istream *)NULL) {
|
|
// For some reason--compiler bug in gcc 3.2?--explicitly deleting
|
|
// the stream pointer does not call the appropriate global delete
|
|
// function; instead apparently calling the system delete
|
|
// function. So we call the delete function by hand instead.
|
|
#if !defined(WIN32_VC) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
|
|
stream->~istream();
|
|
(*global_operator_delete)(stream);
|
|
#else
|
|
delete stream;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::extract_subfile
|
|
// Access: Published
|
|
// Description: Extracts the nth subfile into a file with the given
|
|
// name.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
extract_subfile(int index, const Filename &filename) {
|
|
nassertr(is_read_valid(), false);
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), false);
|
|
|
|
Filename fname = filename;
|
|
fname.set_binary();
|
|
fname.make_dir();
|
|
pofstream out;
|
|
if (!fname.open_write(out, true)) {
|
|
express_cat.info()
|
|
<< "Unable to write to file " << filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
return extract_subfile_to(index, out);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::extract_subfile_to
|
|
// Access: Public
|
|
// Description: Extracts the nth subfile to the indicated ostream.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
extract_subfile_to(int index, ostream &out) {
|
|
nassertr(is_read_valid(), false);
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), false);
|
|
|
|
istream *in = open_read_subfile(index);
|
|
if (in == (istream *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
static const size_t buffer_size = 1024;
|
|
char buffer[buffer_size];
|
|
|
|
in->read(buffer, buffer_size);
|
|
size_t count = in->gcount();
|
|
while (count != 0) {
|
|
out.write(buffer, count);
|
|
in->read(buffer, buffer_size);
|
|
count = in->gcount();
|
|
}
|
|
|
|
bool failed = (in->fail() && !in->eof());
|
|
close_read_subfile(in);
|
|
nassertr(!failed, false);
|
|
|
|
return (!out.fail());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::compare_subfile
|
|
// Access: Published
|
|
// Description: Performs a byte-for-byte comparison of the indicated
|
|
// file on disk with the nth subfile. Returns true if
|
|
// the files are equivalent, or false if they are
|
|
// different (or the file is missing).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
compare_subfile(int index, const Filename &filename) {
|
|
nassertr(is_read_valid(), false);
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), false);
|
|
|
|
if (!filename.exists()) {
|
|
express_cat.info()
|
|
<< "File is missing: " << filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
istream *in1 = open_read_subfile(index);
|
|
if (in1 == (istream *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
pifstream in2;
|
|
Filename bin_filename = Filename::binary_filename(filename);
|
|
if (!bin_filename.open_read(in2)) {
|
|
express_cat.info()
|
|
<< "Cannot read " << filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
// Check the file size.
|
|
in2.seekg(0, ios::end);
|
|
streampos file_size = in2.tellg();
|
|
|
|
if (file_size != (streampos)get_subfile_length(index)) {
|
|
// The files have different sizes.
|
|
close_read_subfile(in1);
|
|
return false;
|
|
}
|
|
|
|
// Check the file data, byte-for-byte.
|
|
in2.seekg(0);
|
|
int byte1 = in1->get();
|
|
int byte2 = in2.get();
|
|
while (!in1->fail() && !in1->eof() &&
|
|
!in2.fail() && !in2.eof()) {
|
|
if (byte1 != byte2) {
|
|
close_read_subfile(in1);
|
|
return false;
|
|
}
|
|
byte1 = in1->get();
|
|
byte2 = in2.get();
|
|
}
|
|
|
|
bool failed = (in1->fail() && !in1->eof()) || (in2.fail() && !in2.eof());
|
|
close_read_subfile(in1);
|
|
|
|
nassertr(!failed, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::output
|
|
// Access: Published
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
output(ostream &out) const {
|
|
out << "Multifile " << _multifile_name << ", " << get_num_subfiles()
|
|
<< " subfiles.\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::ls
|
|
// Access: Published
|
|
// Description: Shows a list of all subfiles within the Multifile.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
ls(ostream &out) const {
|
|
int num_subfiles = get_num_subfiles();
|
|
for (int i = 0; i < num_subfiles; i++) {
|
|
string subfile_name = get_subfile_name(i);
|
|
out << subfile_name << "\n";
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::read_subfile
|
|
// Access: Public
|
|
// Description: Fills a string with the entire contents of
|
|
// the indicated subfile.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
read_subfile(int index, string &result) {
|
|
result = string();
|
|
|
|
pvector<unsigned char> pv;
|
|
if (!read_subfile(index, pv)) {
|
|
return false;
|
|
}
|
|
|
|
if (!pv.empty()) {
|
|
result.append((const char *)&pv[0], pv.size());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::read_subfile
|
|
// Access: Public
|
|
// Description: Fills a pvector with the entire contents of
|
|
// the indicated subfile.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
read_subfile(int index, pvector<unsigned char> &result) {
|
|
nassertr(is_read_valid(), false);
|
|
nassertr(index >= 0 && index < (int)_subfiles.size(), false);
|
|
result.clear();
|
|
|
|
istream *in = open_read_subfile(index);
|
|
if (in == (istream *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
static const size_t buffer_size = 1024;
|
|
char buffer[buffer_size];
|
|
|
|
in->read(buffer, buffer_size);
|
|
size_t count = in->gcount();
|
|
while (count != 0) {
|
|
result.insert(result.end(), buffer, buffer + count);
|
|
in->read(buffer, buffer_size);
|
|
count = in->gcount();
|
|
}
|
|
|
|
bool failed = in->fail() && !in->eof();
|
|
close_read_subfile(in);
|
|
nassertr(!failed, false);
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::pad_to_streampos
|
|
// Access: Private
|
|
// Description: Assumes the _write pointer is at the indicated fpos,
|
|
// rounds the fpos up to the next legitimate address
|
|
// (using normalize_streampos()), and writes enough
|
|
// zeroes to the stream to fill the gap. Returns the
|
|
// new fpos.
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::
|
|
pad_to_streampos(streampos fpos) {
|
|
nassertr(_write != (ostream *)NULL, fpos);
|
|
nassertr(_write->tellp() == fpos, fpos);
|
|
streampos new_fpos = normalize_streampos(fpos);
|
|
while (fpos < new_fpos) {
|
|
_write->put(0);
|
|
fpos += 1; // VC++ doesn't define streampos++ (!)
|
|
}
|
|
nassertr(_write->tellp() == fpos, fpos);
|
|
return fpos;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::add_new_subfile
|
|
// Access: Private
|
|
// Description: Adds a newly-allocated Subfile pointer to the
|
|
// Multifile.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
add_new_subfile(Subfile *subfile, int compression_level) {
|
|
if (compression_level != 0) {
|
|
#ifndef HAVE_ZLIB
|
|
express_cat.warning()
|
|
<< "zlib not compiled in; cannot generated compressed multifiles.\n";
|
|
compression_level = 0;
|
|
#else // HAVE_ZLIB
|
|
subfile->_flags |= SF_compressed;
|
|
subfile->_compression_level = compression_level;
|
|
#endif // HAVE_ZLIB
|
|
}
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
if (_encryption_flag) {
|
|
subfile->_flags |= SF_encrypted;
|
|
}
|
|
#endif // HAVE_OPENSSL
|
|
|
|
if (_next_index != (streampos)0) {
|
|
// If we're adding a Subfile to an already-existing Multifile, we
|
|
// will eventually need to repack the file.
|
|
_needs_repack = true;
|
|
}
|
|
|
|
pair<Subfiles::iterator, bool> insert_result = _subfiles.insert(subfile);
|
|
if (!insert_result.second) {
|
|
// Hmm, unable to insert. There must already be a subfile by that
|
|
// name. Remove the old one.
|
|
Subfile *old_subfile = (*insert_result.first);
|
|
old_subfile->_flags |= SF_deleted;
|
|
|
|
// Maybe it was just added to the _new_subfiles list. In this case, remove it from that list.
|
|
PendingSubfiles::iterator ni = find(_new_subfiles.begin(), _new_subfiles.end(), old_subfile);
|
|
if (ni != _new_subfiles.end()) {
|
|
_new_subfiles.erase(ni);
|
|
|
|
} else {
|
|
// Otherwise, add it to the _removed_subfiles list, so we can remove the old one.
|
|
_removed_subfiles.push_back(old_subfile);
|
|
}
|
|
|
|
(*insert_result.first) = subfile;
|
|
}
|
|
|
|
_new_subfiles.push_back(subfile);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::standardize_subfile_name
|
|
// Access: Private
|
|
// Description: Returns the standard form of the subfile name.
|
|
////////////////////////////////////////////////////////////////////
|
|
string Multifile::
|
|
standardize_subfile_name(const string &subfile_name) const {
|
|
Filename name = subfile_name;
|
|
name.standardize();
|
|
if (name.empty() || name == "/") {
|
|
// Invalid empty name.
|
|
return string();
|
|
}
|
|
|
|
if (name[0] == '/') {
|
|
return name.get_fullpath().substr(1);
|
|
} else {
|
|
return name.get_fullpath();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::clear_subfiles
|
|
// Access: Private
|
|
// Description: Removes the set of subfiles from the tables and frees
|
|
// their associated memory.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::
|
|
clear_subfiles() {
|
|
PendingSubfiles::iterator pi;
|
|
for (pi = _removed_subfiles.begin(); pi != _removed_subfiles.end(); ++pi) {
|
|
Subfile *subfile = (*pi);
|
|
subfile->rewrite_index_flags(*_write);
|
|
delete subfile;
|
|
}
|
|
_removed_subfiles.clear();
|
|
|
|
// We don't have to delete the ones in _new_subfiles, because these
|
|
// also appear in _subfiles.
|
|
_new_subfiles.clear();
|
|
|
|
Subfiles::iterator fi;
|
|
for (fi = _subfiles.begin(); fi != _subfiles.end(); ++fi) {
|
|
Subfile *subfile = (*fi);
|
|
delete subfile;
|
|
}
|
|
_subfiles.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::read_index
|
|
// Access: Private
|
|
// Description: Reads the Multifile header and index. Returns true
|
|
// if successful, false if the Multifile is not valid.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
read_index() {
|
|
nassertr(_read != (IStreamWrapper *)NULL, false);
|
|
|
|
// We acquire the IStreamWrapper lock for the duration of this
|
|
// method.
|
|
_read->acquire();
|
|
istream *read = _read->get_istream();
|
|
|
|
char this_header[_header_size];
|
|
read->seekg(0);
|
|
read->read(this_header, _header_size);
|
|
if (read->fail() || read->gcount() != (unsigned)_header_size) {
|
|
express_cat.info()
|
|
<< "Unable to read Multifile header " << _multifile_name << ".\n";
|
|
_read->release();
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Here's a special case: if the multifile begins with a hash
|
|
// character, then we skip at least 6 characters (the length of the
|
|
// original header string we already read), and continue reading and
|
|
// discarding lines of ASCII text, until we come across a nonempty
|
|
// line that does not begin with a hash character. This allows a
|
|
// P3D application (which is a multifile) to be run directly on the
|
|
// command line on Unix-based systems.
|
|
_header_prefix = string();
|
|
|
|
if (this_header[0] == '#') {
|
|
_header_prefix = string(this_header, _header_size);
|
|
int ch = '#';
|
|
|
|
while (ch != EOF && ch == '#') {
|
|
// Skip to the end of the line.
|
|
while (ch != EOF && ch != '\n') {
|
|
_header_prefix += ch;
|
|
ch = read->get();
|
|
}
|
|
// Skip to the first non-whitespace character of the line.
|
|
while (ch != EOF && (isspace(ch) || ch == '\r')) {
|
|
_header_prefix += ch;
|
|
ch = read->get();
|
|
}
|
|
}
|
|
|
|
// Now fill up the header.
|
|
this_header[0] = ch;
|
|
read->read(this_header + 1, _header_size - 1);
|
|
}
|
|
|
|
if (memcmp(this_header, _header, _header_size) != 0) {
|
|
express_cat.info()
|
|
<< _multifile_name << " is not a Multifile.\n";
|
|
_read->release();
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Now get the version numbers out.
|
|
StreamReader reader(read, false);
|
|
_file_major_ver = reader.get_int16();
|
|
_file_minor_ver = reader.get_int16();
|
|
_scale_factor = reader.get_uint32();
|
|
_new_scale_factor = _scale_factor;
|
|
|
|
if (read->eof() || read->fail()) {
|
|
express_cat.info()
|
|
<< _multifile_name << " header is truncated.\n";
|
|
_read->release();
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (_file_major_ver != _current_major_ver ||
|
|
(_file_major_ver == _current_major_ver &&
|
|
_file_minor_ver > _current_minor_ver)) {
|
|
express_cat.info()
|
|
<< _multifile_name << " has version " << _file_major_ver << "."
|
|
<< _file_minor_ver << ", expecting version "
|
|
<< _current_major_ver << "." << _current_minor_ver << ".\n";
|
|
_read->release();
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_record_timestamp = true;
|
|
if (_file_minor_ver >= 1) {
|
|
time_t read_timestamp = reader.get_uint32();
|
|
if (read_timestamp == 0) {
|
|
// If we read a 0 timestamp from the file, that implies that we
|
|
// don't want to record a timestamp in this particular file.
|
|
_record_timestamp = false;
|
|
} else {
|
|
_timestamp = read_timestamp;
|
|
}
|
|
_timestamp_dirty = false;
|
|
}
|
|
|
|
// Now read the index out.
|
|
_next_index = read->tellg();
|
|
_next_index = normalize_streampos(_next_index);
|
|
read->seekg(_next_index);
|
|
_last_index = 0;
|
|
streampos index_forward;
|
|
|
|
Subfile *subfile = new Subfile;
|
|
index_forward = subfile->read_index(*read, _next_index, this);
|
|
while (index_forward != (streampos)0) {
|
|
_last_index = _next_index;
|
|
if (subfile->is_deleted()) {
|
|
// Ignore deleted Subfiles in the index.
|
|
_needs_repack = true;
|
|
delete subfile;
|
|
} else {
|
|
_subfiles.push_back(subfile);
|
|
}
|
|
if (index_forward != normalize_streampos(read->tellg())) {
|
|
// If the index entries don't follow exactly sequentially, the
|
|
// file ought to be repacked.
|
|
_needs_repack = true;
|
|
}
|
|
read->seekg(index_forward);
|
|
_next_index = index_forward;
|
|
subfile = new Subfile;
|
|
index_forward = subfile->read_index(*read, _next_index, this);
|
|
}
|
|
if (subfile->is_index_invalid()) {
|
|
express_cat.info()
|
|
<< "Error reading index for " << _multifile_name << ".\n";
|
|
_read->release();
|
|
close();
|
|
delete subfile;
|
|
return false;
|
|
}
|
|
|
|
// Check if the list is already sorted. If it is not, we need a
|
|
// repack.
|
|
for (size_t si = 1; si < _subfiles.size() && !_needs_repack; ++si) {
|
|
if (*_subfiles[si] < *_subfiles[si - 1]) {
|
|
_needs_repack = true;
|
|
}
|
|
}
|
|
|
|
if (_needs_repack) {
|
|
// At least sort them now.
|
|
size_t before_size = _subfiles.size();
|
|
_subfiles.sort();
|
|
size_t after_size = _subfiles.size();
|
|
|
|
// If these don't match, the same filename appeared twice in the
|
|
// index, which shouldn't be possible.
|
|
nassertr(before_size == after_size, true);
|
|
}
|
|
|
|
delete subfile;
|
|
_read->release();
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::write_header
|
|
// Access: Private
|
|
// Description: Writes just the header part of the Multifile, not the
|
|
// index.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Multifile::
|
|
write_header() {
|
|
_file_major_ver = _current_major_ver;
|
|
_file_minor_ver = _current_minor_ver;
|
|
|
|
nassertr(_write != (ostream *)NULL, false);
|
|
nassertr(_write->tellp() == (streampos)0, false);
|
|
_write->write(_header_prefix.data(), _header_prefix.size());
|
|
_write->write(_header, _header_size);
|
|
StreamWriter writer(_write, false);
|
|
writer.add_int16(_current_major_ver);
|
|
writer.add_int16(_current_minor_ver);
|
|
writer.add_uint32(_scale_factor);
|
|
|
|
if (_record_timestamp) {
|
|
writer.add_uint32(_timestamp);
|
|
} else {
|
|
writer.add_uint32(0);
|
|
_timestamp_dirty = false;
|
|
}
|
|
|
|
_next_index = _write->tellp();
|
|
_next_index = pad_to_streampos(_next_index);
|
|
_last_index = 0;
|
|
|
|
if (_write->fail()) {
|
|
express_cat.info()
|
|
<< "Unable to write header for " << _multifile_name << ".\n";
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Subfile::read_index
|
|
// Access: Public
|
|
// Description: Reads the index record for the Subfile from the
|
|
// indicated istream. Assumes the istream has already
|
|
// been positioned to the indicated stream position,
|
|
// fpos, the start of the index record. Returns the
|
|
// position within the file of the next index record.
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::Subfile::
|
|
read_index(istream &read, streampos fpos, Multifile *multifile) {
|
|
nassertr(read.tellg() == fpos, fpos);
|
|
|
|
// First, get the next stream position. We do this separately,
|
|
// because if it is zero, we don't get anything else.
|
|
StreamReader reader(read);
|
|
|
|
streampos next_index = multifile->word_to_streampos(reader.get_uint32());
|
|
if (read.eof() || read.fail()) {
|
|
_flags |= SF_index_invalid;
|
|
return 0;
|
|
}
|
|
|
|
if (next_index == (streampos)0) {
|
|
return 0;
|
|
}
|
|
|
|
// Now get the rest of the index.
|
|
|
|
_index_start = fpos;
|
|
|
|
_data_start = multifile->word_to_streampos(reader.get_uint32());
|
|
_data_length = reader.get_uint32();
|
|
_flags = reader.get_uint16();
|
|
if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
|
|
_uncompressed_length = reader.get_uint32();
|
|
} else {
|
|
_uncompressed_length = _data_length;
|
|
}
|
|
if (multifile->_file_minor_ver < 1) {
|
|
_timestamp = multifile->get_timestamp();
|
|
} else {
|
|
_timestamp = reader.get_uint32();
|
|
if (_timestamp == 0) {
|
|
_timestamp = multifile->get_timestamp();
|
|
}
|
|
}
|
|
|
|
size_t name_length = reader.get_uint16();
|
|
if (read.eof() || read.fail()) {
|
|
_flags |= SF_index_invalid;
|
|
return 0;
|
|
}
|
|
|
|
// And finally, get the rest of the name.
|
|
char *name_buffer = (char *)PANDA_MALLOC_ARRAY(name_length);
|
|
nassertr(name_buffer != (char *)NULL, next_index);
|
|
for (size_t ni = 0; ni < name_length; ni++) {
|
|
name_buffer[ni] = read.get() ^ 0xff;
|
|
}
|
|
_name = string(name_buffer, name_length);
|
|
PANDA_FREE_ARRAY(name_buffer);
|
|
|
|
if (read.eof() || read.fail()) {
|
|
_flags |= SF_index_invalid;
|
|
return 0;
|
|
}
|
|
|
|
return next_index;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Subfile::write_index
|
|
// Access: Public
|
|
// Description: Writes the index record for the Subfile to the
|
|
// indicated ostream. Assumes the istream has already
|
|
// been positioned to the indicated stream position,
|
|
// fpos, the start of the index record, and that this is
|
|
// the effective end of the file. Returns the position
|
|
// within the file of the next index record.
|
|
//
|
|
// The _index_start member is updated by this operation.
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::Subfile::
|
|
write_index(ostream &write, streampos fpos, Multifile *multifile) {
|
|
nassertr(write.tellp() == fpos, fpos);
|
|
|
|
_index_start = fpos;
|
|
|
|
// This will be the contents of this particular index record. We
|
|
// build it up first since it will be variable length.
|
|
Datagram dg;
|
|
dg.add_uint32(multifile->streampos_to_word(_data_start));
|
|
dg.add_uint32(_data_length);
|
|
dg.add_uint16(_flags);
|
|
if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
|
|
dg.add_uint32(_uncompressed_length);
|
|
}
|
|
dg.add_uint32(_timestamp);
|
|
dg.add_uint16(_name.length());
|
|
|
|
// For no real good reason, we'll invert all the bits in the name.
|
|
// The only reason we do this is to make it inconvenient for a
|
|
// casual browser of the Multifile to discover the names of the
|
|
// files stored within it. Naturally, this isn't real obfuscation
|
|
// or security.
|
|
string::iterator ni;
|
|
for (ni = _name.begin(); ni != _name.end(); ++ni) {
|
|
dg.add_int8((*ni) ^ 0xff);
|
|
}
|
|
|
|
size_t this_index_size = 4 + dg.get_length();
|
|
|
|
// Plus, we will write out the next index address first.
|
|
streampos next_index = fpos + (streampos)this_index_size;
|
|
|
|
Datagram idg;
|
|
idg.add_uint32(multifile->streampos_to_word(next_index));
|
|
|
|
write.write((const char *)idg.get_data(), idg.get_length());
|
|
write.write((const char *)dg.get_data(), dg.get_length());
|
|
|
|
return next_index;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Subfile::write_data
|
|
// Access: Public
|
|
// Description: Writes the data record for the Subfile to the
|
|
// indicated ostream: the actual contents of the
|
|
// Subfile. Assumes the istream has already been
|
|
// positioned to the indicated stream position, fpos,
|
|
// the start of the data record, and that this is the
|
|
// effective end of the file. Returns the position
|
|
// within the file of the next data record.
|
|
//
|
|
// The _data_start, _data_length, and
|
|
// _uncompressed_length members are updated by this
|
|
// operation.
|
|
//
|
|
// If the "read" pointer is non-NULL, it is the readable
|
|
// istream of a Multifile in which the Subfile might
|
|
// already be packed. This is used for reading the
|
|
// contents of the Subfile during a repack() operation.
|
|
////////////////////////////////////////////////////////////////////
|
|
streampos Multifile::Subfile::
|
|
write_data(ostream &write, istream *read, streampos fpos,
|
|
Multifile *multifile) {
|
|
nassertr(write.tellp() == fpos, fpos);
|
|
|
|
istream *source = _source;
|
|
pifstream source_file;
|
|
if (source == (istream *)NULL && !_source_filename.empty()) {
|
|
// If we have a filename, open it up and read that.
|
|
if (!_source_filename.open_read(source_file)) {
|
|
// Unable to open the source file.
|
|
express_cat.info()
|
|
<< "Unable to read " << _source_filename << ".\n";
|
|
_flags |= SF_data_invalid;
|
|
_data_length = 0;
|
|
_uncompressed_length = 0;
|
|
} else {
|
|
source = &source_file;
|
|
}
|
|
}
|
|
|
|
if (source == (istream *)NULL) {
|
|
// We don't have any source data. Perhaps we're reading from an
|
|
// already-packed Subfile (e.g. during repack()).
|
|
if (read == (istream *)NULL) {
|
|
// No, we're just screwed.
|
|
express_cat.info()
|
|
<< "No source for subfile " << _name << ".\n";
|
|
_flags |= SF_data_invalid;
|
|
} else {
|
|
// Read the data from the original Multifile.
|
|
read->seekg(_data_start);
|
|
for (size_t p = 0; p < _data_length; p++) {
|
|
int byte = read->get();
|
|
if (read->eof() || read->fail()) {
|
|
// Unexpected EOF or other failure on the source file.
|
|
express_cat.info()
|
|
<< "Unexpected EOF for subfile " << _name << ".\n";
|
|
_flags |= SF_data_invalid;
|
|
break;
|
|
}
|
|
write.put(byte);
|
|
}
|
|
}
|
|
} else {
|
|
// We do have source data. Copy it in, and also measure its
|
|
// length.
|
|
ostream *putter = &write;
|
|
bool delete_putter = false;
|
|
|
|
#ifndef HAVE_OPENSSL
|
|
// Without OpenSSL, we can't support encryption. The flag had
|
|
// better not be set.
|
|
nassertr((_flags & SF_encrypted) == 0, fpos);
|
|
#else // HAVE_ZLIB
|
|
if ((_flags & SF_encrypted) != 0) {
|
|
// Write it encrypted.
|
|
OEncryptStream *encrypt = new OEncryptStream;
|
|
encrypt->set_iteration_count(multifile->_encryption_iteration_count);
|
|
encrypt->open(putter, delete_putter, multifile->_encryption_password);
|
|
|
|
putter = encrypt;
|
|
delete_putter = true;
|
|
|
|
// Also write the encrypt_header to the beginning of the
|
|
// encrypted stream, so we can validate the password on
|
|
// decryption.
|
|
putter->write(_encrypt_header, _encrypt_header_size);
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
|
|
#ifndef HAVE_ZLIB
|
|
// Without ZLIB, we can't support compression. The flag had
|
|
// better not be set.
|
|
nassertr((_flags & SF_compressed) == 0, fpos);
|
|
#else // HAVE_ZLIB
|
|
if ((_flags & SF_compressed) != 0) {
|
|
// Write it compressed.
|
|
putter = new OCompressStream(putter, delete_putter, _compression_level);
|
|
delete_putter = true;
|
|
}
|
|
#endif // HAVE_ZLIB
|
|
|
|
streampos write_start = fpos;
|
|
_uncompressed_length = 0;
|
|
|
|
static const size_t buffer_size = 1024;
|
|
char buffer[buffer_size];
|
|
|
|
source->read(buffer, buffer_size);
|
|
size_t count = source->gcount();
|
|
while (count != 0) {
|
|
_uncompressed_length += count;
|
|
putter->write(buffer, count);
|
|
source->read(buffer, buffer_size);
|
|
count = source->gcount();
|
|
}
|
|
|
|
if (delete_putter) {
|
|
delete putter;
|
|
}
|
|
|
|
streampos write_end = write.tellp();
|
|
_data_length = (size_t)(write_end - write_start);
|
|
}
|
|
|
|
// We can't set _data_start until down here, after we have read the
|
|
// Subfile. (In case we are running during repack()).
|
|
_data_start = fpos;
|
|
|
|
// Get the modification timestamp for this subfile. This is read
|
|
// from the source file, if we have a filename; otherwise, it's the
|
|
// current time.
|
|
if (!_source_filename.empty()) {
|
|
_timestamp = _source_filename.get_timestamp();
|
|
}
|
|
if (_timestamp == 0) {
|
|
_timestamp = time(NULL);
|
|
}
|
|
|
|
_source = (istream *)NULL;
|
|
_source_filename = Filename();
|
|
source_file.close();
|
|
|
|
return fpos + (streampos)_data_length;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Subfile::rewrite_index_data_start
|
|
// Access: Public
|
|
// Description: Seeks within the indicate pfstream back to the index
|
|
// record and rewrites just the _data_start and
|
|
// _data_length part of the index record.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::Subfile::
|
|
rewrite_index_data_start(ostream &write, Multifile *multifile) {
|
|
nassertv(_index_start != (streampos)0);
|
|
|
|
static const size_t data_start_offset = 4;
|
|
size_t data_start_pos = _index_start + (streampos)data_start_offset;
|
|
write.seekp(data_start_pos);
|
|
nassertv(!write.fail());
|
|
|
|
StreamWriter writer(write);
|
|
writer.add_uint32(multifile->streampos_to_word(_data_start));
|
|
writer.add_uint32(_data_length);
|
|
writer.add_uint16(_flags);
|
|
if ((_flags & (SF_compressed | SF_encrypted)) != 0) {
|
|
writer.add_uint32(_uncompressed_length);
|
|
}
|
|
if (multifile->_record_timestamp) {
|
|
writer.add_uint32(_timestamp);
|
|
} else {
|
|
writer.add_uint32(0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Multifile::Subfile::rewrite_index_flags
|
|
// Access: Public
|
|
// Description: Seeks within the indicate pfstream back to the index
|
|
// record and rewrites just the _flags part of the
|
|
// index record.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Multifile::Subfile::
|
|
rewrite_index_flags(ostream &write) {
|
|
// If the subfile has never even been recorded to disk, we don't
|
|
// need to do anything at all in this function.
|
|
if (_index_start != (streampos)0) {
|
|
static const size_t flags_offset = 4 + 4 + 4;
|
|
size_t flags_pos = _index_start + (streampos)flags_offset;
|
|
write.seekp(flags_pos);
|
|
nassertv(!write.fail());
|
|
|
|
StreamWriter writer(write);
|
|
writer.add_uint16(_flags);
|
|
}
|
|
}
|