From a441498c098df886675a47c5fcaeae82d6cb7f5c Mon Sep 17 00:00:00 2001 From: zznty <94796179+zznty@users.noreply.github.com> Date: Tue, 13 May 2025 00:19:22 +0700 Subject: [PATCH] import all shipped nuget packages as built-in also would now throw if version gets changed --- CringePlugins/Loader/PluginsLifetime.cs | 8 +- CringePlugins/Resolver/BuiltInPackages.cs | 76 ++++++++++++++----- NuGet/Converters/FrameworkJsonConverter.cs | 2 +- .../ManifestPackageKeyJsonConverter.cs | 2 +- .../RuntimeFrameworkJsonConverter.cs | 29 +++++++ NuGet/Deps/DependenciesManifest.cs | 35 ++++++--- NuGet/Models/NuGetRuntimeFramework.cs | 27 +++++++ 7 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 NuGet/Converters/RuntimeFrameworkJsonConverter.cs create mode 100644 NuGet/Models/NuGetRuntimeFramework.cs diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index 3fc27c9..5614f2a 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -10,6 +10,7 @@ using NLog; using NuGet; using NuGet.Deps; using NuGet.Frameworks; +using NuGet.Models; using NuGet.Versioning; using SharedCringe.Loader; @@ -26,7 +27,7 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage private ImmutableArray _plugins = []; // TODO move this as api for other plugins private readonly DirectoryInfo _dir = Directory.CreateDirectory(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CringeLauncher")); - private readonly NuGetFramework _runtimeFramework = NuGetFramework.ParseFolder("net9.0-windows10.0.19041.0"); + private readonly NuGetRuntimeFramework _runtimeFramework = new(NuGetFramework.ParseFolder("net9.0-windows10.0.19041.0"), RuntimeInformation.RuntimeIdentifier); public async ValueTask Load(ISplashProgress progress) { @@ -54,13 +55,14 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage progress.Report("Resolving packages"); var sourceMapping = new PackageSourceMapping(packagesConfig.Sources); - var resolver = new PackageResolver(_runtimeFramework, packagesConfig.Packages, sourceMapping); + // TODO take into account the target framework runtime identifier + var resolver = new PackageResolver(_runtimeFramework.Framework, packagesConfig.Packages, sourceMapping); var packages = await resolver.ResolveAsync(); progress.Report("Downloading packages"); - var builtInPackages = BuiltInPackages.GetPackages(_runtimeFramework).ToImmutableDictionary(package => package.Package.Id); + var builtInPackages = await BuiltInPackages.GetPackagesAsync(_runtimeFramework); var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, builtInPackages.Keys.ToHashSet(), progress); progress.Report("Loading plugins"); diff --git a/CringePlugins/Resolver/BuiltInPackages.cs b/CringePlugins/Resolver/BuiltInPackages.cs index 32d12af..ed0d75a 100644 --- a/CringePlugins/Resolver/BuiltInPackages.cs +++ b/CringePlugins/Resolver/BuiltInPackages.cs @@ -7,12 +7,14 @@ using dnlib.DotNet; using ImGuiNET; using Microsoft.CodeAnalysis; using NLog; +using NuGet.Deps; using NuGet.Frameworks; using NuGet.Models; using NuGet.Versioning; using Sandbox.Game; using SpaceEngineers.Game; using VRage.Utils; +using Dependency = NuGet.Models.Dependency; namespace CringePlugins.Resolver; @@ -22,18 +24,25 @@ public static class BuiltInPackages private const string ImGui = "ImGui.NET.DirectX"; private const string Harmony = "Lib.Harmony.Thin"; private const string Steamworks = "Steamworks.NET"; + private const string NLog = "NLog"; - public static ImmutableArray GetPackages(NuGetFramework runtimeFramework) + public static async ValueTask> GetPackagesAsync(NuGetRuntimeFramework runtimeFramework) { - var nlog = FromAssembly(runtimeFramework, version: new(5, 3, 4)); + ImmutableDictionary libraries; + await using (var stream = File.OpenRead(Path.ChangeExtension(Assembly.GetEntryAssembly()!.Location, "deps.json"))) + (_, _, _, libraries) = await DependencyManifestSerializer.DeserializeAsync(stream); + + var framework = runtimeFramework.Framework; + + var nlog = FromAssembly(framework, version: libraries.Keys.Single(b => b.Id == NLog).Version); Version seVersion = new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion!.Value); - var se = FromAssembly(runtimeFramework, [ - nlog.AsDependency() + var se = FromAssembly(framework, [ + nlog.AsDependency(libraries) ], SeReferenceAssemblies, new(seVersion)); - var imGui = FromAssembly(runtimeFramework, id: ImGui); - var harmony = FromAssembly(runtimeFramework, id: Harmony); - var steam = FromAssembly(runtimeFramework, id: Steamworks); + var imGui = FromAssembly(framework, id: ImGui); + var harmony = FromAssembly(framework, id: Harmony, version: NuGetVersion.Parse("2.3.4-torch")); + var steam = FromAssembly(framework, id: Steamworks); BuiltInSdkPackage MapSdkPackage( (string FileName, byte[] ImageBytes, PortableExecutableReference Reference, Guid Mvid) r) @@ -43,29 +52,60 @@ public static class BuiltInPackages 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, [])); + new(0, Path.GetFileNameWithoutExtension(r.FileName), version), framework, + new(Path.GetFileNameWithoutExtension(r.FileName), version, [new(framework, [])], null, [])); } - return + BuiltInPackage MapPackage(ManifestPackageKey key) + { + return new(new(0, key.Id, key.Version), framework, + new(key.Id, key.Version, [new(framework, [])], null, [])); + } + + ResolvedPackage[] packages = [ ..Net90.ReferenceInfos.AllValues.Select(MapSdkPackage), - // ..Net80Windows.ReferenceInfos.AllValues.Select(MapSdkPackage), - nlog, se, - imGui, - harmony, - steam, - FromAssembly(runtimeFramework, - [se.AsDependency(), imGui.AsDependency(), harmony.AsDependency()] + + ..libraries.Where(kvp => + { + if (kvp.Value.Type != LibraryType.Package) return false; + + // Special case as we want to claim we have currently running version of package + // so that even if launcher is built with older version, plugins could still take explicit dependency on it + if (kvp.Key.Id == SeReferenceAssemblies) return false; + + return true; + }).Select(kvp => MapPackage(kvp.Key)), + + // CringePlugins package itself + FromAssembly(framework, + [ + se.AsDependency(libraries), + imGui.AsDependency(libraries), + harmony.AsDependency(libraries), + steam.AsDependency(libraries) + ] #if DEBUG , version: new(0, 1, 21) #endif ), ]; + + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var package in packages) + builder.TryAdd(package.Package.Id, package); + + return builder.ToImmutable(); } - private static Dependency AsDependency(this ResolvedPackage package) => new(package.Package.Id, new(package.Package.Version)); + private static Dependency AsDependency(this ResolvedPackage package, ImmutableDictionary libraries) + { + if (!libraries.ContainsKey(new(package.Package.Id, package.Package.Version))) + throw new KeyNotFoundException($"Package {package.Package} not found in root dependencies manifest"); + + return new Dependency(package.Package.Id, new(package.Package.Version)); + } private static BuiltInPackage FromAssembly(NuGetFramework runtimeFramework, ImmutableArray? dependencies = null, string? id = null, NuGetVersion? version = null) { diff --git a/NuGet/Converters/FrameworkJsonConverter.cs b/NuGet/Converters/FrameworkJsonConverter.cs index 24cb9bb..ab11c1b 100644 --- a/NuGet/Converters/FrameworkJsonConverter.cs +++ b/NuGet/Converters/FrameworkJsonConverter.cs @@ -10,7 +10,7 @@ public class FrameworkJsonConverter(FrameworkNameFormat format) : JsonConverter< { if (reader.TokenType != JsonTokenType.String) throw new JsonException("Invalid framework string"); - + var s = reader.GetString()!; return format switch { diff --git a/NuGet/Converters/ManifestPackageKeyJsonConverter.cs b/NuGet/Converters/ManifestPackageKeyJsonConverter.cs index 46058fc..d4e08b6 100644 --- a/NuGet/Converters/ManifestPackageKeyJsonConverter.cs +++ b/NuGet/Converters/ManifestPackageKeyJsonConverter.cs @@ -8,7 +8,7 @@ public class ManifestPackageKeyJsonConverter : JsonConverter { public override ManifestPackageKey Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType != JsonTokenType.String) + if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName)) throw new JsonException("Invalid package key string"); return ManifestPackageKey.Parse(reader.GetString()!); diff --git a/NuGet/Converters/RuntimeFrameworkJsonConverter.cs b/NuGet/Converters/RuntimeFrameworkJsonConverter.cs new file mode 100644 index 0000000..85c6f74 --- /dev/null +++ b/NuGet/Converters/RuntimeFrameworkJsonConverter.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Models; + +namespace NuGet.Converters; + +public class RuntimeFrameworkJsonConverter : JsonConverter +{ + public override NuGetRuntimeFramework Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName)) + throw new JsonException("Invalid runtime framework string"); + + return NuGetRuntimeFramework.Parse(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, NuGetRuntimeFramework value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + + public override NuGetRuntimeFramework ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, + JsonSerializerOptions options) => Read(ref reader, typeToConvert, options); + + public override void WriteAsPropertyName(Utf8JsonWriter writer, NuGetRuntimeFramework value, JsonSerializerOptions options) + { + writer.WritePropertyName(value.ToString()); + } +} \ No newline at end of file diff --git a/NuGet/Deps/DependenciesManifest.cs b/NuGet/Deps/DependenciesManifest.cs index ec72de4..844c3ca 100644 --- a/NuGet/Deps/DependenciesManifest.cs +++ b/NuGet/Deps/DependenciesManifest.cs @@ -10,9 +10,10 @@ using NuGet.Versioning; namespace NuGet.Deps; -public record DependenciesManifest(RuntimeTarget RuntimeTarget, - ImmutableDictionary CompilationOptions, - ImmutableDictionary> Targets, +public record DependenciesManifest( + RuntimeTarget RuntimeTarget, + ImmutableDictionary CompilationOptions, + ImmutableDictionary> Targets, ImmutableDictionary Libraries); public record DependencyLibrary( @@ -26,7 +27,9 @@ public record DependencyLibrary( public enum LibraryType { Project, - Package + Package, + Reference, + Runtimepack } public record DependencyTarget(ImmutableDictionary? Dependencies, @@ -39,7 +42,7 @@ public record Dependency(Version? FileVersion = null); public record RuntimeDependency(Version? AssemblyVersion = null, Version? FileVersion = null) : Dependency(FileVersion); -public record RuntimeTarget([property: JsonPropertyName("name")] NuGetFramework Framework, string Signature = ""); +public record RuntimeTarget([property: JsonPropertyName("name")] NuGetRuntimeFramework RuntimeFramework, string Signature = ""); [JsonConverter(typeof(ManifestPackageKeyJsonConverter))] public record ManifestPackageKey(string Id, NuGetVersion Version) @@ -56,7 +59,7 @@ public record ManifestPackageKey(string Id, NuGetVersion Version) public override string ToString() => $"{Id}/{Version}"; } -public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSourceMapping packageSources, Func catalogEntryResolver) +public static class DependencyManifestSerializer { private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) { @@ -68,8 +71,15 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour new VersionJsonConverter() } }; + + public static Task SerializeAsync(Stream stream, DependenciesManifest manifest) => JsonSerializer.SerializeAsync(stream, manifest, SerializerOptions); + + public static ValueTask DeserializeAsync(Stream stream) => JsonSerializer.DeserializeAsync(stream, SerializerOptions)!; +} - public async ValueTask WriteDependencyManifestAsync(Stream stream, CatalogEntry catalogEntry, NuGetFramework targetFramework) +public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSourceMapping packageSources, Func catalogEntryResolver) +{ + public async ValueTask WriteDependencyManifestAsync(Stream stream, CatalogEntry catalogEntry, NuGetRuntimeFramework targetFramework) { var runtimeTarget = new RuntimeTarget(targetFramework); @@ -77,21 +87,22 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour await MapCatalogEntryAsync(catalogEntry, targetFramework, targets); - var manifest = new DependenciesManifest(runtimeTarget, ImmutableDictionary.Empty, - ImmutableDictionary>.Empty + var manifest = new DependenciesManifest(runtimeTarget, ImmutableDictionary.Empty, + ImmutableDictionary>.Empty .Add(targetFramework, targets.ToImmutable()), ImmutableDictionary.Empty); - await JsonSerializer.SerializeAsync(stream, manifest, SerializerOptions); + await DependencyManifestSerializer.SerializeAsync(stream, manifest); } - private async Task MapCatalogEntryAsync(CatalogEntry catalogEntry, NuGetFramework targetFramework, + private async Task MapCatalogEntryAsync(CatalogEntry catalogEntry, NuGetRuntimeFramework targetFramework, ImmutableDictionary.Builder targets) { if (targets.ContainsKey(new(catalogEntry.Id, catalogEntry.Version)) || !catalogEntry.DependencyGroups.HasValue) return; - var nearest = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups.Value, targetFramework, + // TODO take into account the target framework runtime identifier + var nearest = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups.Value, targetFramework.Framework, group => group.TargetFramework); if (nearest is null) diff --git a/NuGet/Models/NuGetRuntimeFramework.cs b/NuGet/Models/NuGetRuntimeFramework.cs new file mode 100644 index 0000000..1cccff6 --- /dev/null +++ b/NuGet/Models/NuGetRuntimeFramework.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; +using NuGet.Converters; +using NuGet.Frameworks; + +namespace NuGet.Models; + +/// +/// Represents a NuGetFramework with a runtime identifier +/// +[JsonConverter(typeof(RuntimeFrameworkJsonConverter))] +public record NuGetRuntimeFramework(NuGetFramework Framework, string? RuntimeIdentifier) +{ + public static NuGetRuntimeFramework Parse(string str) + { + var index = str.IndexOf('/'); + + if (index < 0) + return new NuGetRuntimeFramework(NuGetFramework.Parse(str), null); + + return new NuGetRuntimeFramework(NuGetFramework.Parse(str[..index]), str[(index + 1)..]); + } + + public override string ToString() + { + return string.IsNullOrEmpty(RuntimeIdentifier) ? Framework.ToString() : $"{Framework}/{RuntimeIdentifier}"; + } +} \ No newline at end of file