diff --git a/OpenRA.Game/FileSystem/Folder.cs b/OpenRA.Game/FileSystem/Folder.cs index 0d21358108..be866eca3a 100644 --- a/OpenRA.Game/FileSystem/Folder.cs +++ b/OpenRA.Game/FileSystem/Folder.cs @@ -53,9 +53,14 @@ namespace OpenRA.FileSystem public IReadOnlyPackage OpenPackage(string filename, FileSystem context) { - var subFolder = Platform.ResolvePath(Path.Combine(Name, filename)); - if (Directory.Exists(subFolder)) - return new Folder(subFolder); + var resolvedPath = Platform.ResolvePath(Path.Combine(Name, filename)); + if (Directory.Exists(resolvedPath)) + return new Folder(resolvedPath); + + // Zip files loaded from Folders (and *only* from Folders) can be read-write + IReadWritePackage readWritePackage; + if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage)) + return readWritePackage; // Other package types can be loaded normally IReadOnlyPackage package; diff --git a/OpenRA.Game/FileSystem/ZipFile.cs b/OpenRA.Game/FileSystem/ZipFile.cs index cecdb08033..3dc44b8ffd 100644 --- a/OpenRA.Game/FileSystem/ZipFile.cs +++ b/OpenRA.Game/FileSystem/ZipFile.cs @@ -23,38 +23,23 @@ namespace OpenRA.FileSystem { static readonly string[] Extensions = { ".zip", ".oramap" }; - sealed class ZipFile : IReadWritePackage + class ReadOnlyZipFile : IReadOnlyPackage { - public IReadWritePackage Parent { get; private set; } - public string Name { get; private set; } - readonly Stream pkgStream; - readonly SZipFile pkg; + public string Name { get; protected set; } + protected SZipFile pkg; - static ZipFile() + static ReadOnlyZipFile() { ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage; } - public ZipFile(Stream stream, string name, IReadOnlyPackage parent = null) + // Dummy constructor for use with ReadWriteZipFile + protected ReadOnlyZipFile() { } + + public ReadOnlyZipFile(Stream s, string filename) { - // SharpZipLib breaks when asked to update archives loaded from outside streams or files - // We can work around this by creating a clean in-memory-only file, cutting all outside references - pkgStream = new MemoryStream(); - stream.CopyTo(pkgStream); - pkgStream.Position = 0; - - Name = name; - Parent = parent as IReadWritePackage; - pkg = new SZipFile(pkgStream); - } - - public ZipFile(string filename, IReadWritePackage parent) - { - pkgStream = new MemoryStream(); - Name = filename; - Parent = parent; - pkg = SZipFile.Create(pkgStream); + pkg = new SZipFile(s); } public Stream GetStream(string filename) @@ -86,14 +71,61 @@ namespace OpenRA.FileSystem return pkg.GetEntry(filename) != null; } + public void Dispose() + { + if (pkg != null) + pkg.Close(); + } + + public IReadOnlyPackage OpenPackage(string filename, FileSystem context) + { + // Directories are stored with a trailing "/" in the index + var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/"); + if (entry == null) + return null; + + if (entry.IsDirectory) + return new ZipFolder(this, filename); + + // Other package types can be loaded normally + IReadOnlyPackage package; + var s = GetStream(filename); + if (s == null) + return null; + + if (context.TryParsePackage(s, filename, out package)) + return package; + + s.Dispose(); + return null; + } + } + + sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage + { + readonly MemoryStream pkgStream; + + public ReadWriteZipFile(Stream stream, string filename) + { + // SharpZipLib breaks when asked to update archives loaded from outside streams or files + // We can work around this by creating a clean in-memory-only file, cutting all outside references + pkgStream = new MemoryStream(); + if (stream != null) + { + stream.CopyTo(pkgStream); + stream.Dispose(); + } + + pkgStream.Position = 0; + pkg = new SZipFile(pkgStream); + Name = filename; + } + void Commit() { - if (Parent == null) - throw new InvalidDataException("Cannot update ZipFile without writable parent"); - var pos = pkgStream.Position; pkgStream.Position = 0; - Parent.Update(Name, pkgStream.ReadBytes((int)pkgStream.Length)); + File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length)); pkgStream.Position = pos; } @@ -112,47 +144,12 @@ namespace OpenRA.FileSystem pkg.CommitUpdate(); Commit(); } - - public void Dispose() - { - if (pkg != null) - pkg.Close(); - - if (pkgStream != null) - pkgStream.Dispose(); - } - - public IReadOnlyPackage OpenPackage(string filename, FileSystem context) - { - // Directories are stored with a trailing "/" in the index - var entry = pkg.GetEntry(filename) ?? pkg.GetEntry(filename + "/"); - if (entry == null) - return null; - - if (entry.IsDirectory) - return new ZipFolder(this, filename); - - if (Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase))) - return new ZipFile(GetStream(filename), filename, this); - - // Other package types can be loaded normally - IReadOnlyPackage package; - var s = GetStream(filename); - if (s == null) - return null; - - if (context.TryParsePackage(s, filename, out package)) - return package; - - s.Dispose(); - return null; - } } sealed class ZipFolder : IReadOnlyPackage { public string Name { get { return path; } } - public ZipFile Parent { get; private set; } + public ReadOnlyZipFile Parent { get; private set; } readonly string path; static ZipFolder() @@ -160,7 +157,7 @@ namespace OpenRA.FileSystem ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage; } - public ZipFolder(ZipFile parent, string path) + public ZipFolder(ReadOnlyZipFile parent, string path) { if (path.EndsWith("/", StringComparison.Ordinal)) path = path.Substring(0, path.Length - 1); @@ -227,19 +224,26 @@ namespace OpenRA.FileSystem return false; } - string name; - IReadOnlyPackage p; - if (context.TryGetPackageContaining(filename, out p, out name)) - package = new ZipFile(p.GetStream(name), name, p); - else - package = new ZipFile(s, filename, null); - + package = new ReadOnlyZipFile(s, filename); return true; } - public static IReadWritePackage Create(string filename, IReadWritePackage parent) + public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package) { - return new ZipFile(filename, parent); + if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase))) + { + package = null; + return false; + } + + var s = new FileStream(filename, FileMode.Open); + package = new ReadWriteZipFile(s, filename); + return true; + } + + public static IReadWritePackage Create(string filename) + { + return new ReadWriteZipFile(null, filename); } } } diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs index 84ad6b8dde..bbddb75e8e 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs @@ -271,7 +271,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands map.PlayerDefinitions = mapPlayers.ToMiniYaml(); var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - map.Save(ZipFileLoader.Create(dest, new Folder("."))); + map.Save(ZipFileLoader.Create(dest)); Console.WriteLine(dest + " saved."); } diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index f67e7a5a0f..a92a9fad18 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.UtilityCommands var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - Map.Save(ZipFileLoader.Create(dest, new Folder("."))); + Map.Save(ZipFileLoader.Create(dest)); Console.WriteLine(dest + " saved."); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs index 4490db75de..d8afcd7661 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -185,7 +185,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { selectedDirectory.Folder.Delete(combinedPath); if (fileType == MapFileType.OraMap) - package = ZipFileLoader.Create(combinedPath, selectedDirectory.Folder); + package = ZipFileLoader.Create(combinedPath); else package = new Folder(combinedPath); } diff --git a/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs index 5dd6ea204d..c030c46083 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands return; var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - map.Save(ZipFileLoader.Create(dest, new Folder("."))); + map.Save(ZipFileLoader.Create(dest)); Console.WriteLine(dest + " saved."); } }