Files
se-launcher/CringePlugins/Loader/PluginsLifetime.cs
pas2704 c25bf3bb3d
All checks were successful
Build / Compute Version (push) Successful in 5s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 1m24s
Build / Build Nuget package (NuGet) (push) Successful in 2m8s
Build / Build Nuget package (CringePlugins) (push) Successful in 3m21s
Build / Build Nuget package (SharedCringe) (push) Successful in 2m32s
Build / Build Launcher (push) Successful in 3m31s
Implement Migration of PluginLoader configs (UI is temporary atm)
Add profiles to Config
Error handling for package resolution
Remove debug code from wndproc hook
2024-11-09 18:23:40 -05:00

156 lines
5.8 KiB
C#

using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Text.Json;
using CringePlugins.Config;
using CringePlugins.Render;
using CringePlugins.Resolver;
using CringePlugins.Splash;
using CringePlugins.Ui;
using NLog;
using NuGet;
using NuGet.Deps;
using NuGet.Frameworks;
using NuGet.Versioning;
using SharedCringe.Loader;
namespace CringePlugins.Loader;
public class PluginsLifetime(string gameFolder) : ILoadingStage
{
public static ImmutableArray<DerivedAssemblyLoadContext> Contexts { get; private set; } = [];
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string Name => "Loading Plugins";
private ImmutableArray<PluginInstance> _plugins = [];
private readonly DirectoryInfo _dir = Directory.CreateDirectory(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CringeLauncher"));
private readonly NuGetFramework _runtimeFramework = NuGetFramework.ParseFolder("net8.0-windows10.0.19041.0");
public async ValueTask Load(ISplashProgress progress)
{
progress.DefineStepsCount(6);
progress.Report("Discovering local plugins");
DiscoverLocalPlugins(_dir.CreateSubdirectory("plugins"));
progress.Report("Loading config");
PackagesConfig? packagesConfig = null;
var configPath = Path.Join(_dir.FullName, "packages.json");
if (File.Exists(configPath))
await using (var stream = File.OpenRead(configPath))
packagesConfig = JsonSerializer.Deserialize<PackagesConfig>(stream, NuGetClient.SerializerOptions)!;
if (packagesConfig == null)
{
packagesConfig = PackagesConfig.Default;
await using var stream = File.Create(configPath);
await JsonSerializer.SerializeAsync(stream, packagesConfig, NuGetClient.SerializerOptions);
}
progress.Report("Resolving packages");
var sourceMapping = new PackageSourceMapping(packagesConfig.Sources);
var resolver = new PackageResolver(_runtimeFramework, packagesConfig.Packages, sourceMapping);
var packages = await resolver.ResolveAsync();
progress.Report("Downloading packages");
var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, progress);
progress.Report("Loading plugins");
await LoadPlugins(cachedPackages, sourceMapping, packagesConfig);
RenderHandler.Current.RegisterComponent(new PluginListComponent(packagesConfig, sourceMapping, configPath, gameFolder, _plugins));
}
public void RegisterLifetime()
{
var contextBuilder = Contexts.ToBuilder();
foreach (var instance in _plugins)
{
try
{
instance.Instantiate(contextBuilder);
instance.RegisterLifetime();
}
catch (Exception e)
{
Log.Error(e, "Failed to instantiate plugin {Plugin}", instance.Metadata);
}
}
Contexts = contextBuilder.ToImmutable();
}
private async Task LoadPlugins(IReadOnlySet<CachedPackage> packages, PackageSourceMapping sourceMapping,
PackagesConfig packagesConfig)
{
var plugins = _plugins.ToBuilder();
var packageVersions = BuiltInPackages.GetPackages(_runtimeFramework)
.ToImmutableDictionary(b => b.Package.Id, b => b.Package.Version,
StringComparer.OrdinalIgnoreCase);
packageVersions = packageVersions.AddRange(packages.Select(b =>
new KeyValuePair<string, NuGetVersion>(b.Package.Id, b.Package.Version)));
var manifestBuilder = new DependencyManifestBuilder(_dir.CreateSubdirectory("cache"), sourceMapping,
dependency => packageVersions.TryGetValue(dependency.Id, out var version) && version.Major != 99
? version
: dependency.Range.MinVersion ?? dependency.Range.MaxVersion);
foreach (var package in packages)
{
var dir = Path.Join(package.Directory.FullName, "lib", package.ResolvedFramework.GetShortFolderName());
var path = Path.Join(dir, $"{package.Package.Id}.deps.json");
if (!File.Exists(path))
{
await using (var stream = File.Create(path))
await manifestBuilder.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework);
}
var client = await sourceMapping.GetClientAsync(package.Package.Id);
var sourceName = packagesConfig.Sources.First(b => b.Url == client.ToString()).Name;
LoadComponent(plugins, Path.Join(dir, $"{package.Package.Id}.dll"),
new(package.Package.Id, package.Package.Version, sourceName));
}
_plugins = plugins.ToImmutable();
}
private void DiscoverLocalPlugins(DirectoryInfo dir)
{
var plugins = ImmutableArray<PluginInstance>.Empty.ToBuilder();
foreach (var directory in dir.EnumerateDirectories())
{
var files = directory.GetFiles("*.deps.json");
if (files.Length != 1) continue;
var path = files[0].FullName[..^".deps.json".Length] + ".dll";
LoadComponent(plugins, path);
}
_plugins = plugins.ToImmutable();
}
private static void LoadComponent(ImmutableArray<PluginInstance>.Builder plugins, string path, PluginMetadata? metadata = null)
{
try
{
plugins.Add(metadata is null ? new PluginInstance(path) : new(metadata, path));
}
catch (Exception e)
{
Log.Error(e, "Failed to load plugin {PluginPath}", path);
}
}
}