Merge pull request #13223 from pchote/mod-package-loaders
Unhardcode mod package loaders
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ using System.IO;
|
|||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
|
|
||||||
namespace OpenRA.FileSystem
|
namespace OpenRA.FileSystem
|
||||||
|
{
|
||||||
|
public class InstallShieldLoader : IPackageLoader
|
||||||
{
|
{
|
||||||
public sealed class InstallShieldPackage : IReadOnlyPackage
|
public sealed class InstallShieldPackage : IReadOnlyPackage
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
|
|
||||||
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;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.FileSystem
|
namespace OpenRA.FileSystem
|
||||||
|
{
|
||||||
|
public class MixLoader : IPackageLoader
|
||||||
{
|
{
|
||||||
public sealed class MixFile : IReadOnlyPackage
|
public sealed class MixFile : IReadOnlyPackage
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,13 @@
|
|||||||
*/
|
*/
|
||||||
#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
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
|||||||
103
OpenRA.Mods.D2k/PackageLoaders/D2kSoundResources.cs
Normal file
103
OpenRA.Mods.D2k/PackageLoaders/D2kSoundResources.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user