Added options to disable launcher/plugin auto updates
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 4m4s
Build / Build Nuget package (NuGet) (push) Successful in 4m7s
Build / Build Nuget package (SharedCringe) (push) Successful in 4m5s
Build / Build Nuget package (CringePlugins) (push) Successful in 4m25s
Build / Build Launcher (push) Successful in 5m12s
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 4m4s
Build / Build Nuget package (NuGet) (push) Successful in 4m7s
Build / Build Nuget package (SharedCringe) (push) Successful in 4m5s
Build / Build Nuget package (CringePlugins) (push) Successful in 4m25s
Build / Build Launcher (push) Successful in 5m12s
Also ran cleanup
This commit is contained in:
@@ -9,16 +9,18 @@ namespace CringePlugins.Config;
|
||||
|
||||
public sealed class ConfigHandler
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly DirectoryInfo _configDirectory;
|
||||
private readonly JsonSerializerOptions _serializerOptions = new(NuGetClient.SerializerOptions)
|
||||
public static readonly JsonSerializerOptions SerializerOptions = new(NuGetClient.SerializerOptions)
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly DirectoryInfo _configDirectory;
|
||||
|
||||
|
||||
private readonly EvaluationOptions _evaluationOptions = new()
|
||||
{
|
||||
OutputFormat = OutputFormat.List,
|
||||
@@ -67,7 +69,7 @@ public sealed class ConfigHandler
|
||||
T instance;
|
||||
try
|
||||
{
|
||||
instance = jsonNode.Deserialize<T>(_serializerOptions)!;
|
||||
instance = jsonNode.Deserialize<T>(SerializerOptions)!;
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
@@ -84,7 +86,7 @@ public sealed class ConfigHandler
|
||||
{
|
||||
var spec = IConfigurationSpecProvider.FromType(typeof(T));
|
||||
|
||||
var jsonNode = JsonSerializer.SerializeToNode(newValue, _serializerOptions)!;
|
||||
var jsonNode = JsonSerializer.SerializeToNode(newValue, SerializerOptions)!;
|
||||
|
||||
if (spec != null && !TryValidate(name, spec, jsonNode))
|
||||
throw new JsonException($"Supplied config value for {name} is invalid");
|
||||
@@ -96,7 +98,7 @@ public sealed class ConfigHandler
|
||||
{
|
||||
Indented = true
|
||||
});
|
||||
jsonNode.WriteTo(writer, _serializerOptions);
|
||||
jsonNode.WriteTo(writer, SerializerOptions);
|
||||
|
||||
ConfigReloaded?.Invoke(this, new ConfigValue<T>(name, newValue));
|
||||
}
|
||||
|
5
CringePlugins/Config/LauncherConfig.cs
Normal file
5
CringePlugins/Config/LauncherConfig.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace CringePlugins.Config;
|
||||
public sealed record LauncherConfig(bool DisableLauncherUpdates, bool DisablePluginUpdates)
|
||||
{
|
||||
public static LauncherConfig Default => new(false, false);
|
||||
}
|
@@ -51,7 +51,7 @@ internal class PluginAssemblyLoadContext : DerivedAssemblyLoadContext
|
||||
{
|
||||
if (_dependencyResolver.ResolveAssemblyToPath(assemblyName) is { } path)
|
||||
return LoadFromAssemblyPath(path);
|
||||
|
||||
|
||||
return base.Load(assemblyName);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ internal class PluginAssemblyLoadContext : DerivedAssemblyLoadContext
|
||||
{
|
||||
if (_dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName) is { } path)
|
||||
return LoadUnmanagedDllFromPath(path);
|
||||
|
||||
|
||||
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||
}
|
||||
|
||||
|
@@ -29,14 +29,14 @@ internal sealed class PluginInstance(PluginMetadata metadata, string entrypointP
|
||||
{
|
||||
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);
|
||||
contextBuilder.Add(_context);
|
||||
|
||||
|
||||
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)
|
||||
@@ -66,7 +66,7 @@ internal sealed class PluginInstance(PluginMetadata metadata, string entrypointP
|
||||
{
|
||||
if (_instance is null)
|
||||
throw new InvalidOperationException("Must call Instantiate first");
|
||||
|
||||
|
||||
MyPlugins.m_plugins.Add(WrappedInstance);
|
||||
if (_instance is IHandleInputPlugin)
|
||||
MyPlugins.m_handleInputPlugins.Add(WrappedInstance);
|
||||
|
@@ -19,7 +19,7 @@ public record PluginMetadata(string Name, NuGetVersion Version, string Source)
|
||||
(versionAttribute ?? fileVersionAttribute)?.ConstructorArguments[0].Value as UTF8String ?? "0.0.0.0",
|
||||
out var version))
|
||||
version = new(0, 0, 0, 0);
|
||||
|
||||
|
||||
return new(name, version, "Local");
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I
|
||||
public Type InstanceType => plugin.GetType();
|
||||
|
||||
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
private readonly IHandleInputPlugin? _handleInputPlugin = plugin as IHandleInputPlugin;
|
||||
|
||||
private const float ErrorShowTime = 10f;
|
||||
|
@@ -20,14 +20,15 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
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 NuGetRuntimeFramework _runtimeFramework = new(NuGetFramework.ParseFolder("net9.0-windows10.0.19041.0"), RuntimeInformation.RuntimeIdentifier);
|
||||
|
||||
|
||||
private ConfigReference<PackagesConfig>? _configReference;
|
||||
private ConfigReference<LauncherConfig>? _launcherConfig;
|
||||
|
||||
public async ValueTask Load(ISplashProgress progress)
|
||||
{
|
||||
@@ -40,7 +41,9 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
progress.Report("Loading config");
|
||||
|
||||
_configReference = configHandler.RegisterConfig("packages", PackagesConfig.Default);
|
||||
_launcherConfig = configHandler.RegisterConfig("launcher", LauncherConfig.Default);
|
||||
var packagesConfig = _configReference.Value;
|
||||
var launcherConfig = _launcherConfig.Value;
|
||||
|
||||
progress.Report("Resolving packages");
|
||||
|
||||
@@ -48,12 +51,14 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
// TODO take into account the target framework runtime identifier
|
||||
var resolver = new PackageResolver(_runtimeFramework.Framework, packagesConfig.Packages, sourceMapping);
|
||||
|
||||
var packages = await resolver.ResolveAsync();
|
||||
var cacheDir = _dir.CreateSubdirectory("cache");
|
||||
|
||||
var packages = await resolver.ResolveAsync(cacheDir, launcherConfig.DisablePluginUpdates);
|
||||
|
||||
progress.Report("Downloading packages");
|
||||
|
||||
var builtInPackages = await BuiltInPackages.GetPackagesAsync(_runtimeFramework);
|
||||
var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, builtInPackages.Keys.ToHashSet(), progress);
|
||||
var cachedPackages = await PackageResolver.DownloadPackagesAsync(cacheDir, packages, builtInPackages.Keys.ToHashSet(), progress);
|
||||
|
||||
progress.Report("Loading plugins");
|
||||
|
||||
@@ -62,7 +67,7 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
|
||||
await LoadPlugins(cachedPackages, sourceMapping, packagesConfig, builtInPackages);
|
||||
|
||||
RenderHandler.Current.RegisterComponent(new PluginListComponent(_configReference, sourceMapping, MyFileSystem.ExePath, _plugins));
|
||||
RenderHandler.Current.RegisterComponent(new PluginListComponent(_configReference, _launcherConfig, sourceMapping, MyFileSystem.ExePath, _plugins));
|
||||
}
|
||||
|
||||
public void RegisterLifetime()
|
||||
@@ -87,7 +92,7 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
PackagesConfig packagesConfig, ImmutableDictionary<string, ResolvedPackage> builtInPackages)
|
||||
{
|
||||
var plugins = _plugins.ToBuilder();
|
||||
|
||||
|
||||
var resolvedPackages = builtInPackages.ToDictionary();
|
||||
foreach (var package in packages)
|
||||
{
|
||||
@@ -100,14 +105,14 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
resolvedPackages.TryGetValue(dependency.Id, out var package);
|
||||
return package?.Entry;
|
||||
});
|
||||
|
||||
|
||||
foreach (var package in packages)
|
||||
{
|
||||
if (builtInPackages.ContainsKey(package.Package.Id)) continue;
|
||||
|
||||
var client = await sourceMapping.GetClientAsync(package.Package.Id);
|
||||
var packageClient = await sourceMapping.GetClientAsync(package.Package.Id);
|
||||
|
||||
if (client == null)
|
||||
if (packageClient == null)
|
||||
{
|
||||
Log.Warn("Client not found for {Package}", package.Package.Id);
|
||||
continue;
|
||||
@@ -133,29 +138,29 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
}
|
||||
}
|
||||
|
||||
var sourceName = packagesConfig.Sources.First(b => b.Url == client.ToString()).Name;
|
||||
var sourceName = packagesConfig.Sources.First(b => b.Url == packageClient.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();
|
||||
}
|
||||
|
||||
@@ -166,7 +171,7 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
|
||||
plugins.Add(metadata is null ? new PluginInstance(path) : new(metadata, path));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
{
|
||||
Log.Error(e, "Failed to load plugin {PluginPath}", path);
|
||||
}
|
||||
}
|
||||
|
@@ -33,10 +33,10 @@ public static class BuiltInPackages
|
||||
(_, _, _, libraries) = await DependencyManifestSerializer.DeserializeAsync(stream);
|
||||
|
||||
var framework = runtimeFramework.Framework;
|
||||
|
||||
|
||||
var nlog = FromAssembly<LogFactory>(framework, version: libraries.Keys.Single(b => b.Id == NLog).Version);
|
||||
Version seVersion = new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion!.Value);
|
||||
|
||||
|
||||
var se = FromAssembly<SpaceEngineersGame>(framework, [
|
||||
nlog.AsDependency(libraries)
|
||||
], SeReferenceAssemblies, new(seVersion));
|
||||
@@ -50,7 +50,7 @@ public static class BuiltInPackages
|
||||
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), framework,
|
||||
new(Path.GetFileNameWithoutExtension(r.FileName), version, [new(framework, [])], null, []));
|
||||
@@ -95,16 +95,16 @@ public static class BuiltInPackages
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, ResolvedPackage>();
|
||||
foreach (var package in packages)
|
||||
builder.TryAdd(package.Package.Id, package);
|
||||
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
|
||||
private static Dependency AsDependency(this ResolvedPackage package, ImmutableDictionary<ManifestPackageKey, DependencyLibrary> libraries)
|
||||
{
|
||||
//ignore the SE reference because the game can update without a launcher update
|
||||
if (package.Entry.Id != SeReferenceAssemblies && !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));
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,17 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.IO.Compression;
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NuGet;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Models;
|
||||
using NuGet.Versioning;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace CringePlugins.Resolver;
|
||||
|
||||
public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<PackageReference> references, PackageSourceMapping packageSources)
|
||||
{
|
||||
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
public async Task<ImmutableSortedSet<ResolvedPackage>> ResolveAsync()
|
||||
public async Task<ImmutableSortedSet<ResolvedPackage>> ResolveAsync(DirectoryInfo baseDir, bool disableUpdates)
|
||||
{
|
||||
var order = 0;
|
||||
var packages = new Dictionary<Package, CatalogEntry>();
|
||||
@@ -39,26 +39,43 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
page.Items!.Where(b => b.CatalogEntry.PackageTypes is ["CringePlugin"]))
|
||||
.ToImmutableDictionary(b => b.CatalogEntry.Version);
|
||||
|
||||
var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().First(b => reference.Range.Satisfies(b));
|
||||
|
||||
var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().First(reference.Range.Satisfies);
|
||||
|
||||
if (disableUpdates)
|
||||
{
|
||||
if (GetLatestInstalledVersion(baseDir, reference.Id, reference.Range) is { } installedVersion && items.ContainsKey(installedVersion))
|
||||
{
|
||||
if (installedVersion < version)
|
||||
{
|
||||
Log.Warn("Using outdated version of package {Package} {InstalledVersion} instead of {AvailableVersion} due to updates being disabled",
|
||||
reference.Id, installedVersion, version);
|
||||
}
|
||||
version = installedVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn("No valid installed version found for package {Package}", reference.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (version is null)
|
||||
throw new NotSupportedException($"Unable to find version for package {reference.Id}");
|
||||
|
||||
var catalogEntry = items[version].CatalogEntry;
|
||||
|
||||
|
||||
var package = new Package(order, reference.Id, version);
|
||||
|
||||
if (packages.TryAdd(package, catalogEntry))
|
||||
continue;
|
||||
|
||||
|
||||
if (!packages.TryGetValue(package, out var existingEntry))
|
||||
throw new InvalidOperationException($"Duplicate package error {package.Id}");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -69,13 +86,13 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
foreach (var (package, catalogEntry) in packages)
|
||||
{
|
||||
var client = await packageSources.GetClientAsync(package.Id);
|
||||
|
||||
|
||||
if (client == null || !catalogEntry.DependencyGroups.HasValue)
|
||||
continue;
|
||||
|
||||
var nearestGroup = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups.Value, runtimeFramework,
|
||||
g => g.TargetFramework);
|
||||
|
||||
|
||||
if (nearestGroup is null)
|
||||
throw new NotSupportedException($"Unable to find compatible dependency group for package {package.Id}");
|
||||
|
||||
@@ -91,14 +108,14 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
var dependencies = package.Entry.DependencyGroups
|
||||
?.Single(b => b.TargetFramework == package.ResolvedFramework)?.Dependencies ??
|
||||
[];
|
||||
|
||||
|
||||
foreach (var (id, versionRange) in dependencies)
|
||||
{
|
||||
var client = await packageSources.GetClientAsync(id);
|
||||
|
||||
if (client == null)
|
||||
continue;
|
||||
|
||||
|
||||
RegistrationRoot? registrationRoot;
|
||||
|
||||
try
|
||||
@@ -109,15 +126,29 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to resolve dependency {id} for {package.Package}", ex);
|
||||
}
|
||||
|
||||
|
||||
var items = registrationRoot.Items.SelectMany(page => page.Items!)
|
||||
.ToImmutableDictionary(b => b.CatalogEntry.Version);
|
||||
|
||||
var version = items.Values.Select(b => b.CatalogEntry.Version).OrderDescending().FirstOrDefault(versionRange.Satisfies);
|
||||
|
||||
|
||||
if (version is null)
|
||||
throw new NotSupportedException($"Unable to find version for package {id} as dependency of {package.Package}");
|
||||
|
||||
|
||||
if (disableUpdates)
|
||||
{
|
||||
if (GetLatestInstalledVersion(baseDir, id, versionRange) is { } installedVersion && items.ContainsKey(installedVersion))
|
||||
{
|
||||
if (installedVersion < version)
|
||||
{
|
||||
Log.Warn("Using outdated version of dependency package {Package} {InstalledVersion} instead of {AvailableVersion} due to updates being disabled",
|
||||
id, installedVersion, version);
|
||||
}
|
||||
version = installedVersion;
|
||||
}
|
||||
//todo: warnings here? we'd need to check against builtin packages
|
||||
}
|
||||
|
||||
var catalogEntry = items[version].CatalogEntry;
|
||||
|
||||
var dependencyPackage = new Package(i, id, version);
|
||||
@@ -165,10 +196,10 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
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) ?? throw new NotSupportedException($"Unable to find compatible dependency group for {dependencyPackage} as dependency of {package.Package}");
|
||||
|
||||
@@ -182,11 +213,30 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
return set.ToImmutable();
|
||||
}
|
||||
|
||||
public async Task<ImmutableHashSet<CachedPackage>> DownloadPackagesAsync(DirectoryInfo baseDirectory,
|
||||
private static NuGetVersion? GetLatestInstalledVersion(DirectoryInfo baseDirectory, string id, VersionRange range)
|
||||
{
|
||||
var dir = new DirectoryInfo(Path.Join(baseDirectory.FullName, id));
|
||||
|
||||
if (!dir.Exists)
|
||||
return null;
|
||||
|
||||
NuGetVersion? maxVersion = null;
|
||||
foreach (var subdir in dir.GetDirectories())
|
||||
{
|
||||
if (NuGetVersion.TryParse(subdir.Name, out var version) && range.Satisfies(version) && (maxVersion == null || version > maxVersion))
|
||||
{
|
||||
maxVersion = version;
|
||||
}
|
||||
}
|
||||
|
||||
return maxVersion;
|
||||
}
|
||||
|
||||
public static async Task<ImmutableHashSet<CachedPackage>> DownloadPackagesAsync(DirectoryInfo baseDirectory,
|
||||
IReadOnlySet<ResolvedPackage> resolvedPackages, IReadOnlySet<string>? ignorePackages = null, IProgress<float>? progress = null)
|
||||
{
|
||||
var packages = ImmutableHashSet<CachedPackage>.Empty.ToBuilder();
|
||||
|
||||
|
||||
var i = 0f;
|
||||
foreach (var package in resolvedPackages)
|
||||
{
|
||||
@@ -206,26 +256,26 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
});
|
||||
|
||||
var srv = new ShaderResourceView(MyRender11.DeviceInstance, tex);
|
||||
|
||||
|
||||
_placeholderImage = new Image(null!, srv, new(1, 1));
|
||||
}
|
||||
|
||||
@@ -88,12 +88,12 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
if (_webCacheEtag.TryGetValue(new(url), out var existingEtag))
|
||||
if (_webCacheEtag.TryGetValue(new(url), out var existingEtag))
|
||||
request.Headers.IfNoneMatch.Add(existingEtag);
|
||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
|
||||
if (response.Headers.ETag is { } etag)
|
||||
_webCacheEtag[new(url)] = etag;
|
||||
|
||||
@@ -125,7 +125,7 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
if (cacheControl.MaxAge.HasValue)
|
||||
{
|
||||
var responseAge = DateTimeOffset.UtcNow - cacheControl.MaxAge.Value;
|
||||
return File.GetLastWriteTimeUtc(path) > responseAge;
|
||||
return File.GetLastWriteTimeUtc(path) > responseAge;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -160,14 +160,14 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
CpuAccessFlags = CpuAccessFlags.None,
|
||||
OptionFlags = ResourceOptionFlags.None,
|
||||
}, img.ToDataBox());
|
||||
|
||||
|
||||
var srv = new ShaderResourceView(MyRender11.DeviceInstance, tex);
|
||||
|
||||
|
||||
image = new Image(identifier, srv, new(desc.Width, desc.Height));
|
||||
_images.Add(identifier, image, true);
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
private class ImageReference(ImGuiImage placeholderImage) : ImGuiImage
|
||||
{
|
||||
public ImGuiImage? Image;
|
||||
@@ -175,14 +175,14 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
|
||||
public override nint TextureId => Image ?? ErrorImage ?? placeholderImage;
|
||||
public override Vector2 Size => Image ?? ErrorImage ?? placeholderImage;
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Image?.Dispose();
|
||||
ErrorImage?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class Image(ImageIdentifier identifier, ShaderResourceView srv, Vector2 size) : ImGuiImage
|
||||
{
|
||||
private bool _disposed;
|
||||
@@ -210,7 +210,7 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
|
||||
private void OnUse()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_lastUse = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
@@ -223,20 +223,20 @@ internal sealed class ImGuiImageService(HttpClient client) : IImGuiImageService
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Image {{ {identifier} {size} }}";
|
||||
return $"Image {{ {identifier} {size} }}";
|
||||
}
|
||||
}
|
||||
|
||||
private abstract record ImageIdentifier;
|
||||
private record WebImageIdentifier(Uri Url) : ImageIdentifier;
|
||||
private record FileImageIdentifier(string Path) : ImageIdentifier;
|
||||
private record FileImageIdentifier(string Path) : ImageIdentifier;
|
||||
}
|
||||
|
||||
public abstract class ImGuiImage : IDisposable
|
||||
{
|
||||
public abstract nint TextureId { get; }
|
||||
public abstract Vector2 Size { get; }
|
||||
|
||||
|
||||
public static implicit operator nint(ImGuiImage image) => image.TextureId;
|
||||
public static implicit operator Vector2(ImGuiImage image) => image.Size;
|
||||
public abstract void Dispose();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
namespace CringePlugins.Splash;
|
||||
|
||||
public interface ISplashProgress : IProgress<ProgressInfo>, IProgress<float>
|
||||
public interface ISplashProgress : IProgress<ProgressInfo>, IProgress<float>
|
||||
{
|
||||
void DefineStage(ILoadingStage stage);
|
||||
void DefineStepsCount(int count);
|
||||
|
@@ -10,16 +10,16 @@ namespace CringePlugins.Splash;
|
||||
public class Splash : ISplashProgress, IRenderComponent
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
private readonly List<ILoadingStage> _loadingStages = [];
|
||||
|
||||
private ProgressInfo? _lastInfo;
|
||||
private bool _done;
|
||||
|
||||
|
||||
public void Report(ProgressInfo value)
|
||||
{
|
||||
_lastInfo = value;
|
||||
|
||||
|
||||
if (value is PercentProgressInfo percentProgressInfo)
|
||||
Logger.Info("{Text} {Percent:P0}", percentProgressInfo.Text, percentProgressInfo.Percent);
|
||||
else
|
||||
@@ -58,7 +58,7 @@ public class Splash : ISplashProgress, IRenderComponent
|
||||
public void OnFrame()
|
||||
{
|
||||
if (_done) return;
|
||||
|
||||
|
||||
SetNextWindowPos(GetMainViewport().GetCenter(), ImGuiCond.Always, new(.5f, .5f));
|
||||
SetNextWindowSize(new(400, GetFrameHeightWithSpacing()), ImGuiCond.Always);
|
||||
Begin("Splash", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs);
|
||||
@@ -68,13 +68,13 @@ public class Splash : ISplashProgress, IRenderComponent
|
||||
{
|
||||
const string text = "Loading...";
|
||||
var size = CalcTextSize(text);
|
||||
|
||||
|
||||
SetCursorPosX((GetWindowWidth() - size.X) * .5f);
|
||||
Text(text);
|
||||
}
|
||||
else
|
||||
ProgressBar((_lastInfo as PercentProgressInfo)?.Percent ?? 0, sizeArg, _lastInfo.Text);
|
||||
|
||||
|
||||
End();
|
||||
}
|
||||
}
|
@@ -25,7 +25,6 @@ public sealed class NotificationsComponent : IRenderComponent
|
||||
var lastY = _notificationSize.Y;
|
||||
var viewportPos = ImGui.GetMainViewport().Pos;
|
||||
|
||||
|
||||
//todo: consider adding a limit to the number of messages that can be displayed at once
|
||||
for (var i = Notifications.Count; i-- > 0;)
|
||||
{
|
||||
@@ -76,7 +75,7 @@ public sealed class NotificationsComponent : IRenderComponent
|
||||
|
||||
|
||||
Notifications.RemoveAll(x => x.IsGarbage);
|
||||
|
||||
|
||||
_time += MyCommon.GetLastFrameDelta();
|
||||
}
|
||||
public static void SpawnNotification(float showTime, Action renderCallback)
|
||||
|
@@ -38,10 +38,13 @@ internal class PluginListComponent : IRenderComponent
|
||||
private int _selectedProfile = -1;
|
||||
private ImmutableArray<Profile> _profiles;
|
||||
|
||||
private bool _disableUpdates;
|
||||
private bool _disablePluginUpdates;
|
||||
|
||||
private bool _changed;
|
||||
private bool _restartRequired;
|
||||
private bool _open = true;
|
||||
private readonly ConfigReference<PackagesConfig> _packagesConfig;
|
||||
private readonly ConfigReference<LauncherConfig> _launcherConfig;
|
||||
private readonly PackageSourceMapping _sourceMapping;
|
||||
private readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web);
|
||||
private ImmutableHashSet<PackageSource>? _selectedSources;
|
||||
@@ -51,10 +54,11 @@ internal class PluginListComponent : IRenderComponent
|
||||
private (PackageSource source, int index)? _selectedSource;
|
||||
private readonly IImGuiImageService _imageService = GameServicesExtension.GameServices.GetRequiredService<IImGuiImageService>();
|
||||
|
||||
public PluginListComponent(ConfigReference<PackagesConfig> packagesConfig, PackageSourceMapping sourceMapping, string gameFolder,
|
||||
ImmutableArray<PluginInstance> plugins)
|
||||
public PluginListComponent(ConfigReference<PackagesConfig> packagesConfig, ConfigReference<LauncherConfig> launcherConfig,
|
||||
PackageSourceMapping sourceMapping, string gameFolder, ImmutableArray<PluginInstance> plugins)
|
||||
{
|
||||
_packagesConfig = packagesConfig;
|
||||
_launcherConfig = launcherConfig;
|
||||
_sourceMapping = sourceMapping;
|
||||
_gameFolder = gameFolder;
|
||||
_plugins = plugins;
|
||||
@@ -62,6 +66,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
_profiles = packagesConfig.Value.Profiles;
|
||||
|
||||
_disablePluginUpdates = _launcherConfig.Value.DisablePluginUpdates;
|
||||
_disableUpdates = _launcherConfig.Value.DisableLauncherUpdates;
|
||||
|
||||
MyScreenManager.ScreenAdded += ScreenChanged;
|
||||
MyScreenManager.ScreenRemoved += ScreenChanged;
|
||||
}
|
||||
@@ -83,9 +90,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
return;
|
||||
}
|
||||
|
||||
if (_changed)
|
||||
if (_restartRequired)
|
||||
{
|
||||
TextDisabled("Changes would be applied on the next restart");
|
||||
TextDisabled("Changes will be applied on the next restart");
|
||||
SameLine();
|
||||
if (Button("Restart Now"))
|
||||
{
|
||||
@@ -172,7 +179,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
{
|
||||
var source = _packagesConfig.Value.Sources[index];
|
||||
TableNextRow();
|
||||
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
if (Selectable(source.Name, index == _selectedSource?.index, ImGuiSelectableFlags.SpanAllColumns))
|
||||
@@ -187,12 +194,12 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
EndTable();
|
||||
}
|
||||
|
||||
|
||||
EndChild();
|
||||
}
|
||||
|
||||
|
||||
SameLine();
|
||||
|
||||
|
||||
BeginGroup();
|
||||
|
||||
BeginChild("Source View", new(0, -GetFrameHeightWithSpacing())); // Leave room for 1 line below us
|
||||
@@ -200,7 +207,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
if (_selectedSource is not null)
|
||||
{
|
||||
var (selectedSource, index) = _selectedSource.Value;
|
||||
|
||||
|
||||
var name = selectedSource.Name;
|
||||
if (InputText("Name", ref name, 256))
|
||||
selectedSource = selectedSource with
|
||||
@@ -214,7 +221,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
{
|
||||
Url = url
|
||||
};
|
||||
|
||||
|
||||
var pattern = selectedSource.Pattern;
|
||||
if (InputText("Pattern", ref pattern, 1024))
|
||||
selectedSource = selectedSource with
|
||||
@@ -237,7 +244,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
|
||||
SameLine();
|
||||
|
||||
if (Button("Delete"))
|
||||
@@ -254,7 +261,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EndChild();
|
||||
}
|
||||
|
||||
@@ -271,14 +278,22 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
_selectedSource = (source, array.Length - 1);
|
||||
}
|
||||
|
||||
|
||||
EndGroup();
|
||||
|
||||
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem("Settings"))
|
||||
{
|
||||
if (Checkbox("Disable Plugin Updates", ref _disablePluginUpdates))
|
||||
{
|
||||
_launcherConfig.Value = _launcherConfig.Value with { DisablePluginUpdates = _disablePluginUpdates };
|
||||
}
|
||||
if (Checkbox("Disable Launcher Updates", ref _disableUpdates))
|
||||
{
|
||||
_launcherConfig.Value = _launcherConfig.Value with { DisableLauncherUpdates = _disableUpdates };
|
||||
}
|
||||
var oldConfigPath = Path.Join(_gameFolder, "Plugins", "config.xml");
|
||||
if (File.Exists(oldConfigPath))
|
||||
{
|
||||
@@ -299,6 +314,8 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
var hasModLodaer = _packages.ContainsKey("Plugin.ClientModLoader");
|
||||
|
||||
SameLine();
|
||||
|
||||
if (!hasModLodaer)
|
||||
BeginDisabled();
|
||||
|
||||
@@ -310,11 +327,12 @@ internal class PluginListComponent : IRenderComponent
|
||||
if (configSerializer.Deserialize(fs) is PluginLoaderConfig plConfig)
|
||||
{
|
||||
var dir = new DirectoryInfo(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"CringeLauncher"));
|
||||
"config", "CringeLauncher"));
|
||||
var file = Path.Join(dir.FullName, "mods.json");
|
||||
|
||||
using var modsFile = File.Create(file);
|
||||
JsonSerializer.Serialize(modsFile, plConfig.GetMods(), _serializerOptions);
|
||||
_restartRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +553,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
{
|
||||
_selectedSources = selected
|
||||
? (_selectedSources?.Count ?? 0) + 1 == _packagesConfig.Value.Sources.Length ? null : _selectedSources?.Add(source)
|
||||
: (_selectedSources ?? _packagesConfig.Value.Sources.ToImmutableHashSet()).Remove(source);
|
||||
: (_selectedSources ?? [.. _packagesConfig.Value.Sources]).Remove(source);
|
||||
|
||||
_searchTask = RefreshAsync();
|
||||
EndCombo();
|
||||
@@ -545,9 +563,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
EndCombo();
|
||||
}
|
||||
|
||||
|
||||
Spacing();
|
||||
|
||||
|
||||
switch (_searchTask)
|
||||
{
|
||||
case { IsCompleted: false }:
|
||||
@@ -733,9 +751,9 @@ internal class PluginListComponent : IRenderComponent
|
||||
|
||||
await foreach (var source in _sourceMapping)
|
||||
{
|
||||
if (source == null || _selectedSources is not null && _selectedSources.All(b => b.Url != source.ToString()))
|
||||
if (source == null || _selectedSources?.All(b => b.Url != source.ToString()) == true)
|
||||
continue;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var result = await source.SearchPackagesAsync(_searchQuery, take: 1000, packageType: "CringePlugin");
|
||||
@@ -758,7 +776,7 @@ internal class PluginListComponent : IRenderComponent
|
||||
Packages = [.. _packages.Select(b => new PackageReference(b.Key, b.Value))]
|
||||
} : _packagesConfig;
|
||||
|
||||
_changed = true;
|
||||
_restartRequired = true;
|
||||
}
|
||||
|
||||
private static unsafe int ComparePlugins(PluginInstance x, PluginInstance y, ImGuiTableSortSpecsPtr specs)
|
||||
|
@@ -13,17 +13,17 @@ public class IntrospectionContext
|
||||
public IntrospectionContext()
|
||||
{
|
||||
var assemblyResolver = new AssemblyResolver();
|
||||
|
||||
|
||||
assemblyResolver.PreSearchPaths.Add(AppContext.BaseDirectory);
|
||||
assemblyResolver.PreSearchPaths.Add(MyFileSystem.ExePath);
|
||||
|
||||
|
||||
Context = new(assemblyResolver);
|
||||
}
|
||||
|
||||
public IEnumerable<Type> CollectAttributedTypes<TAttribute>(Module module, bool allowAbstract = false) where TAttribute : Attribute
|
||||
{
|
||||
var moduleDef = ModuleDefMD.Load(module, Context);
|
||||
|
||||
|
||||
var token = moduleDef.ImportAsTypeSig(typeof(TAttribute));
|
||||
|
||||
return moduleDef.GetTypes()
|
||||
@@ -53,7 +53,7 @@ public class IntrospectionContext
|
||||
if (defOrRef.FullName == token.FullName)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user