From ce73bb909efdc3587ef9ec93d4a97451a2a2520f Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 17 Jul 2015 22:27:24 +0100 Subject: [PATCH] Make IFolder interface inherently IDisposable. Fix up implementations to ensure they dispose any stream they acquire, and ensure the constructor will not leave a stream open if it fails. Dispose folders when unmounting them in GlobalFileSystem. --- OpenRA.Game/FileSystem/BagFile.cs | 15 ++--- OpenRA.Game/FileSystem/BigFile.cs | 53 +++++++++------ OpenRA.Game/FileSystem/D2kSoundResources.cs | 39 +++++++---- OpenRA.Game/FileSystem/Folder.cs | 4 +- OpenRA.Game/FileSystem/GlobalFileSystem.cs | 8 ++- .../FileSystem/InstallShieldPackage.cs | 66 +++++++++++-------- OpenRA.Game/FileSystem/MixFile.cs | 66 +++++++++++-------- OpenRA.Game/FileSystem/Pak.cs | 47 ++++++++----- OpenRA.Game/FileSystem/ZipFile.cs | 6 +- 9 files changed, 186 insertions(+), 118 deletions(-) diff --git a/OpenRA.Game/FileSystem/BagFile.cs b/OpenRA.Game/FileSystem/BagFile.cs index 7cc6abea80..812e5359fe 100644 --- a/OpenRA.Game/FileSystem/BagFile.cs +++ b/OpenRA.Game/FileSystem/BagFile.cs @@ -19,7 +19,7 @@ using OpenRA.Primitives; namespace OpenRA.FileSystem { - public sealed class BagFile : IFolder, IDisposable + public sealed class BagFile : IFolder { static readonly uint[] Nothing = { }; @@ -37,20 +37,16 @@ namespace OpenRA.FileSystem // For example: audio.bag requires the audio.idx file var indexFilename = Path.ChangeExtension(filename, ".idx"); - s = GlobalFileSystem.Open(filename); - // Build the index and dispose the stream, it is no longer needed after this List entries; using (var indexStream = GlobalFileSystem.Open(indexFilename)) - { - var reader = new IdxReader(indexStream); - - entries = reader.Entries; - } + entries = new IdxReader(indexStream).Entries; index = entries.ToDictionaryWithConflictLog(x => x.Hash, "{0} (bag format)".F(filename), null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)); + + s = GlobalFileSystem.Open(filename); } public int Priority { get { return 1000 + bagFilePriority; } } @@ -185,8 +181,7 @@ namespace OpenRA.FileSystem public void Dispose() { - if (s != null) - s.Dispose(); + s.Dispose(); } } } diff --git a/OpenRA.Game/FileSystem/BigFile.cs b/OpenRA.Game/FileSystem/BigFile.cs index 1d9329bba1..50e3069a4c 100644 --- a/OpenRA.Game/FileSystem/BigFile.cs +++ b/OpenRA.Game/FileSystem/BigFile.cs @@ -15,37 +15,45 @@ using System.Linq; namespace OpenRA.FileSystem { - public class BigFile : IFolder + public sealed class BigFile : IFolder { public string Name { get; private set; } public int Priority { get; private set; } readonly Dictionary entries = new Dictionary(); + readonly Stream s; public BigFile(string filename, int priority) { Name = filename; Priority = priority; - var s = GlobalFileSystem.Open(filename); - - if (s.ReadASCII(4) != "BIGF") - throw new InvalidDataException("Header is not BIGF"); - - // Total archive size. - s.ReadUInt32(); - - var entryCount = s.ReadUInt32(); - if (BitConverter.IsLittleEndian) - entryCount = int2.Swap(entryCount); - - // First entry offset? This is apparently bogus for EA's .big files - // and we don't have to try seeking there since the entries typically start next in EA's .big files. - s.ReadUInt32(); - - for (var i = 0; i < entryCount; i++) + s = GlobalFileSystem.Open(filename); + try { - var entry = new Entry(s); - entries.Add(entry.Path, entry); + if (s.ReadASCII(4) != "BIGF") + throw new InvalidDataException("Header is not BIGF"); + + // Total archive size. + s.ReadUInt32(); + + var entryCount = s.ReadUInt32(); + if (BitConverter.IsLittleEndian) + entryCount = int2.Swap(entryCount); + + // First entry offset? This is apparently bogus for EA's .big files + // and we don't have to try seeking there since the entries typically start next in EA's .big files. + s.ReadUInt32(); + + for (var i = 0; i < entryCount; i++) + { + var entry = new Entry(s); + entries.Add(entry.Path, entry); + } + } + catch + { + Dispose(); + throw; } } @@ -107,5 +115,10 @@ namespace OpenRA.FileSystem { throw new NotImplementedException(); } + + public void Dispose() + { + s.Dispose(); + } } } \ No newline at end of file diff --git a/OpenRA.Game/FileSystem/D2kSoundResources.cs b/OpenRA.Game/FileSystem/D2kSoundResources.cs index 5770f424f2..ca21c4f5a5 100644 --- a/OpenRA.Game/FileSystem/D2kSoundResources.cs +++ b/OpenRA.Game/FileSystem/D2kSoundResources.cs @@ -14,7 +14,7 @@ using System.IO; namespace OpenRA.FileSystem { - public class D2kSoundResources : IFolder + public sealed class D2kSoundResources : IFolder { readonly Stream s; @@ -30,22 +30,28 @@ namespace OpenRA.FileSystem this.priority = priority; s = GlobalFileSystem.Open(filename); - s.Seek(0, SeekOrigin.Begin); - - filenames = new List(); - - var headerLength = s.ReadUInt32(); - while (s.Position < headerLength + 4) + try { - var name = s.ReadASCIIZ(); - var offset = s.ReadUInt32(); - var length = s.ReadUInt32(); + filenames = new List(); - var hash = PackageEntry.HashFilename(name, PackageHashType.Classic); - if (!index.ContainsKey(hash)) - index.Add(hash, new PackageEntry(hash, offset, length)); + var headerLength = s.ReadUInt32(); + while (s.Position < headerLength + 4) + { + var name = s.ReadASCIIZ(); + var offset = s.ReadUInt32(); + var length = s.ReadUInt32(); - filenames.Add(name); + var hash = PackageEntry.HashFilename(name, PackageHashType.Classic); + if (!index.ContainsKey(hash)) + index.Add(hash, new PackageEntry(hash, offset, length)); + + filenames.Add(name); + } + } + catch + { + Dispose(); + throw; } } @@ -92,5 +98,10 @@ namespace OpenRA.FileSystem { throw new NotImplementedException("Cannot save Dune 2000 Sound Resources."); } + + public void Dispose() + { + s.Dispose(); + } } } diff --git a/OpenRA.Game/FileSystem/Folder.cs b/OpenRA.Game/FileSystem/Folder.cs index c96c708aef..1b62809d0d 100644 --- a/OpenRA.Game/FileSystem/Folder.cs +++ b/OpenRA.Game/FileSystem/Folder.cs @@ -13,7 +13,7 @@ using System.IO; namespace OpenRA.FileSystem { - public class Folder : IFolder + public sealed class Folder : IFolder { readonly string path; readonly int priority; @@ -78,5 +78,7 @@ namespace OpenRA.FileSystem using (var writer = new BinaryWriter(dataStream)) writer.Write(file.Value); } + + public void Dispose() { } } } diff --git a/OpenRA.Game/FileSystem/GlobalFileSystem.cs b/OpenRA.Game/FileSystem/GlobalFileSystem.cs index 42e9cba512..183216ded0 100644 --- a/OpenRA.Game/FileSystem/GlobalFileSystem.cs +++ b/OpenRA.Game/FileSystem/GlobalFileSystem.cs @@ -17,7 +17,7 @@ using OpenRA.Primitives; namespace OpenRA.FileSystem { - public interface IFolder + public interface IFolder : IDisposable { Stream GetContent(string filename); bool Exists(string filename); @@ -127,6 +127,9 @@ namespace OpenRA.FileSystem public static void UnmountAll() { + foreach (var folder in MountedFolders) + folder.Dispose(); + MountedFolders.Clear(); FolderPaths.Clear(); classicHashIndex = new Cache>(_ => new List()); @@ -135,6 +138,9 @@ namespace OpenRA.FileSystem public static bool Unmount(IFolder mount) { + if (MountedFolders.Contains(mount)) + mount.Dispose(); + return MountedFolders.RemoveAll(f => f == mount) > 0; } diff --git a/OpenRA.Game/FileSystem/InstallShieldPackage.cs b/OpenRA.Game/FileSystem/InstallShieldPackage.cs index e6d5fe7d2f..c5c5350077 100644 --- a/OpenRA.Game/FileSystem/InstallShieldPackage.cs +++ b/OpenRA.Game/FileSystem/InstallShieldPackage.cs @@ -15,7 +15,7 @@ using OpenRA.FileFormats; namespace OpenRA.FileSystem { - public class InstallShieldPackage : IFolder + public sealed class InstallShieldPackage : IFolder { readonly Dictionary index = new Dictionary(); readonly List filenames; @@ -28,38 +28,47 @@ namespace OpenRA.FileSystem { this.filename = filename; this.priority = priority; + filenames = new List(); + s = GlobalFileSystem.Open(filename); + try + { + // Parse package header + var reader = new BinaryReader(s); + var signature = reader.ReadUInt32(); + if (signature != 0x8C655D13) + throw new InvalidDataException("Not an Installshield package"); - // Parse package header - var reader = new BinaryReader(s); - var signature = reader.ReadUInt32(); - if (signature != 0x8C655D13) - throw new InvalidDataException("Not an Installshield package"); + reader.ReadBytes(8); + /*var FileCount = */reader.ReadUInt16(); + reader.ReadBytes(4); + /*var ArchiveSize = */reader.ReadUInt32(); + reader.ReadBytes(19); + var tocAddress = reader.ReadInt32(); + reader.ReadBytes(4); + var dirCount = reader.ReadUInt16(); - reader.ReadBytes(8); - /*var FileCount = */reader.ReadUInt16(); - reader.ReadBytes(4); - /*var ArchiveSize = */reader.ReadUInt32(); - reader.ReadBytes(19); - var tocAddress = reader.ReadInt32(); - reader.ReadBytes(4); - var dirCount = reader.ReadUInt16(); + // Parse the directory list + s.Seek(tocAddress, SeekOrigin.Begin); + var tocReader = new BinaryReader(s); - // Parse the directory list - s.Seek(tocAddress, SeekOrigin.Begin); - var tocReader = new BinaryReader(s); + var fileCountInDirs = new List(); - var fileCountInDirs = new List(); + // Parse directories + for (var i = 0; i < dirCount; i++) + fileCountInDirs.Add(ParseDirectory(tocReader)); - // Parse directories - for (var i = 0; i < dirCount; i++) - fileCountInDirs.Add(ParseDirectory(tocReader)); - - // Parse files - foreach (var fileCount in fileCountInDirs) - for (var i = 0; i < fileCount; i++) - ParseFile(reader); + // Parse files + foreach (var fileCount in fileCountInDirs) + for (var i = 0; i < fileCount; i++) + ParseFile(reader); + } + catch + { + Dispose(); + throw; + } } static uint ParseDirectory(BinaryReader reader) @@ -140,5 +149,10 @@ namespace OpenRA.FileSystem { throw new NotImplementedException("Cannot save InstallShieldPackages."); } + + public void Dispose() + { + s.Dispose(); + } } } diff --git a/OpenRA.Game/FileSystem/MixFile.cs b/OpenRA.Game/FileSystem/MixFile.cs index cfc7634e3c..f1da7bef18 100644 --- a/OpenRA.Game/FileSystem/MixFile.cs +++ b/OpenRA.Game/FileSystem/MixFile.cs @@ -17,7 +17,7 @@ using OpenRA.FileFormats; namespace OpenRA.FileSystem { - public sealed class MixFile : IFolder, IDisposable + public sealed class MixFile : IFolder { readonly Dictionary index; readonly long dataStart; @@ -37,9 +37,17 @@ namespace OpenRA.FileSystem File.Delete(filename); s = File.Create(filename); - index = new Dictionary(); - contents.Add("local mix database.dat", new XccLocalDatabase(contents.Keys.Append("local mix database.dat")).Data()); - Write(contents); + try + { + index = new Dictionary(); + contents.Add("local mix database.dat", new XccLocalDatabase(contents.Keys.Append("local mix database.dat")).Data()); + Write(contents); + } + catch + { + Dispose(); + throw; + } } public MixFile(string filename, PackageHashType type, int priority) @@ -47,29 +55,36 @@ namespace OpenRA.FileSystem this.filename = filename; this.priority = priority; this.type = type; + s = GlobalFileSystem.Open(filename); - - // Detect format type - s.Seek(0, SeekOrigin.Begin); - var isCncMix = s.ReadUInt16() != 0; - - // The C&C mix format doesn't contain any flags or encryption - var isEncrypted = false; - if (!isCncMix) - isEncrypted = (s.ReadUInt16() & 0x2) != 0; - - List entries; - if (isEncrypted) + try { - long unused; - entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused); - } - else - entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart); + // Detect format type + var isCncMix = s.ReadUInt16() != 0; - index = entries.ToDictionaryWithConflictLog(x => x.Hash, - "{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart), - null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)); + // The C&C mix format doesn't contain any flags or encryption + var isEncrypted = false; + if (!isCncMix) + isEncrypted = (s.ReadUInt16() & 0x2) != 0; + + List entries; + if (isEncrypted) + { + long unused; + entries = ParseHeader(DecryptHeader(s, 4, out dataStart), 0, out unused); + } + else + entries = ParseHeader(s, isCncMix ? 0 : 4, out dataStart); + + index = entries.ToDictionaryWithConflictLog(x => x.Hash, + "{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart), + null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)); + } + catch (Exception) + { + Dispose(); + throw; + } } static List ParseHeader(Stream s, long offset, out long headerEnd) @@ -269,8 +284,7 @@ namespace OpenRA.FileSystem public void Dispose() { - if (s != null) - s.Dispose(); + s.Dispose(); } } } diff --git a/OpenRA.Game/FileSystem/Pak.cs b/OpenRA.Game/FileSystem/Pak.cs index 9a354da08f..eb15e1dab0 100644 --- a/OpenRA.Game/FileSystem/Pak.cs +++ b/OpenRA.Game/FileSystem/Pak.cs @@ -21,34 +21,42 @@ namespace OpenRA.FileSystem public string Filename; } - public class PakFile : IFolder + public sealed class PakFile : IFolder { - string filename; - int priority; - Dictionary index; - Stream stream; + readonly string filename; + readonly int priority; + readonly Dictionary index; + readonly Stream stream; public PakFile(string filename, int priority) { this.filename = filename; this.priority = priority; index = new Dictionary(); + stream = GlobalFileSystem.Open(filename); - - index = new Dictionary(); - var offset = stream.ReadUInt32(); - while (offset != 0) + try { - var file = stream.ReadASCIIZ(); - var next = stream.ReadUInt32(); - var length = (next == 0 ? (uint)stream.Length : next) - offset; + index = new Dictionary(); + var offset = stream.ReadUInt32(); + while (offset != 0) + { + var file = stream.ReadASCIIZ(); + var next = stream.ReadUInt32(); + var length = (next == 0 ? (uint)stream.Length : next) - offset; - // Ignore duplicate files - if (index.ContainsKey(file)) - continue; + // Ignore duplicate files + if (index.ContainsKey(file)) + continue; - index.Add(file, new Entry { Offset = offset, Length = length, Filename = file }); - offset = next; + index.Add(file, new Entry { Offset = offset, Length = length, Filename = file }); + offset = next; + } + } + catch + { + Dispose(); + throw; } } @@ -92,5 +100,10 @@ namespace OpenRA.FileSystem public int Priority { get { return 1000 + priority; } } public string Name { get { return filename; } } + + public void Dispose() + { + stream.Dispose(); + } } } diff --git a/OpenRA.Game/FileSystem/ZipFile.cs b/OpenRA.Game/FileSystem/ZipFile.cs index cfe96ba44a..a9615ae55f 100644 --- a/OpenRA.Game/FileSystem/ZipFile.cs +++ b/OpenRA.Game/FileSystem/ZipFile.cs @@ -17,11 +17,11 @@ using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile; namespace OpenRA.FileSystem { - public sealed class ZipFile : IFolder, IDisposable + public sealed class ZipFile : IFolder { - string filename; + readonly string filename; + readonly int priority; SZipFile pkg; - int priority; static ZipFile() {