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

Also ran cleanup
This commit is contained in:
2025-06-06 01:35:09 -04:00
parent bc88f0c28a
commit 94fc8a55c0
48 changed files with 381 additions and 267 deletions

View File

@@ -51,9 +51,9 @@ public class GameDirectoryAssemblyLoadContext : AssemblyLoadContext, ICoreLoadCo
AddOverride(new(name), Path.Join(AppContext.BaseDirectory, name + ".dll"));
}
protected override Assembly? Load(AssemblyName name)
protected override Assembly? Load(AssemblyName assemblyName)
{
var key = name.Name ?? name.FullName[..','];
var key = assemblyName.Name ?? assemblyName.FullName[..','];
try
{

View File

@@ -1,9 +1,4 @@
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using CringeBootstrap.Abstractions;
using CringeBootstrap.Abstractions;
using CringeLauncher.Utils;
using CringePlugins.Config;
using CringePlugins.Loader;
@@ -25,6 +20,12 @@ using Sandbox.Game;
using SpaceEngineers.Game;
using SpaceEngineers.Game.Achievements;
using SpaceEngineers.Game.GUI;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text.Json;
using Velopack;
using VRage;
using VRage.Audio;
@@ -81,6 +82,8 @@ public class Launcher : ICorePlugin
#if !DEBUG
CheckUpdates(args, logger).GetAwaiter().GetResult();
#else
logger.Info("Updates disabled: {Flag}", CheckUpdatesDisabledAsync(logger).GetAwaiter().GetResult());
#endif
// hook up steam as we ship it inside base context as an override
@@ -222,9 +225,39 @@ public class Launcher : ICorePlugin
return MyVRage.Platform.Windows.Window;
}
private async Task<bool> CheckUpdatesDisabledAsync(Logger logger)
{
var path = Path.Join(_configDir.FullName, "launcher.json");
if (!File.Exists(path))
return false;
try
{
await using var stream = File.OpenRead(path);
var conf = await JsonSerializer.DeserializeAsync<LauncherConfig>(stream, ConfigHandler.SerializerOptions);
return conf?.DisableLauncherUpdates ?? false;
}
catch (Exception ex)
{
logger.Error(ex, "Error reading launcher config");
}
return false;
}
private async Task CheckUpdates(string[] args, Logger logger)
{
if (await CheckUpdatesDisabledAsync(logger))
{
logger.Warn("Updates Disabled (may break from keen update)");
return;
}
logger.Info("Checking for updates...");
var mgr = new UpdateManager("https://dl.zznty.ru/CringeLauncher/");
// check for new version
@@ -297,7 +330,7 @@ public class Launcher : ICorePlugin
"AKGM16qoFtct0IIIA8RCqEIYG4d4gXPPDNpzGuvlhLA", "24b1cd652a18461fa9b3d533ac8d6b5b",
"1958fe26c66d4151a327ec162e4d49c8", "07c169b3b641401496d352cad1c905d6",
"https://retail.epicgames.com/", EosService.CreatePlatform(),
MyPlatformGameSettings.VERBOSE_NETWORK_LOGGING, ArraySegment<string>.Empty, aggregator,
MyPlatformGameSettings.VERBOSE_NETWORK_LOGGING, [], aggregator,
MyMultiplayer.Channels);
MyServiceManager.Instance.AddService<IMyServerDiscovery>(aggregator);

View File

@@ -17,13 +17,13 @@ public static class EosInitPatch
var stIndex = ins.FindIndex(b => b.opcode == OpCodes.Stloc_1);
ins.InsertRange(stIndex, new []
{
ins.InsertRange(stIndex,
[
new CodeInstruction(OpCodes.Dup),
new(OpCodes.Ldc_I4_2), // PlatformFlags.DisableOverlay
new(OpCodes.Conv_I8),
CodeInstruction.Call("Epic.OnlineServices.Platform.Options:set_Flags"),
});
]);
return ins;
}

View File

@@ -42,16 +42,19 @@ public static class IntrospectionPatches
//mods need to look for specific derived types
Debug.WriteLine($"Getting special types for {__instance.FullName}");
var module = __instance.GetMainModule();
__result = IntrospectionContext.Global.CollectDerivedTypes<MyObjectBuilder_Base>(module)
.Concat(IntrospectionContext.Global.CollectDerivedTypes<MyStatLogic>(module))
.Concat(IntrospectionContext.Global.CollectAttributedTypes<MyObjectBuilderDefinitionAttribute>(module))
.Concat(IntrospectionContext.Global.CollectDerivedTypes<MyComponentBase>(module))
.Concat(IntrospectionContext.Global.CollectAttributedTypes<MyComponentBuilderAttribute>(module))
.Concat(IntrospectionContext.Global.CollectDerivedTypes<IMyTextSurfaceScript>(module))
.Concat(IntrospectionContext.Global.CollectDerivedTypes<IMyUseObject>(module))
.Concat(IntrospectionContext.Global.CollectDerivedTypes<IMyHudStat>(module))
.Concat(IntrospectionContext.Global.CollectAttributedTypes<MySessionComponentDescriptor>(module))
.ToArray();
__result =
[
.. IntrospectionContext.Global.CollectDerivedTypes<MyObjectBuilder_Base>(module)
,
.. IntrospectionContext.Global.CollectDerivedTypes<MyStatLogic>(module),
.. IntrospectionContext.Global.CollectAttributedTypes<MyObjectBuilderDefinitionAttribute>(module),
.. IntrospectionContext.Global.CollectDerivedTypes<MyComponentBase>(module),
.. IntrospectionContext.Global.CollectAttributedTypes<MyComponentBuilderAttribute>(module),
.. IntrospectionContext.Global.CollectDerivedTypes<IMyTextSurfaceScript>(module),
.. IntrospectionContext.Global.CollectDerivedTypes<IMyUseObject>(module),
.. IntrospectionContext.Global.CollectDerivedTypes<IMyHudStat>(module),
.. IntrospectionContext.Global.CollectAttributedTypes<MySessionComponentDescriptor>(module),
];
return false;
@@ -76,8 +79,7 @@ public static class IntrospectionPatches
}
// static classes are abstract
__result = IntrospectionContext.Global.CollectAttributedTypes<HarmonyAttribute>(assembly.GetMainModule(), true)
.ToArray();
__result = [.. IntrospectionContext.Global.CollectAttributedTypes<HarmonyAttribute>(assembly.GetMainModule(), true)];
return false;
}

View File

@@ -10,7 +10,7 @@ public static class WhitelistAllowPatch
private static void Prefix(ref MemberInfo[] members)
{
if (members.Any(b => b is null))
members = members.Where(b => b is { }).ToArray();
members = [.. members.Where(b => b is { })];
}
private static Exception? Finalizer(Exception __exception)

View File

@@ -16,20 +16,20 @@ public static class XmlRootWriterPatch
b.opcode == OpCodes.Ldstr && b.operand is "xsi:type");
ins[index].operand = "xsi";
ins.InsertRange(index + 1, new[]
{
ins.InsertRange(index + 1,
[
new CodeInstruction(OpCodes.Ldstr, "type"),
new CodeInstruction(OpCodes.Ldstr, "http://www.w3.org/2001/XMLSchema-instance")
});
]);
var instruction = ins[ins.FindIndex(b => b.opcode == OpCodes.Callvirt)];
instruction.operand = AccessTools.Method(typeof(XmlWriter), "WriteAttributeString", new[]
{
instruction.operand = AccessTools.Method(typeof(XmlWriter), "WriteAttributeString",
[
typeof(string),
typeof(string),
typeof(string),
typeof(string)
});
]);
return ins;
}

View File

@@ -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));
}

View File

@@ -0,0 +1,5 @@
namespace CringePlugins.Config;
public sealed record LauncherConfig(bool DisableLauncherUpdates, bool DisablePluginUpdates)
{
public static LauncherConfig Default => new(false, false);
}

View File

@@ -28,6 +28,7 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
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()
@@ -105,9 +110,9 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) :
{
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,7 +138,7 @@ 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));
}

View File

@@ -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,7 +39,24 @@ 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}");
@@ -118,6 +135,20 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray<Pac
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);
@@ -182,7 +213,26 @@ 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();
@@ -206,7 +256,7 @@ 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);

View File

@@ -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;)
{

View File

@@ -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"))
{
@@ -279,6 +286,14 @@ internal class PluginListComponent : IRenderComponent
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();
@@ -733,7 +751,7 @@ 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
@@ -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)

View File

@@ -156,7 +156,7 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour
{
await using var stream = await client.GetPackageContentStreamAsync(entry.Id, entry.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);

View File

@@ -7,7 +7,7 @@ using NuGet.Versioning;
namespace NuGet;
public class NuGetClient
public sealed class NuGetClient
{
private readonly Uri _index;
private readonly HttpClient _client;