From 328bae550c8124b9b072ef29351c8c56ddf02544 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 20 Jan 2017 19:09:02 +0000 Subject: [PATCH] Implement external mod registration and launching. --- OpenRA.Game/ExternalMods.cs | 125 +++++++++++++++++++++++++++++++++ OpenRA.Game/Game.cs | 32 ++++++++- OpenRA.Game/OpenRA.Game.csproj | 1 + 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 OpenRA.Game/ExternalMods.cs diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs new file mode 100644 index 0000000000..b95df2b5ff --- /dev/null +++ b/OpenRA.Game/ExternalMods.cs @@ -0,0 +1,125 @@ +#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; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.Graphics; + +namespace OpenRA +{ + public class ExternalMod + { + public readonly string Id; + public readonly string Version; + public readonly string Title; + public readonly string LaunchPath; + public readonly string[] LaunchArgs; + public Sprite Icon { get; internal set; } + + public static string MakeKey(Manifest mod) { return MakeKey(mod.Id, mod.Metadata.Version); } + public static string MakeKey(ExternalMod mod) { return MakeKey(mod.Id, mod.Version); } + public static string MakeKey(string modId, string modVersion) { return modId + "-" + modVersion; } + } + + public class ExternalMods : IReadOnlyDictionary + { + readonly Dictionary mods; + readonly SheetBuilder sheetBuilder; + readonly string launchPath; + + public ExternalMods(string launchPath) + { + // Process.Start requires paths to not be quoted, even if they contain spaces + if (launchPath.First() == '"' && launchPath.Last() == '"') + launchPath = launchPath.Substring(1, launchPath.Length - 2); + + this.launchPath = launchPath; + sheetBuilder = new SheetBuilder(SheetType.BGRA, 256); + mods = LoadMods(); + } + + Dictionary LoadMods() + { + var ret = new Dictionary(); + var supportPath = Platform.ResolvePath(Path.Combine("^", "ModMetadata")); + if (!Directory.Exists(supportPath)) + return ret; + + foreach (var path in Directory.GetFiles(supportPath, "*.yaml")) + { + try + { + var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value; + var mod = FieldLoader.Load(yaml); + var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon"); + if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value)) + { + using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value))) + using (var bitmap = new Bitmap(stream)) + mod.Icon = sheetBuilder.Add(bitmap); + } + + ret.Add(ExternalMod.MakeKey(mod), mod); + } + catch (Exception e) + { + Log.Write("debug", "Failed to parse mod metadata file '{0}'", path); + Log.Write("debug", e.ToString()); + } + } + + return ret; + } + + internal void Register(Manifest mod) + { + if (mod.Metadata.Hidden) + return; + + var iconData = ""; + using (var stream = mod.Package.GetStream("icon.png")) + if (stream != null) + iconData = Convert.ToBase64String(stream.ReadAllBytes()); + + var key = ExternalMod.MakeKey(mod); + var yaml = new List() + { + new MiniYamlNode("Registration", new MiniYaml("", new List() + { + new MiniYamlNode("Id", mod.Id), + new MiniYamlNode("Version", mod.Metadata.Version), + new MiniYamlNode("Title", mod.Metadata.Title), + new MiniYamlNode("Icon", iconData), + new MiniYamlNode("LaunchPath", launchPath), + new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id) + })) + }; + + var supportPath = Platform.ResolvePath(Path.Combine("^", "ModMetadata")); + Directory.CreateDirectory(supportPath); + + File.WriteAllLines(Path.Combine(supportPath, key + ".yaml"), yaml.ToLines(false).ToArray()); + } + + public ExternalMod this[string key] { get { return mods[key]; } } + public int Count { get { return mods.Count; } } + public ICollection Keys { get { return mods.Keys; } } + public ICollection Values { get { return mods.Values; } } + public bool ContainsKey(string key) { return mods.ContainsKey(key); } + public IEnumerator> GetEnumerator() { return mods.GetEnumerator(); } + public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); } + IEnumerator IEnumerable.GetEnumerator() { return mods.GetEnumerator(); } + } +} diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 6a764b488c..50bb78bab7 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -37,6 +37,7 @@ namespace OpenRA public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms public static InstalledMods Mods { get; private set; } + public static ExternalMods ExternalMods { get; private set; } public static ModData ModData; public static Settings Settings; @@ -314,10 +315,16 @@ namespace OpenRA GlobalChat = new GlobalChat(); Mods = new InstalledMods(customModPath); - Console.WriteLine("Available mods:"); + Console.WriteLine("Internal mods:"); foreach (var mod in Mods) Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version); + var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location); + ExternalMods = new ExternalMods(launchPath); + Console.WriteLine("External mods:"); + foreach (var mod in ExternalMods) + Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version); + InitializeMod(Settings.Game.Mod, args); } @@ -370,6 +377,7 @@ namespace OpenRA Sound.StopVideo(); ModData = new ModData(Mods[mod], Mods, true); + ExternalMods.Register(ModData.Manifest); using (new PerfTimer("LoadMaps")) ModData.MapCache.LoadMaps(); @@ -457,6 +465,28 @@ namespace OpenRA return shellmaps.Random(CosmeticRandom); } + public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null) + { + try + { + var argsString = mod.LaunchArgs.Append(launchArguments) + .Select(a => "\"" + a + "\"").JoinWith(" "); + + var p = Process.Start(mod.LaunchPath, argsString); + if (p == null || p.HasExited) + onFailed(); + else + { + p.Close(); + Exit(); + } + } + catch + { + onFailed(); + } + } + static RunStatus state = RunStatus.Running; public static event Action OnQuit = () => { }; diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 96b0fe5cf7..e7788cf931 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -246,6 +246,7 @@ +