diff --git a/OpenRA.Game/FileSystem/FileSystem.cs b/OpenRA.Game/FileSystem/FileSystem.cs index 290cb50fc0..1bce3f8d4e 100644 --- a/OpenRA.Game/FileSystem/FileSystem.cs +++ b/OpenRA.Game/FileSystem/FileSystem.cs @@ -71,26 +71,13 @@ namespace OpenRA.FileSystem public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent) { // HACK: limit support to zip and folder until we generalize the PackageLoader support - if (parent is Folder) + if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase) || + filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) { - var path = Path.Combine(parent.Name, filename); - - // HACK: work around SharpZipLib's lack of support for writing to in-memory files - if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, path); - if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, path); - - var subFolder = Platform.ResolvePath(path); - if (Directory.Exists(subFolder)) - return new Folder(subFolder); + using (var s = parent.GetStream(filename)) + return new ZipFile(s, filename, parent); } - if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, filename, parent.GetStream(filename)); - if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, filename, parent.GetStream(filename)); - if (parent is ZipFile) return new ZipFolder(this, (ZipFile)parent, filename, filename); @@ -100,19 +87,16 @@ namespace OpenRA.FileSystem return new ZipFolder(this, folder.Parent, folder.Name + "/" + filename, filename); } + if (parent is Folder) + { + var subFolder = Platform.ResolvePath(Path.Combine(parent.Name, filename)); + if (Directory.Exists(subFolder)) + return new Folder(subFolder); + } + return null; } - public IReadWritePackage OpenWritablePackage(string filename) - { - if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, filename); - if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) - return new ZipFile(this, filename); - - return new Folder(filename); - } - public void Mount(string name, string explicitName = null) { var optional = name.StartsWith("~"); diff --git a/OpenRA.Game/FileSystem/ZipFile.cs b/OpenRA.Game/FileSystem/ZipFile.cs index 217ec076e5..63d35c2788 100644 --- a/OpenRA.Game/FileSystem/ZipFile.cs +++ b/OpenRA.Game/FileSystem/ZipFile.cs @@ -9,8 +9,10 @@ */ #endregion +using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using ICSharpCode.SharpZipLib.Zip; using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile; @@ -19,32 +21,60 @@ namespace OpenRA.FileSystem { public sealed class ZipFile : IReadWritePackage { + public IReadWritePackage Parent { get; private set; } public string Name { get; private set; } - SZipFile pkg; + readonly Stream pkgStream; + readonly SZipFile pkg; static ZipFile() { ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage; } - public ZipFile(FileSystem context, string filename, Stream stream, bool createOrClearContents = false) + public ZipFile(Stream stream, string name, IReadOnlyPackage parent = null) { - Name = 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; - if (createOrClearContents) - pkg = SZipFile.Create(stream); - else - pkg = new SZipFile(stream); + Name = name; + Parent = parent as IReadWritePackage; + pkg = new SZipFile(pkgStream); } - public ZipFile(IReadOnlyFileSystem context, string filename, bool createOrClearContents = false) + public ZipFile(IReadOnlyFileSystem context, string filename) { - Name = filename; + string name; + IReadOnlyPackage p; + if (!context.TryGetPackageContaining(filename, out p, out name)) + throw new FileNotFoundException("Unable to find parent package for " + filename); - if (createOrClearContents) - pkg = SZipFile.Create(filename); - else - pkg = new SZipFile(filename); + Name = name; + Parent = p as IReadWritePackage; + + // 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(); + p.GetStream(name).CopyTo(pkgStream); + pkgStream.Position = 0; + + pkg = new SZipFile(pkgStream); + } + + ZipFile(string filename, IReadWritePackage parent) + { + pkgStream = new MemoryStream(); + + Name = filename; + Parent = parent; + pkg = SZipFile.Create(pkgStream); + } + + public static ZipFile Create(string filename, IReadWritePackage parent) + { + return new ZipFile(filename, parent); } public Stream GetStream(string filename) @@ -76,11 +106,23 @@ namespace OpenRA.FileSystem return pkg.GetEntry(filename) != null; } + 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)); + pkgStream.Position = pos; + } + public void Update(string filename, byte[] contents) { pkg.BeginUpdate(); - pkg.Add(new StaticMemoryDataSource(contents), filename); + pkg.Add(new StaticStreamDataSource(new MemoryStream(contents)), filename); pkg.CommitUpdate(); + Commit(); } public void Delete(string filename) @@ -88,6 +130,7 @@ namespace OpenRA.FileSystem pkg.BeginUpdate(); pkg.Delete(filename); pkg.CommitUpdate(); + Commit(); } public void Dispose() @@ -97,17 +140,17 @@ namespace OpenRA.FileSystem } } - class StaticMemoryDataSource : IStaticDataSource + class StaticStreamDataSource : IStaticDataSource { - byte[] data; - public StaticMemoryDataSource(byte[] data) + readonly Stream s; + public StaticStreamDataSource(Stream s) { - this.data = data; + this.s = s; } public Stream GetSource() { - return new MemoryStream(data); + return s; } } } diff --git a/OpenRA.Game/ModMetadata.cs b/OpenRA.Game/ModMetadata.cs index a85728d426..d74ec7abc0 100644 --- a/OpenRA.Game/ModMetadata.cs +++ b/OpenRA.Game/ModMetadata.cs @@ -47,7 +47,7 @@ namespace OpenRA { try { - package = new ZipFile(null, pair.Second); + package = new ZipFile(File.OpenRead(pair.Second), pair.Second); } catch { diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index 87bb41bf72..c9058d3c07 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -105,9 +105,8 @@ namespace OpenRA.Mods.Common.UtilityCommands Map.FixOpenAreas(); var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - var package = new ZipFile(modData.ModFiles, dest, true); - Map.Save(package); + Map.Save(ZipFile.Create(dest, new Folder("."))); Console.WriteLine(dest + " saved."); } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs index ccccfe446a..60228e4f8f 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeMapCommand.cs @@ -78,7 +78,8 @@ namespace OpenRA.Mods.Common.UtilityCommands // HACK: The engine code assumes that Game.modData is set. Game.ModData = modData; - var package = modData.ModFiles.OpenWritablePackage(args[1]); + // HACK: We know that maps can only be oramap or folders, which are ReadWrite + var package = modData.ModFiles.OpenPackage(args[1], new Folder(".")) as IReadWritePackage; var engineDate = Exts.ParseIntegerInvariant(args[2]); UpgradeMap(modData, package, engineDate); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs index 4f89cebd16..436bbfcadf 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -180,7 +180,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { selectedDirectory.Folder.Delete(combinedPath); if (fileType == MapFileType.OraMap) - package = new ZipFile(modData.DefaultFileSystem, combinedPath, true); + package = ZipFile.Create(combinedPath, selectedDirectory.Folder); else package = new Folder(combinedPath); } diff --git a/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs index b26530ba71..ae7d97c380 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs @@ -37,8 +37,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands return; var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - var package = new ZipFile(modData.DefaultFileSystem, dest, true); - map.Save(package); + map.Save(ZipFile.Create(dest, new Folder("."))); Console.WriteLine(dest + " saved."); } } diff --git a/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs index b44c4d03e8..e160fc76be 100644 --- a/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.TS/UtilityCommands/ImportTSMapCommand.cs @@ -186,8 +186,7 @@ namespace OpenRA.Mods.TS.UtilityCommands map.PlayerDefinitions = mapPlayers.ToMiniYaml(); var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; - var package = new ZipFile(modData.DefaultFileSystem, dest, true); - map.Save(package); + map.Save(ZipFile.Create(dest, new Folder("."))); Console.WriteLine(dest + " saved."); }