Merge pull request #11771 from pchote/split-content-files
Rework mod enumeration and split content metadata into their own files.
This commit is contained in:
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
@@ -34,9 +33,15 @@ namespace OpenRA.FileSystem
|
||||
|
||||
// Mod packages that should not be disposed
|
||||
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
|
||||
readonly IReadOnlyDictionary<string, Manifest> installedMods;
|
||||
|
||||
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||
|
||||
public FileSystem(IReadOnlyDictionary<string, Manifest> installedMods)
|
||||
{
|
||||
this.installedMods = installedMods;
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename)
|
||||
{
|
||||
if (filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -108,9 +113,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
ModMetadata mod;
|
||||
if (!ModMetadata.AllMods.TryGetValue(name, out mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, ModMetadata.AllMods.Keys.JoinWith(", ")));
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(name, out mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
|
||||
package = mod.Package;
|
||||
modPackages.Add(package);
|
||||
|
||||
@@ -21,6 +21,7 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Chat;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
@@ -35,6 +36,8 @@ namespace OpenRA
|
||||
public const int Timestep = 40;
|
||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||
|
||||
public static InstalledMods Mods { get; private set; }
|
||||
|
||||
public static ModData ModData;
|
||||
public static Settings Settings;
|
||||
public static ICursor Cursor;
|
||||
@@ -243,6 +246,16 @@ namespace OpenRA
|
||||
{
|
||||
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
|
||||
|
||||
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
||||
// then we use this to override the mod search path, and replace it with the mod id
|
||||
var modArgument = args.GetValue("Game.Mod", null);
|
||||
string customModPath = null;
|
||||
if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument)))
|
||||
{
|
||||
customModPath = modArgument;
|
||||
args.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument));
|
||||
}
|
||||
|
||||
InitializeSettings(args);
|
||||
|
||||
Log.AddChannel("perf", "perf.log");
|
||||
@@ -302,22 +315,23 @@ namespace OpenRA
|
||||
|
||||
GlobalChat = new GlobalChat();
|
||||
|
||||
Mods = new InstalledMods(customModPath);
|
||||
Console.WriteLine("Available mods:");
|
||||
foreach (var mod in ModMetadata.AllMods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
|
||||
InitializeMod(Settings.Game.Mod, args);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(string modId)
|
||||
{
|
||||
return ModMetadata.AllMods[modId].RequiresMods.All(IsModInstalled);
|
||||
return Mods.ContainsKey(modId) && Mods[modId].RequiresMods.All(IsModInstalled);
|
||||
}
|
||||
|
||||
public static bool IsModInstalled(KeyValuePair<string, string> mod)
|
||||
{
|
||||
return ModMetadata.AllMods.ContainsKey(mod.Key)
|
||||
&& ModMetadata.AllMods[mod.Key].Version == mod.Value
|
||||
return Mods.ContainsKey(mod.Key)
|
||||
&& Mods[mod.Key].Metadata.Version == mod.Value
|
||||
&& IsModInstalled(mod.Key);
|
||||
}
|
||||
|
||||
@@ -349,7 +363,7 @@ namespace OpenRA
|
||||
ModData = null;
|
||||
|
||||
// Fall back to default if the mod doesn't exist or has missing prerequisites.
|
||||
if (!ModMetadata.AllMods.ContainsKey(mod) || !IsModInstalled(mod))
|
||||
if (!IsModInstalled(mod))
|
||||
mod = new GameSettings().Mod;
|
||||
|
||||
Console.WriteLine("Loading mod: {0}", mod);
|
||||
@@ -357,18 +371,13 @@ namespace OpenRA
|
||||
|
||||
Sound.StopVideo();
|
||||
|
||||
ModData = new ModData(mod, true);
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
var content = ModData.Manifest.Get<ModContent>();
|
||||
var isModContentInstalled = content.Packages
|
||||
.Where(p => p.Value.Required)
|
||||
.All(p => p.Value.TestFiles.All(f => File.Exists(Platform.ResolvePath(f))));
|
||||
|
||||
// Mod assets are missing!
|
||||
if (!isModContentInstalled)
|
||||
if (!ModData.LoadScreen.RequiredContentIsInstalled())
|
||||
{
|
||||
InitializeMod("modchooser", new Arguments());
|
||||
return;
|
||||
@@ -469,8 +478,8 @@ namespace OpenRA
|
||||
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
var mod = ModData.Manifest.Mod;
|
||||
var directory = Platform.ResolvePath("^", "Screenshots", mod.Id, mod.Version);
|
||||
var mod = ModData.Manifest.Metadata;
|
||||
var directory = Platform.ResolvePath("^", "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var filename = TimestampedFilename(true);
|
||||
|
||||
@@ -9,8 +9,23 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class Utility
|
||||
{
|
||||
public readonly ModData ModData;
|
||||
public readonly InstalledMods Mods;
|
||||
|
||||
public Utility(ModData modData, InstalledMods mods)
|
||||
{
|
||||
ModData = modData;
|
||||
Mods = mods;
|
||||
}
|
||||
}
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface IUtilityCommand
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,6 +35,6 @@ namespace OpenRA
|
||||
|
||||
bool ValidateArguments(string[] args);
|
||||
|
||||
void Run(ModData modData, string[] args);
|
||||
void Run(Utility utility, string[] args);
|
||||
}
|
||||
}
|
||||
|
||||
121
OpenRA.Game/InstalledMods.cs
Normal file
121
OpenRA.Game/InstalledMods.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class InstalledMods : IReadOnlyDictionary<string, Manifest>
|
||||
{
|
||||
readonly Dictionary<string, Manifest> mods;
|
||||
|
||||
public InstalledMods(string customModPath)
|
||||
{
|
||||
mods = GetInstalledMods(customModPath);
|
||||
}
|
||||
|
||||
static IEnumerable<Pair<string, string>> GetCandidateMods()
|
||||
{
|
||||
// Get mods that are in the game folder.
|
||||
var basePath = Platform.ResolvePath(Path.Combine(".", "mods"));
|
||||
var mods = Directory.GetDirectories(basePath)
|
||||
.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.
|
||||
var supportPath = Platform.ResolvePath(Path.Combine("^", "mods"));
|
||||
if (!Directory.Exists(supportPath))
|
||||
return mods;
|
||||
|
||||
foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1)))
|
||||
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;
|
||||
}
|
||||
|
||||
static Manifest LoadMod(string id, string path)
|
||||
{
|
||||
IReadOnlyPackage package = null;
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
if (!package.Contains("mod.yaml"))
|
||||
throw new InvalidDataException(path + " is not a valid mod package");
|
||||
|
||||
// Mods in the support directory and oramod packages (which are listed later
|
||||
// in the CandidateMods list) override mods in the main install.
|
||||
return new Manifest(id, package);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<string, Manifest> GetInstalledMods(string customModPath)
|
||||
{
|
||||
var ret = new Dictionary<string, Manifest>();
|
||||
var candidates = GetCandidateMods();
|
||||
if (customModPath != null)
|
||||
candidates = candidates.Append(Pair.New(Path.GetFileNameWithoutExtension(customModPath), customModPath));
|
||||
|
||||
foreach (var pair in candidates)
|
||||
{
|
||||
var mod = LoadMod(pair.First, pair.Second);
|
||||
|
||||
// Mods in the support directory and oramod packages (which are listed later
|
||||
// in the CandidateMods list) override mods in the main install.
|
||||
if (mod != null)
|
||||
ret[pair.First] = mod;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Manifest this[string key] { get { return mods[key]; } }
|
||||
public int Count { get { return mods.Count; } }
|
||||
public ICollection<string> Keys { get { return mods.Keys; } }
|
||||
public ICollection<Manifest> Values { get { return mods.Values; } }
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); }
|
||||
IEnumerator IEnumerable.GetEnumerator() { return mods.GetEnumerator(); }
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,21 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public class ModMetadata
|
||||
{
|
||||
public string Title;
|
||||
public string Description;
|
||||
public string Version;
|
||||
public string Author;
|
||||
public bool Hidden;
|
||||
}
|
||||
|
||||
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
|
||||
public class Manifest
|
||||
{
|
||||
public readonly ModMetadata Mod;
|
||||
public readonly string Id;
|
||||
public readonly IReadOnlyPackage Package;
|
||||
public readonly ModMetadata Metadata;
|
||||
public readonly string[]
|
||||
Rules, ServerTraits,
|
||||
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
@@ -60,14 +71,15 @@ namespace OpenRA
|
||||
readonly TypeDictionary modules = new TypeDictionary();
|
||||
readonly Dictionary<string, MiniYaml> yaml;
|
||||
|
||||
public Manifest(string modId)
|
||||
{
|
||||
var package = ModMetadata.AllMods[modId].Package;
|
||||
bool customDataLoaded;
|
||||
|
||||
public Manifest(string modId, IReadOnlyPackage package)
|
||||
{
|
||||
Id = modId;
|
||||
Package = package;
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml")).ToDictionary();
|
||||
|
||||
Mod = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
||||
Mod.Id = modId;
|
||||
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
||||
|
||||
// TODO: Use fieldloader
|
||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||
@@ -106,7 +118,7 @@ namespace OpenRA
|
||||
RequiresMods = yaml["RequiresMods"].ToDictionary(my => my.Value);
|
||||
|
||||
// Allow inherited mods to import parent maps.
|
||||
var compat = new List<string> { Mod.Id };
|
||||
var compat = new List<string> { Id };
|
||||
|
||||
if (yaml.ContainsKey("SupportsMapsFrom"))
|
||||
compat.AddRange(yaml["SupportsMapsFrom"].Value.Split(',').Select(c => c.Trim()));
|
||||
@@ -147,6 +159,8 @@ namespace OpenRA
|
||||
|
||||
modules.Add(module);
|
||||
}
|
||||
|
||||
customDataLoaded = true;
|
||||
}
|
||||
|
||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key, bool parsePaths = false)
|
||||
@@ -171,8 +185,12 @@ namespace OpenRA
|
||||
return modules.Contains<T>();
|
||||
}
|
||||
|
||||
/// <summary>Load a cached IGlobalModData instance.</summary>
|
||||
public T Get<T>() where T : IGlobalModData
|
||||
{
|
||||
if (!customDataLoaded)
|
||||
throw new InvalidOperationException("Attempted to call Manifest.Get() before loading custom data!");
|
||||
|
||||
var module = modules.GetOrDefault<T>();
|
||||
|
||||
// Lazily create the default values if not explicitly defined.
|
||||
@@ -184,5 +202,36 @@ namespace OpenRA
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load an uncached IGlobalModData instance directly from the manifest yaml.
|
||||
/// This should only be used by external mods that want to query data from this mod.
|
||||
/// </summary>
|
||||
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
|
||||
{
|
||||
MiniYaml data;
|
||||
var t = typeof(T);
|
||||
if (!yaml.TryGetValue(t.Name, out data))
|
||||
{
|
||||
// Lazily create the default values if not explicitly defined.
|
||||
return (T)oc.CreateBasic(t);
|
||||
}
|
||||
|
||||
IGlobalModData module;
|
||||
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
|
||||
if (ctor != null)
|
||||
{
|
||||
// Class has opted-in to DIY initialization
|
||||
module = (IGlobalModData)ctor.Invoke(new object[] { data.Value });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Automatically load the child nodes using FieldLoader
|
||||
module = oc.CreateObject<IGlobalModData>(t.Name);
|
||||
FieldLoader.Load(module, data);
|
||||
}
|
||||
|
||||
return (T)module;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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 System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class ModContent : IGlobalModData
|
||||
{
|
||||
public enum SourceType { Disc, Install }
|
||||
public class ModPackage
|
||||
{
|
||||
public readonly string Title;
|
||||
public readonly string[] TestFiles = { };
|
||||
public readonly string[] Sources = { };
|
||||
public readonly bool Required;
|
||||
public readonly string Download;
|
||||
|
||||
public ModPackage(MiniYaml yaml)
|
||||
{
|
||||
Title = yaml.Value;
|
||||
FieldLoader.Load(this, yaml);
|
||||
}
|
||||
|
||||
public bool IsInstalled()
|
||||
{
|
||||
return TestFiles.All(file => File.Exists(Path.GetFullPath(Platform.ResolvePath(file))));
|
||||
}
|
||||
}
|
||||
|
||||
public class ModSource
|
||||
{
|
||||
public readonly SourceType Type = SourceType.Disc;
|
||||
|
||||
// Used to find installation locations for SourceType.Install
|
||||
public readonly string RegistryKey;
|
||||
public readonly string RegistryValue;
|
||||
|
||||
public readonly string Title;
|
||||
public readonly Dictionary<string, string> IDFiles;
|
||||
|
||||
[FieldLoader.Ignore] public readonly List<MiniYamlNode> Install;
|
||||
|
||||
public ModSource(MiniYaml yaml)
|
||||
{
|
||||
Title = yaml.Value;
|
||||
var installNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Install");
|
||||
if (installNode != null)
|
||||
Install = installNode.Value.Nodes;
|
||||
|
||||
FieldLoader.Load(this, yaml);
|
||||
}
|
||||
}
|
||||
|
||||
public class ModDownload
|
||||
{
|
||||
public readonly string Title;
|
||||
public readonly string URL;
|
||||
public readonly string MirrorList;
|
||||
public readonly Dictionary<string, string> Extract;
|
||||
|
||||
public ModDownload(MiniYaml yaml)
|
||||
{
|
||||
Title = yaml.Value;
|
||||
FieldLoader.Load(this, yaml);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string InstallPromptMessage;
|
||||
public readonly string QuickDownload;
|
||||
public readonly string HeaderMessage;
|
||||
|
||||
[FieldLoader.LoadUsing("LoadPackages")]
|
||||
public readonly Dictionary<string, ModPackage> Packages = new Dictionary<string, ModPackage>();
|
||||
|
||||
static object LoadPackages(MiniYaml yaml)
|
||||
{
|
||||
var packages = new Dictionary<string, ModPackage>();
|
||||
var packageNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Packages");
|
||||
if (packageNode != null)
|
||||
foreach (var node in packageNode.Value.Nodes)
|
||||
packages.Add(node.Key, new ModPackage(node.Value));
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
[FieldLoader.LoadUsing("LoadDownloads")]
|
||||
public readonly Dictionary<string, ModDownload> Downloads;
|
||||
|
||||
static object LoadDownloads(MiniYaml yaml)
|
||||
{
|
||||
var downloads = new Dictionary<string, ModDownload>();
|
||||
var downloadNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Downloads");
|
||||
if (downloadNode != null)
|
||||
foreach (var node in downloadNode.Value.Nodes)
|
||||
downloads.Add(node.Key, new ModDownload(node.Value));
|
||||
|
||||
return downloads;
|
||||
}
|
||||
|
||||
[FieldLoader.LoadUsing("LoadSources")]
|
||||
public readonly Dictionary<string, ModSource> Sources;
|
||||
|
||||
static object LoadSources(MiniYaml yaml)
|
||||
{
|
||||
var sources = new Dictionary<string, ModSource>();
|
||||
var sourceNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sources");
|
||||
if (sourceNode != null)
|
||||
foreach (var node in sourceNode.Value.Nodes)
|
||||
sources.Add(node.Key, new ModSource(node.Value));
|
||||
|
||||
return sources;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA
|
||||
public ILoadScreen LoadScreen { get; private set; }
|
||||
public VoxelLoader VoxelLoader { get; private set; }
|
||||
public CursorProvider CursorProvider { get; private set; }
|
||||
public FS ModFiles = new FS();
|
||||
public FS ModFiles;
|
||||
public IReadOnlyFileSystem DefaultFileSystem { get { return ModFiles; } }
|
||||
|
||||
readonly Lazy<Ruleset> defaultRules;
|
||||
@@ -45,10 +45,14 @@ namespace OpenRA
|
||||
readonly Lazy<IReadOnlyDictionary<string, SequenceProvider>> defaultSequences;
|
||||
public IReadOnlyDictionary<string, SequenceProvider> DefaultSequences { get { return defaultSequences.Value; } }
|
||||
|
||||
public ModData(string mod, bool useLoadScreen = false)
|
||||
public ModData(Manifest mod, InstalledMods mods, bool useLoadScreen = false)
|
||||
{
|
||||
Languages = new string[0];
|
||||
Manifest = new Manifest(mod);
|
||||
|
||||
ModFiles = new FS(mods);
|
||||
|
||||
// Take a local copy of the manifest
|
||||
Manifest = new Manifest(mod.Id, mod.Package);
|
||||
ModFiles.LoadFromManifest(Manifest);
|
||||
|
||||
ObjectCreator = new ObjectCreator(Manifest, ModFiles);
|
||||
@@ -220,6 +224,7 @@ namespace OpenRA
|
||||
{
|
||||
void Init(ModData m, Dictionary<string, string> info);
|
||||
void Display();
|
||||
bool RequiredContentIsInstalled();
|
||||
void StartGame(Arguments args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2016 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 OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class ModMetadata
|
||||
{
|
||||
public static readonly Dictionary<string, ModMetadata> AllMods = ValidateMods();
|
||||
|
||||
public string Id;
|
||||
public string Title;
|
||||
public string Description;
|
||||
public string Version;
|
||||
public string Author;
|
||||
public bool Hidden;
|
||||
|
||||
public Dictionary<string, string> RequiresMods;
|
||||
public ModContent ModContent;
|
||||
public IReadOnlyPackage Package;
|
||||
|
||||
static Dictionary<string, ModMetadata> ValidateMods()
|
||||
{
|
||||
var ret = new Dictionary<string, ModMetadata>();
|
||||
foreach (var pair in GetCandidateMods())
|
||||
{
|
||||
IReadOnlyPackage package = null;
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(pair.Second))
|
||||
package = new Folder(pair.Second);
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var fileStream = File.OpenRead(pair.Second))
|
||||
package = new ZipFile(fileStream, pair.Second);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidDataException(pair.Second + " is not a valid mod package");
|
||||
}
|
||||
}
|
||||
|
||||
if (!package.Contains("mod.yaml"))
|
||||
{
|
||||
package.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml"));
|
||||
var nd = yaml.ToDictionary();
|
||||
if (!nd.ContainsKey("Metadata"))
|
||||
{
|
||||
package.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var metadata = FieldLoader.Load<ModMetadata>(nd["Metadata"]);
|
||||
metadata.Id = pair.First;
|
||||
metadata.Package = package;
|
||||
|
||||
if (nd.ContainsKey("RequiresMods"))
|
||||
metadata.RequiresMods = nd["RequiresMods"].ToDictionary(my => my.Value);
|
||||
else
|
||||
metadata.RequiresMods = new Dictionary<string, string>();
|
||||
|
||||
if (nd.ContainsKey("ModContent"))
|
||||
metadata.ModContent = FieldLoader.Load<ModContent>(nd["ModContent"]);
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
Console.WriteLine("An exception occurred when trying to load ModMetadata for `{0}`:".F(pair.First));
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static IEnumerable<Pair<string, string>> GetCandidateMods()
|
||||
{
|
||||
// Get mods that are in the game folder.
|
||||
var basePath = Platform.ResolvePath(Path.Combine(".", "mods"));
|
||||
var mods = Directory.GetDirectories(basePath)
|
||||
.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.
|
||||
var supportPath = Platform.ResolvePath(Path.Combine("^", "mods"));
|
||||
if (!Directory.Exists(supportPath))
|
||||
return mods;
|
||||
|
||||
foreach (var pair in Directory.GetDirectories(supportPath).ToDictionary(x => x.Substring(supportPath.Length + 1)))
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,14 +38,14 @@ namespace OpenRA.Network
|
||||
{
|
||||
FieldLoader.Load(this, yaml);
|
||||
|
||||
ModMetadata mod;
|
||||
Manifest mod;
|
||||
var modVersion = Mods.Split('@');
|
||||
if (modVersion.Length == 2 && ModMetadata.AllMods.TryGetValue(modVersion[0], out mod))
|
||||
if (modVersion.Length == 2 && Game.Mods.TryGetValue(modVersion[0], out mod))
|
||||
{
|
||||
ModId = modVersion[0];
|
||||
ModVersion = modVersion[1];
|
||||
ModLabel = "{0} ({1})".F(mod.Title, modVersion[1]);
|
||||
IsCompatible = Game.Settings.Debug.IgnoreVersionMismatch || ModVersion == mod.Version;
|
||||
ModLabel = "{0} ({1})".F(mod.Metadata.Title, modVersion[1]);
|
||||
IsCompatible = Game.Settings.Debug.IgnoreVersionMismatch || ModVersion == mod.Metadata.Version;
|
||||
}
|
||||
else
|
||||
ModLabel = "Unknown mod: {0}".F(Mods);
|
||||
|
||||
@@ -44,8 +44,8 @@ namespace OpenRA.Network
|
||||
void StartSavingReplay(byte[] initialContent)
|
||||
{
|
||||
var filename = chooseFilename();
|
||||
var mod = Game.ModData.Manifest.Mod;
|
||||
var dir = Platform.ResolvePath("^", "Replays", mod.Id, mod.Version);
|
||||
var mod = Game.ModData.Manifest;
|
||||
var dir = Platform.ResolvePath("^", "Replays", mod.Id, mod.Metadata.Version);
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
if (r.Frame == frame)
|
||||
{
|
||||
var mod = Game.ModData.Manifest.Mod;
|
||||
var mod = Game.ModData.Manifest.Metadata;
|
||||
Log.Write("sync", "Player: {0} ({1} {2} {3})", Game.Settings.Player.Name, Platform.CurrentPlatform, Environment.OSVersion, Platform.RuntimeVersion);
|
||||
Log.Write("sync", "Game ID: {0} (Mod: {1} at Version {2})", orderManager.LobbyInfo.GlobalSettings.GameUid, mod.Title, mod.Version);
|
||||
Log.Write("sync", "Sync for net frame {0} -------------", r.Frame);
|
||||
|
||||
@@ -132,13 +132,13 @@ namespace OpenRA.Network
|
||||
case "HandshakeRequest":
|
||||
{
|
||||
// Switch to the server's mod if we need and are able to
|
||||
var mod = Game.ModData.Manifest.Mod;
|
||||
var mod = Game.ModData.Manifest;
|
||||
var request = HandshakeRequest.Deserialize(order.TargetString);
|
||||
|
||||
ModMetadata serverMod;
|
||||
Manifest serverMod;
|
||||
if (request.Mod != mod.Id &&
|
||||
ModMetadata.AllMods.TryGetValue(request.Mod, out serverMod) &&
|
||||
serverMod.Version == request.Version)
|
||||
Game.Mods.TryGetValue(request.Mod, out serverMod) &&
|
||||
serverMod.Metadata.Version == request.Version)
|
||||
{
|
||||
var replay = orderManager.Connection as ReplayConnection;
|
||||
var launchCommand = replay != null ?
|
||||
@@ -170,7 +170,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
Client = info,
|
||||
Mod = mod.Id,
|
||||
Version = mod.Version,
|
||||
Version = mod.Metadata.Version,
|
||||
Password = orderManager.Password
|
||||
};
|
||||
|
||||
|
||||
@@ -204,7 +204,6 @@
|
||||
<Compile Include="Traits\Player\FixedColorPalette.cs" />
|
||||
<Compile Include="Primitives\ReadOnlyDictionary.cs" />
|
||||
<Compile Include="Primitives\ReadOnlyList.cs" />
|
||||
<Compile Include="ModMetadata.cs" />
|
||||
<Compile Include="GameRules\Ruleset.cs" />
|
||||
<Compile Include="Support\MersenneTwister.cs" />
|
||||
<Compile Include="GameInformation.cs" />
|
||||
@@ -244,7 +243,7 @@
|
||||
<Compile Include="Traits\ActivityUtils.cs" />
|
||||
<Compile Include="FileSystem\ZipFolder.cs" />
|
||||
<Compile Include="Primitives\float3.cs" />
|
||||
<Compile Include="ModContent.cs" />
|
||||
<Compile Include="InstalledMods.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace OpenRA.Server
|
||||
foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
|
||||
t.ServerStarted(this);
|
||||
|
||||
Log.Write("server", "Initial mod: {0}", ModData.Manifest.Mod.Id);
|
||||
Log.Write("server", "Initial mod: {0}", ModData.Manifest.Id);
|
||||
Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map);
|
||||
|
||||
var timeout = serverTraits.WithInterface<ITick>().Min(t => t.TickTimeout);
|
||||
@@ -262,8 +262,8 @@ namespace OpenRA.Server
|
||||
// Dispatch a handshake order
|
||||
var request = new HandshakeRequest
|
||||
{
|
||||
Mod = ModData.Manifest.Mod.Id,
|
||||
Version = ModData.Manifest.Mod.Version,
|
||||
Mod = ModData.Manifest.Id,
|
||||
Version = ModData.Manifest.Metadata.Version,
|
||||
Map = LobbyInfo.GlobalSettings.Map
|
||||
};
|
||||
|
||||
@@ -327,7 +327,7 @@ namespace OpenRA.Server
|
||||
else
|
||||
client.Color = HSLColor.FromRGB(255, 255, 255);
|
||||
|
||||
if (ModData.Manifest.Mod.Id != handshake.Mod)
|
||||
if (ModData.Manifest.Id != handshake.Mod)
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; mods do not match.",
|
||||
newConn.Socket.RemoteEndPoint);
|
||||
@@ -337,7 +337,7 @@ namespace OpenRA.Server
|
||||
return;
|
||||
}
|
||||
|
||||
if (ModData.Manifest.Mod.Version != handshake.Version && !LobbyInfo.GlobalSettings.AllowVersionMismatch)
|
||||
if (ModData.Manifest.Metadata.Version != handshake.Version && !LobbyInfo.GlobalSettings.AllowVersionMismatch)
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; Not running the same version.",
|
||||
newConn.Socket.RemoteEndPoint);
|
||||
|
||||
@@ -35,5 +35,6 @@ namespace OpenRA
|
||||
|
||||
public bool Contains(string key) { return args.ContainsKey(key); }
|
||||
public string GetValue(string key, string defaultValue) { return Contains(key) ? args[key] : defaultValue; }
|
||||
public void ReplaceValue(string key, string value) { args[key] = value; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace OpenRA
|
||||
|
||||
if (Game.ModData != null)
|
||||
{
|
||||
var mod = Game.ModData.Manifest.Mod;
|
||||
var mod = Game.ModData.Manifest.Metadata;
|
||||
Log.Write("exception", "{0} Mod at Version {1}", mod.Title, mod.Version);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,8 +176,8 @@ namespace OpenRA
|
||||
|
||||
gameInfo = new GameInformation
|
||||
{
|
||||
Mod = Game.ModData.Manifest.Mod.Id,
|
||||
Version = Game.ModData.Manifest.Mod.Version,
|
||||
Mod = Game.ModData.Manifest.Id,
|
||||
Version = Game.ModData.Manifest.Metadata.Version,
|
||||
|
||||
MapUid = Map.Uid,
|
||||
MapTitle = Map.Title
|
||||
|
||||
Reference in New Issue
Block a user