diff --git a/CringeBootstrap/GameDirectoryAssemblyLoadContext.cs b/CringeBootstrap/GameDirectoryAssemblyLoadContext.cs index 3eeafa3..32d245b 100644 --- a/CringeBootstrap/GameDirectoryAssemblyLoadContext.cs +++ b/CringeBootstrap/GameDirectoryAssemblyLoadContext.cs @@ -42,7 +42,7 @@ public class GameDirectoryAssemblyLoadContext : AssemblyLoadContext, ICoreLoadCo if (key.StartsWith("System.") || ReferenceAssemblies.Contains(key)) return; - + _assemblyNames.TryAdd(key, 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 { @@ -79,11 +79,11 @@ public class GameDirectoryAssemblyLoadContext : AssemblyLoadContext, ICoreLoadCo var path = Path.Join(dir, unmanagedDllName); if (!Path.HasExtension(path)) path += ".dll"; - + if (File.Exists(path)) return LoadUnmanagedDllFromPath(path); } - + throw new DllNotFoundException($"Unable to load {unmanagedDllName}, module not found in valid locations"); } diff --git a/CringeBootstrap/Program.cs b/CringeBootstrap/Program.cs index 66903cb..a1a8ef9 100644 --- a/CringeBootstrap/Program.cs +++ b/CringeBootstrap/Program.cs @@ -16,7 +16,7 @@ if (args.Length == 0) { var path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CringeLauncher", "current", "CringeBootstrap.exe"); - + Console.Write("Set your Launch Options under "); Console.ForegroundColor = ConsoleColor.Cyan; Console.Write("Space Engineers -> Properties -> Launch Options"); @@ -38,7 +38,7 @@ AssemblyLoadContext.Default.Resolving += (loadContext, name) => return null; }; #endif - + var dir = Path.GetDirectoryName(args[0])!; var context = new GameDirectoryAssemblyLoadContext(dir); diff --git a/CringeLauncher/ImGuiHandler.cs b/CringeLauncher/ImGuiHandler.cs index 491a85c..de61fe5 100644 --- a/CringeLauncher/ImGuiHandler.cs +++ b/CringeLauncher/ImGuiHandler.cs @@ -66,7 +66,7 @@ internal sealed class ImGuiHandler : IGuiHandler, IDisposable ImGui_ImplWin32_Init(windowHandle); ImGui_ImplDX11_Init(device.NativePointer, deviceContext.NativePointer); _init = true; - + _imageService.Initialize(); } @@ -123,7 +123,7 @@ internal sealed class ImGuiHandler : IGuiHandler, IDisposable UpdatePlatformWindows(); RenderPlatformWindowsDefault(); - + _imageService.Update(); } diff --git a/CringeLauncher/Launcher.cs b/CringeLauncher/Launcher.cs index 4cec06b..465be3b 100644 --- a/CringeLauncher/Launcher.cs +++ b/CringeLauncher/Launcher.cs @@ -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; @@ -58,7 +59,7 @@ public class Launcher : ICorePlugin { if (Type.GetType("GameAnalyticsSDK.Net.Logging.GALogger, GameAnalytics.Mono") is { } gaLoggerType) RuntimeHelpers.RunClassConstructor(gaLoggerType.TypeHandle); - + LogManager.Setup() .LoadConfigurationFromFile() .SetupExtensions(s => @@ -75,19 +76,21 @@ public class Launcher : ICorePlugin var logger = LogManager.GetLogger("CringeBootstrap"); logger.Info("Bootstrapping"); - + //environment variable for viktor's plugins Environment.SetEnvironmentVariable("SE_PLUGIN_DISABLE_METHOD_VERIFICATION", "True"); - + #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 if (AssemblyLoadContext.GetLoadContext(typeof(Launcher).Assembly) is ICoreLoadContext coreLoadContext) NativeLibrary.SetDllImportResolver(typeof(Steamworks.Constants).Assembly, (name, _, _) => coreLoadContext.ResolveUnmanagedDll(name)); NativeLibrary.SetDllImportResolver(typeof(EosService).Assembly, (name, _, _) => NativeLibrary.Load(Path.Join(AppContext.BaseDirectory, name))); - + _harmony.PatchAll(typeof(Launcher).Assembly); MyFileSystem.ExePath = Path.GetDirectoryName(args.ElementAtOrDefault(0) ?? Assembly.GetExecutingAssembly().Location)!; @@ -105,12 +108,12 @@ public class Launcher : ICorePlugin MyShaderCompiler.Init(MyShaderCompiler.TargetPlatform.PC, false); MyVRageWindows.Init(MyPerGameSettings.BasicGameInfo.ApplicationName, MySandboxGame.Log, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - MyPerGameSettings.BasicGameInfo.ApplicationName), + MyPerGameSettings.BasicGameInfo.ApplicationName), false, false); - + MyPlatformGameSettings.SAVE_TO_CLOUD_OPTION_AVAILABLE = true; MyXAudio2.DEVICE_DETAILS_SUPPORTED = false; - + if (MyVRage.Platform.System.SimulationQuality == SimulationQuality.Normal) { MyPlatformGameSettings.SIMPLIFIED_SIMULATION_OVERRIDE = false; @@ -133,17 +136,17 @@ public class Launcher : ICorePlugin _renderComponent = new(); _renderComponent.Start(new(), () => InitEarlyWindow(splash), MyVideoSettingsManager.Initialize(), MyPerGameSettings.MaxFrameRate); _renderComponent.RenderThread.BeforeDraw += MyFpsManager.Update; - + // this technically should wait for render thread init, but who cares splash.ExecuteLoadingStages(); - + InitUgc(); MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString()); - + _lifetime.RegisterLifetime(); - + WaitForDevice(); - + _game = new(args) { GameRenderComponent = _renderComponent, @@ -164,14 +167,14 @@ public class Launcher : ICorePlugin var retryPolicy = HttpPolicyExtensions.HandleTransientHttpError() .WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(1)); - + services.AddHttpClient() .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }) .AddPolicyHandler(retryPolicy); - + services.AddHttpClient() .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { @@ -208,25 +211,55 @@ public class Launcher : ICorePlugin private IVRageWindow InitEarlyWindow(Splash splash) { ImGuiHandler.Instance = new(_configDir); - + RenderHandler.Current.RegisterComponent(splash); - + MyVRage.Platform.Windows.CreateWindow("Cringe Launcher", MyPerGameSettings.GameIcon, null); MyVRage.Platform.Windows.Window.OnExit += MySandboxGame.ExitThreadSafe; - + MyRenderProxy.RenderThread = _renderComponent!.RenderThread; - + MyVRage.Platform.Windows.Window.ShowAndFocus(); return MyVRage.Platform.Windows.Window; } + private async Task 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(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 var newVersion = await mgr.CheckForUpdatesAsync(); if (newVersion == null) @@ -234,7 +267,7 @@ public class Launcher : ICorePlugin logger.Info("Up to date"); return; // no update available } - + // print update info Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"New version available: {mgr.CurrentVersion} -> {newVersion.TargetFullRelease.Version}"); @@ -250,7 +283,7 @@ public class Launcher : ICorePlugin // download new version await mgr.DownloadUpdatesAsync(newVersion); - + logger.Info("Done! Restarting..."); // install new version and restart app @@ -278,11 +311,11 @@ public class Launcher : ICorePlugin var textsPath = Path.Combine(MyFileSystem.RootPath, @"Content\Data\Localization\CoreTexts"); var hashSet = new HashSet(); MyTexts.LoadSupportedLanguages(textsPath, hashSet); - + if (!MyTexts.Languages.TryGetValue(MyLanguage.Instance.GetOsLanguageCurrentOfficial(), out var description) && !MyTexts.Languages.TryGetValue(MyLanguagesEnum.English, out description)) return; - + MyTexts.LoadTexts(textsPath, description.CultureName, description.SubcultureName); } @@ -290,20 +323,20 @@ public class Launcher : ICorePlugin { var steamGameService = MySteamGameService.Create(false, AppId); MyServiceManager.Instance.AddService(steamGameService); - + var aggregator = new MyServerDiscoveryAggregator(); MySteamGameService.InitNetworking(false, steamGameService, MyPerGameSettings.GameName, aggregator); EosService.InitNetworking(false, false, MyPerGameSettings.GameName, steamGameService, "xyza7891964JhtVD93nm3nZp8t1MbnhC", "AKGM16qoFtct0IIIA8RCqEIYG4d4gXPPDNpzGuvlhLA", "24b1cd652a18461fa9b3d533ac8d6b5b", "1958fe26c66d4151a327ec162e4d49c8", "07c169b3b641401496d352cad1c905d6", "https://retail.epicgames.com/", EosService.CreatePlatform(), - MyPlatformGameSettings.VERBOSE_NETWORK_LOGGING, ArraySegment.Empty, aggregator, + MyPlatformGameSettings.VERBOSE_NETWORK_LOGGING, [], aggregator, MyMultiplayer.Channels); - + MyServiceManager.Instance.AddService(aggregator); - + MyServiceManager.Instance.AddService(MySteamGameService.CreateMicrophone()); - + MyGameService.WorkshopService.AddAggregate(MySteamUgcService.Create(AppId, steamGameService)); var modUgc = MyModIoService.Create(MyServiceManager.Instance.GetService(), "spaceengineers", @@ -313,7 +346,7 @@ public class Launcher : ICorePlugin MyPlatformGameSettings.MODIO_PORTAL); modUgc.IsConsentGiven = MySandboxGame.Config.ModIoConsent; MyGameService.WorkshopService.AddAggregate(modUgc); - + MySpaceEngineersAchievements.Initialize(); } @@ -321,13 +354,13 @@ public class Launcher : ICorePlugin { var renderQualityHint = MyVRage.Platform.Render.GetRenderQualityHint(); var preset = MyGuiScreenOptionsGraphics.GetPreset(renderQualityHint); - + MyRenderProxy.Settings.User = MyVideoSettingsManager .GetGraphicsSettingsFromConfig(ref preset, renderQualityHint > MyRenderPresetEnum.CUSTOM) .PerformanceSettings.RenderSettings; MyRenderProxy.Settings.EnableAnsel = MyPlatformGameSettings.ENABLE_ANSEL; MyRenderProxy.Settings.EnableAnselWithSprites = MyPlatformGameSettings.ENABLE_ANSEL_WITH_SPRITES; - + var graphicsRenderer = MySandboxGame.Config.GraphicsRenderer; MySandboxGame.Config.GraphicsRenderer = graphicsRenderer; diff --git a/CringeLauncher/Patches/EosInitPatch.cs b/CringeLauncher/Patches/EosInitPatch.cs index 4e375dc..7e8a867 100644 --- a/CringeLauncher/Patches/EosInitPatch.cs +++ b/CringeLauncher/Patches/EosInitPatch.cs @@ -16,15 +16,15 @@ public static class EosInitPatch var ins = instructions.ToList(); 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"), - }); - + CodeInstruction.Call("Epic.OnlineServices.Platform.Options:set_Flags"), + ]); + return ins; } } \ No newline at end of file diff --git a/CringeLauncher/Patches/IntrospectionPatches.cs b/CringeLauncher/Patches/IntrospectionPatches.cs index 91e79c2..1c973c7 100644 --- a/CringeLauncher/Patches/IntrospectionPatches.cs +++ b/CringeLauncher/Patches/IntrospectionPatches.cs @@ -42,23 +42,26 @@ 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(module) - .Concat(IntrospectionContext.Global.CollectDerivedTypes(module)) - .Concat(IntrospectionContext.Global.CollectAttributedTypes(module)) - .Concat(IntrospectionContext.Global.CollectDerivedTypes(module)) - .Concat(IntrospectionContext.Global.CollectAttributedTypes(module)) - .Concat(IntrospectionContext.Global.CollectDerivedTypes(module)) - .Concat(IntrospectionContext.Global.CollectDerivedTypes(module)) - .Concat(IntrospectionContext.Global.CollectDerivedTypes(module)) - .Concat(IntrospectionContext.Global.CollectAttributedTypes(module)) - .ToArray(); + __result = + [ + .. IntrospectionContext.Global.CollectDerivedTypes(module) +, + .. IntrospectionContext.Global.CollectDerivedTypes(module), + .. IntrospectionContext.Global.CollectAttributedTypes(module), + .. IntrospectionContext.Global.CollectDerivedTypes(module), + .. IntrospectionContext.Global.CollectAttributedTypes(module), + .. IntrospectionContext.Global.CollectDerivedTypes(module), + .. IntrospectionContext.Global.CollectDerivedTypes(module), + .. IntrospectionContext.Global.CollectDerivedTypes(module), + .. IntrospectionContext.Global.CollectAttributedTypes(module), + ]; return false; } Debug.WriteLine($"Blocking GetTypes for {__instance.FullName}"); - + __result = []; return false; } @@ -74,10 +77,9 @@ public static class IntrospectionPatches __result = []; return false; } - + // static classes are abstract - __result = IntrospectionContext.Global.CollectAttributedTypes(assembly.GetMainModule(), true) - .ToArray(); + __result = [.. IntrospectionContext.Global.CollectAttributedTypes(assembly.GetMainModule(), true)]; return false; } @@ -101,13 +103,13 @@ public static class IntrospectionPatches .Concat(PluginsLifetime.Contexts.SelectMany(x => x.Assemblies)) ?? []; return false; } - + [HarmonyPrefix, HarmonyPatch(typeof(MySession), "PrepareBaseSession", typeof(MyObjectBuilder_Checkpoint), typeof(MyObjectBuilder_Sector))] private static void PrepareSessionPrefix() { // i hate keen for that in MyUseObjectFactory..cctor // MyUseObjectFactory.RegisterAssemblyTypes(Assembly.LoadFrom(Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll"))); - + MyUseObjectFactory.RegisterAssemblyTypes(MyPlugins.SandboxGameAssembly); } @@ -117,10 +119,10 @@ public static class IntrospectionPatches foreach (var type in assemblies.SelectMany(b => IntrospectionContext.Global.CollectDerivedTypes(b.GetMainModule()))) { var instance = Activator.CreateInstance(type); - + if (instance is IPlugin plugin) ___m_plugins.Add(plugin); - + if (instance is IHandleInputPlugin handleInputPlugin) ___m_handleInputPlugins.Add(handleInputPlugin); } @@ -138,22 +140,22 @@ public static class IntrospectionPatches if (assembly?.GetType($"Microsoft.Xml.Serialization.GeneratedAssembly.{typeName}Serializer") is not { } type) return false; - + __result = Activator.CreateInstance(type) as XmlSerializer; - + return false; } [HarmonyPrefix, HarmonyPatch(typeof(MyXmlSerializerManager), nameof(MyXmlSerializerManager.RegisterFromAssembly))] private static bool XmlManagerRegisterPrefix(Assembly assembly) => AssemblyLoadContext.GetLoadContext(assembly) is ICoreLoadContext; - + [HarmonyPatch] private static class GameAssembliesPatch { private static IEnumerable TargetMethods() { return AccessTools.GetDeclaredMethods(typeof(MyPlugins)) - .Where(b => b.Name.StartsWith("Register") && + .Where(b => b.Name.StartsWith("Register") && b.GetParameters() is [var param] && param.ParameterType == typeof(string)); } diff --git a/CringeLauncher/Patches/PluginNamePatch.cs b/CringeLauncher/Patches/PluginNamePatch.cs index 99c5b73..d3fdbed 100644 --- a/CringeLauncher/Patches/PluginNamePatch.cs +++ b/CringeLauncher/Patches/PluginNamePatch.cs @@ -13,7 +13,7 @@ public static class PluginNamePatch // to be just $"Plugin Init: {plugin}" // so you could override .ToString // doesn't change default behavior since base .ToString is .GetType().ToString() - + return new CodeMatcher(instructions) .SearchForward(b => b.Is(OpCodes.Ldstr, "Plugin Init: ")) .Advance(2) diff --git a/CringeLauncher/Patches/ScriptCompilationSettingsPatch.cs b/CringeLauncher/Patches/ScriptCompilationSettingsPatch.cs index fd825fa..762f2cd 100644 --- a/CringeLauncher/Patches/ScriptCompilationSettingsPatch.cs +++ b/CringeLauncher/Patches/ScriptCompilationSettingsPatch.cs @@ -10,7 +10,7 @@ namespace CringeLauncher.Patches; public static class ScriptCompilationSettingsPatch { private static readonly CSharpParseOptions Options = new(LanguageVersion.Latest, DocumentationMode.None); - + private static IEnumerable Transpiler(IEnumerable instructions) { var field = AccessTools.Field(typeof(MyScriptCompiler), nameof(MyScriptCompiler.m_conditionalParseOptions)); diff --git a/CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs b/CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs index 2ea506b..4239eb1 100644 --- a/CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs +++ b/CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs @@ -14,7 +14,7 @@ public static class ScriptCompilerInitializationPatch return AccessTools.Method(Type.GetType("VRage.Scripting.MyVRageScriptingInternal, VRage.Scripting", true), "Initialize"); } - + private static bool Prefix(Thread updateThread, Type[] referencedTypes, string[] symbols) { MyModWatchdog.Init(updateThread); diff --git a/CringeLauncher/Patches/SwapChainPatch.cs b/CringeLauncher/Patches/SwapChainPatch.cs index 996a9a9..13638f9 100644 --- a/CringeLauncher/Patches/SwapChainPatch.cs +++ b/CringeLauncher/Patches/SwapChainPatch.cs @@ -18,10 +18,10 @@ public static class SwapChainPatch WindowHandle = windowHandle; MyPlatformRender.DisposeSwapChain(); MyPlatformRender.Log.WriteLine("CreateDeviceInternal create swapchain"); - + if (MyPlatformRender.m_swapchain != null) return false; - + var chainDescription = new SwapChainDescription { BufferCount = 2, @@ -39,7 +39,7 @@ public static class SwapChainPatch Usage = Usage.ShaderInput | Usage.RenderTargetOutput, SwapEffect = SwapEffect.Discard }; - + var factory = MyPlatformRender.GetFactory(); try { @@ -62,7 +62,7 @@ public static class SwapChainPatch { ImGuiHandler.Instance?.Init(WindowHandle, MyRender11.DeviceInstance, MyRender11.RC.DeviceContext); } - + [HarmonyPrefix, HarmonyPatch(typeof(MyBackbuffer), MethodType.Constructor, typeof(SharpDX.Direct3D11.Resource))] private static bool SwapChainBBPrefix(MyBackbuffer __instance, SharpDX.Direct3D11.Resource swapChainBB) { @@ -73,13 +73,13 @@ public static class SwapChainPatch Dimension = RenderTargetViewDimension.Texture2D, }); __instance.m_srv = new ShaderResourceView(MyRender11.DeviceInstance, swapChainBB); - + ImGuiHandler.Rtv = new RenderTargetView(MyRender11.DeviceInstance, swapChainBB, new() { Format = Format.R8G8B8A8_UNorm, Dimension = RenderTargetViewDimension.Texture2D, }); - + return false; } @@ -87,7 +87,7 @@ public static class SwapChainPatch private static void SwapChainBBReleasePrefix(MyBackbuffer __instance) { if (ImGuiHandler.Rtv is null) return; - + ImGuiHandler.Rtv.Dispose(); ImGuiHandler.Rtv = null; } diff --git a/CringeLauncher/Patches/WhitelistAllowPatch.cs b/CringeLauncher/Patches/WhitelistAllowPatch.cs index 9b9b1fa..7c2ef09 100644 --- a/CringeLauncher/Patches/WhitelistAllowPatch.cs +++ b/CringeLauncher/Patches/WhitelistAllowPatch.cs @@ -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) diff --git a/CringeLauncher/Patches/WhitelistPatch.cs b/CringeLauncher/Patches/WhitelistPatch.cs index 2c3fc49..90c64ae 100644 --- a/CringeLauncher/Patches/WhitelistPatch.cs +++ b/CringeLauncher/Patches/WhitelistPatch.cs @@ -14,7 +14,7 @@ public static class WhitelistPatch private static void Prefix(MyScriptCompiler scriptCompiler) { var baseDir = new FileInfo(typeof(Type).Assembly.Location).DirectoryName!; - + scriptCompiler.AddReferencedAssemblies( typeof(Type).Assembly.Location, typeof(LinkedList<>).Assembly.Location, diff --git a/CringeLauncher/Patches/WhitelistTypeResolutionPatch.cs b/CringeLauncher/Patches/WhitelistTypeResolutionPatch.cs index d7aa232..5465815 100644 --- a/CringeLauncher/Patches/WhitelistTypeResolutionPatch.cs +++ b/CringeLauncher/Patches/WhitelistTypeResolutionPatch.cs @@ -9,7 +9,7 @@ public static class WhitelistTypeResolutionPatch { [HarmonyReversePatch] private static INamedTypeSymbol ResolveTypeSymbol(MyScriptWhitelist.Batch batch, Type type) => throw null!; - + // cant be assed to write a transpiler so heres a prefix private static bool Prefix(MyScriptWhitelist.Batch __instance, Type type, ref INamedTypeSymbol __result) { @@ -26,18 +26,18 @@ public static class WhitelistTypeResolutionPatch // if type is not generic or constructed generic, run regular lookup if (!type.IsGenericType || !type.IsConstructedGenericType) return ResolveTypeSymbol(batch, type); - + var unconstructedSymbol = ResolveTypeSymbol(batch, type.GetGenericTypeDefinition()); - + var typeArguments = type.GetGenericArguments(); - + var typeSymbolArguments = new ITypeSymbol[typeArguments.Length]; for (var i = 0; i < typeArguments.Length; i++) { // recursively resolve (possibly) generic arguments typeSymbolArguments[i] = ResolveGenericTypeSymbol(batch, typeArguments[i]); } - + return unconstructedSymbol.Construct(typeSymbolArguments); } } \ No newline at end of file diff --git a/CringeLauncher/Patches/XmlRootWriterPatch.cs b/CringeLauncher/Patches/XmlRootWriterPatch.cs index 5a2402e..4d9b261 100644 --- a/CringeLauncher/Patches/XmlRootWriterPatch.cs +++ b/CringeLauncher/Patches/XmlRootWriterPatch.cs @@ -11,26 +11,26 @@ public static class XmlRootWriterPatch private static IEnumerable Transpiler(IEnumerable instructions) { var ins = instructions.ToList(); - + var index = ins.FindIndex(b => 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; } } \ No newline at end of file diff --git a/CringeLauncher/Utils/ExceptionFormatter.cs b/CringeLauncher/Utils/ExceptionFormatter.cs index 57ff115..795ab9e 100644 --- a/CringeLauncher/Utils/ExceptionFormatter.cs +++ b/CringeLauncher/Utils/ExceptionFormatter.cs @@ -9,11 +9,11 @@ namespace CringeLauncher.Utils; public static class ExceptionFormatter { private static readonly AccessTools.FieldRef StackTraceField = AccessTools.FieldRefAccess("_remoteStackTraceString"); - + public static void FormatStackTrace(this Exception exception) { var stackTrace = new StackTrace(exception, true); - + var sb = new StringBuilder(); var i = 0; @@ -22,12 +22,12 @@ public static class ExceptionFormatter var method = frame.GetMethod(); if (method is null) continue; - + sb.Append("at "); if (method.DeclaringType is { } declaringType && AssemblyLoadContext.GetLoadContext(declaringType.Assembly) is { } assemblyLoadContext) sb.Append(assemblyLoadContext).Append("//"); - + if (method.IsStatic) sb.Append("static "); @@ -35,7 +35,7 @@ public static class ExceptionFormatter sb.Append(methodInfo.ReturnType, false); else sb.Append("new"); - + sb.Append(' '); if (method.DeclaringType is null) @@ -48,7 +48,7 @@ public static class ExceptionFormatter sb.Append('.'); sb.Append(method.Name); } - + if (method.ContainsGenericParameters) sb.Append(method.GetGenericArguments(), false); @@ -63,7 +63,7 @@ public static class ExceptionFormatter if (j < parameters.Length - 1) sb.Append(", "); } - + sb.Append(')'); if (frame.GetFileName() is { } fileName) @@ -75,7 +75,7 @@ public static class ExceptionFormatter } ref var stackTraceString = ref StackTraceField(exception); - + stackTraceString = sb.ToString(); } @@ -84,9 +84,9 @@ public static class ExceptionFormatter if (fullName && !string.IsNullOrEmpty(type.Namespace)) sb.Append(type.Namespace).Append('.'); sb.Append(type.Name); - if (type.ContainsGenericParameters) + if (type.ContainsGenericParameters) sb.Append(type.GetGenericArguments(), fullName); - + return sb; } diff --git a/CringeLauncher/Utils/MethodTools.cs b/CringeLauncher/Utils/MethodTools.cs index adea0c2..7badd67 100644 --- a/CringeLauncher/Utils/MethodTools.cs +++ b/CringeLauncher/Utils/MethodTools.cs @@ -8,7 +8,7 @@ public static class MethodTools public static MethodInfo AsyncMethodBody(MethodInfo method) { var (_, operand) = PatchProcessor.ReadMethodBody(method).First(); - + if (operand is not LocalVariableInfo localVar) throw new InvalidOperationException($"Method {method.FullDescription()} does not contain a valid async state machine"); diff --git a/CringePlugins/Config/ConfigHandler.cs b/CringePlugins/Config/ConfigHandler.cs index b784670..b068091 100644 --- a/CringePlugins/Config/ConfigHandler.cs +++ b/CringePlugins/Config/ConfigHandler.cs @@ -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(_serializerOptions)!; + instance = jsonNode.Deserialize(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(name, newValue)); } diff --git a/CringePlugins/Config/LauncherConfig.cs b/CringePlugins/Config/LauncherConfig.cs new file mode 100644 index 0000000..20ae2ec --- /dev/null +++ b/CringePlugins/Config/LauncherConfig.cs @@ -0,0 +1,5 @@ +namespace CringePlugins.Config; +public sealed record LauncherConfig(bool DisableLauncherUpdates, bool DisablePluginUpdates) +{ + public static LauncherConfig Default => new(false, false); +} diff --git a/CringePlugins/Loader/PluginAssemblyLoadContext.cs b/CringePlugins/Loader/PluginAssemblyLoadContext.cs index dc65715..b8b7bfc 100644 --- a/CringePlugins/Loader/PluginAssemblyLoadContext.cs +++ b/CringePlugins/Loader/PluginAssemblyLoadContext.cs @@ -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); } diff --git a/CringePlugins/Loader/PluginInstance.cs b/CringePlugins/Loader/PluginInstance.cs index 114024b..82834d0 100644 --- a/CringePlugins/Loader/PluginInstance.cs +++ b/CringePlugins/Loader/PluginInstance.cs @@ -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(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); diff --git a/CringePlugins/Loader/PluginMetadata.cs b/CringePlugins/Loader/PluginMetadata.cs index 027abf5..f9270fb 100644 --- a/CringePlugins/Loader/PluginMetadata.cs +++ b/CringePlugins/Loader/PluginMetadata.cs @@ -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"); } } \ No newline at end of file diff --git a/CringePlugins/Loader/PluginWrapper.cs b/CringePlugins/Loader/PluginWrapper.cs index d7b883b..85e8542 100644 --- a/CringePlugins/Loader/PluginWrapper.cs +++ b/CringePlugins/Loader/PluginWrapper.cs @@ -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; diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index 9413cee..355944e 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -20,14 +20,15 @@ internal class PluginsLifetime(ConfigHandler configHandler, HttpClient client) : public static ImmutableArray Contexts { get; private set; } = []; private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - + public string Name => "Loading Plugins"; - + private ImmutableArray _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? _configReference; + private ConfigReference? _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 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.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); } } diff --git a/CringePlugins/Resolver/BuiltInPackages.cs b/CringePlugins/Resolver/BuiltInPackages.cs index 12830a9..2a20d46 100644 --- a/CringePlugins/Resolver/BuiltInPackages.cs +++ b/CringePlugins/Resolver/BuiltInPackages.cs @@ -33,10 +33,10 @@ public static class BuiltInPackages (_, _, _, 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(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(); foreach (var package in packages) builder.TryAdd(package.Package.Id, package); - + return builder.ToImmutable(); } - + private static Dependency AsDependency(this ResolvedPackage package, ImmutableDictionary 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)); } diff --git a/CringePlugins/Resolver/PackageResolver.cs b/CringePlugins/Resolver/PackageResolver.cs index 4c1ec61..ffd173a 100644 --- a/CringePlugins/Resolver/PackageResolver.cs +++ b/CringePlugins/Resolver/PackageResolver.cs @@ -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 references, PackageSourceMapping packageSources) { private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); - public async Task> ResolveAsync() + public async Task> ResolveAsync(DirectoryInfo baseDir, bool disableUpdates) { var order = 0; var packages = new Dictionary(); @@ -39,26 +39,43 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray 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 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 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 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 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> 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> DownloadPackagesAsync(DirectoryInfo baseDirectory, IReadOnlySet resolvedPackages, IReadOnlySet? ignorePackages = null, IProgress? progress = null) { var packages = ImmutableHashSet.Empty.ToBuilder(); - + var i = 0f; foreach (var package in resolvedPackages) { @@ -206,26 +256,26 @@ public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray 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(); diff --git a/CringePlugins/Splash/ISplashProgress.cs b/CringePlugins/Splash/ISplashProgress.cs index 5951f19..523b691 100644 --- a/CringePlugins/Splash/ISplashProgress.cs +++ b/CringePlugins/Splash/ISplashProgress.cs @@ -1,6 +1,6 @@ namespace CringePlugins.Splash; -public interface ISplashProgress : IProgress, IProgress +public interface ISplashProgress : IProgress, IProgress { void DefineStage(ILoadingStage stage); void DefineStepsCount(int count); diff --git a/CringePlugins/Splash/Splash.cs b/CringePlugins/Splash/Splash.cs index c288f21..6f70c3a 100644 --- a/CringePlugins/Splash/Splash.cs +++ b/CringePlugins/Splash/Splash.cs @@ -10,16 +10,16 @@ namespace CringePlugins.Splash; public class Splash : ISplashProgress, IRenderComponent { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private readonly List _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(); } } \ No newline at end of file diff --git a/CringePlugins/Ui/NotificationsComponent.cs b/CringePlugins/Ui/NotificationsComponent.cs index daa9b26..4abbbee 100644 --- a/CringePlugins/Ui/NotificationsComponent.cs +++ b/CringePlugins/Ui/NotificationsComponent.cs @@ -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) diff --git a/CringePlugins/Ui/PluginListComponent.cs b/CringePlugins/Ui/PluginListComponent.cs index 2e29832..0eded09 100644 --- a/CringePlugins/Ui/PluginListComponent.cs +++ b/CringePlugins/Ui/PluginListComponent.cs @@ -38,10 +38,13 @@ internal class PluginListComponent : IRenderComponent private int _selectedProfile = -1; private ImmutableArray _profiles; + private bool _disableUpdates; + private bool _disablePluginUpdates; - private bool _changed; + private bool _restartRequired; private bool _open = true; private readonly ConfigReference _packagesConfig; + private readonly ConfigReference _launcherConfig; private readonly PackageSourceMapping _sourceMapping; private readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web); private ImmutableHashSet? _selectedSources; @@ -51,10 +54,11 @@ internal class PluginListComponent : IRenderComponent private (PackageSource source, int index)? _selectedSource; private readonly IImGuiImageService _imageService = GameServicesExtension.GameServices.GetRequiredService(); - public PluginListComponent(ConfigReference packagesConfig, PackageSourceMapping sourceMapping, string gameFolder, - ImmutableArray plugins) + public PluginListComponent(ConfigReference packagesConfig, ConfigReference launcherConfig, + PackageSourceMapping sourceMapping, string gameFolder, ImmutableArray 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) diff --git a/CringePlugins/Utils/IntrospectionContext.cs b/CringePlugins/Utils/IntrospectionContext.cs index 2a35cae..42e26a5 100644 --- a/CringePlugins/Utils/IntrospectionContext.cs +++ b/CringePlugins/Utils/IntrospectionContext.cs @@ -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 CollectAttributedTypes(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; } } diff --git a/NuGet/Converters/FrameworkJsonConverter.cs b/NuGet/Converters/FrameworkJsonConverter.cs index ab11c1b..24cb9bb 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 d4e08b6..0f59151 100644 --- a/NuGet/Converters/ManifestPackageKeyJsonConverter.cs +++ b/NuGet/Converters/ManifestPackageKeyJsonConverter.cs @@ -10,7 +10,7 @@ public class ManifestPackageKeyJsonConverter : JsonConverter { 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/PackageAuthorsJsonConverter.cs b/NuGet/Converters/PackageAuthorsJsonConverter.cs index a186234..0c0ea8d 100644 --- a/NuGet/Converters/PackageAuthorsJsonConverter.cs +++ b/NuGet/Converters/PackageAuthorsJsonConverter.cs @@ -19,12 +19,12 @@ public class PackageAuthorsJsonConverter : JsonConverter case JsonTokenType.StartArray: { var builder = ImmutableArray.CreateBuilder(); - + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { builder.Add(reader.GetString()!); } - + return new PackageAuthors(string.Join(", ", builder), builder.ToImmutable()); } case JsonTokenType.Null: @@ -41,14 +41,14 @@ public class PackageAuthorsJsonConverter : JsonConverter writer.WriteStringValue(value.Author); return; } - + writer.WriteStartArray(); - + foreach (var author in value.Authors) { writer.WriteStringValue(author); } - + writer.WriteEndArray(); } } \ No newline at end of file diff --git a/NuGet/Converters/ResourceTypeJsonConverter.cs b/NuGet/Converters/ResourceTypeJsonConverter.cs index edb3929..7ef4591 100644 --- a/NuGet/Converters/ResourceTypeJsonConverter.cs +++ b/NuGet/Converters/ResourceTypeJsonConverter.cs @@ -10,7 +10,7 @@ public class ResourceTypeJsonConverter : JsonConverter { if (reader.TokenType != JsonTokenType.String) throw new JsonException("Invalid resource type"); - + return ResourceType.Parse(reader.GetString()!); } diff --git a/NuGet/Converters/RuntimeFrameworkJsonConverter.cs b/NuGet/Converters/RuntimeFrameworkJsonConverter.cs index 85c6f74..057fded 100644 --- a/NuGet/Converters/RuntimeFrameworkJsonConverter.cs +++ b/NuGet/Converters/RuntimeFrameworkJsonConverter.cs @@ -10,7 +10,7 @@ public class RuntimeFrameworkJsonConverter : JsonConverter case JsonTokenType.StartArray: { var builder = ImmutableArray.CreateBuilder(); - + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { builder.Add(reader.GetString()!); } - + return builder.ToImmutable(); } default: @@ -35,14 +35,14 @@ public class StringOrStringArrayConverter : JsonConverter writer.WriteStringValue(value[0]); return; } - + writer.WriteStartArray(); - + foreach (var author in value) { writer.WriteStringValue(author); } - + writer.WriteEndArray(); } } \ No newline at end of file diff --git a/NuGet/Converters/VersionJsonConverter.cs b/NuGet/Converters/VersionJsonConverter.cs index 4addc75..d95a5f3 100644 --- a/NuGet/Converters/VersionJsonConverter.cs +++ b/NuGet/Converters/VersionJsonConverter.cs @@ -10,7 +10,7 @@ public class VersionJsonConverter : JsonConverter { if (reader.TokenType != JsonTokenType.String) throw new JsonException("Invalid version string"); - + return NuGetVersion.Parse(reader.GetString()!); } diff --git a/NuGet/Converters/VersionRangeJsonConverter.cs b/NuGet/Converters/VersionRangeJsonConverter.cs index b15c898..7d4472c 100644 --- a/NuGet/Converters/VersionRangeJsonConverter.cs +++ b/NuGet/Converters/VersionRangeJsonConverter.cs @@ -10,7 +10,7 @@ public class VersionRangeJsonConverter : JsonConverter { if (reader.TokenType != JsonTokenType.String) throw new JsonException("Invalid version range"); - + return VersionRange.Parse(reader.GetString()!); } diff --git a/NuGet/Deps/DependenciesManifest.cs b/NuGet/Deps/DependenciesManifest.cs index 5976446..53b6800 100644 --- a/NuGet/Deps/DependenciesManifest.cs +++ b/NuGet/Deps/DependenciesManifest.cs @@ -52,10 +52,10 @@ public record ManifestPackageKey(string Id, NuGetVersion Version) var index = str.IndexOf('/'); if (index < 0) throw new FormatException("Invalid package key: " + str); - + return new ManifestPackageKey(str[..index], NuGetVersion.Parse(str[(index + 1)..])); } - + public override string ToString() => $"{Id}/{Version}"; } @@ -71,9 +71,9 @@ public static class DependencyManifestSerializer 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)!; } @@ -86,12 +86,12 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour var targets = ImmutableDictionary.Empty.ToBuilder(); await MapCatalogEntryAsync(catalogEntry, targetFramework, targets); - + var manifest = new DependenciesManifest(runtimeTarget, ImmutableDictionary.Empty, ImmutableDictionary>.Empty - .Add(targetFramework, targets.ToImmutable()), + .Add(targetFramework, targets.ToImmutable()), ImmutableDictionary.Empty); - + await DependencyManifestSerializer.SerializeAsync(stream, manifest); } @@ -100,14 +100,14 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour { if (targets.ContainsKey(new(catalogEntry.Id, catalogEntry.Version)) || !catalogEntry.DependencyGroups.HasValue) return; - + // 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) return; - + targets.Add(new(catalogEntry.Id, catalogEntry.Version), await MapEntryAsync(catalogEntry, nearest)); @@ -115,7 +115,7 @@ public class DependencyManifestBuilder(DirectoryInfo cacheDirectory, PackageSour { if (entry is null) continue; - + await MapCatalogEntryAsync(entry, targetFramework, targets); } } @@ -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); diff --git a/NuGet/Models/CatalogEntry.cs b/NuGet/Models/CatalogEntry.cs index 5c10e6e..d1d4f63 100644 --- a/NuGet/Models/CatalogEntry.cs +++ b/NuGet/Models/CatalogEntry.cs @@ -5,5 +5,5 @@ namespace NuGet.Models; public record CatalogEntry(string Id, NuGetVersion Version, ImmutableArray? DependencyGroups, ImmutableArray? PackageTypes, ImmutableArray? PackageEntries); - + public record CatalogPackageEntry(string Name, string FullName, long CompressedLength, long Length); \ No newline at end of file diff --git a/NuGet/Models/NuGetRuntimeFramework.cs b/NuGet/Models/NuGetRuntimeFramework.cs index 1cccff6..5305acd 100644 --- a/NuGet/Models/NuGetRuntimeFramework.cs +++ b/NuGet/Models/NuGetRuntimeFramework.cs @@ -13,10 +13,10 @@ public record NuGetRuntimeFramework(NuGetFramework Framework, string? RuntimeIde 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)..]); } diff --git a/NuGet/Models/Registration.cs b/NuGet/Models/Registration.cs index 3e1ce58..7838d90 100644 --- a/NuGet/Models/Registration.cs +++ b/NuGet/Models/Registration.cs @@ -2,5 +2,5 @@ namespace NuGet.Models; -public record Registration([property: JsonPropertyName("catalogEntry")] string CatalogEntryUrl, +public record Registration([property: JsonPropertyName("catalogEntry")] string CatalogEntryUrl, [property: JsonPropertyName("sleet:catalogEntry")] CatalogEntry? SleetEntry); \ No newline at end of file diff --git a/NuGet/Models/ResourceType.cs b/NuGet/Models/ResourceType.cs index fc3c8cc..6a3c613 100644 --- a/NuGet/Models/ResourceType.cs +++ b/NuGet/Models/ResourceType.cs @@ -10,10 +10,10 @@ public record ResourceType(string Id, NuGetVersion? Version) public static ResourceType Parse(string typeString) { var slash = typeString.IndexOf('/'); - + if (slash < 0) return new ResourceType(typeString, null); - + var id = typeString[..slash]; var versionStr = typeString[(slash + 1)..]; diff --git a/NuGet/NuGetClient.cs b/NuGet/NuGetClient.cs index 733bd02..3b57637 100644 --- a/NuGet/NuGetClient.cs +++ b/NuGet/NuGetClient.cs @@ -7,7 +7,7 @@ using NuGet.Versioning; namespace NuGet; -public class NuGetClient +public sealed class NuGetClient { private readonly Uri _index; private readonly HttpClient _client; @@ -69,7 +69,7 @@ public class NuGetClient bool? includePrerelease = null, NuGetVersion? minVersion = null, string? packageType = null) { var queryParameters = HttpUtility.ParseQueryString(string.Empty); - + if (!string.IsNullOrEmpty(query)) queryParameters.Add("q", query); @@ -78,13 +78,13 @@ public class NuGetClient if (take.HasValue) queryParameters.Add("take", take.Value.ToString()); - + if (includePrerelease.HasValue) queryParameters.Add("prerelease", includePrerelease.Value.ToString()); if (minVersion is not null) queryParameters.Add("semVerLevel", minVersion.ToString()); - + if (!string.IsNullOrEmpty(packageType)) queryParameters.Add("packageType", packageType); diff --git a/SharedCringe/Loader/DerivedAssemblyLoadContext.cs b/SharedCringe/Loader/DerivedAssemblyLoadContext.cs index ffa7990..236ddf7 100644 --- a/SharedCringe/Loader/DerivedAssemblyLoadContext.cs +++ b/SharedCringe/Loader/DerivedAssemblyLoadContext.cs @@ -8,7 +8,7 @@ public abstract class DerivedAssemblyLoadContext(ICoreLoadContext parentContext, : AssemblyLoadContext(name, true) { protected readonly ICoreLoadContext ParentContext = parentContext; - + protected override Assembly? Load(AssemblyName assemblyName) => ParentContext.ResolveFromAssemblyName(assemblyName); protected override nint LoadUnmanagedDll(string unmanagedDllName) => ParentContext.ResolveUnmanagedDll(unmanagedDllName); } \ No newline at end of file diff --git a/TestPlugin/Plugin.cs b/TestPlugin/Plugin.cs index 65ddb4b..efc0f2d 100644 --- a/TestPlugin/Plugin.cs +++ b/TestPlugin/Plugin.cs @@ -7,7 +7,7 @@ namespace TestPlugin; public class Plugin : IPlugin { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - + public void Dispose() { } @@ -15,7 +15,7 @@ public class Plugin : IPlugin public void Init(object gameInstance) { Log.Info("Test Plugin init"); - + RenderHandler.Current.RegisterComponent(new TestRenderComponent()); } diff --git a/TestPlugin/TestRenderComponent.cs b/TestPlugin/TestRenderComponent.cs index cc3e7e6..c75c8ec 100644 --- a/TestPlugin/TestRenderComponent.cs +++ b/TestPlugin/TestRenderComponent.cs @@ -10,7 +10,7 @@ public class TestRenderComponent : IRenderComponent if (ImGui.Begin("Test Window")) { ImGui.Button("Test"); - + ImGui.End(); } }