using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using Tesla.Net;
namespace TeslaConsole;
/// Role a launch entry fills; only meaningful for products that prompt with the start-params dialog.
public enum AppHostType
{
None,
GameClient,
LiveCamera,
MissionReview
}
/// One launchable application a product registers on a pod.
public class LaunchEntryDefinition
{
public Guid LaunchKey;
public string DisplayName = "";
public string Exe = "";
/// Arguments; the token "{res}" expands to " -res W H" when a custom resolution is chosen, else "".
public string Args = "";
/// Optional; when null/empty the working directory is derived from .
public string WorkingDirectory;
public bool AutoRestart = true;
public AppHostType HostType;
/// Builds the wire for this entry, expanding {res} from the optional resolution.
public LaunchData ToLaunchData(Size? resolution)
{
string resArg = resolution.HasValue
? $" -res {resolution.Value.Width} {resolution.Value.Height}"
: "";
LaunchData data = default(LaunchData);
data.LaunchPair.DisplayName = DisplayName;
data.LaunchPair.LaunchKey = LaunchKey;
data.ExeFile = Exe;
data.Arguments = (Args ?? "").Replace("{res}", resArg);
data.WorkingDirectory = string.IsNullOrEmpty(WorkingDirectory)
? Path.GetDirectoryName(Exe)
: WorkingDirectory;
data.AutoRestart = AutoRestart;
return data;
}
}
/// An installable product: an install/package key plus the launch entries it registers.
public class ProductDefinition
{
public Guid Id;
public string Name = "";
public string MenuText = "";
/// When true, installing prompts for resolution + roles and only selected-role entries are registered.
public bool HostTypeDialog;
public List Entries = new List();
}
///
/// Data-driven catalog of installable products, loaded from RedPlanet\Apps.xml (next to the exe).
/// Replaces the products that were hardcoded in .
///
public static class AppRegistry
{
private static readonly List mProducts = new List();
private static readonly Dictionary mById = new Dictionary();
public static IReadOnlyList Products => mProducts;
public static string CatalogPath =>
Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "RedPlanet\\Apps.xml");
static AppRegistry()
{
Reload();
}
public static ProductDefinition GetProduct(Guid id)
{
return mById.TryGetValue(id, out var product) ? product : null;
}
/// (Re)loads the catalog from . Safe if the file is missing — yields an empty catalog.
public static void Reload()
{
LoadFromFile(CatalogPath);
}
/// Loads the catalog from an explicit path. Safe if the file is missing — yields an empty catalog.
public static void LoadFromFile(string path)
{
mProducts.Clear();
mById.Clear();
if (!File.Exists(path))
{
return;
}
XmlDocument doc = new XmlDocument();
doc.Load(path);
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
if (node.NodeType == XmlNodeType.Element && node.Name == "Product")
{
ProductDefinition product = ParseProduct(node);
mProducts.Add(product);
mById[product.Id] = product;
}
}
}
/// Adds a product to the catalog and persists the whole catalog to disk.
public static void AddProduct(ProductDefinition product)
{
mProducts.Add(product);
mById[product.Id] = product;
Save();
}
/// Writes the catalog back to .
public static void Save()
{
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null));
doc.AppendChild(doc.CreateComment(
" Tesla Console product catalog. Managed by Site Management (Add Product...). "));
XmlElement root = doc.CreateElement("AppCatalog");
doc.AppendChild(root);
foreach (ProductDefinition product in mProducts)
{
XmlElement pe = doc.CreateElement("Product");
pe.SetAttribute("id", product.Id.ToString("D"));
pe.SetAttribute("name", product.Name);
pe.SetAttribute("menuText", product.MenuText);
pe.SetAttribute("hostTypeDialog", product.HostTypeDialog ? "true" : "false");
foreach (LaunchEntryDefinition entry in product.Entries)
{
XmlElement le = doc.CreateElement("Launch");
le.SetAttribute("key", entry.LaunchKey.ToString("D"));
le.SetAttribute("displayName", entry.DisplayName);
le.SetAttribute("exe", entry.Exe);
le.SetAttribute("args", entry.Args ?? "");
if (!string.IsNullOrEmpty(entry.WorkingDirectory))
{
le.SetAttribute("workingDirectory", entry.WorkingDirectory);
}
le.SetAttribute("autoRestart", entry.AutoRestart ? "true" : "false");
le.SetAttribute("hostType", entry.HostType.ToString());
pe.AppendChild(le);
}
root.AppendChild(pe);
}
Directory.CreateDirectory(Path.GetDirectoryName(CatalogPath));
doc.Save(CatalogPath);
}
private static ProductDefinition ParseProduct(XmlNode node)
{
ProductDefinition product = new ProductDefinition
{
Id = ParseGuid(Attr(node, "id")),
Name = Attr(node, "name"),
MenuText = Attr(node, "menuText"),
HostTypeDialog = ParseBool(Attr(node, "hostTypeDialog"))
};
if (string.IsNullOrEmpty(product.MenuText))
{
product.MenuText = product.Name + "...";
}
foreach (XmlNode child in node.ChildNodes)
{
if (child.NodeType == XmlNodeType.Element && child.Name == "Launch")
{
product.Entries.Add(ParseLaunch(child));
}
}
return product;
}
private static LaunchEntryDefinition ParseLaunch(XmlNode node)
{
return new LaunchEntryDefinition
{
LaunchKey = ParseGuid(Attr(node, "key")),
DisplayName = Attr(node, "displayName"),
Exe = Attr(node, "exe"),
Args = Attr(node, "args"),
WorkingDirectory = HasAttr(node, "workingDirectory") ? Attr(node, "workingDirectory") : null,
AutoRestart = !HasAttr(node, "autoRestart") || ParseBool(Attr(node, "autoRestart")),
HostType = ParseHostType(Attr(node, "hostType"))
};
}
private static bool HasAttr(XmlNode node, string name)
{
return node.Attributes?[name] != null;
}
private static string Attr(XmlNode node, string name)
{
return node.Attributes?[name]?.InnerText ?? "";
}
private static Guid ParseGuid(string value)
{
return Guid.TryParse(value, out var guid) ? guid : Guid.Empty;
}
private static bool ParseBool(string value)
{
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || value == "1";
}
private static AppHostType ParseHostType(string value)
{
return Enum.TryParse(value, ignoreCase: true, out var result) ? result : AppHostType.None;
}
}