102 lines
6.9 KiB
C#
102 lines
6.9 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
|
|
namespace Launcher.Next.Core;
|
|
|
|
public enum StartupRosterCandidateStatus
|
|
{
|
|
CandidateOnly,
|
|
LegacyCandidatePresent,
|
|
ConflictingCandidatePresent,
|
|
UnexpectedAutoloadRegistration,
|
|
UnresolvedStartupEntry
|
|
}
|
|
|
|
public sealed record StartupRosterEntry(string Source, string? Registration, string? PackageId,
|
|
string? Version, string? EntryAssembly, string? EntryPath, string? EntrySha256,
|
|
bool Eligible, string LoadTiming);
|
|
|
|
public sealed record StartupRosterReport(StartupRosterCandidateStatus Status,
|
|
string RequestedPackageId, string RequestedVersion, string? RequestedEntrySha256,
|
|
IReadOnlyList<StartupRosterEntry> Entries, DateTimeOffset CreatedUtc)
|
|
{
|
|
public bool IsSafe => Status == StartupRosterCandidateStatus.CandidateOnly;
|
|
}
|
|
|
|
/// Read-only, bounded discovery of P_Rec_Annot startup candidates in controlled local roots.
|
|
public static class StartupRosterPreflight
|
|
{
|
|
public static StartupRosterReport Inspect(string requestedPackageRoot, PackageManifest requested,
|
|
IEnumerable<string>? additionalRoots = null)
|
|
{
|
|
var roots = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Path.GetFullPath(requestedPackageRoot) };
|
|
foreach (var root in additionalRoots ?? DiscoverKnownRoots())
|
|
if (!string.IsNullOrWhiteSpace(root) && Directory.Exists(root)) roots.Add(Path.GetFullPath(root));
|
|
var entries = new List<StartupRosterEntry>();
|
|
var requestedPath = Path.Combine(Path.GetFullPath(requestedPackageRoot), requested.EntryAssembly);
|
|
var requestedHash = requested.Files.FirstOrDefault(f => f.Path.Equals(requested.EntryAssembly, StringComparison.OrdinalIgnoreCase))?.Sha256;
|
|
if (File.Exists(requestedPath))
|
|
entries.Add(new("RequestedPackage", requested.Startup.Type + ":" + requested.Startup.Command, requested.PackageId, requested.Version,
|
|
requested.EntryAssembly, requestedPath, Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(requestedPath))), true, "before-netload"));
|
|
else entries.Add(new("RequestedPackage", requested.Startup.Type + ":" + requested.Startup.Command, requested.PackageId, requested.Version,
|
|
requested.EntryAssembly, null, requestedHash, false, "before-netload"));
|
|
foreach (var root in roots) DiscoverRoot(root, requested, entries);
|
|
var eligible = entries.Where(e => e.Eligible).ToArray();
|
|
StartupRosterCandidateStatus status;
|
|
if (entries.Any(e => e.EntryPath is null)) status = StartupRosterCandidateStatus.UnresolvedStartupEntry;
|
|
else if (eligible.Length == 0) status = StartupRosterCandidateStatus.UnresolvedStartupEntry;
|
|
else if (eligible.Length == 1 && string.Equals(eligible[0].EntrySha256, requestedHash, StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(eligible[0].PackageId, requested.PackageId, StringComparison.OrdinalIgnoreCase)
|
|
&& string.Equals(eligible[0].Version, requested.Version, StringComparison.OrdinalIgnoreCase)) status = StartupRosterCandidateStatus.CandidateOnly;
|
|
else if (eligible.Any(e => string.Equals(e.PackageId, requested.PackageId, StringComparison.OrdinalIgnoreCase)
|
|
&& !string.Equals(e.Version, requested.Version, StringComparison.OrdinalIgnoreCase))) status = StartupRosterCandidateStatus.ConflictingCandidatePresent;
|
|
else if (eligible.Any(e => !string.Equals(e.EntrySha256, requestedHash, StringComparison.OrdinalIgnoreCase))) status = StartupRosterCandidateStatus.LegacyCandidatePresent;
|
|
else status = StartupRosterCandidateStatus.UnexpectedAutoloadRegistration;
|
|
return new(status, requested.PackageId, requested.Version, requestedHash, entries, DateTimeOffset.UtcNow);
|
|
}
|
|
|
|
public static void Write(string path, StartupRosterReport report) =>
|
|
File.WriteAllText(path, JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true }));
|
|
|
|
private static IEnumerable<string> DiscoverKnownRoots()
|
|
{
|
|
foreach (var temp in new[] { Path.GetTempPath(), @"C:\dev\TEMP" }.Distinct(StringComparer.OrdinalIgnoreCase))
|
|
if (Directory.Exists(temp))
|
|
foreach (var root in Directory.EnumerateDirectories(temp, "local-parity-launch-*", SearchOption.TopDirectoryOnly))
|
|
yield return Path.Combine(root, "Applications");
|
|
foreach (var root in new[] { Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Autodesk\\ApplicationPlugins",
|
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\Autodesk\\ApplicationPlugins" }) yield return root;
|
|
}
|
|
|
|
private static void DiscoverRoot(string root, PackageManifest requested, List<StartupRosterEntry> entries)
|
|
{
|
|
if (!Directory.Exists(root)) return;
|
|
foreach (var dir in Directory.EnumerateDirectories(root, "P_Rec_Annot", SearchOption.TopDirectoryOnly))
|
|
{
|
|
var current = Path.Combine(dir, "current.json");
|
|
if (File.Exists(current))
|
|
{
|
|
using var doc = JsonDocument.Parse(File.ReadAllText(current));
|
|
var version = doc.RootElement.TryGetProperty("Current", out var c) ? c.GetString() : null;
|
|
var packageRoot = version is null ? null : Path.Combine(dir, "versions", version);
|
|
AddManifest(packageRoot, "LauncherCurrent", "netload", requested, entries);
|
|
}
|
|
else AddManifest(dir, "ApplicationPlugins", "autoload", requested, entries);
|
|
}
|
|
foreach (var bundle in Directory.EnumerateDirectories(root, "P_Rec_Annot*.bundle", SearchOption.TopDirectoryOnly)) AddManifest(bundle, "PackageContents", "autoload", requested, entries);
|
|
}
|
|
|
|
private static void AddManifest(string? packageRoot, string source, string registration, PackageManifest requested, List<StartupRosterEntry> entries)
|
|
{
|
|
if (packageRoot is null) { entries.Add(new(source, registration, "P_Rec_Annot", null, null, null, null, false, "before-netload")); return; }
|
|
var manifestPath = Path.Combine(packageRoot, "package.manifest.json");
|
|
if (!File.Exists(manifestPath)) { entries.Add(new(source, registration, "P_Rec_Annot", Path.GetFileName(packageRoot), null, null, null, false, "before-netload")); return; }
|
|
using var doc = JsonDocument.Parse(File.ReadAllText(manifestPath));
|
|
var root = doc.RootElement; string? S(string n) => root.TryGetProperty(n, out var p) ? p.GetString() : null;
|
|
var id = S("PackageId") ?? "P_Rec_Annot"; var version = S("Version"); var asm = S("EntryAssembly") ?? requested.EntryAssembly;
|
|
var path = Path.Combine(packageRoot, asm); string? hash = File.Exists(path) ? Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path))) : null;
|
|
var eligible = File.Exists(path);
|
|
entries.Add(new(source, registration, id, version, asm, eligible ? path : null, hash, eligible, "before-netload"));
|
|
}
|
|
}
|