diff --git a/OpenRA.Game/FileFormats/Blast.cs b/OpenRA.Game/FileFormats/Blast.cs index 74d124b5a6..2f4ec39e52 100644 --- a/OpenRA.Game/FileFormats/Blast.cs +++ b/OpenRA.Game/FileFormats/Blast.cs @@ -57,10 +57,13 @@ namespace OpenRA.FileFormats static Huffman lencode = new Huffman(lenlen, lenlen.Length, 16); static Huffman distcode = new Huffman(distlen, distlen.Length, 64); - // Decode PKWare Compression Library stream. - public static byte[] Decompress(byte[] src) + /// PKWare Compression Library stream. + /// Compressed input stream. + /// Stream to write the decompressed output. + /// Progress callback, invoked with (read bytes, written bytes). + public static void Decompress(Stream input, Stream output, Action onProgress = null) { - var br = new BitReader(src); + var br = new BitReader(input); // Are literals coded? var coded = br.ReadBits(8); @@ -78,7 +81,9 @@ namespace OpenRA.FileFormats ushort next = 0; // index of next write location in out[] var first = true; // true to check distances (for first 4K) var outBuffer = new byte[MAXWIN]; // output buffer and sliding window - var ms = new MemoryStream(); + + var inputStart = input.Position; + var outputStart = output.Position; // decode literals and length/distance pairs do @@ -94,7 +99,10 @@ namespace OpenRA.FileFormats if (len == 519) { for (var i = 0; i < next; i++) - ms.WriteByte(outBuffer[i]); + output.WriteByte(outBuffer[i]); + + if (onProgress != null) + onProgress(input.Position - inputStart, output.Position - outputStart); break; } @@ -137,9 +145,12 @@ namespace OpenRA.FileFormats if (next == MAXWIN) { for (var i = 0; i < next; i++) - ms.WriteByte(outBuffer[i]); + output.WriteByte(outBuffer[i]); next = 0; first = false; + + if (onProgress != null) + onProgress(input.Position - inputStart, output.Position - outputStart); } } while (len != 0); } @@ -151,14 +162,15 @@ namespace OpenRA.FileFormats if (next == MAXWIN) { for (var i = 0; i < next; i++) - ms.WriteByte(outBuffer[i]); + output.WriteByte(outBuffer[i]); next = 0; first = false; + + if (onProgress != null) + onProgress(input.Position - inputStart, output.Position - outputStart); } } } while (true); - - return ms.ToArray(); } // Decode a code using Huffman table h. @@ -185,14 +197,13 @@ namespace OpenRA.FileFormats class BitReader { - readonly byte[] src; - int offset = 0; - int bitBuffer = 0; + readonly Stream stream; + byte bitBuffer = 0; int bitCount = 0; - public BitReader(byte[] src) + public BitReader(Stream stream) { - this.src = src; + this.stream = stream; } public int ReadBits(int count) @@ -203,7 +214,7 @@ namespace OpenRA.FileFormats { if (bitCount == 0) { - bitBuffer = src[offset++]; + bitBuffer = stream.ReadUInt8(); bitCount = 8; } diff --git a/OpenRA.Game/FileSystem/InstallShieldPackage.cs b/OpenRA.Game/FileSystem/InstallShieldPackage.cs index 5089edd74b..906299a54e 100644 --- a/OpenRA.Game/FileSystem/InstallShieldPackage.cs +++ b/OpenRA.Game/FileSystem/InstallShieldPackage.cs @@ -114,9 +114,12 @@ namespace OpenRA.FileSystem return null; s.Seek(dataStart + e.Offset, SeekOrigin.Begin); - var data = s.ReadBytes((int)e.Length); - return new MemoryStream(Blast.Decompress(data)); + var ret = new MemoryStream(); + Blast.Decompress(s, ret); + ret.Seek(0, SeekOrigin.Begin); + + return ret; } public bool Contains(string filename) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs index b451812e6e..dbd28ceacb 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs @@ -183,13 +183,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic case "extract-raw": { - ExtractFromPackage(ExtractionType.Raw, path, i.Value, extracted, ref message); + ExtractFromPackage(ExtractionType.Raw, path, i.Value, extracted, m => message = m); break; } case "extract-blast": { - ExtractFromPackage(ExtractionType.Blast, path, i.Value, extracted, ref message); + ExtractFromPackage(ExtractionType.Blast, path, i.Value, extracted, m => message = m); break; } @@ -228,7 +228,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic enum ExtractionType { Raw, Blast } - static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List extractedFiles, ref string progressMessage) + static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) { var sourcePath = Path.Combine(path, actionYaml.Value); using (var source = File.OpenRead(sourcePath)) @@ -265,15 +265,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); using (var target = File.OpenWrite(targetPath)) { - // This is a bit dumb memory-wise, but we load the whole thing when running the game anyway Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath)); - progressMessage = "Extracting " + Path.GetFileName(Path.GetFileName(targetPath)); - - var data = source.ReadBytes(length); + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); if (type == ExtractionType.Blast) - data = Blast.Decompress(data); - - target.Write(data); + { + Action onProgress = (read, _) => + updateMessage("Extracting " + displayFilename + " ({0}%)".F(100 * read / length)); + Blast.Decompress(source, target, onProgress); + } + else + { + updateMessage("Extracting " + displayFilename); + // This is a bit dumb memory-wise, but we load the whole thing when running the game anyway + target.Write(source.ReadBytes(length)); + } } } }