Merge pull request #10841 from pchote/oramod

Add support for loading mods from `oramod` packages.
This commit is contained in:
Oliver Brakmann
2016-02-29 22:13:40 +01:00
5 changed files with 146 additions and 24 deletions

View File

@@ -32,6 +32,9 @@ namespace OpenRA.FileSystem
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>(); readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>(); readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
// Mod packages that should not be disposed
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
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 IReadOnlyPackage OpenPackage(string filename) public IReadOnlyPackage OpenPackage(string filename)
@@ -42,6 +45,8 @@ namespace OpenRA.FileSystem
return new ZipFile(this, filename); return new ZipFile(this, filename);
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(this, filename); return new ZipFile(this, filename);
if (filename.EndsWith(".oramod", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(this, filename);
if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase)) if (filename.EndsWith(".RS", StringComparison.InvariantCultureIgnoreCase))
return new D2kSoundResources(this, filename); return new D2kSoundResources(this, filename);
if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase)) if (filename.EndsWith(".Z", StringComparison.InvariantCultureIgnoreCase))
@@ -58,8 +63,7 @@ namespace OpenRA.FileSystem
IReadOnlyPackage parent; IReadOnlyPackage parent;
string subPath = null; string subPath = null;
if (TryGetPackageContaining(filename, out parent, out subPath)) if (TryGetPackageContaining(filename, out parent, out subPath))
if (parent is Folder) return OpenPackage(subPath, parent);
return new Folder(Path.Combine(((Folder)parent).Name, subPath));
return new Folder(Platform.ResolvePath(filename)); return new Folder(Platform.ResolvePath(filename));
} }
@@ -87,6 +91,15 @@ namespace OpenRA.FileSystem
if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase)) if (filename.EndsWith(".oramap", StringComparison.InvariantCultureIgnoreCase))
return new ZipFile(this, filename, parent.GetStream(filename)); return new ZipFile(this, filename, parent.GetStream(filename));
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);
}
return null; return null;
} }
@@ -106,18 +119,25 @@ namespace OpenRA.FileSystem
if (optional) if (optional)
name = name.Substring(1); name = name.Substring(1);
var modPackage = name.StartsWith("$"); try
if (modPackage)
name = name.Substring(1);
Action a = () => Mount(modPackage ? ModMetadata.AllMods[name].Package : OpenPackage(name), explicitName);
if (optional)
{ {
try { a(); } IReadOnlyPackage package;
catch { } if (name.StartsWith("$"))
{
name = name.Substring(1);
package = ModMetadata.AllMods[name].Package;
modPackages.Add(package);
}
else
package = OpenPackage(name);
Mount(package, explicitName);
}
catch
{
if (!optional)
throw;
} }
else
a();
} }
public void Mount(IReadOnlyPackage package, string explicitName = null) public void Mount(IReadOnlyPackage package, string explicitName = null)
@@ -166,7 +186,11 @@ namespace OpenRA.FileSystem
foreach (var key in explicitKeys) foreach (var key in explicitKeys)
explicitMounts.Remove(key); explicitMounts.Remove(key);
package.Dispose(); // Mod packages aren't owned by us, so we shouldn't dispose them
if (modPackages.Contains(package))
modPackages.Remove(package);
else
package.Dispose();
} }
else else
mountedPackages[package] = mountCount; mountedPackages[package] = mountCount;
@@ -177,10 +201,13 @@ namespace OpenRA.FileSystem
public void UnmountAll() public void UnmountAll()
{ {
foreach (var package in mountedPackages.Keys) foreach (var package in mountedPackages.Keys)
package.Dispose(); if (!modPackages.Contains(package))
package.Dispose();
mountedPackages.Clear(); mountedPackages.Clear();
explicitMounts.Clear(); explicitMounts.Clear();
modPackages.Clear();
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>()); fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
} }

View File

@@ -0,0 +1,75 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 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. 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

@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Primitives;
namespace OpenRA namespace OpenRA
{ {
@@ -40,10 +41,19 @@ namespace OpenRA
try try
{ {
IReadOnlyPackage package = null; IReadOnlyPackage package = null;
if (Directory.Exists(pair.Value)) if (Directory.Exists(pair.Second))
package = new Folder(pair.Value); package = new Folder(pair.Second);
else else
throw new InvalidDataException(pair.Value + " is not a valid mod package"); {
try
{
package = new ZipFile(null, pair.Second);
}
catch
{
throw new InvalidDataException(pair.Second + " is not a valid mod package");
}
}
if (!package.Contains("mod.yaml")) if (!package.Contains("mod.yaml"))
continue; continue;
@@ -54,7 +64,7 @@ namespace OpenRA
continue; continue;
var metadata = FieldLoader.Load<ModMetadata>(nd["Metadata"]); var metadata = FieldLoader.Load<ModMetadata>(nd["Metadata"]);
metadata.Id = pair.Key; metadata.Id = pair.First;
metadata.Package = package; metadata.Package = package;
if (nd.ContainsKey("RequiresMods")) if (nd.ContainsKey("RequiresMods"))
@@ -65,11 +75,13 @@ namespace OpenRA
if (nd.ContainsKey("ContentInstaller")) if (nd.ContainsKey("ContentInstaller"))
metadata.Content = FieldLoader.Load<ContentInstaller>(nd["ContentInstaller"]); metadata.Content = FieldLoader.Load<ContentInstaller>(nd["ContentInstaller"]);
ret.Add(pair.Key, metadata); // Mods in the support directory and oramod packages (which are listed later
// in the CandidateMods list) override mods in the main install.
ret[pair.First] = metadata;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("An exception occurred when trying to load ModMetadata for `{0}`:".F(pair.Key)); Console.WriteLine("An exception occurred when trying to load ModMetadata for `{0}`:".F(pair.First));
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }
} }
@@ -77,12 +89,16 @@ namespace OpenRA
return ret; return ret;
} }
static Dictionary<string, string> GetCandidateMods() static IEnumerable<Pair<string, string>> GetCandidateMods()
{ {
// Get mods that are in the game folder. // Get mods that are in the game folder.
var basePath = Platform.ResolvePath(Path.Combine(".", "mods")); var basePath = Platform.ResolvePath(Path.Combine(".", "mods"));
var mods = Directory.GetDirectories(basePath) var mods = Directory.GetDirectories(basePath)
.ToDictionary(x => x.Substring(basePath.Length + 1)); .Select(x => Pair.New(x.Substring(basePath.Length + 1), x))
.ToList();
foreach (var m in Directory.GetFiles(basePath, "*.oramod"))
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m));
// Get mods that are in the support folder. // Get mods that are in the support folder.
var supportPath = Platform.ResolvePath(Path.Combine("^", "mods")); var supportPath = Platform.ResolvePath(Path.Combine("^", "mods"));
@@ -90,7 +106,10 @@ namespace OpenRA
return mods; return mods;
foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1))) foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1)))
mods.Add(pair.Key, pair.Value); mods.Add(Pair.New(pair.Key, pair.Value));
foreach (var m in Directory.GetFiles(supportPath, "*.oramod"))
mods.Add(Pair.New(Path.GetFileNameWithoutExtension(m), m));
return mods; return mods;
} }

View File

@@ -255,6 +255,7 @@
<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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" /> <Compile Include="FileSystem\D2kSoundResources.cs" />

View File

@@ -121,7 +121,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; var mapIsUnpacked = map.Package != null && (map.Package is Folder || map.Package is ZipFolder);
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);