Merge pull request #13223 from pchote/mod-package-loaders

Unhardcode mod package loaders
This commit is contained in:
reaperrr
2017-05-12 14:04:58 +02:00
committed by GitHub
30 changed files with 1106 additions and 919 deletions

View File

@@ -11,16 +11,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.FileSystem namespace OpenRA.FileSystem
{ {
public sealed class BagFile : IReadOnlyPackage public class AudioBagLoader : IPackageLoader
{
sealed class BagFile : IReadOnlyPackage
{ {
public string Name { get; private set; } public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } } public IEnumerable<string> Contents { get { return index.Keys; } }
@@ -28,24 +28,14 @@ namespace OpenRA.FileSystem
readonly Stream s; readonly Stream s;
readonly Dictionary<string, IdxEntry> index; readonly Dictionary<string, IdxEntry> index;
public BagFile(FileSystem context, string filename) public BagFile(Stream s, List<IdxEntry> entries, string filename)
{ {
Name = filename; Name = filename;
this.s = s;
// 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;
index = entries.ToDictionaryWithConflictLog(x => x.Filename, index = entries.ToDictionaryWithConflictLog(x => x.Filename,
"{0} (bag format)".F(filename), "{0} (bag format)".F(filename),
null, x => "(offs={0}, len={1})".F(x.Offset, x.Length)); null, x => "(offs={0}, len={1})".F(x.Offset, x.Length));
s = context.Open(filename);
} }
public Stream GetStream(string filename) public Stream GetStream(string filename)
@@ -119,9 +109,45 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename); return index.ContainsKey(filename);
} }
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose() public void Dispose()
{ {
s.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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
namespace OpenRA.FileSystem namespace OpenRA.FileSystem
{ {
public sealed class BigFile : IReadOnlyPackage public class BigLoader : IPackageLoader
{
sealed class BigFile : IReadOnlyPackage
{ {
public string Name { get; private set; } public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } } 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 Dictionary<string, Entry> index = new Dictionary<string, Entry>();
readonly Stream s; readonly Stream s;
public BigFile(FileSystem context, string filename) public BigFile(Stream s, string filename)
{ {
Name = filename; Name = filename;
this.s = s;
s = context.Open(filename);
try try
{ {
if (s.ReadASCII(4) != "BIGF") /* var signature = */ s.ReadASCII(4);
throw new InvalidDataException("Header is not BIGF");
// Total archive size. // Total archive size.
s.ReadUInt32(); s.ReadUInt32();
@@ -97,9 +97,32 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename); return index.ContainsKey(filename);
} }
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose() public void Dispose()
{ {
s.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

@@ -1,81 +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.Collections.Generic;
using System.IO;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
public sealed class D2kSoundResources : IReadOnlyPackage
{
struct Entry
{
public readonly uint Offset;
public readonly uint Length;
public Entry(uint offset, uint length)
{
Offset = offset;
Length = length;
}
}
public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } }
readonly Stream s;
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
public D2kSoundResources(FileSystem context, string filename)
{
Name = filename;
s = context.Open(filename);
try
{
var headerLength = s.ReadUInt32();
while (s.Position < headerLength + 4)
{
var name = s.ReadASCIIZ();
var offset = s.ReadUInt32();
var length = s.ReadUInt32();
index.Add(name, new Entry(offset, length));
}
}
catch
{
Dispose();
throw;
}
}
public Stream GetStream(string filename)
{
Entry e;
if (!index.TryGetValue(filename, out e))
return null;
s.Seek(e.Offset, SeekOrigin.Begin);
return new MemoryStream(s.ReadBytes((int)e.Length));
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
}
public void Dispose()
{
s.Dispose();
}
}
}

View File

@@ -34,80 +34,69 @@ namespace OpenRA.FileSystem
// Mod packages that should not be disposed // Mod packages that should not be disposed
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>(); readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
readonly IReadOnlyDictionary<string, Manifest> installedMods; readonly IReadOnlyDictionary<string, Manifest> installedMods;
readonly IPackageLoader[] packageLoaders;
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>()); 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.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) public IReadOnlyPackage OpenPackage(string filename)
{ {
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase)) // Raw directories are the easiest and one of the most common cases, so try these first
return new MixFile(this, filename); var resolvedPath = Platform.ResolvePath(filename);
if (filename.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) if (!filename.Contains("|") && Directory.Exists(resolvedPath))
return new ZipFile(this, filename); return new Folder(resolvedPath);
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);
// Children of another package require special handling
IReadOnlyPackage parent; IReadOnlyPackage parent;
string subPath = null; string subPath = null;
if (TryGetPackageContaining(filename, out parent, out subPath)) 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) // No package loaders took ownership of the stream, so clean it up
{ stream.Dispose();
// 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);
}
return null; 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) public void Mount(string name, string explicitName = null)
{ {
var optional = name.StartsWith("~"); var optional = name.StartsWith("~", StringComparison.Ordinal);
if (optional) if (optional)
name = name.Substring(1); name = name.Substring(1);
try try
{ {
IReadOnlyPackage package; IReadOnlyPackage package;
if (name.StartsWith("$")) if (name.StartsWith("$", StringComparison.Ordinal))
{ {
name = name.Substring(1); name = name.Substring(1);
@@ -297,5 +286,40 @@ namespace OpenRA.FileSystem
return fileIndex.ContainsKey(filename); return fileIndex.ContainsKey(filename);
} }
/// <summary>
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
/// </summary>
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
{
var explicitSplit = path.IndexOf('|');
if (explicitSplit > 0)
{
var parent = path.Substring(0, explicitSplit);
var filename = path.Substring(explicitSplit + 1);
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
if (parentPath == null)
return null;
if (parentPath.StartsWith("$", StringComparison.Ordinal))
{
Manifest mod;
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
return null;
if (!(mod.Package is Folder))
return null;
path = Path.Combine(mod.Package.Name, filename);
}
else
path = Path.Combine(parentPath, filename);
}
var resolvedPath = Platform.ResolvePath(path);
return File.Exists(resolvedPath) ? resolvedPath : null;
}
} }
} }

View File

@@ -51,6 +51,25 @@ namespace OpenRA.FileSystem
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined); return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
} }
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
var subFolder = Platform.ResolvePath(Path.Combine(Name, filename));
if (Directory.Exists(subFolder))
return new Folder(subFolder);
// 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;
}
public void Update(string filename, byte[] contents) public void Update(string filename, byte[] contents)
{ {
// HACK: ZipFiles can't be loaded as read-write from a stream, so we are // HACK: ZipFiles can't be loaded as read-write from a stream, so we are

View File

@@ -15,12 +15,23 @@ using System.IO;
namespace OpenRA.FileSystem 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 public interface IReadOnlyPackage : IDisposable
{ {
string Name { get; } string Name { get; }
IEnumerable<string> Contents { get; } IEnumerable<string> Contents { get; }
Stream GetStream(string filename); Stream GetStream(string filename);
bool Contains(string filename); bool Contains(string filename);
IReadOnlyPackage OpenPackage(string filename, FileSystem context);
} }
public interface IReadWritePackage : IReadOnlyPackage public interface IReadWritePackage : IReadOnlyPackage

View File

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

View File

@@ -11,7 +11,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileFormats; using OpenRA.FileFormats;
@@ -19,6 +18,8 @@ using OpenRA.Primitives;
namespace OpenRA.FileSystem namespace OpenRA.FileSystem
{ {
public class MixLoader : IPackageLoader
{
public sealed class MixFile : IReadOnlyPackage public sealed class MixFile : IReadOnlyPackage
{ {
public string Name { get; private set; } public string Name { get; private set; }
@@ -27,14 +28,12 @@ namespace OpenRA.FileSystem
readonly Dictionary<string, PackageEntry> index; readonly Dictionary<string, PackageEntry> index;
readonly long dataStart; readonly long dataStart;
readonly Stream s; readonly Stream s;
readonly FileSystem context;
public MixFile(FileSystem context, string filename) public MixFile(Stream s, string filename, HashSet<string> allPossibleFilenames)
{ {
Name = filename; Name = filename;
this.context = context; this.s = s;
s = context.Open(filename);
try try
{ {
// Detect format type // Detect format type
@@ -56,7 +55,7 @@ namespace OpenRA.FileSystem
index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash, index = ParseIndex(entries.ToDictionaryWithConflictLog(x => x.Hash,
"{0} ({1} format, Encrypted: {2}, DataStart: {3})".F(filename, isCncMix ? "C&C" : "RA/TS/RA2", isEncrypted, dataStart), "{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) 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 classicIndex = new Dictionary<string, PackageEntry>();
var crcIndex = new Dictionary<string, PackageEntry>(); var crcIndex = new Dictionary<string, PackageEntry>();
var allPossibleFilenames = new HashSet<string>();
// Try and find a local mix database // Try and find a local mix database
var dbNameClassic = PackageEntry.HashFilename("local mix database.dat", PackageHashType.Classic); 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) foreach (var filename in allPossibleFilenames)
{ {
var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic); var classicHash = PackageEntry.HashFilename(filename, PackageHashType.Classic);
@@ -235,9 +222,44 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename); 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() public void Dispose()
{ {
s.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,11 +9,14 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace OpenRA.FileSystem namespace OpenRA.FileSystem
{ {
public class PakFileLoader : IPackageLoader
{
struct Entry struct Entry
{ {
public uint Offset; public uint Offset;
@@ -21,23 +24,21 @@ namespace OpenRA.FileSystem
public string Filename; public string Filename;
} }
public sealed class PakFile : IReadOnlyPackage sealed class PakFile : IReadOnlyPackage
{ {
public string Name { get; private set; } public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } } 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; readonly Stream stream;
public PakFile(FileSystem context, string filename) public PakFile(Stream stream, string filename)
{ {
Name = filename; Name = filename;
index = new Dictionary<string, Entry>(); this.stream = stream;
stream = context.Open(filename);
try try
{ {
index = new Dictionary<string, Entry>();
var offset = stream.ReadUInt32(); var offset = stream.ReadUInt32();
while (offset != 0) while (offset != 0)
{ {
@@ -76,9 +77,28 @@ namespace OpenRA.FileSystem
return index.ContainsKey(filename); return index.ContainsKey(filename);
} }
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
// Not implemented
return null;
}
public void Dispose() public void Dispose()
{ {
stream.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 #endregion
using System.Collections; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -19,7 +19,11 @@ using SZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile;
namespace OpenRA.FileSystem 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 IReadWritePackage Parent { get; private set; }
public string Name { get; private set; } public string Name { get; private set; }
@@ -44,27 +48,7 @@ namespace OpenRA.FileSystem
pkg = new SZipFile(pkgStream); pkg = new SZipFile(pkgStream);
} }
public ZipFile(IReadOnlyFileSystem context, string filename) public ZipFile(string filename, IReadWritePackage parent)
{
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)
{ {
pkgStream = new MemoryStream(); pkgStream = new MemoryStream();
@@ -73,11 +57,6 @@ namespace OpenRA.FileSystem
pkg = SZipFile.Create(pkgStream); pkg = SZipFile.Create(pkgStream);
} }
public static ZipFile Create(string filename, IReadWritePackage parent)
{
return new ZipFile(filename, parent);
}
public Stream GetStream(string filename) public Stream GetStream(string filename)
{ {
var entry = pkg.GetEntry(filename); var entry = pkg.GetEntry(filename);
@@ -142,6 +121,88 @@ namespace OpenRA.FileSystem
if (pkgStream != null) if (pkgStream != null)
pkgStream.Dispose(); 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 class StaticStreamDataSource : IStaticDataSource
@@ -157,4 +218,28 @@ namespace OpenRA.FileSystem
return s; 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

@@ -68,21 +68,10 @@ namespace OpenRA
IReadOnlyPackage package = null; IReadOnlyPackage package = null;
try try
{ {
if (Directory.Exists(path)) if (!Directory.Exists(path))
package = new Folder(path);
else
{
try
{
using (var fileStream = File.OpenRead(path))
package = new ZipFile(fileStream, path);
}
catch
{
throw new InvalidDataException(path + " is not a valid mod package"); throw new InvalidDataException(path + " is not a valid mod package");
}
}
package = new Folder(path);
if (!package.Contains("mod.yaml")) if (!package.Contains("mod.yaml"))
throw new InvalidDataException(path + " is not a valid mod package"); throw new InvalidDataException(path + " is not a valid mod package");

View File

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

View File

@@ -13,7 +13,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -27,6 +26,7 @@ namespace OpenRA
public readonly ObjectCreator ObjectCreator; public readonly ObjectCreator ObjectCreator;
public readonly WidgetLoader WidgetLoader; public readonly WidgetLoader WidgetLoader;
public readonly MapCache MapCache; public readonly MapCache MapCache;
public readonly IPackageLoader[] PackageLoaders;
public readonly ISoundLoader[] SoundLoaders; public readonly ISoundLoader[] SoundLoaders;
public readonly ISpriteLoader[] SpriteLoaders; public readonly ISpriteLoader[] SpriteLoaders;
public readonly ISpriteSequenceLoader SpriteSequenceLoader; public readonly ISpriteSequenceLoader SpriteSequenceLoader;
@@ -49,13 +49,13 @@ namespace OpenRA
{ {
Languages = new string[0]; Languages = new string[0];
ModFiles = new FS(mods);
// Take a local copy of the manifest // Take a local copy of the manifest
Manifest = new Manifest(mod.Id, mod.Package); Manifest = new Manifest(mod.Id, mod.Package);
ModFiles.LoadFromManifest(Manifest); ObjectCreator = new ObjectCreator(Manifest, mods);
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
ObjectCreator = new ObjectCreator(Manifest, ModFiles); ModFiles = new FS(mods, PackageLoaders);
ModFiles.LoadFromManifest(Manifest);
Manifest.LoadCustomData(ObjectCreator); Manifest.LoadCustomData(ObjectCreator);
if (useLoadScreen) if (useLoadScreen)
@@ -68,8 +68,8 @@ namespace OpenRA
WidgetLoader = new WidgetLoader(this); WidgetLoader = new WidgetLoader(this);
MapCache = new MapCache(this); MapCache = new MapCache(this);
SoundLoaders = GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound"); SoundLoaders = ObjectCreator.GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
SpriteLoaders = GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite"); SpriteLoaders = ObjectCreator.GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
var sequenceFormat = Manifest.Get<SpriteSequenceFormat>(); var sequenceFormat = Manifest.Get<SpriteSequenceFormat>();
var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader"); var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader");
@@ -129,21 +129,6 @@ namespace OpenRA
CursorProvider = new CursorProvider(this); CursorProvider = new CursorProvider(this);
} }
TLoader[] GetLoaders<TLoader>(IEnumerable<string> formats, string name)
{
var loaders = new List<TLoader>();
foreach (var format in formats)
{
var loader = ObjectCreator.FindType(format + "Loader");
if (loader == null || !loader.GetInterfaces().Contains(typeof(TLoader)))
throw new InvalidOperationException("Unable to find a {0} loader for type '{1}'.".F(name, format));
loaders.Add((TLoader)ObjectCreator.CreateBasic(loader));
}
return loaders.ToArray();
}
public IEnumerable<string> Languages { get; private set; } public IEnumerable<string> Languages { get; private set; }
void LoadTranslations(Map map) void LoadTranslations(Map map)

View File

@@ -27,40 +27,31 @@ namespace OpenRA
readonly Cache<string, Type> typeCache; readonly Cache<string, Type> typeCache;
readonly Cache<Type, ConstructorInfo> ctorCache; readonly Cache<Type, ConstructorInfo> ctorCache;
readonly Pair<Assembly, string>[] assemblies; readonly Pair<Assembly, string>[] assemblies;
readonly bool isMonoRuntime = Type.GetType("Mono.Runtime") != null;
public ObjectCreator(Manifest manifest, FileSystem.FileSystem modFiles) public ObjectCreator(Manifest manifest, InstalledMods mods)
{ {
typeCache = new Cache<string, Type>(FindType); typeCache = new Cache<string, Type>(FindType);
ctorCache = new Cache<Type, ConstructorInfo>(GetCtor); ctorCache = new Cache<Type, ConstructorInfo>(GetCtor);
// Allow mods to load types from the core Game assembly, and any additional assemblies they specify. // Allow mods to load types from the core Game assembly, and any additional assemblies they specify.
// Assemblies can only be loaded from directories to avoid circular dependencies on package loaders.
var assemblyList = new List<Assembly>() { typeof(Game).Assembly }; var assemblyList = new List<Assembly>() { typeof(Game).Assembly };
foreach (var path in manifest.Assemblies) foreach (var path in manifest.Assemblies)
{ {
var data = modFiles.Open(path).ReadAllBytes(); var resolvedPath = FileSystem.FileSystem.ResolveAssemblyPath(path, manifest, mods);
if (resolvedPath == null)
throw new FileNotFoundException("Assembly `{0}` not found.".F(path));
// .NET doesn't provide any way of querying the metadata of an assembly without either: // .NET doesn't provide any way of querying the metadata of an assembly without either:
// (a) loading duplicate data into the application domain, breaking the world. // (a) loading duplicate data into the application domain, breaking the world.
// (b) crashing if the assembly has already been loaded. // (b) crashing if the assembly has already been loaded.
// We can't check the internal name of the assembly, so we'll work off the data instead // We can't check the internal name of the assembly, so we'll work off the data instead
var hash = CryptoUtil.SHA1Hash(data); var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
Assembly assembly; Assembly assembly;
if (!ResolvedAssemblies.TryGetValue(hash, out assembly)) if (!ResolvedAssemblies.TryGetValue(hash, out assembly))
{ {
Stream symbolStream = null; assembly = Assembly.LoadFile(resolvedPath);
var hasSymbols = false;
// Mono has its own symbol format.
if (isMonoRuntime)
hasSymbols = modFiles.TryOpen(path + ".mdb", out symbolStream);
// .NET uses .pdb files.
else
hasSymbols = modFiles.TryOpen(path.Substring(0, path.Length - 4) + ".pdb", out symbolStream);
assembly = hasSymbols ? Assembly.Load(data, symbolStream.ReadAllBytes()) : Assembly.Load(data);
ResolvedAssemblies.Add(hash, assembly); ResolvedAssemblies.Add(hash, assembly);
} }
@@ -154,6 +145,21 @@ namespace OpenRA
.SelectMany(ma => ma.GetTypes()); .SelectMany(ma => ma.GetTypes());
} }
public TLoader[] GetLoaders<TLoader>(IEnumerable<string> formats, string name)
{
var loaders = new List<TLoader>();
foreach (var format in formats)
{
var loader = FindType(format + "Loader");
if (loader == null || !loader.GetInterfaces().Contains(typeof(TLoader)))
throw new InvalidOperationException("Unable to find a {0} loader for type '{1}'.".F(name, format));
loaders.Add((TLoader)CreateBasic(loader));
}
return loaders.ToArray();
}
~ObjectCreator() ~ObjectCreator()
{ {
Dispose(false); Dispose(false);

View File

@@ -254,7 +254,6 @@
<Compile Include="Graphics\RgbaColorRenderer.cs" /> <Compile Include="Graphics\RgbaColorRenderer.cs" />
<Compile Include="Traits\Player\IndexedPlayerPalette.cs" /> <Compile Include="Traits\Player\IndexedPlayerPalette.cs" />
<Compile Include="Traits\ActivityUtils.cs" /> <Compile Include="Traits\ActivityUtils.cs" />
<Compile Include="FileSystem\ZipFolder.cs" />
<Compile Include="Primitives\float3.cs" /> <Compile Include="Primitives\float3.cs" />
<Compile Include="InstalledMods.cs" /> <Compile Include="InstalledMods.cs" />
<Compile Include="CryptoUtil.cs" /> <Compile Include="CryptoUtil.cs" />
@@ -262,7 +261,6 @@
<Compile Include="ExternalMods.cs" /> <Compile Include="ExternalMods.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" />
<Compile Include="FileSystem\Folder.cs" /> <Compile Include="FileSystem\Folder.cs" />
<Compile Include="FileSystem\InstallShieldPackage.cs" /> <Compile Include="FileSystem\InstallShieldPackage.cs" />
<Compile Include="FileSystem\MixFile.cs" /> <Compile Include="FileSystem\MixFile.cs" />

View File

@@ -271,7 +271,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
map.PlayerDefinitions = mapPlayers.ToMiniYaml(); map.PlayerDefinitions = mapPlayers.ToMiniYaml();
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; 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."); Console.WriteLine(dest + " saved.");
} }

View File

@@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; 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."); 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")] [Desc("ARCHIVE.Z", "Lists the content ranges for a InstallShield V3 file")]
void IUtilityCommand.Run(Utility utility, string[] args) void IUtilityCommand.Run(Utility utility, string[] args)
{ {
var filename = Path.GetFileName(args[1]); var package = new InstallShieldLoader.InstallShieldPackage(File.OpenRead(args[1]), 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);
foreach (var kv in package.Index) foreach (var kv in package.Index)
{ {
Console.WriteLine("{0}:", kv.Key); Console.WriteLine("{0}:", kv.Key);

View File

@@ -10,8 +10,10 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileSystem; using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UtilityCommands namespace OpenRA.Mods.Common.UtilityCommands
@@ -22,23 +24,18 @@ namespace OpenRA.Mods.Common.UtilityCommands
bool IUtilityCommand.ValidateArguments(string[] args) 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) void IUtilityCommand.Run(Utility utility, string[] args)
{ {
var filename = Path.GetFileName(args[1]); var allPossibleFilenames = new HashSet<string>();
var path = Path.GetDirectoryName(args[1]); using (var db = new XccGlobalDatabase(File.OpenRead(args[2])))
foreach (var e in db.Entries)
var fs = new FileSystem.FileSystem(utility.Mods); allPossibleFilenames.Add(e);
// Needed to access the global mix database
fs.LoadFromManifest(utility.ModData.Manifest);
fs.Mount(path, "parent");
var package = new MixFile(fs, "parent|" + filename);
var package = new MixLoader.MixFile(File.OpenRead(args[1]), args[1], allPossibleFilenames);
foreach (var kv in package.Index.OrderBy(kv => kv.Value.Offset)) foreach (var kv in package.Index.OrderBy(kv => kv.Value.Offset))
{ {
Console.WriteLine("{0}:", kv.Key); 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); 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"); var filename = widget.Get<TextFieldWidget>("FILENAME");
filename.Text = map.Package == null ? "" : mapIsUnpacked ? Path.GetFileName(map.Package.Name) : Path.GetFileNameWithoutExtension(map.Package.Name); 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); selectedDirectory.Folder.Delete(combinedPath);
if (fileType == MapFileType.OraMap) if (fileType == MapFileType.OraMap)
package = ZipFile.Create(combinedPath, selectedDirectory.Folder); package = ZipFileLoader.Create(combinedPath, selectedDirectory.Folder);
else else
package = new Folder(combinedPath); package = new Folder(combinedPath);
} }

View File

@@ -12,6 +12,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic namespace OpenRA.Mods.Common.Widgets.Logic
@@ -28,13 +29,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
bool discAvailable; bool discAvailable;
[ObjectCreator.UseCtor] [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; this.content = content;
var panel = widget.Get("CONTENT_PANEL"); 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); modFileSystem.LoadFromManifest(mod);
var sourceYaml = MiniYaml.Load(modFileSystem, content.Sources, null); var sourceYaml = MiniYaml.Load(modFileSystem, content.Sources, null);

View File

@@ -11,6 +11,7 @@
using System; using System;
using System.Linq; using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic namespace OpenRA.Mods.Common.Widgets.Logic
@@ -18,7 +19,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public class ModContentPromptLogic : ChromeLogic public class ModContentPromptLogic : ChromeLogic
{ {
[ObjectCreator.UseCtor] [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"); var panel = widget.Get("CONTENT_PROMPT_PANEL");
@@ -55,8 +56,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
quickButton.Bounds.Y += headerHeight; quickButton.Bounds.Y += headerHeight;
quickButton.OnClick = () => 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); modFileSystem.LoadFromManifest(mod);
var downloadYaml = MiniYaml.Load(modFileSystem, content.Downloads, null); var downloadYaml = MiniYaml.Load(modFileSystem, content.Downloads, null);
modFileSystem.UnmountAll(); modFileSystem.UnmountAll();

View File

@@ -87,6 +87,7 @@
<Compile Include="UtilityCommands\ImportD2kMapCommand.cs" /> <Compile Include="UtilityCommands\ImportD2kMapCommand.cs" />
<Compile Include="Traits\World\D2kEditorResourceLayer.cs" /> <Compile Include="Traits\World\D2kEditorResourceLayer.cs" />
<Compile Include="Lint\CheckImportActors.cs" /> <Compile Include="Lint\CheckImportActors.cs" />
<Compile Include="PackageLoaders\D2kSoundResources.cs" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>mkdir "$(SolutionDir)mods/d2k/" <PostBuildEvent>mkdir "$(SolutionDir)mods/d2k/"

View File

@@ -0,0 +1,103 @@
#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 OpenRA.FileSystem;
namespace OpenRA.Mods.D2k.PackageLoaders
{
public class D2kSoundResourcesLoader : IPackageLoader
{
sealed class D2kSoundResources : IReadOnlyPackage
{
struct Entry
{
public readonly uint Offset;
public readonly uint Length;
public Entry(uint offset, uint length)
{
Offset = offset;
Length = length;
}
}
public string Name { get; private set; }
public IEnumerable<string> Contents { get { return index.Keys; } }
readonly Stream s;
readonly Dictionary<string, Entry> index = new Dictionary<string, Entry>();
public D2kSoundResources(Stream s, string filename)
{
Name = filename;
this.s = s;
try
{
var headerLength = s.ReadUInt32();
while (s.Position < headerLength + 4)
{
var name = s.ReadASCIIZ();
var offset = s.ReadUInt32();
var length = s.ReadUInt32();
index.Add(name, new Entry(offset, length));
}
}
catch
{
Dispose();
throw;
}
}
public Stream GetStream(string filename)
{
Entry e;
if (!index.TryGetValue(filename, out e))
return null;
s.Seek(e.Offset, SeekOrigin.Begin);
return new MemoryStream(s.ReadBytes((int)e.Length));
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem.FileSystem context)
{
// Not implemented
return null;
}
public bool Contains(string filename)
{
return index.ContainsKey(filename);
}
public void Dispose()
{
s.Dispose();
}
}
bool IPackageLoader.TryParsePackage(Stream s, string filename, FileSystem.FileSystem context, out IReadOnlyPackage package)
{
if (!filename.EndsWith(".rs", StringComparison.InvariantCultureIgnoreCase))
{
package = null;
return false;
}
package = new D2kSoundResources(s, filename);
return true;
}
}
}

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands
return; return;
var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; 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."); Console.WriteLine(dest + " saved.");
} }
} }

View File

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

View File

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

View File

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

View File

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