feature: first
All checks were successful
Build / Compute Version (push) Successful in 4s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 2m47s
Build / Build Nuget package (CringePlugins) (push) Successful in 5m31s
Build / Build Nuget package (NuGet) (push) Successful in 6m2s
Build / Build Nuget package (SharedCringe) (push) Successful in 7m25s
Build / Build Launcher (push) Successful in 9m11s
All checks were successful
Build / Compute Version (push) Successful in 4s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 2m47s
Build / Build Nuget package (CringePlugins) (push) Successful in 5m31s
Build / Build Nuget package (NuGet) (push) Successful in 6m2s
Build / Build Nuget package (SharedCringe) (push) Successful in 7m25s
Build / Build Launcher (push) Successful in 9m11s
This commit is contained in:
11
CringePlugins/Abstractions/IRenderComponent.cs
Normal file
11
CringePlugins/Abstractions/IRenderComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CringePlugins.Abstractions;
|
||||
|
||||
public interface IRenderComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets called while the dear ImGui frame is being composed
|
||||
/// </summary>
|
||||
void OnFrame();
|
||||
}
|
||||
|
||||
internal interface IRootRenderComponent : IRenderComponent, IDisposable;
|
15
CringePlugins/Config/PackagesConfig.cs
Normal file
15
CringePlugins/Config/PackagesConfig.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Immutable;
|
||||
using CringePlugins.Resolver;
|
||||
using NuGet;
|
||||
using NuGet.Models;
|
||||
|
||||
namespace CringePlugins.Config;
|
||||
|
||||
public record PackagesConfig(ImmutableArray<PackageSource> Sources, ImmutableArray<PackageReference> Packages)
|
||||
{
|
||||
public static PackagesConfig Default { get; } = new([
|
||||
new("SpaceEngineersDedicated.ReferenceAssemblies", "https://nuget.storage.yandexcloud.net/index.json"),
|
||||
new("ImGui.NET.DirectX", "https://nuget.storage.yandexcloud.net/index.json"),
|
||||
new(string.Empty, "https://api.nuget.org/v3/index.json")
|
||||
], []);
|
||||
}
|
40
CringePlugins/CringePlugins.csproj
Normal file
40
CringePlugins/CringePlugins.csproj
Normal file
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NuGet\NuGet.csproj" />
|
||||
<ProjectReference Include="..\SharedCringe\SharedCringe.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Publicize Include="VRage:VRage.Plugins.MyPlugins.m_plugins" />
|
||||
<Publicize Include="VRage:VRage.Plugins.MyPlugins.m_handleInputPlugins" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.7.9" />
|
||||
<PackageReference Include="Basic.Reference.Assemblies.Net80Windows" Version="1.7.9" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="ImGui.NET.DirectX" Version="1.91.0.1" />
|
||||
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.*" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="dnlib" Version="4.4.0" />
|
||||
<PackageReference Include="Krafs.Publicizer" Version="2.2.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="splash.gif" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
3
CringePlugins/Globals.cs
Normal file
3
CringePlugins/Globals.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("CringeLauncher")]
|
37
CringePlugins/Loader/PluginAssemblyLoadContext.cs
Normal file
37
CringePlugins/Loader/PluginAssemblyLoadContext.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using CringeBootstrap.Abstractions;
|
||||
using SharedCringe.Loader;
|
||||
|
||||
namespace CringePlugins.Loader;
|
||||
|
||||
internal class PluginAssemblyLoadContext : DerivedAssemblyLoadContext
|
||||
{
|
||||
private readonly string _entrypointPath;
|
||||
private readonly AssemblyDependencyResolver _dependencyResolver;
|
||||
private Assembly? _assembly;
|
||||
|
||||
internal PluginAssemblyLoadContext(ICoreLoadContext parentContext, string entrypointPath) : base(parentContext, $"Plugin Context {Path.GetFileNameWithoutExtension(entrypointPath)}")
|
||||
{
|
||||
_entrypointPath = entrypointPath;
|
||||
_dependencyResolver = new(entrypointPath);
|
||||
}
|
||||
|
||||
public Assembly LoadEntrypoint() => _assembly ??= LoadFromAssemblyPath(_entrypointPath);
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
if (_dependencyResolver.ResolveAssemblyToPath(assemblyName) is { } path)
|
||||
return LoadFromAssemblyPath(path);
|
||||
|
||||
return base.Load(assemblyName);
|
||||
}
|
||||
|
||||
protected override nint LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
if (_dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName) is { } path)
|
||||
return LoadUnmanagedDllFromPath(path);
|
||||
|
||||
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||
}
|
||||
}
|
55
CringePlugins/Loader/PluginInstance.cs
Normal file
55
CringePlugins/Loader/PluginInstance.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Runtime.Loader;
|
||||
using CringeBootstrap.Abstractions;
|
||||
using CringePlugins.Utils;
|
||||
using VRage.Plugins;
|
||||
|
||||
namespace CringePlugins.Loader;
|
||||
|
||||
internal sealed class PluginInstance
|
||||
{
|
||||
private readonly string _entrypointPath;
|
||||
private PluginAssemblyLoadContext? _context;
|
||||
private IPlugin? _instance;
|
||||
private IHandleInputPlugin? _handleInputInstance;
|
||||
public PluginMetadata Metadata { get; }
|
||||
|
||||
public PluginInstance(PluginMetadata metadata, string entrypointPath)
|
||||
{
|
||||
_entrypointPath = entrypointPath;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public PluginInstance(string entrypointPath) : this(PluginMetadata.ReadFromEntrypoint(entrypointPath), entrypointPath)
|
||||
{
|
||||
}
|
||||
|
||||
public void Instantiate()
|
||||
{
|
||||
if (AssemblyLoadContext.GetLoadContext(typeof(PluginInstance).Assembly) is not ICoreLoadContext parentContext)
|
||||
throw new NotSupportedException("Plugin instantiation is not supported in this context");
|
||||
|
||||
_context = new PluginAssemblyLoadContext(parentContext, _entrypointPath);
|
||||
|
||||
var entrypoint = _context.LoadEntrypoint();
|
||||
|
||||
var plugins = IntrospectionContext.Global.CollectDerivedTypes<IPlugin>(entrypoint.GetMainModule()).ToArray();
|
||||
|
||||
if (plugins.Length == 0)
|
||||
throw new InvalidOperationException("Entrypoint does not contain any plugins");
|
||||
if (plugins.Length > 1)
|
||||
throw new InvalidOperationException("Entrypoint contains multiple plugins");
|
||||
|
||||
_instance = (IPlugin) Activator.CreateInstance(plugins[0])!;
|
||||
_handleInputInstance = _instance as IHandleInputPlugin;
|
||||
}
|
||||
|
||||
public void RegisterLifetime()
|
||||
{
|
||||
if (_instance is null)
|
||||
throw new InvalidOperationException("Must call Instantiate first");
|
||||
|
||||
MyPlugins.m_plugins.Add(_instance);
|
||||
if (_handleInputInstance is not null)
|
||||
MyPlugins.m_handleInputPlugins.Add(_handleInputInstance);
|
||||
}
|
||||
}
|
21
CringePlugins/Loader/PluginMetadata.cs
Normal file
21
CringePlugins/Loader/PluginMetadata.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using dnlib.DotNet;
|
||||
|
||||
namespace CringePlugins.Loader;
|
||||
|
||||
public record PluginMetadata(string Name, Version Version)
|
||||
{
|
||||
public static PluginMetadata ReadFromEntrypoint(string entrypointPath)
|
||||
{
|
||||
var module = ModuleDefMD.Load(entrypointPath);
|
||||
|
||||
var titleAttribute = module.CustomAttributes.Find(typeof(AssemblyTitleAttribute).FullName);
|
||||
var versionAttribute = module.CustomAttributes.Find(typeof(AssemblyVersionAttribute).FullName);
|
||||
|
||||
var name = titleAttribute?.ConstructorArguments[0].Value as string ?? module.FullName;
|
||||
if (!Version.TryParse(versionAttribute?.ConstructorArguments[0].Value as string ?? "0.0.0.0", out var version))
|
||||
version = new();
|
||||
|
||||
return new(name, version);
|
||||
}
|
||||
}
|
142
CringePlugins/Loader/PluginsLifetime.cs
Normal file
142
CringePlugins/Loader/PluginsLifetime.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using CringePlugins.Config;
|
||||
using CringePlugins.Resolver;
|
||||
using CringePlugins.Splash;
|
||||
using NLog;
|
||||
using NuGet;
|
||||
using NuGet.Deps;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace CringePlugins.Loader;
|
||||
|
||||
public class PluginsLifetime : ILoadingStage
|
||||
{
|
||||
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);
|
||||
|
||||
progress.Report("Registering plugins");
|
||||
|
||||
RegisterLifetime();
|
||||
}
|
||||
|
||||
private void RegisterLifetime()
|
||||
{
|
||||
foreach (var instance in _plugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
instance.Instantiate();
|
||||
instance.RegisterLifetime();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failed to instantiate plugin {Plugin}", instance.Metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPlugins(IReadOnlySet<CachedPackage> packages, PackageSourceMapping sourceMapping)
|
||||
{
|
||||
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());
|
||||
|
||||
await using (var stream = File.Create(Path.Join(dir, $"{package.Package.Id}.deps.json")))
|
||||
await manifestBuilder.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework);
|
||||
|
||||
LoadComponent(plugins, Path.Join(dir, $"{package.Package.Id}.dll"));
|
||||
}
|
||||
|
||||
_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)
|
||||
{
|
||||
try
|
||||
{
|
||||
plugins.Add(new PluginInstance(path));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failed to load plugin {PluginPath}", path);
|
||||
}
|
||||
}
|
||||
}
|
48
CringePlugins/Render/RenderHandler.cs
Normal file
48
CringePlugins/Render/RenderHandler.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Concurrent;
|
||||
using CringePlugins.Abstractions;
|
||||
using NLog;
|
||||
|
||||
namespace CringePlugins.Render;
|
||||
|
||||
public sealed class RenderHandler : IRootRenderComponent
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private static RenderHandler? _current;
|
||||
public static RenderHandler Current => _current ?? throw new InvalidOperationException("Render is not yet initialized");
|
||||
|
||||
private readonly ConcurrentBag<ComponentRegistration> _components = [];
|
||||
|
||||
internal RenderHandler()
|
||||
{
|
||||
_current = this;
|
||||
}
|
||||
|
||||
public void RegisterComponent<TComponent>(TComponent instance) where TComponent : IRenderComponent
|
||||
{
|
||||
_components.Add(new ComponentRegistration(typeof(TComponent), instance));
|
||||
}
|
||||
|
||||
void IRenderComponent.OnFrame()
|
||||
{
|
||||
foreach (var (instanceType, renderComponent) in _components)
|
||||
{
|
||||
try
|
||||
{
|
||||
renderComponent.OnFrame();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Component {TypeName} failed to render a new frame", instanceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record ComponentRegistration(Type InstanceType, IRenderComponent Instance);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_current = null;
|
||||
_components.Clear();
|
||||
}
|
||||
}
|
78
CringePlugins/Resolver/BuiltInPackages.cs
Normal file
78
CringePlugins/Resolver/BuiltInPackages.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Basic.Reference.Assemblies;
|
||||
using CringePlugins.Loader;
|
||||
using CringePlugins.Utils;
|
||||
using dnlib.DotNet;
|
||||
using ImGuiNET;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using NLog;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Models;
|
||||
using NuGet.Versioning;
|
||||
using Sandbox.Game;
|
||||
using SpaceEngineers.Game;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace CringePlugins.Resolver;
|
||||
|
||||
public static class BuiltInPackages
|
||||
{
|
||||
private const string SeReferenceAssemblies = "SpaceEngineersDedicated.ReferenceAssemblies";
|
||||
private const string ImGui = "ImGui.NET.DirectX";
|
||||
|
||||
public static ImmutableArray<ResolvedPackage> GetPackages(NuGetFramework runtimeFramework)
|
||||
{
|
||||
var nlog = FromAssembly<LogFactory>(runtimeFramework, version: new(5, 3, 4));
|
||||
Version seVersion = new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion!.Value);
|
||||
|
||||
var se = FromAssembly<SpaceEngineersGame>(runtimeFramework, [
|
||||
nlog.AsDependency()
|
||||
], SeReferenceAssemblies, new(seVersion));
|
||||
var imGui = FromAssembly<ImGuiKey>(runtimeFramework, id: ImGui);
|
||||
|
||||
BuiltInSdkPackage MapSdkPackage(
|
||||
(string FileName, byte[] ImageBytes, PortableExecutableReference Reference, Guid Mvid) r)
|
||||
{
|
||||
var def = ModuleDefMD.Load(r.ImageBytes, IntrospectionContext.Global.Context);
|
||||
var attribute = def.CustomAttributes.Find(typeof(AssemblyFileVersionAttribute).FullName);
|
||||
var version = attribute is null ? new(99, 0, 0) : NuGetVersion.Parse((string)attribute.ConstructorArguments[0].Value);
|
||||
|
||||
return new BuiltInSdkPackage(
|
||||
new(0, Path.GetFileNameWithoutExtension(r.FileName), version, []), runtimeFramework,
|
||||
new(Path.GetFileNameWithoutExtension(r.FileName), version, [new(runtimeFramework, [])], null, []));
|
||||
}
|
||||
|
||||
return
|
||||
[
|
||||
..Net80.ReferenceInfos.AllValues.Select(MapSdkPackage),
|
||||
..Net80Windows.ReferenceInfos.AllValues.Select(MapSdkPackage),
|
||||
nlog,
|
||||
se,
|
||||
imGui,
|
||||
FromAssembly<PluginsLifetime>(runtimeFramework, [se.AsDependency(), imGui.AsDependency()]),
|
||||
];
|
||||
}
|
||||
|
||||
private static Dependency AsDependency(this ResolvedPackage package) => new(package.Package.Id, new(package.Package.Version));
|
||||
|
||||
private static BuiltInPackage FromAssembly<T>(NuGetFramework runtimeFramework, ImmutableArray<Dependency>? dependencies = null, string? id = null, NuGetVersion? version = null)
|
||||
{
|
||||
var assembly = typeof(T).Assembly.GetName();
|
||||
id ??= assembly.Name!;
|
||||
version ??= new NuGetVersion(assembly.Version ?? new(0, 0, 0));
|
||||
dependencies ??= [];
|
||||
|
||||
return new(
|
||||
new(0, id, version, [..dependencies.Value.Select(b => b.Id)]),
|
||||
runtimeFramework,
|
||||
new(id, version, [
|
||||
new(runtimeFramework, dependencies.Value)
|
||||
], null, [])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public record BuiltInPackage(Package Package, NuGetFramework ResolvedFramework, CatalogEntry Entry) : ResolvedPackage(Package, ResolvedFramework, Entry);
|
||||
|
||||
public record BuiltInSdkPackage(Package Package, NuGetFramework ResolvedFramework, CatalogEntry Entry) : BuiltInPackage(Package, ResolvedFramework, Entry);
|
144
CringePlugins/Resolver/PackageResolver.cs
Normal file
144
CringePlugins/Resolver/PackageResolver.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.IO.Compression;
|
||||
using NuGet;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Models;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace CringePlugins.Resolver;
|
||||
|
||||
public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<PackageReference> references, PackageSourceMapping packageSources)
|
||||
{
|
||||
public async Task<ImmutableHashSet<ResolvedPackage>> ResolveAsync()
|
||||
{
|
||||
var order = 0;
|
||||
var packages = new SortedDictionary<Package, CatalogEntry>();
|
||||
|
||||
foreach (var reference in references)
|
||||
{
|
||||
var client = await packageSources.GetClientAsync(reference.Id);
|
||||
|
||||
var registrationRoot = await client.GetPackageRegistrationRootAsync(reference.Id);
|
||||
|
||||
var items = registrationRoot.Items.SelectMany(page =>
|
||||
page.Items.Where(b => b.CatalogEntry.PackageTypes is ["CringePlugin"]))
|
||||
.ToImmutableDictionary(b => b.CatalogEntry.Version);
|
||||
|
||||
var version = reference.Range.FindBestMatch(items.Values.Select(b => b.CatalogEntry.Version));
|
||||
|
||||
if (version is null)
|
||||
throw new Exception($"Unable to find version for package {reference.Id}");
|
||||
|
||||
var package = new Package(order, reference.Id, version, []); // todo resolve dependencies
|
||||
|
||||
if (packages.TryAdd(package, items[version].CatalogEntry))
|
||||
continue;
|
||||
|
||||
if (!packages.TryGetValue(package, out _))
|
||||
throw new Exception($"Duplicate package {package.Id}");
|
||||
|
||||
var existingPackage = packages.Keys.First(b => b.Version == package.Version && b.Id == package.Id);
|
||||
|
||||
if (package.Version < existingPackage.Version)
|
||||
throw new Exception($"Package reference {package.Id} has lower version {package.Version} than already resolved {existingPackage.Version}");
|
||||
|
||||
if (package.Version == existingPackage.Version)
|
||||
continue;
|
||||
|
||||
packages.Remove(existingPackage);
|
||||
packages.Add(package with
|
||||
{
|
||||
Order = ++order
|
||||
}, items[version].CatalogEntry);
|
||||
}
|
||||
|
||||
var set = ImmutableHashSet<ResolvedPackage>.Empty.ToBuilder();
|
||||
foreach (var (package, catalogEntry) in packages)
|
||||
{
|
||||
var client = await packageSources.GetClientAsync(package.Id);
|
||||
|
||||
var nearestGroup = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups, runtimeFramework,
|
||||
g => g.TargetFramework);
|
||||
|
||||
if (nearestGroup is null)
|
||||
throw new Exception($"Unable to find compatible dependency group for package {package.Id}");
|
||||
|
||||
set.Add(new RemotePackage(package, nearestGroup.TargetFramework, client, catalogEntry));
|
||||
}
|
||||
|
||||
return set.ToImmutable();
|
||||
}
|
||||
|
||||
public async Task<ImmutableHashSet<CachedPackage>> DownloadPackagesAsync(DirectoryInfo baseDirectory,
|
||||
IReadOnlySet<ResolvedPackage> resolvedPackages, IProgress<float>? progress = null)
|
||||
{
|
||||
var packages = ImmutableHashSet<CachedPackage>.Empty.ToBuilder();
|
||||
|
||||
var i = 0f;
|
||||
foreach (var package in resolvedPackages)
|
||||
{
|
||||
switch (package)
|
||||
{
|
||||
case RemotePackage remotePackage:
|
||||
{
|
||||
var dir = new DirectoryInfo(Path.Join(baseDirectory.FullName, package.Package.Id, package.Package.Version.ToString()));
|
||||
if (!dir.Exists)
|
||||
{
|
||||
dir.Create();
|
||||
|
||||
await using var stream = await remotePackage.Client.GetPackageContentStreamAsync(remotePackage.Package.Id, remotePackage.Package.Version);
|
||||
using var memStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memStream);
|
||||
memStream.Position = 0;
|
||||
using var archive = new ZipArchive(memStream, ZipArchiveMode.Read);
|
||||
archive.ExtractToDirectory(dir.FullName);
|
||||
}
|
||||
|
||||
packages.Add(new CachedPackage(package.Package, package.ResolvedFramework, dir, package.Entry));
|
||||
break;
|
||||
}
|
||||
case CachedPackage cachedPackage:
|
||||
packages.Add(cachedPackage);
|
||||
break;
|
||||
}
|
||||
|
||||
progress?.Report(i++ / resolvedPackages.Count);
|
||||
}
|
||||
|
||||
return packages.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
public record CachedPackage(Package Package, NuGetFramework ResolvedFramework, DirectoryInfo Directory, CatalogEntry Entry) : ResolvedPackage(Package, ResolvedFramework, Entry);
|
||||
public record RemotePackage(Package Package, NuGetFramework ResolvedFramework, NuGetClient Client, CatalogEntry Entry) : ResolvedPackage(Package, ResolvedFramework, Entry);
|
||||
|
||||
public abstract record ResolvedPackage(Package Package, NuGetFramework ResolvedFramework, CatalogEntry Entry);
|
||||
|
||||
public record Package(int Order, string Id, NuGetVersion Version, ImmutableArray<string> Dependencies) : IComparable<Package>, IComparable
|
||||
{
|
||||
public int CompareTo(Package? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
if (other is null) return 1;
|
||||
var orderComparison = Order.CompareTo(other.Order);
|
||||
if (orderComparison != 0) return orderComparison;
|
||||
return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
if (ReferenceEquals(this, obj)) return 0;
|
||||
return obj is Package other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Package)}");
|
||||
}
|
||||
|
||||
public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Id);
|
||||
|
||||
public virtual bool Equals(Package? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public record PackageReference(string Id, VersionRange Range);
|
13
CringePlugins/Splash/ISplashProgress.cs
Normal file
13
CringePlugins/Splash/ISplashProgress.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace CringePlugins.Splash;
|
||||
|
||||
public interface ISplashProgress : IProgress<ProgressInfo>, IProgress<float>
|
||||
{
|
||||
void DefineStage(ILoadingStage stage);
|
||||
void DefineStepsCount(int count);
|
||||
}
|
||||
|
||||
public interface ILoadingStage
|
||||
{
|
||||
string Name { get; }
|
||||
ValueTask Load(ISplashProgress progress);
|
||||
}
|
9
CringePlugins/Splash/ProgressInfo.cs
Normal file
9
CringePlugins/Splash/ProgressInfo.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CringePlugins.Splash;
|
||||
|
||||
public record ProgressInfo(string Text)
|
||||
{
|
||||
public static implicit operator ProgressInfo(string s) => new(s);
|
||||
public static implicit operator ProgressInfo((string, float) s) => new PercentProgressInfo(s.Item1, s.Item2);
|
||||
}
|
||||
|
||||
public record PercentProgressInfo(string Text, float Percent = 0) : ProgressInfo(Text);
|
29
CringePlugins/Splash/Splash.cs
Normal file
29
CringePlugins/Splash/Splash.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace CringePlugins.Splash;
|
||||
|
||||
public class Splash : ISplashProgress
|
||||
{
|
||||
private readonly List<ILoadingStage> _loadingStages = [];
|
||||
|
||||
public void Report(ProgressInfo value)
|
||||
{
|
||||
}
|
||||
|
||||
public void Report(float value)
|
||||
{
|
||||
}
|
||||
|
||||
public void DefineStage(ILoadingStage stage) => _loadingStages.Add(stage);
|
||||
|
||||
public void DefineStepsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void ExecuteLoadingStages()
|
||||
{
|
||||
foreach (var loadingStage in _loadingStages)
|
||||
{
|
||||
// todo sync context
|
||||
loadingStage.Load(this).AsTask().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
49
CringePlugins/Utils/IntrospectionContext.cs
Normal file
49
CringePlugins/Utils/IntrospectionContext.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Reflection;
|
||||
using dnlib.DotNet;
|
||||
|
||||
namespace CringePlugins.Utils;
|
||||
|
||||
public class IntrospectionContext
|
||||
{
|
||||
public static IntrospectionContext Global { get; } = new();
|
||||
|
||||
internal readonly ModuleContext Context = ModuleDef.CreateModuleContext();
|
||||
|
||||
public IEnumerable<Type> CollectAttributedTypes<TAttribute>(Module module, bool allowAbstract = false) where TAttribute : Attribute
|
||||
{
|
||||
var moduleDef = ModuleDefMD.Load(module, Context);
|
||||
|
||||
return moduleDef.GetTypes()
|
||||
.Where(b => b.CustomAttributes.IsDefined(typeof(TAttribute).FullName) && (allowAbstract || !b.IsAbstract))
|
||||
.Select(b => module.GetType(b.FullName, true, false)!);
|
||||
}
|
||||
|
||||
public IEnumerable<Type> CollectDerivedTypes<T>(Module module, bool allowAbstract = false)
|
||||
{
|
||||
var moduleDef = ModuleDefMD.Load(module, Context);
|
||||
|
||||
var token = moduleDef.ImportAsTypeSig(typeof(T));
|
||||
|
||||
return moduleDef.GetTypes()
|
||||
.Where(b => (typeof(T).IsInterface
|
||||
? b.Interfaces.Any(i => i.Interface.FullName == token.FullName)
|
||||
: MatchBaseType(b, token)) && (allowAbstract || !b.IsAbstract))
|
||||
.Select(b => module.GetType(b.FullName, true, false)!);
|
||||
}
|
||||
|
||||
private static bool MatchBaseType(ITypeDefOrRef? defOrRef, TypeSig token)
|
||||
{
|
||||
while ((defOrRef = defOrRef.GetBaseType()) != null)
|
||||
{
|
||||
if (defOrRef.FullName == token.FullName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AssemblyExtensions
|
||||
{
|
||||
public static Module GetMainModule(this Assembly assembly) => assembly.GetModule(assembly.GetName().Name! + ".dll") ?? assembly.GetModules()[0];
|
||||
}
|
158
CringePlugins/packages.lock.json
Normal file
158
CringePlugins/packages.lock.json
Normal file
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net8.0-windows7.0": {
|
||||
"Basic.Reference.Assemblies.Net80": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.7.9, )",
|
||||
"resolved": "1.7.9",
|
||||
"contentHash": "1wbS9ZJLFVrKD2jqv27gekIrpjpLffR9sitLQh5drWoG9KbyR/CgrAhw5I0c8Eq3zFMOToCmrpZi3VpRoInCgg==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Common": "4.11.0"
|
||||
}
|
||||
},
|
||||
"Basic.Reference.Assemblies.Net80Windows": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.7.9, )",
|
||||
"resolved": "1.7.9",
|
||||
"contentHash": "98GFm8MC+pv37rTHaxBm5KFucqdJj0jK0XRHSGt2sXK9HNqtGImIFCFahxjUzskQjiUkPAzVhTou2OYZOuhhEg==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Common": "4.11.0"
|
||||
}
|
||||
},
|
||||
"dnlib": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.4.0, )",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "cKHI720q+zfEEvzklWVGt6B0TH3AibAyJbpUJl4U6KvTP13tycfnqJpkGHRZ/oQ45BTIoIxIwltHIJVDN+iCqQ=="
|
||||
},
|
||||
"ImGui.NET.DirectX": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.91.0.1, )",
|
||||
"resolved": "1.91.0.1",
|
||||
"contentHash": "PpW1gQ9g97h6Hm/h/tkSBOmsBYgGwN8wKNmlJomcQFD/zRY1HPkJZz18XRSfRLHPmH2eeh4hhhZv1KHug7dF9g==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.5.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Krafs.Publicizer": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.1, )",
|
||||
"resolved": "2.2.1",
|
||||
"contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q=="
|
||||
},
|
||||
"NLog": {
|
||||
"type": "Direct",
|
||||
"requested": "[5.3.4, )",
|
||||
"resolved": "5.3.4",
|
||||
"contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A=="
|
||||
},
|
||||
"SpaceEngineersDedicated.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.*, )",
|
||||
"resolved": "1.205.25",
|
||||
"contentHash": "+70s6nJnBxEFYZY1qwKfM7FgYBYY6YDSPvbltEXrn7CVAeiWIxbtdcUZ4nDBGVTYqUWEI/r3zbLP1zlcNE27Dg==",
|
||||
"dependencies": {
|
||||
"SharpDX": "4.2.0-keen-cringe",
|
||||
"protobuf-net": "1.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.3.4",
|
||||
"contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.11.0",
|
||||
"contentHash": "djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
||||
"System.Collections.Immutable": "8.0.0",
|
||||
"System.Reflection.Metadata": "8.0.0"
|
||||
}
|
||||
},
|
||||
"NuGet.Frameworks": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.11.1",
|
||||
"contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg=="
|
||||
},
|
||||
"NuGet.Versioning": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.11.1",
|
||||
"contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg=="
|
||||
},
|
||||
"protobuf-net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw=="
|
||||
},
|
||||
"SharpDX": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.0-keen-cringe",
|
||||
"contentHash": "LaJN3h1Gi1FWVdef2I5WtOH9gwzKCBniH0CragarbkN2QheYY6Lqm+91PcOfp1w/4wdVb+k8Kjv3sO393Tphtw=="
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
|
||||
},
|
||||
"System.Collections.Immutable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg=="
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
"contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
|
||||
"dependencies": {
|
||||
"System.Collections.Immutable": "8.0.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"cringebootstrap.abstractions": {
|
||||
"type": "Project"
|
||||
},
|
||||
"nuget": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"NuGet.Frameworks": "[6.11.1, )",
|
||||
"NuGet.Versioning": "[6.11.1, )"
|
||||
}
|
||||
},
|
||||
"sharedcringe": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"CringeBootstrap.Abstractions": "[1.0.0, )",
|
||||
"NLog": "[5.3.4, )",
|
||||
"SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0-windows7.0/win-x64": {
|
||||
"ImGui.NET.DirectX": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.91.0.1, )",
|
||||
"resolved": "1.91.0.1",
|
||||
"contentHash": "PpW1gQ9g97h6Hm/h/tkSBOmsBYgGwN8wKNmlJomcQFD/zRY1HPkJZz18XRSfRLHPmH2eeh4hhhZv1KHug7dF9g==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.5.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
CringePlugins/splash.gif
Normal file
BIN
CringePlugins/splash.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 515 KiB |
Reference in New Issue
Block a user