#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2007 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using SevenZip.Compression.LZMA;
namespace Nuclex.Game.ContentCompressor {
/// Constructs compressed LZMA packages
public static class LzmaPackageBuilder {
#region struct PackageFile
/// Stores informations about a file that is to be packaged
public struct PackageFile {
/// Initializes a new package file
/// Absolute path of the file to be packaged
/// Name to assign to the file inside the package
public PackageFile(string path, string name) {
this.Path = path;
this.Name = name;
}
/// The absolute path of the file to package
public string Path;
/// Name the file will have inside the package
public string Name;
}
#endregion // struct PackageFile
#region struct PackageFileHeader
/// Stores informations about a file that is to be packaged
private struct PackageFileHeader {
/// Name of the asset
public string Name;
/// Where in the LZMA package the file's data is stored
public long DataOffset;
/// Length the data has in uncompressed form
public int UncompressedLength;
/// Length of the compressed data
public int CompressedLength;
/// Writes the header of a package file into a binary writer
/// Binary writer the package file header is written to
public void Write(BinaryWriter writer) {
writer.Write(this.Name);
writer.Write(this.DataOffset);
writer.Write(this.CompressedLength);
writer.Write(this.UncompressedLength);
}
}
#endregion // struct PackageFileHeader
/// Adds one or more files to the list of files to be packed
/// Path to the package that assets are read from
/// Enumerable list with the paths of the files to add
public static void Build(string packagePath, params PackageFile[] packageFiles) {
Build(packagePath, (IEnumerable)packageFiles);
}
/// Adds one or more files to the list of files to be packed
/// Path to the package that assets are read from
/// Enumerable list with the paths of the files to add
public static void Build(string packagePath, IEnumerable packageFiles) {
List packageFileHeaders = setupPackageFileHeaders(packageFiles);
using(
FileStream package = new FileStream(
packagePath, FileMode.Create, FileAccess.Write, FileShare.None
)
) {
BinaryWriter packageWriter = new BinaryWriter(package);
packageWriter.Write(packageFileHeaders.Count);
// Write the preliminary headers to the package (we don't know some informations,
// like the compressed file sizes, yet). The headers will be written a second
// time after the data offsets and compressed file sizes have been filled in.
foreach(PackageFileHeader packageFileHeader in packageFileHeaders)
packageFileHeader.Write(packageWriter);
// Compress all files and write them into the package, filling the missing fields
// in the package file headers in the process.
int index = 0;
foreach(PackageFile packageFile in packageFiles) {
using(
FileStream asset = new FileStream(
packageFile.Path, FileMode.Open, FileAccess.Read, FileShare.Read
)
) {
PackageFileHeader header = packageFileHeaders[index];
header.DataOffset = package.Position;
header.CompressedLength = compress(package, asset);
packageFileHeaders[index] = header;
}
++index;
}
// Second run writing the package file headers, this time we know all the
// informations. This relies on the headers not changing in size, so any
// strings contained in the headers must not be modified.
package.Position = 4;
foreach(PackageFileHeader packageFileHeader in packageFileHeaders)
packageFileHeader.Write(packageWriter);
}
}
///
/// Compresses data in a stream and writes the compressed data into another stream
///
/// Destination stream the data is written to
/// Stream from which the data to be compressed is taken
/// The number of bytes that were written into the destination stream
private static int compress(Stream destination, Stream source) {
long startingPosition = destination.Position;
Encoder lzmaEncoder = new Encoder();
lzmaEncoder.WriteCoderProperties(destination);
lzmaEncoder.Code(
source, destination,
0, 0, null
);
return (int)(destination.Position - startingPosition);
}
///
/// Sets up a list of package file headers for the provided file names.
///
/// File name to set up the package file headers for
/// A list of package file headers for the provided file names
private static List setupPackageFileHeaders(
IEnumerable packageFiles
) {
List packageFileHeaders = new List();
foreach(PackageFile packageFile in packageFiles) {
PackageFileHeader packageFileHeader;
packageFileHeader.Name = packageFile.Name;
packageFileHeader.DataOffset = 0; // filled in later
packageFileHeader.UncompressedLength = (int)(new FileInfo(packageFile.Path).Length);
packageFileHeader.CompressedLength = 0; // filled in later
packageFileHeaders.Add(packageFileHeader);
}
return packageFileHeaders;
}
}
} // namespace Nuclex.Game.ContentCompressor