private void Read(Stream input) { UInt32 magic = input.ReadValueU32(); if (magic != 0x51890ACE && magic != 0xCE0A8951) { throw new FormatException("not a volition package"); } this.LittleEndian = (magic == 0x51890ACE) ? true : false; this.Version = input.ReadValueU32(this.LittleEndian); if (this.Version != 3 && this.Version != 4 && this.Version != 6) { throw new FormatException("unsupported volition package version"); } input.Seek(-8, SeekOrigin.Current); IPackageFile packageFile = null; if (this.Version == 3) { packageFile = new Packages.PackageFile3(); } else if (this.Version == 4) { packageFile = new Packages.PackageFile4(); } else if (this.Version == 6) { packageFile = new Packages.PackageFile6(); } else { throw new NotSupportedException(); } packageFile.Deserialize(input, this.LittleEndian); this.Entries.Clear(); this.OriginalEntries.Clear(); if (packageFile.IsSolid == false) { foreach (Packages.PackageEntry packageEntry in packageFile.Entries) { StreamEntry entry = new StreamEntry(); entry.Offset = packageEntry.Offset; entry.Size = packageEntry.UncompressedSize; entry.CompressedSize = packageEntry.CompressedSize; entry.CompressionType = packageEntry.CompressionType; if (this.Entries.ContainsKey(packageEntry.Name) == false) { this.Entries.Add(packageEntry.Name, entry); } else { // Saints Row 1 seems to have bugged duplicate entries that point to // different offsets with the same data. this.Entries.Add(packageEntry.Name + "_DUPLICATE_" + packageEntry.Offset.ToString("X8"), entry); } } } else { this.IsSolid = true; byte[] solid = new byte[packageFile.SolidUncompressedSize]; input.Seek(packageFile.SolidOffset, SeekOrigin.Begin); // Decompress solid data var zlib = new InflaterInputStream(input); if (zlib.Read(solid, 0, solid.Length) != solid.Length) { throw new InvalidOperationException("zlib error"); } foreach (Packages.PackageEntry packageEntry in packageFile.Entries) { var entry = new MemoryEntry(); entry.Data = new byte[packageEntry.UncompressedSize]; Array.Copy(solid, (int)packageEntry.Offset, entry.Data, 0, entry.Data.Length); entry.Size = entry.Data.Length; this.Entries.Add(packageEntry.Name, entry); } } }
public void Commit(Packages.PackageCompressionType compressionType) { Stream clean; string tempFileName = Path.GetTempFileName(); tempFileName = Path.GetTempFileName(); clean = File.Open(tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); IPackageFile packageFile = null; if (this.Version == 3) { packageFile = new Packages.PackageFile3(); } else if (this.Version == 4) { packageFile = new Packages.PackageFile4(); } else if (this.Version == 6) { packageFile = new Packages.PackageFile6(); } else { throw new NotSupportedException(); } foreach (KeyValuePair <string, Entry> kvp in this.Entries) { var packageEntry = new Packages.PackageEntry(); packageEntry.Name = kvp.Key; packageEntry.CompressedSize = -1; packageEntry.UncompressedSize = kvp.Value.Size; packageEntry.Offset = 0; packageFile.Entries.Add(packageEntry); } int headerSize = packageFile.EstimateHeaderSize(); clean.Seek(headerSize, SeekOrigin.Begin); int uncompressedDataSize = 0; int compressedDataSize = 0; if (compressionType == Packages.PackageCompressionType.None) { long offset = 0; foreach (Packages.PackageEntry packageEntry in packageFile.Entries) { packageEntry.Offset = offset; this.ExportEntry(packageEntry.Name, clean); int align = packageEntry.UncompressedSize.Align(2048) - packageEntry.UncompressedSize; if (align > 0) { byte[] block = new byte[align]; clean.Write(block, 0, (int)align); } offset += packageEntry.UncompressedSize + align; uncompressedDataSize += packageEntry.UncompressedSize + align; } } else if (compressionType == Packages.PackageCompressionType.Zlib) { long offset = 0; foreach (Packages.PackageEntry packageEntry in packageFile.Entries) { packageEntry.Offset = offset; byte[] uncompressedData = this.GetEntry(packageEntry.Name); using (var temp = new MemoryStream()) { var zlib = new DeflaterOutputStream(temp); zlib.Write(uncompressedData, 0, uncompressedData.Length); zlib.Finish(); temp.Position = 0; clean.WriteFromStream(temp, temp.Length); packageEntry.CompressedSize = (int)temp.Length; int align = packageEntry.CompressedSize.Align(2048) - packageEntry.CompressedSize; if (align > 0) { byte[] block = new byte[align]; clean.Write(block, 0, (int)align); } offset += packageEntry.CompressedSize + align; uncompressedDataSize += packageEntry.UncompressedSize; compressedDataSize += packageEntry.CompressedSize + align; } } } else if (compressionType == Packages.PackageCompressionType.SolidZlib) { using (var compressed = new MemoryStream()) { var zlib = new DeflaterOutputStream(compressed, new Deflater(Deflater.DEFAULT_COMPRESSION)); long offset = 0; foreach (Packages.PackageEntry packageEntry in packageFile.Entries) { packageEntry.Offset = offset; this.ExportEntry(packageEntry.Name, zlib); int align = packageEntry.UncompressedSize.Align(2048) - packageEntry.UncompressedSize; if (align > 0) { byte[] block = new byte[align]; zlib.Write(block, 0, (int)align); } offset += packageEntry.UncompressedSize + align; uncompressedDataSize += packageEntry.UncompressedSize + align; } zlib.Close(); compressed.Seek(0, SeekOrigin.Begin); clean.WriteFromStream(compressed, compressed.Length); compressedDataSize = (int)compressed.Length; } } else { throw new InvalidOperationException(); } packageFile.PackageSize = (int)clean.Length; packageFile.UncompressedDataSize = uncompressedDataSize; packageFile.CompressedDataSize = compressedDataSize; clean.Seek(0, SeekOrigin.Begin); packageFile.Serialize(clean, this.LittleEndian, compressionType); // copy clean to real stream { this.Stream.Seek(0, SeekOrigin.Begin); clean.Seek(0, SeekOrigin.Begin); this.Stream.WriteFromStream(clean, clean.Length); } this.Stream.SetLength(clean.Length); clean.Close(); if (tempFileName != null) { File.Delete(tempFileName); } this.Entries.Clear(); this.OriginalEntries.Clear(); foreach (Packages.PackageEntry entry in packageFile.Entries) { this.Entries.Add(entry.Name, new StreamEntry() { Offset = entry.Offset, Size = entry.UncompressedSize, CompressedSize = entry.CompressedSize, CompressionType = entry.CompressionType, }); } }