Implement mod-defined package loaders.

This commit is contained in:
Paul Chote
2017-05-07 12:25:04 +00:00
parent 9b4f602cca
commit 0222ea675c
26 changed files with 993 additions and 838 deletions

View File

@@ -11,16 +11,16 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using OpenRA.FileFormats;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
public sealed class BagFile : IReadOnlyPackage
public class AudioBagLoader : IPackageLoader
{
sealed class BagFile : IReadOnlyPackage
{
public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } }
@@ -28,24 +28,14 @@ namespace OpenRA.FileSystem
readonly Stream s;
readonly Dictionary<string, IdxEntry> index;
public BagFile(FileSystem context, string filename)
public BagFile(Stream s, List<IdxEntry> entries, string filename)
{
Name = filename;
// A bag file is always accompanied with an .idx counterpart
// For example: audio.bag requires the audio.idx file
var indexFilename = Path.ChangeExtension(filename, ".idx");
// Build the index and dispose the stream, it is no longer needed after this
List<IdxEntry> entries;
using (var indexStream = context.Open(indexFilename))
entries = new IdxReader(indexStream).Entries;
this.s = s;
index = entries.ToDictionaryWithConflictLog(x => x.Filename,
"{0} (bag format)".F(filename),
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
s = context.Open(filename);
}
public Stream GetStream(string filename)
@@ -119,9 +109,45 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
if (!filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
{
package = null;
return false;
}
// A bag file is always accompanied with an .idx counterpart
// For example: audio.bag requires the audio.idx file
var indexFilename = Path.ChangeExtension(filename, ".idx");
List<IdxEntry> entries = null;
try
{
// Build the index and dispose the stream, it is no longer needed after this
using (var indexStream = context.Open(indexFilename))
entries = new IdxReader(indexStream).Entries;
}
catch
{
package = null;
return false;
}
package = new BagFile(s, entries, filename);
return true;
}
}
}

View File

@@ -12,11 +12,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA.FileSystem
{
public sealed class BigFile : IReadOnlyPackage
public class BigLoader : IPackageLoader
{
sealed class BigFile : IReadOnlyPackage
{
public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } }
@@ -24,15 +25,14 @@ namespace OpenRA.FileSystem
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
readonly Stream s;
public BigFile(FileSystem context, string filename)
public BigFile(Stream s, string filename)
{
Name = filename;
this.s = s;
s = context.Open(filename);
try
{
if (s.ReadASCII(4) != "BIGF")
throw new InvalidDataException("Header is not BIGF");
/* var signature = */ s.ReadASCII(4);
// Total archive size.
s.ReadUInt32();
@@ -97,9 +97,32 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
// Take a peek at the file signature
var signature = s.ReadASCII(4);
s.Position -= 4;
if (signature != "BIGF")
{
package = null;
return false;
}
package = new BigFile(s, filename);
return true;
}
}
}

View File

@@ -9,13 +9,15 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
public sealed class D2kSoundResources : IReadOnlyPackage
public class D2kSoundResourcesLoader : IPackageLoader
{
sealed class D2kSoundResources : IReadOnlyPackage
{
struct Entry
{
@@ -35,11 +37,11 @@ namespace OpenRA.FileSystem
readonly Stream s;
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
public D2kSoundResources(FileSystem context, string filename)
public D2kSoundResources(Stream s, string filename)
{
Name = filename;
this.s = s;
s = context.Open(filename);
try
{
var headerLength = s.ReadUInt32();
@@ -68,6 +70,12 @@ namespace OpenRA.FileSystem
return new MemoryStream(s.ReadBytes((int)e.Length));
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
@@ -78,4 +86,17 @@ namespace OpenRA.FileSystem
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
if (!filename.EndsWith(".rs", StringComparison.InvariantCultureIgnoreCase))
{
package = null;
return false;
}
package = new D2kSoundResources(s, filename);
return true;
}
}
}

View File

@@ -34,80 +34,69 @@ namespace OpenRA.FileSystem
// Mod packages that should not be disposed
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
readonly IReadOnlyDictionary<string, Manifest> installedMods;
readonly IPackageLoader[] packageLoaders;
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods)
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
{
this.installedMods = installedMods;
this.packageLoaders = packageLoaders
.Append(new ZipFileLoader())
.ToArray();
}
public bool TryParsePackage(Stream stream, string filename, out IReadOnlyPackage package)
{
package = null;
foreach (var packageLoader in packageLoaders)
if (packageLoader.TryParsePackage(stream, filename, this, out package))
return true;
return false;
}
public IReadOnlyPackage OpenPackage(string filename)
{
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
return new MixFile(this, filename);
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(this, filename);
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(this, filename);
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
return new D2kSoundResources(this, filename);
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
return new InstallShieldPackage(this, filename);
if (filename.EndsWith(".PAK", StringComparison.InvariantCultureIgnoreCase))
return new PakFile(this, filename);
if (filename.EndsWith(".big", StringComparison.InvariantCultureIgnoreCase))
return new BigFile(this, filename);
if (filename.EndsWith(".bag", StringComparison.InvariantCultureIgnoreCase))
return new BagFile(this, filename);
// Raw directories are the easiest and one of the most common cases, so try these first
var resolvedPath = Platform.ResolvePath(filename);
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
return new Folder(resolvedPath);
// Children of another package require special handling
IReadOnlyPackage parent;
string subPath = null;
if (TryGetPackageContaining(filename, out parent, out subPath))
return OpenPackage(subPath, parent);
return parent.OpenPackage(subPath, this);
return new Folder(Platform.ResolvePath(filename));
}
// Try and open it normally
IReadOnlyPackage package;
var stream = Open(filename);
if (TryParsePackage(stream, filename, out package))
return package;
public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent)
{
// HACK: limit support to zip and folder until we generalize the PackageLoader support
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase) ||
filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
{
using (var s = parent.GetStream(filename))
return new ZipFile(s, filename, parent);
}
if (parent is ZipFile)
return new ZipFolder(this, (ZipFile)parent, filename, filename);
if (parent is ZipFolder)
{
var folder = (ZipFolder)parent;
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);
}
// No package loaders took ownership of the stream, so clean it up
stream.Dispose();
return null;
}
public IReadOnlyPackage OpenPackage(string filename, IReadOnlyPackage parent)
{
// TODO: Make legacy callers access the parent package directly
return parent.OpenPackage(filename, this);
}
public void Mount(string name, string explicitName = null)
{
var optional = name.StartsWith("~");
var optional = name.StartsWith("~", StringComparison.Ordinal);
if (optional)
name = name.Substring(1);
try
{
IReadOnlyPackage package;
if (name.StartsWith("$"))
if (name.StartsWith("$", StringComparison.Ordinal))
{
name = name.Substring(1);

View File

@@ -60,6 +60,9 @@ namespace OpenRA.FileSystem
// 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;

View File

@@ -15,12 +15,23 @@ using System.IO;
namespace OpenRA.FileSystem
{
public interface IPackageLoader
{
/// <summary>
/// Attempt to parse a stream as this type of package.
/// If successful, the loader is expected to take ownership of `s` and dispose it once done.
/// If unsuccessful, the loader is expected to return the stream position to where it started.
/// </summary>
bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package);
}
public interface IReadOnlyPackage : IDisposable
{
string Name { get; }
IEnumerable<string> Contents { get; }
Stream GetStream(string filename);
bool Contains(string filename);
IReadOnlyPackage OpenPackage(string filename, FileSystem context);
}
public interface IReadWritePackage : IReadOnlyPackage

View File

@@ -15,6 +15,8 @@ using System.IO;
using OpenRA.FileFormats;
namespace OpenRA.FileSystem
{
public class InstallShieldLoader : IPackageLoader
{
public sealed class InstallShieldPackage : IReadOnlyPackage
{
@@ -37,18 +39,15 @@ namespace OpenRA.FileSystem
readonly Stream s;
readonly long dataStart = 255;
public InstallShieldPackage(FileSystem context, string filename)
public InstallShieldPackage(Stream s, string filename)
{
Name = filename;
this.s = s;
s = context.Open(filename);
try
{
// Parse package header
var signature = s.ReadUInt32();
if (signature != 0x8C655D13)
throw new InvalidDataException("Not an Installshield package");
/*var signature = */s.ReadUInt32();
s.Position += 8;
/*var FileCount = */s.ReadUInt16();
s.Position += 4;
@@ -79,7 +78,7 @@ namespace OpenRA.FileSystem
// Parse files
foreach (var dir in directories)
for (var i = 0; i < dir.Value; i++)
ParseFile(s, dir.Key);
ParseFile(dir.Key);
}
catch
{
@@ -89,7 +88,7 @@ namespace OpenRA.FileSystem
}
uint accumulatedData = 0;
void ParseFile(Stream s, string dirName)
void ParseFile(string dirName)
{
s.Position += 7;
var compressedSize = s.ReadUInt32();
@@ -122,6 +121,12 @@ namespace OpenRA.FileSystem
return ret;
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
@@ -134,4 +139,21 @@ namespace OpenRA.FileSystem
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
// Take a peek at the file signature
var signature = s.ReadUInt32();
s.Position -= 4;
if (signature != 0x8C655D13)
{
package = null;
return false;
}
package = new InstallShieldPackage(s, filename);
return true;
}
}
}

View File

@@ -11,13 +11,14 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
public class MixLoader : IPackageLoader
{
public sealed class MixFile : IReadOnlyPackage
{
@@ -27,14 +28,12 @@ namespace OpenRA.FileSystem
readonly Dictionary<string, PackageEntry> index;
readonly long dataStart;
readonly Stream s;
readonly FileSystem context;
public MixFile(FileSystem context, string filename)
public MixFile(Stream s, string filename, HashSet<string> allPossibleFilenames)
{
Name = filename;
this.context = context;
this.s = s;
s = context.Open(filename);
try
{
// Detect format type
@@ -56,7 +55,7 @@ namespace OpenRA.FileSystem
index = ParseIndex(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)));
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)), allPossibleFilenames);
}
catch (Exception)
{
@@ -65,11 +64,10 @@ namespace OpenRA.FileSystem
}
}
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries)
Dictionary<string, PackageEntry> ParseIndex(Dictionary<uint, PackageEntry> entries, HashSet<string> allPossibleFilenames)
{
var classicIndex = new Dictionary<string, PackageEntry>();
var crcIndex = new Dictionary<string, PackageEntry>();
var allPossibleFilenames = new HashSet<string>();
// Try and find a local mix database
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic);
@@ -89,17 +87,6 @@ namespace OpenRA.FileSystem
}
}
// Load the global mix database
// TODO: This should be passed to the mix file ctor
if (context.Exists("global mix database.dat"))
{
using (var db = new XccGlobalDatabase(context.Open("global mix database.dat")))
{
foreach (var e in db.Entries)
allPossibleFilenames.Add(e);
}
}
foreach (var filename in allPossibleFilenames)
{
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
@@ -235,9 +222,44 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
IReadOnlyPackage package;
var childStream = GetStream(filename);
if (childStream == null)
return null;
if (context.TryParsePackage(childStream, filename, out package))
return package;
childStream.Dispose();
return null;
}
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
if (!filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
{
package = null;
return false;
}
// Load the global mix database
Stream mixDatabase;
var allPossibleFilenames = new HashSet<string>();
if (context.TryOpen("global mix database.dat", out mixDatabase))
using (var db = new XccGlobalDatabase(mixDatabase))
foreach (var e in db.Entries)
allPossibleFilenames.Add(e);
package = new MixFile(s, filename, allPossibleFilenames);
return true;
}
}
}

View File

@@ -9,10 +9,13 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
namespace OpenRA.FileSystem
{
public class PakFileLoader : IPackageLoader
{
struct Entry
{
@@ -21,23 +24,21 @@ namespace OpenRA.FileSystem
public string Filename;
}
public sealed class PakFile : IReadOnlyPackage
sealed class PakFile : IReadOnlyPackage
{
public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } }
readonly Dictionary<string, Entry> index;
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
readonly Stream stream;
public PakFile(FileSystem context, string filename)
public PakFile(Stream stream, string filename)
{
Name = filename;
index = new Dictionary<string, Entry>();
this.stream = stream;
stream = context.Open(filename);
try
{
index = new Dictionary<string, Entry>();
var offset = stream.ReadUInt32();
while (offset != 0)
{
@@ -76,9 +77,28 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose()
{
stream.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
if (!filename.EndsWith(".pak", StringComparison.InvariantCultureIgnoreCase))
{
package = null;
return false;
}
package = new PakFile(s, filename);
return true;
}
}
}

View File

@@ -9,7 +9,7 @@
*/
#endregion
using System.Collections;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -19,7 +19,11 @@ using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
namespace OpenRA.FileSystem
{
public sealed class ZipFile : IReadWritePackage
public class ZipFileLoader : IPackageLoader
{
static readonly string[] Extensions = { ".zip", ".oramap" };
sealed class ZipFile : IReadWritePackage
{
public IReadWritePackage Parent { get; private set; }
public string Name { get; private set; }
@@ -44,27 +48,7 @@ namespace OpenRA.FileSystem
pkg = new SZipFile(pkgStream);
}
public ZipFile(IReadOnlyFileSystem context, string filename)
{
string name;
IReadOnlyPackage p;
if (!context.TryGetPackageContaining(filename, out p, out name))
throw new FileNotFoundException("Unable to find parent package for " + 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();
using (var sourceStream = p.GetStream(name))
sourceStream.CopyTo(pkgStream);
pkgStream.Position = 0;
pkg = new SZipFile(pkgStream);
}
ZipFile(string filename, IReadWritePackage parent)
public ZipFile(string filename, IReadWritePackage parent)
{
pkgStream = new MemoryStream();
@@ -73,11 +57,6 @@ namespace OpenRA.FileSystem
pkg = SZipFile.Create(pkgStream);
}
public static ZipFile Create(string filename, IReadWritePackage parent)
{
return new ZipFile(filename, parent);
}
public Stream GetStream(string filename)
{
var entry = pkg.GetEntry(filename);
@@ -142,6 +121,88 @@ namespace OpenRA.FileSystem
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; }
readonly string path;
static ZipFolder()
{
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
}
public ZipFolder(ZipFile parent, string path)
{
if (path.EndsWith("/", StringComparison.Ordinal))
path = path.Substring(0, path.Length - 1);
Parent = parent;
this.path = path;
}
public Stream GetStream(string filename)
{
// Zip files use '/' as a path separator
return Parent.GetStream(path + '/' + filename);
}
public IEnumerable<string> Contents
{
get
{
foreach (var entry in Parent.Contents)
{
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
{
var filename = entry.Substring(path.Length + 1);
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
if (dirLevels == 1)
yield return filename;
}
}
}
}
public bool Contains(string filename)
{
return Parent.Contains(path + '/' + filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
return Parent.OpenPackage(path + '/' + filename, context);
}
public void Dispose() { /* nothing to do */ }
}
class StaticStreamDataSource : IStaticDataSource
@@ -157,4 +218,28 @@ namespace OpenRA.FileSystem
return s;
}
}
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
{
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
{
package = null;
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);
return true;
}
public static IReadWritePackage Create(string filename, IReadWritePackage parent)
{
return new ZipFile(filename, parent);
}
}
}

View File

@@ -1,76 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
namespace OpenRA.FileSystem
{
public sealed class ZipFolder : IReadOnlyPackage
{
public string Name { get; private set; }
public ZipFile Parent { get; private set; }
readonly string path;
static ZipFolder()
{
ZipConstants.DefaultCodePage = Encoding.UTF8.CodePage;
}
public ZipFolder(FileSystem context, ZipFile parent, string path, string filename)
{
if (filename.EndsWith("/"))
filename = filename.Substring(0, filename.Length - 1);
Name = filename;
Parent = parent;
if (path.EndsWith("/"))
path = path.Substring(0, path.Length - 1);
this.path = path;
}
public Stream GetStream(string filename)
{
// Zip files use '/' as a path separator
return Parent.GetStream(path + '/' + filename);
}
public IEnumerable<string> Contents
{
get
{
foreach (var entry in Parent.Contents)
{
if (entry.StartsWith(path) && entry != path)
{
var filename = entry.Substring(path.Length + 1);
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
if (dirLevels == 1)
yield return filename;
}
}
}
}
public bool Contains(string filename)
{
return Parent.Contains(path + '/' + filename);
}
public void Dispose() { /* nothing to do */ }
}
}

View File

@@ -57,12 +57,13 @@ namespace OpenRA
public readonly string[] SoundFormats = { };
public readonly string[] SpriteFormats = { };
public readonly string[] PackageFormats = { };
readonly string[] reservedModuleNames = { "Metadata", "Folders", "MapFolders", "Packages", "Rules",
"Sequences", "VoxelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions",
"ServerTraits", "LoadScreen", "Fonts", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
"RequiresMods" };
"RequiresMods", "PackageFormats" };
readonly TypeDictionary modules = new TypeDictionary();
readonly Dictionary<string, MiniYaml> yaml;
@@ -119,6 +120,9 @@ namespace OpenRA
MapCompatibility = compat.ToArray();
if (yaml.ContainsKey("PackageFormats"))
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
if (yaml.ContainsKey("SoundFormats"))
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);

View File

@@ -13,7 +13,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Widgets;
@@ -27,6 +26,7 @@ namespace OpenRA
public readonly ObjectCreator ObjectCreator;
public readonly WidgetLoader WidgetLoader;
public readonly MapCache MapCache;
public readonly IPackageLoader[] PackageLoaders;
public readonly ISoundLoader[] SoundLoaders;
public readonly ISpriteLoader[] SpriteLoaders;
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
@@ -49,12 +49,12 @@ namespace OpenRA
{
Languages = new string[0];
// Take a local copy of the manifest
Manifest = new Manifest(mod.Id, mod.Package);
ObjectCreator = new ObjectCreator(Manifest, mods);
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
ModFiles = new FS(mods);
ModFiles = new FS(mods, PackageLoaders);
ModFiles.LoadFromManifest(Manifest);
Manifest.LoadCustomData(ObjectCreator);

View File

@@ -254,7 +254,6 @@
<Compile Include="Graphics\RgbaColorRenderer.cs" />
<Compile Include="Traits\Player\IndexedPlayerPalette.cs" />
<Compile Include="Traits\ActivityUtils.cs" />
<Compile Include="FileSystem\ZipFolder.cs" />
<Compile Include="Primitives\float3.cs" />
<Compile Include="InstalledMods.cs" />
<Compile Include="CryptoUtil.cs" />

View File

@@ -271,7 +271,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
map.PlayerDefinitions = mapPlayers.ToMiniYaml();
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
map.Save(ZipFile.Create(dest, new Folder(".")));
map.Save(ZipFileLoader.Create(dest, new Folder(".")));
Console.WriteLine(dest + " saved.");
}

View File

@@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
Map.Save(ZipFile.Create(dest, new Folder(".")));
Map.Save(ZipFileLoader.Create(dest, new Folder(".")));
Console.WriteLine(dest + " saved.");
}

View File

@@ -27,13 +27,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
[Desc("ARCHIVE.Z", "Lists the content ranges for a InstallShield V3 file")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
var filename = Path.GetFileName(args[1]);
var path = Path.GetDirectoryName(args[1]);
var fs = new FileSystem.FileSystem(utility.Mods);
fs.Mount(path, "parent");
var package = new InstallShieldPackage(fs, "parent|" + filename);
var package = new InstallShieldLoader.InstallShieldPackage(File.OpenRead(args[1]), args[1]);
foreach (var kv in package.Index)
{
Console.WriteLine("{0}:", kv.Key);

View File

@@ -10,8 +10,10 @@
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands
@@ -22,23 +24,18 @@ namespace OpenRA.Mods.Common.UtilityCommands
bool IUtilityCommand.ValidateArguments(string[] args)
{
return args.Length == 2;
return args.Length == 3;
}
[Desc("ARCHIVE.MIX", "Lists the content ranges for a mix file")]
[Desc("ARCHIVE.MIX", "MIXDATABASE.DAT", "Lists the content ranges for a mix file")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
var filename = Path.GetFileName(args[1]);
var path = Path.GetDirectoryName(args[1]);
var fs = new FileSystem.FileSystem(utility.Mods);
// Needed to access the global mix database
fs.LoadFromManifest(utility.ModData.Manifest);
fs.Mount(path, "parent");
var package = new MixFile(fs, "parent|" + filename);
var allPossibleFilenames = new HashSet<string>();
using (var db = new XccGlobalDatabase(File.OpenRead(args[2])))
foreach (var e in db.Entries)
allPossibleFilenames.Add(e);
var package = new MixLoader.MixFile(File.OpenRead(args[1]), args[1], allPossibleFilenames);
foreach (var kv in package.Index.OrderBy(kv => kv.Value.Offset))
{
Console.WriteLine("{0}:", kv.Key);

View File

@@ -123,7 +123,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
directoryDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, writableDirectories, setupItem);
}
var mapIsUnpacked = map.Package != null && (map.Package is Folder || map.Package is ZipFolder);
var mapIsUnpacked = map.Package != null && map.Package is Folder;
var filename = widget.Get<TextFieldWidget>("FILENAME");
filename.Text = map.Package == null ? "" : mapIsUnpacked ? Path.GetFileName(map.Package.Name) : Path.GetFileNameWithoutExtension(map.Package.Name);
@@ -185,7 +185,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
selectedDirectory.Folder.Delete(combinedPath);
if (fileType == MapFileType.OraMap)
package = ZipFile.Create(combinedPath, selectedDirectory.Folder);
package = ZipFileLoader.Create(combinedPath, selectedDirectory.Folder);
else
package = new Folder(combinedPath);
}

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
@@ -28,13 +29,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
bool discAvailable;
[ObjectCreator.UseCtor]
public ModContentLogic(Widget widget, Manifest mod, ModContent content, Action onCancel)
public ModContentLogic(Widget widget, ModData modData, Manifest mod, ModContent content, Action onCancel)
{
this.content = content;
var panel = widget.Get("CONTENT_PANEL");
var modFileSystem = new FileSystem.FileSystem(Game.Mods);
var modObjectCreator = new ObjectCreator(mod, Game.Mods);
var modPackageLoaders = modObjectCreator.GetLoaders<IPackageLoader>(mod.PackageFormats, "package");
var modFileSystem = new FileSystem.FileSystem(Game.Mods, modPackageLoaders);
modFileSystem.LoadFromManifest(mod);
var sourceYaml = MiniYaml.Load(modFileSystem, content.Sources, null);

View File

@@ -11,6 +11,7 @@
using System;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
@@ -18,7 +19,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public class ModContentPromptLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public ModContentPromptLogic(Widget widget, Manifest mod, ModContent content, Action continueLoading)
public ModContentPromptLogic(Widget widget, ModData modData, Manifest mod, ModContent content, Action continueLoading)
{
var panel = widget.Get("CONTENT_PROMPT_PANEL");
@@ -55,8 +56,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
quickButton.Bounds.Y += headerHeight;
quickButton.OnClick = () =>
{
var modFileSystem = new FileSystem.FileSystem(Game.Mods);
var modObjectCreator = new ObjectCreator(mod, Game.Mods);
var modPackageLoaders = modObjectCreator.GetLoaders<IPackageLoader>(mod.PackageFormats, "package");
var modFileSystem = new FileSystem.FileSystem(Game.Mods, modPackageLoaders);
modFileSystem.LoadFromManifest(mod);
var downloadYaml = MiniYaml.Load(modFileSystem, content.Downloads, null);
modFileSystem.UnmountAll();

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands
return;
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";
map.Save(ZipFile.Create(dest, new Folder(".")));
map.Save(ZipFileLoader.Create(dest, new Folder(".")));
Console.WriteLine(dest + " saved.");
}
}

View File

@@ -2,6 +2,8 @@ Metadata:
Title: Tiberian Dawn
Version: {DEV_VERSION}
PackageFormats: Mix
Packages:
~^Content/cnc
~^Content/cnc/movies

View File

@@ -2,6 +2,8 @@ Metadata:
Title: Dune 2000
Version: {DEV_VERSION}
PackageFormats: D2kSoundResources
Packages:
~^Content/d2k/v2/
~^Content/d2k/v2/GAMESFX

View File

@@ -2,6 +2,8 @@ Metadata:
Title: Red Alert
Version: {DEV_VERSION}
PackageFormats: Mix
Packages:
~^Content/ra/v2/
~^Content/ra/v2/expand

View File

@@ -2,6 +2,8 @@ Metadata:
Title: Tiberian Sun
Version: {DEV_VERSION}
PackageFormats: Mix
Packages:
~^Content/ts
~^Content/ts/firestorm