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/FileFormats/MSCabCompression.cs b/OpenRA.Mods.Common/FileFormats/MSCabCompression.cs
index 998b8e9992..ba73834d4a 100644
--- a/OpenRA.Mods.Common/FileFormats/MSCabCompression.cs
+++ b/OpenRA.Mods.Common/FileFormats/MSCabCompression.cs
@@ -87,42 +87,44 @@ namespace OpenRA.Mods.Common.FileFormats
files[i] = new CabFile(stream);
}
- public byte[] ExtractFile(string filename, Action onProgress = null)
+ public void ExtractFile(string filename, Stream output, Action onProgress = null)
{
var file = files.FirstOrDefault(f => f.FileName == filename);
if (file == null)
- return null;
+ throw new FileNotFoundException(filename);
var folder = folders[file.FolderIndex];
stream.Seek(folder.BlockOffset, SeekOrigin.Begin);
- using (var outputStream = new MemoryStream())
+ var inflater = new Inflater(true);
+ var buffer = new byte[4096];
+ var decompressedBytes = 0;
+ for (var i = 0; i < folder.BlockCount; i++)
{
- var inflater = new Inflater(true);
- var buffer = new byte[4096];
- for (var i = 0; i < folder.BlockCount; i++)
+ if (onProgress != null)
+ onProgress((int)(100 * output.Position / file.DecompressedLength));
+
+ // Ignore checksums
+ stream.Position += 4;
+ var blockLength = stream.ReadUInt16();
+ stream.Position += 4;
+
+ using (var batch = new MemoryStream(stream.ReadBytes(blockLength - 2)))
+ using (var inflaterStream = new InflaterInputStream(batch, inflater))
{
- if (onProgress != null)
- onProgress((int)(100 * outputStream.Position / file.DecompressedLength));
-
- // Ignore checksums
- stream.Position += 4;
- var blockLength = stream.ReadUInt16();
- stream.Position += 4;
-
- using (var batch = new MemoryStream(stream.ReadBytes(blockLength - 2)))
- using (var inflaterStream = new InflaterInputStream(batch, inflater))
+ int n;
+ while ((n = inflaterStream.Read(buffer, 0, buffer.Length)) > 0)
{
- int n;
- while ((n = inflaterStream.Read(buffer, 0, buffer.Length)) > 0)
- outputStream.Write(buffer, 0, n);
- }
+ var offset = Math.Max(0, file.DecompressedOffset - decompressedBytes);
+ var count = Math.Min(n - offset, file.DecompressedLength - decompressedBytes);
+ if (offset < n)
+ output.Write(buffer, (int)offset, (int)count);
- inflater.Reset();
+ decompressedBytes += n;
+ }
}
- outputStream.Seek(file.DecompressedOffset, SeekOrigin.Begin);
- return outputStream.ReadBytes((int)file.DecompressedLength);
+ inflater.Reset();
}
}
diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs
index b451812e6e..1a5b152b68 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs
@@ -23,6 +23,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallFromDiscLogic : ChromeLogic
{
+ // Hide percentage indicators for files smaller than 25 MB
+ const int ShowPercentageThreshold = 26214400;
+
enum Mode { Progress, Message, List }
readonly ModContent content;
@@ -172,10 +175,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
Log.Write("install", "Copying {0} -> {1}".F(sourcePath, targetPath));
- message = "Copying " + Path.GetFileName(sourcePath);
extracted.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
- File.Copy(sourcePath, targetPath);
+
+ using (var source = File.OpenRead(sourcePath))
+ using (var target = File.OpenWrite(targetPath))
+ {
+ var displayFilename = Path.GetFileName(targetPath);
+ var length = source.Length;
+
+ Action onProgress = null;
+ if (length < ShowPercentageThreshold)
+ message = "Copying " + displayFilename;
+ else
+ onProgress = b => message = "Copying " + displayFilename + " ({0}%)".F(100 * b / length);
+
+ CopyStream(source, target, length, onProgress);
+ }
}
break;
@@ -183,13 +199,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;
}
@@ -226,9 +242,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}).Start();
}
+ static void CopyStream(Stream input, Stream output, long length, Action onProgress = null)
+ {
+ var buffer = new byte[4096];
+ var copied = 0L;
+ while (copied < length)
+ {
+ var read = (int)Math.Min(buffer.Length, length - copied);
+ var write = input.Read(buffer, 0, read);
+ output.Write(buffer, 0, write);
+ copied += write;
+
+ if (onProgress != null)
+ onProgress(copied);
+ }
+ }
+
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))
@@ -258,22 +290,33 @@ namespace OpenRA.Mods.Common.Widgets.Logic
}
var length = FieldLoader.GetValue("Length", lengthNode.Value.Value);
-
source.Position = FieldLoader.GetValue("Offset", offsetNode.Value.Value);
extractedFiles.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
+ var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
+
+ Action onProgress = null;
+ if (length < ShowPercentageThreshold)
+ updateMessage("Extracting " + displayFilename);
+ else
+ onProgress = b => updateMessage("Extracting " + displayFilename + " ({0}%)".F(100 * b / length));
+
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);
if (type == ExtractionType.Blast)
- data = Blast.Decompress(data);
+ {
+ Action onBlastProgress = (read, _) =>
+ {
+ if (onProgress != null)
+ onProgress(read);
+ };
- target.Write(data);
+ Blast.Decompress(source, target, onBlastProgress);
+ }
+ else
+ CopyStream(source, target, length, onProgress);
}
}
}
@@ -299,12 +342,10 @@ 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));
-
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action onProgress = percent => updateMessage("Extracting {0} ({1}%)".F(displayFilename, percent));
- target.Write(reader.ExtractFile(node.Value.Value, onProgress));
+ reader.ExtractFile(node.Value.Value, target, onProgress);
}
}
}