#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