From 295ee6806e5826d3f1c8420860bda93cf35f964f Mon Sep 17 00:00:00 2001 From: pas2704 Date: Mon, 12 May 2025 03:34:26 -0400 Subject: [PATCH] Fixes for dependency resolution steamworks added to builtin packages --- CringeBootstrap/packages.lock.json | 2 +- CringeLauncher/CringeLauncher.csproj | 1 - CringeLauncher/packages.lock.json | 23 ++-- CringePlugins/CringePlugins.csproj | 1 + CringePlugins/Loader/PluginsLifetime.cs | 25 +++-- CringePlugins/Resolver/BuiltInPackages.cs | 3 + CringePlugins/Resolver/PackageResolver.cs | 122 +++++++++++++--------- CringePlugins/packages.lock.json | 12 +++ 8 files changed, 118 insertions(+), 71 deletions(-) diff --git a/CringeBootstrap/packages.lock.json b/CringeBootstrap/packages.lock.json index 572e75b..9e8d188 100644 --- a/CringeBootstrap/packages.lock.json +++ b/CringeBootstrap/packages.lock.json @@ -337,7 +337,6 @@ "Microsoft.CodeAnalysis.CSharp": "[4.13.0, )", "NLog": "[5.4.0, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", - "Steamworks.NET": "[20.1.0, )", "System.Diagnostics.PerformanceCounter": "[9.0.4, )", "System.Management": "[9.0.4, )", "System.Private.ServiceModel": "[4.10.3, )", @@ -355,6 +354,7 @@ "NuGet": "[1.0.0, )", "SharedCringe": "[1.0.0, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", + "Steamworks.NET": "[20.1.0, 20.1.0]", "dnlib": "[4.4.0, )" } }, diff --git a/CringeLauncher/CringeLauncher.csproj b/CringeLauncher/CringeLauncher.csproj index 2a407c1..b0c6efe 100644 --- a/CringeLauncher/CringeLauncher.csproj +++ b/CringeLauncher/CringeLauncher.csproj @@ -40,7 +40,6 @@ - diff --git a/CringeLauncher/packages.lock.json b/CringeLauncher/packages.lock.json index 79a2ec5..f36cf41 100644 --- a/CringeLauncher/packages.lock.json +++ b/CringeLauncher/packages.lock.json @@ -62,12 +62,6 @@ "protobuf-net": "1.0.0" } }, - "Steamworks.NET": { - "type": "Direct", - "requested": "[20.1.0, )", - "resolved": "20.1.0", - "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" - }, "System.Diagnostics.PerformanceCounter": { "type": "Direct", "requested": "[9.0.4, )", @@ -262,6 +256,11 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, + "Steamworks.NET": { + "type": "Transitive", + "resolved": "20.1.0", + "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -376,6 +375,7 @@ "NuGet": "[1.0.0, )", "SharedCringe": "[1.0.0, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", + "Steamworks.NET": "[20.1.0, 20.1.0]", "dnlib": "[4.4.0, )" } }, @@ -397,12 +397,6 @@ } }, "net9.0-windows10.0.19041/win-x64": { - "Steamworks.NET": { - "type": "Direct", - "requested": "[20.1.0, )", - "resolved": "20.1.0", - "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" - }, "System.Diagnostics.PerformanceCounter": { "type": "Direct", "requested": "[9.0.4, )", @@ -431,6 +425,11 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Steamworks.NET": { + "type": "Transitive", + "resolved": "20.1.0", + "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" + }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "9.0.4", diff --git a/CringePlugins/CringePlugins.csproj b/CringePlugins/CringePlugins.csproj index ad87dea..8034bdf 100644 --- a/CringePlugins/CringePlugins.csproj +++ b/CringePlugins/CringePlugins.csproj @@ -28,6 +28,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index df47271..3fc27c9 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -59,15 +59,16 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage var packages = await resolver.ResolveAsync(); progress.Report("Downloading packages"); - - var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, progress); + + var builtInPackages = BuiltInPackages.GetPackages(_runtimeFramework).ToImmutableDictionary(package => package.Package.Id); + var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, builtInPackages.Keys.ToHashSet(), progress); progress.Report("Loading plugins"); //we can move this, but it should be before plugin init RenderHandler.Current.RegisterComponent(new NotificationsComponent()); - await LoadPlugins(cachedPackages, sourceMapping, packagesConfig); + await LoadPlugins(cachedPackages, sourceMapping, packagesConfig, builtInPackages); RenderHandler.Current.RegisterComponent(new PluginListComponent(packagesConfig, sourceMapping, configPath, gameFolder, _plugins)); } @@ -91,12 +92,9 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage } private async Task LoadPlugins(IReadOnlyCollection packages, PackageSourceMapping sourceMapping, - PackagesConfig packagesConfig) + PackagesConfig packagesConfig, ImmutableDictionary builtInPackages) { var plugins = _plugins.ToBuilder(); - - var builtInPackages = BuiltInPackages.GetPackages(_runtimeFramework) - .ToImmutableDictionary(package => package.Package.Id); var resolvedPackages = builtInPackages.ToDictionary(); foreach (var package in packages) @@ -120,8 +118,17 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage 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); + try + { + await using var stream = File.Create(path); + await manifestBuilder.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework); + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to write dependency manifest for {path}"); + File.Delete(path); //delete file to avoid breaking cache + throw; + } } var client = await sourceMapping.GetClientAsync(package.Package.Id); diff --git a/CringePlugins/Resolver/BuiltInPackages.cs b/CringePlugins/Resolver/BuiltInPackages.cs index b16e498..32d12af 100644 --- a/CringePlugins/Resolver/BuiltInPackages.cs +++ b/CringePlugins/Resolver/BuiltInPackages.cs @@ -21,6 +21,7 @@ public static class BuiltInPackages private const string SeReferenceAssemblies = "SpaceEngineersDedicated.ReferenceAssemblies"; private const string ImGui = "ImGui.NET.DirectX"; private const string Harmony = "Lib.Harmony.Thin"; + private const string Steamworks = "Steamworks.NET"; public static ImmutableArray GetPackages(NuGetFramework runtimeFramework) { @@ -32,6 +33,7 @@ public static class BuiltInPackages ], SeReferenceAssemblies, new(seVersion)); var imGui = FromAssembly(runtimeFramework, id: ImGui); var harmony = FromAssembly(runtimeFramework, id: Harmony); + var steam = FromAssembly(runtimeFramework, id: Steamworks); BuiltInSdkPackage MapSdkPackage( (string FileName, byte[] ImageBytes, PortableExecutableReference Reference, Guid Mvid) r) @@ -53,6 +55,7 @@ public static class BuiltInPackages se, imGui, harmony, + steam, FromAssembly(runtimeFramework, [se.AsDependency(), imGui.AsDependency(), harmony.AsDependency()] #if DEBUG diff --git a/CringePlugins/Resolver/PackageResolver.cs b/CringePlugins/Resolver/PackageResolver.cs index 7bcfb10..667a356 100644 --- a/CringePlugins/Resolver/PackageResolver.cs +++ b/CringePlugins/Resolver/PackageResolver.cs @@ -14,7 +14,7 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray> ResolveAsync() { var order = 0; - var packages = new SortedDictionary(); + var packages = new Dictionary(); foreach (var reference in references) { @@ -39,7 +39,7 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray b.CatalogEntry.Version).OrderDescending().First(b => reference.Range.Satisfies(b)); if (version is null) - throw new Exception($"Unable to find version for package {reference.Id}"); + throw new NotSupportedException($"Unable to find version for package {reference.Id}"); var catalogEntry = items[version].CatalogEntry; @@ -48,22 +48,18 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray 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) + if (package.Version < existingEntry.Version) + throw new NotSupportedException($"Package reference {package.Id} has lower version {package.Version} than already resolved {existingEntry.Version}"); + + if (package.Version == existingEntry.Version) continue; - packages.Remove(existingPackage); - packages.Add(package with - { - Order = ++order - }, catalogEntry); + packages[package with { Order = ++order }] = catalogEntry; } var set = ImmutableSortedSet.Empty.ToBuilder(); @@ -78,11 +74,13 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray g.TargetFramework); if (nearestGroup is null) - throw new Exception($"Unable to find compatible dependency group for package {package.Id}"); + throw new NotSupportedException($"Unable to find compatible dependency group for package {package.Id}"); set.Add(new RemotePackage(package, nearestGroup.TargetFramework, client, catalogEntry)); } - + + var dependencyVersions = new Dictionary(); + var dependencyPackages = new HashSet(); for (var i = 0; i < set.Count; i++) { if (set[i] is not RemotePackage package) continue; @@ -103,79 +101,105 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray page.Items!) .ToImmutableDictionary(b => b.CatalogEntry.Version); - var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().FirstOrDefault(b => versionRange.Satisfies(b)); + var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().FirstOrDefault(versionRange.Satisfies); if (version is null) - throw new Exception($"Unable to find version for package {id} as dependency of {package.Package}"); + throw new NotSupportedException($"Unable to find version for package {id} as dependency of {package.Package}"); var catalogEntry = items[version].CatalogEntry; var dependencyPackage = new Package(i, id, version); - if (packages.TryGetValue(dependencyPackage, out var existingPackage)) + if (packages.TryGetValue(dependencyPackage, out var existingCatalog)) { - if (dependencyPackage.Version < existingPackage.Version) - { - // dependency has lower version than already resolved - // need to check if existing fits the version range - // and reorder existing to ensure it's ordered before requesting package + if (dependencyPackage.Version == existingCatalog.Version) + continue; //a dependency with this version has already been resolved - if (!versionRange.Satisfies(existingPackage.Version)) - throw new Exception( - $"Incompatible package version {dependencyPackage} (required by {package.Package}) from {existingPackage}"); - - if (dependencyPackage.CompareTo(existingPackage) < 0) - { - packages.Remove(dependencyPackage); - packages.Add(dependencyPackage, existingPackage); - } - - continue; + //does the existing version support our package? + if (versionRange.Satisfies(existingCatalog.Version)) + continue; //keep the old one + + if (!dependencyVersions.TryGetValue(dependencyPackage, out var minimalVersionRange)) + throw new InvalidOperationException("Missing minimal version range"); + + minimalVersionRange = VersionRange.CommonSubSet([minimalVersionRange, versionRange]); + + if (!minimalVersionRange.Satisfies(version)) + { + //do one last check for a matching version + version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().FirstOrDefault(minimalVersionRange.Satisfies); + + if (version is null) + throw new NotSupportedException($"Unable to find version for package {id} as dependency of {package.Package} (and others) that satisfies {minimalVersionRange}"); + + catalogEntry = items[version].CatalogEntry; + + dependencyPackage = dependencyPackage with { Version = version }; } - - throw new Exception($"Detected package downgrade from {existingPackage} to {dependencyPackage} as dependency of {package.Package}"); + + //swap to this version + packages[dependencyPackage] = catalogEntry; + dependencyVersions[dependencyPackage] = minimalVersionRange; + + var replacementGroup = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups ?? [], runtimeFramework, g => g.TargetFramework) + ?? throw new NotSupportedException($"Unable to find compatible dependency group for {dependencyPackage} as dependency of {package.Package}"); + + var replacement = new RemoteDependencyPackage(dependencyPackage, replacementGroup.TargetFramework, client, package, catalogEntry); + + if (!dependencyPackages.Remove(replacement)) + throw new InvalidOperationException("Replaced dependency wasn't there"); + + dependencyPackages.Add(replacement); + + continue; } - if (!packages.TryAdd(dependencyPackage, catalogEntry)) - throw new Exception($"Duplicate package {dependencyPackage.Id}"); + if (!packages.TryAdd(dependencyPackage, catalogEntry) || !dependencyVersions.TryAdd(dependencyPackage, versionRange)) + throw new InvalidOperationException($"Duplicate package {dependencyPackage.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 {dependencyPackage} as dependency of {package.Package}"); - - set.Add(new RemoteDependencyPackage(dependencyPackage, nearestGroup.TargetFramework, client, package, catalogEntry)); + g => g.TargetFramework) ?? throw new NotSupportedException($"Unable to find compatible dependency group for {dependencyPackage} as dependency of {package.Package}"); + + dependencyPackages.Add(new RemoteDependencyPackage(dependencyPackage, nearestGroup.TargetFramework, client, package, catalogEntry)); } } + foreach (var item in dependencyPackages) + set.Add(item); + return set.ToImmutable(); } public async Task> DownloadPackagesAsync(DirectoryInfo baseDirectory, - IReadOnlySet resolvedPackages, IProgress? progress = null) + IReadOnlySet resolvedPackages, IReadOnlySet? ignorePackages = null, IProgress? progress = null) { var packages = ImmutableHashSet.Empty.ToBuilder(); var i = 0f; foreach (var package in resolvedPackages) { + if (ignorePackages?.Contains(package.Package.Id) == true) + continue; + switch (package) { - case RemotePackage remotePackage: + case RemoteDependencyPackage: + case 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); + var client = (package as RemoteDependencyPackage)?.Client ?? ((RemotePackage)package).Client; + + await using var stream = await client.GetPackageContentStreamAsync(package.Package.Id, package.Package.Version); using var memStream = new MemoryStream(); await stream.CopyToAsync(memStream); memStream.Position = 0; @@ -189,6 +213,8 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray