diff --git a/CringeBootstrap/Program.cs b/CringeBootstrap/Program.cs index fbd3394..a1b05ae 100644 --- a/CringeBootstrap/Program.cs +++ b/CringeBootstrap/Program.cs @@ -25,7 +25,6 @@ var context = new GameDirectoryAssemblyLoadContext(dir); // a list of assemblies which are not in the game binaries but reference them context.AddDependencyOverride("CringeLauncher"); -context.AddDependencyOverride("PluginLoader"); context.AddDependencyOverride("CringePlugins"); var launcher = context.LoadFromAssemblyName(new AssemblyName("CringeLauncher")); diff --git a/CringeBootstrap/packages.lock.json b/CringeBootstrap/packages.lock.json index 749d537..3d42ef5 100644 --- a/CringeBootstrap/packages.lock.json +++ b/CringeBootstrap/packages.lock.json @@ -135,10 +135,15 @@ "resolved": "5.3.4", "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "6.11.1", + "contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg==" + }, "NuGet.Versioning": { "type": "Transitive", - "resolved": "6.10.1", - "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" + "resolved": "6.11.1", + "contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg==" }, "protobuf-net": { "type": "Transitive", @@ -160,8 +165,8 @@ }, "SpaceEngineersDedicated.ReferenceAssemblies": { "type": "Transitive", - "resolved": "1.204.18", - "contentHash": "GT7/9CBMx4jjor41zLOOl87YYM/JdJD8xp9ccXyuhP2oUaz25H3ZmCQuGeAuZNENKru1a/7hZrId4PwlMDGoew==", + "resolved": "1.205.23", + "contentHash": "J7mF5hY39PzzCZps6vhIRzKiq8vD6Af9TgumTJR068vjEi+BzyeEFhqX+cl2Dd1ngOmsBtGWc5m+vxgTfs5YuA==", "dependencies": { "SharpDX": "4.2.0-keen-cringe", "protobuf-net": "1.0.0" @@ -321,7 +326,11 @@ "CringeBootstrap.Abstractions": "[1.0.0, )", "CringePlugins": "[1.0.0, )", "ImGui.NET.DirectX": "[1.91.0.1, )", - "PluginLoader": "[1.0.0, )", + "Lib.Harmony.Thin": "[2.3.3, )", + "Microsoft.CodeAnalysis.CSharp": "[4.11.0, )", + "NLog": "[5.3.4, )", + "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", + "Steamworks.NET": "[20.1.0, )", "System.Diagnostics.PerformanceCounter": "[8.0.0, )", "System.Management": "[8.0.0, )", "System.Private.ServiceModel": "[4.10.3, )", @@ -333,20 +342,26 @@ "cringeplugins": { "type": "Project", "dependencies": { - "CringeBootstrap.Abstractions": "[1.0.0, )", "NLog": "[5.3.4, )", + "NuGet": "[1.0.0, )", + "SharedCringe": "[1.0.0, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", "dnlib": "[4.4.0, )" } }, - "pluginloader": { + "nuget": { "type": "Project", "dependencies": { - "Lib.Harmony.Thin": "[2.3.3, )", - "Microsoft.CodeAnalysis.CSharp": "[4.11.0, )", + "NuGet.Frameworks": "[6.11.1, )", + "NuGet.Versioning": "[6.11.1, )" + } + }, + "sharedcringe": { + "type": "Project", + "dependencies": { + "CringeBootstrap.Abstractions": "[1.0.0, )", "NLog": "[5.3.4, )", - "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", - "Steamworks.NET": "[20.1.0, )" + "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )" } } }, diff --git a/CringeLauncher.sln b/CringeLauncher.sln index c9d9a67..60cb0a0 100644 --- a/CringeLauncher.sln +++ b/CringeLauncher.sln @@ -12,6 +12,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CringePlugins", "CringePlug EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "TestPlugin\TestPlugin.csproj", "{C1670878-2301-4AE5-ABD3-5C4D0882CB02}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet", "NuGet\NuGet.csproj", "{CC5362CA-881A-4867-9642-033269C1F7D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedCringe", "SharedCringe\SharedCringe.csproj", "{BDA680D8-D4E3-48A9-8ED4-C4991098B71D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +46,13 @@ Global {C1670878-2301-4AE5-ABD3-5C4D0882CB02}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1670878-2301-4AE5-ABD3-5C4D0882CB02}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1670878-2301-4AE5-ABD3-5C4D0882CB02}.Release|Any CPU.Build.0 = Release|Any CPU + {CC5362CA-881A-4867-9642-033269C1F7D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC5362CA-881A-4867-9642-033269C1F7D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC5362CA-881A-4867-9642-033269C1F7D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC5362CA-881A-4867-9642-033269C1F7D7}.Release|Any CPU.Build.0 = Release|Any CPU + {BDA680D8-D4E3-48A9-8ED4-C4991098B71D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDA680D8-D4E3-48A9-8ED4-C4991098B71D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDA680D8-D4E3-48A9-8ED4-C4991098B71D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDA680D8-D4E3-48A9-8ED4-C4991098B71D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/CringeLauncher/CringeLauncher.csproj b/CringeLauncher/CringeLauncher.csproj index 5a49894..e4aeac6 100644 --- a/CringeLauncher/CringeLauncher.csproj +++ b/CringeLauncher/CringeLauncher.csproj @@ -13,6 +13,10 @@ + + + + @@ -32,12 +36,16 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + - diff --git a/CringeLauncher/ImGuiHandler.cs b/CringeLauncher/ImGuiHandler.cs index 6be3f64..454fbfb 100644 --- a/CringeLauncher/ImGuiHandler.cs +++ b/CringeLauncher/ImGuiHandler.cs @@ -4,6 +4,7 @@ using System.Runtime.Versioning; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.WindowsAndMessaging; +using CringePlugins.Abstractions; using CringePlugins.Render; using ImGuiNET; using SharpDX.Direct3D11; @@ -19,7 +20,7 @@ internal class ImGuiHandler : IDisposable public static ImGuiHandler? Instance; public static RenderTargetView? Rtv; - private readonly RenderHandler _renderHandler; + private readonly IRootRenderComponent _renderHandler; public ImGuiHandler(nint windowHandle, Device1 device, DeviceContext deviceContext) { diff --git a/CringeLauncher/Launcher.cs b/CringeLauncher/Launcher.cs index ec71d08..8e77203 100644 --- a/CringeLauncher/Launcher.cs +++ b/CringeLauncher/Launcher.cs @@ -1,13 +1,11 @@ -using System.Diagnostics; -using System.Reflection; -using System.Runtime.Loader; +using System.Reflection; using CringeBootstrap.Abstractions; using CringeLauncher.Utils; using CringePlugins.Loader; +using CringePlugins.Splash; using HarmonyLib; using NLog; using Sandbox; -using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Engine.Platform.VideoMode; using Sandbox.Engine.Utils; @@ -15,21 +13,19 @@ using Sandbox.Game; using SpaceEngineers.Game; using SpaceEngineers.Game.Achievements; using SpaceEngineers.Game.GUI; -using Steamworks; using Velopack; using VRage; using VRage.Audio; -using VRage.EOS; using VRage.FileSystem; using VRage.Game; using VRage.Game.Localization; using VRage.GameServices; using VRage.Mod.Io; using VRage.Platform.Windows; -using VRage.Plugins; using VRage.Steam; using VRage.UserInterface; using VRageRender; +using VRageRender.ExternalApp; namespace CringeLauncher; @@ -40,6 +36,8 @@ public class Launcher : ICorePlugin private readonly Harmony _harmony = new("CringeBootstrap"); private PluginsLifetime? _lifetime; + private MyGameRenderComponent? _renderComponent; + public void Initialize(string[] args) { LogManager.Setup() @@ -57,33 +55,25 @@ public class Launcher : ICorePlugin //environment variable for viktor's plugins Environment.SetEnvironmentVariable("SE_PLUGIN_DISABLE_METHOD_VERIFICATION", "True"); - - // early init for plugin loader - ProtoBuf.Meta.RuntimeTypeModel.Create(true); + _harmony.PatchAll(typeof(Launcher).Assembly); - SteamAPI.Init(); - MyPlugins.LoadPlugins([typeof(PluginLoader.Main).Assembly]); #if !DEBUG CheckUpdates().GetAwaiter().GetResult(); #endif + + var splash = new Splash(); - PluginLoader.Main.Instance.Splash?.SetText("Initializing plugins..."); - PluginLoader.Main.Instance.Splash?.SetBarValue(0); + splash.DefineStage(_lifetime = new PluginsLifetime()); - _lifetime = new PluginsLifetime(); - - PluginLoader.Main.Instance.Splash?.SetText("Initializing game..."); InitTexts(); SpaceEngineersGame.SetupBasicGameInfo(); MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion.GetValueOrDefault(); MyShaderCompiler.Init(MyShaderCompiler.TargetPlatform.PC, false); - PluginLoader.Main.Instance.Splash?.SetBarValue(.25f); MyVRageWindows.Init(MyPerGameSettings.BasicGameInfo.ApplicationName, MySandboxGame.Log, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), MyPerGameSettings.BasicGameInfo.ApplicationName), false, false); - PluginLoader.Main.Instance.Splash?.SetBarValue(.35f); MyPlatformGameSettings.SAVE_TO_CLOUD_OPTION_AVAILABLE = true; MyXAudio2.DEVICE_DETAILS_SUPPORTED = false; @@ -101,39 +91,54 @@ public class Launcher : ICorePlugin MyFakes.VOICE_CHAT_MIC_SENSITIVITY = MySandboxGame.Config.MicSensitivity; MyPlatformGameSettings.VOICE_CHAT_AUTOMATIC_ACTIVATION = MySandboxGame.Config.AutomaticVoiceChatActivation; }); - PluginLoader.Main.Instance.Splash?.SetBarValue(.55f); MyVRage.Platform.Init(); - PluginLoader.Main.Instance.Splash?.SetBarValue(.65f); - InitUgc(); - PluginLoader.Main.Instance.Splash?.SetBarValue(.75f); SpaceEngineersGame.SetupPerGameSettings(); ConfigureSettings(); - // MySandboxGame.InitMultithreading(); InitThreadPool(); - PluginLoader.Main.Instance.Splash?.SetBarValue(.85f); MyVRage.Platform.System.OnThreadpoolInitialized(); InitRender(); - PluginLoader.Main.Instance.Splash?.SetBarValue(.95f); + + _renderComponent = new(); + _renderComponent.Start(new(), InitEarlyWindow, MyVideoSettingsManager.Initialize(), MyPerGameSettings.MaxFrameRate); + _renderComponent.RenderThread.BeforeDraw += MyFpsManager.Update; + _renderComponent.RenderThread.SizeChanged += RenderThreadOnSizeChanged; + + splash.ExecuteLoadingStages(); + + InitUgc(); MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString()); - _game = new(args); - PluginLoader.Main.Instance.Splash?.SetText("Launching..."); + _game = new(args) + { + GameRenderComponent = _renderComponent, + DrawThread = _renderComponent.RenderThread.SystemThread, + form = MyVRage.Platform.Windows.Window + }; + + _renderComponent.RenderThread.SizeChanged += _game.RenderThread_SizeChanged; + _renderComponent.RenderThread.UpdateSize(); + } + + private void RenderThreadOnSizeChanged(int width, int height, MyViewport viewport) + { + if (_renderComponent is not null) + _renderComponent.RenderThread.SizeChanged -= RenderThreadOnSizeChanged; + + MyVRage.Platform.Windows.Window.ShowAndFocus(); } public void Run() => _game?.Run(); + private static IVRageWindow InitEarlyWindow() + { + MyVRage.Platform.Windows.CreateWindow("Cringe Launcher", MyPerGameSettings.GameIcon, null); + + MyVRage.Platform.Windows.Window.OnExit += MySandboxGame.ExitThreadSafe; + + return MyVRage.Platform.Windows.Window; + } + private async Task CheckUpdates() { - void Report(string s, float p) - { - PluginLoader.Main.Instance.Splash?.Invoke(() => - { - PluginLoader.Main.Instance.Splash?.SetText(s); - PluginLoader.Main.Instance.Splash?.SetBarValue(p); - }); - } - - PluginLoader.Main.Instance.Splash?.SetText("Checking for updates..."); - var mgr = new UpdateManager("https://the.place/you-host/updates"); // check for new version @@ -142,9 +147,7 @@ public class Launcher : ICorePlugin return; // no update available // download new version - await mgr.DownloadUpdatesAsync(newVersion, i => Report("Downloading update...", i / 100f)); - - Report("Restarting for update", 1f); + await mgr.DownloadUpdatesAsync(newVersion); // install new version and restart app mgr.ApplyUpdatesAndRestart(newVersion); @@ -164,8 +167,7 @@ public class Launcher : ICorePlugin private static void InitTexts() { - //MyLanguage.Instance.ObtainCurrentOSCulture(); - var textsPath = Path.Combine(MyFileSystem.RootPath, "Content\\Data\\Localization\\CoreTexts"); + var textsPath = Path.Combine(MyFileSystem.RootPath, @"Content\Data\Localization\CoreTexts"); var hashSet = new HashSet(); MyTexts.LoadSupportedLanguages(textsPath, hashSet); @@ -176,7 +178,7 @@ public class Launcher : ICorePlugin MyTexts.LoadTexts(textsPath, description.CultureName, description.SubcultureName); } - private static void InitUgc() + public static void InitUgc() { var steamGameService = MySteamGameService.Create(false, AppId); MyServiceManager.Instance.AddService(steamGameService); diff --git a/CringeLauncher/Loader/ModAssemblyLoadContext.cs b/CringeLauncher/Loader/ModAssemblyLoadContext.cs new file mode 100644 index 0000000..47429e9 --- /dev/null +++ b/CringeLauncher/Loader/ModAssemblyLoadContext.cs @@ -0,0 +1,7 @@ +using CringeBootstrap.Abstractions; +using SharedCringe.Loader; + +namespace CringeLauncher.Loader; + +public class ModAssemblyLoadContext(ICoreLoadContext parentContext) + : DerivedAssemblyLoadContext(parentContext, "World Mods Context"); \ No newline at end of file diff --git a/CringeLauncher/Patches/IntrospectionPatches.cs b/CringeLauncher/Patches/IntrospectionPatches.cs index 6c8530e..fdf553d 100644 --- a/CringeLauncher/Patches/IntrospectionPatches.cs +++ b/CringeLauncher/Patches/IntrospectionPatches.cs @@ -1,15 +1,18 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.Loader; +using System.Xml.Serialization; using CringeBootstrap.Abstractions; using CringePlugins.Utils; using HarmonyLib; +using Sandbox.Game.World; using VRage; using VRage.FileSystem; using VRage.Game; using VRage.Game.Common; using VRage.Game.Components; using VRage.Game.Definitions; +using VRage.Game.Entity.UseObject; using VRage.ObjectBuilders; using VRage.ObjectBuilders.Private; using VRage.Plugins; @@ -24,15 +27,31 @@ public static class IntrospectionPatches { if (AssemblyLoadContext.GetLoadContext(__instance) is ICoreLoadContext || __instance.FullName?.StartsWith("System.") == true) return true; - -#if DEBUG - Debugger.Break(); + + Debug.WriteLine($"Blocking GetTypes for {__instance.FullName}"); __result = []; return false; -#else - throw new NotSupportedException($"Getting types from {__instance} is not supported"); -#endif + } + + [HarmonyPrefix, HarmonyPatch(typeof(AccessTools), nameof(AccessTools.GetTypesFromAssembly))] + private static bool GetTypesHarmonyPrefix(Assembly assembly, ref Type[] __result) + { + if (AssemblyLoadContext.GetLoadContext(assembly) is ICoreLoadContext) + return true; + + __result = IntrospectionContext.Global.CollectAttributedTypes(assembly.GetMainModule()) + .ToArray(); + 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); } [HarmonyPrefix, HarmonyPatch(typeof(MyPlugins), nameof(MyPlugins.LoadPlugins))] @@ -52,83 +71,24 @@ public static class IntrospectionPatches return false; } - [HarmonyPrefix, HarmonyPatch(typeof(MyObjectBuilderSerializerKeen), nameof(MyObjectBuilderSerializerKeen.RegisterFromAssembly))] - private static bool RegisterXmlSerializersPrefix( - MyObjectFactory ___m_objectFactory, Assembly assembly) + [HarmonyPrefix, HarmonyPatch(typeof(MyXmlSerializerManager), "TryLoadSerializerFrom")] + private static bool LoadSerializerPrefix(string assemblyName, string typeName, ref XmlSerializer? __result) { - Register(___m_objectFactory, assembly); - return false; - } - - [HarmonyPrefix, HarmonyPatch(typeof(MyComponentFactory), nameof(MyComponentFactory.RegisterFromAssembly))] - private static bool RegisterComponentsPrefix( - MyObjectFactory ___m_objectFactory, Assembly assembly) - { - Register(___m_objectFactory, assembly); - return false; - } + if (AssemblyLoadContext.GetLoadContext(typeof(IntrospectionPatches).Assembly) is not ICoreLoadContext context) + return false; - [HarmonyTranspiler, - HarmonyPatch(typeof(MyDefinitionManagerBase), nameof(MyDefinitionManagerBase.RegisterTypesFromAssembly))] - private static IEnumerable RegisterDefinitionTypesTranspiler( - IEnumerable instructions) - { - var getTypesMethod = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.GetTypes)); - var getDefinitionTypesMethod = AccessTools.DeclaredMethod(typeof(IntrospectionPatches), nameof(GetDefinitionTypes)); - return instructions.MethodReplacer(getTypesMethod, getDefinitionTypesMethod); - } + var assembly = context.ResolveFromAssemblyName(new(assemblyName)); - private static Type[] GetDefinitionTypes(Assembly assembly) - { - return IntrospectionContext.Global.CollectAttributedTypes(assembly.GetMainModule()) - .ToArray(); - } - - [HarmonyTranspiler, - HarmonyPatch(typeof(MyObjectBuilderType), nameof(MyObjectBuilderType.RegisterFromAssembly))] - private static IEnumerable RegisterObjectBuilderTypesTranspiler( - IEnumerable instructions) - { - var getTypesMethod = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.GetTypes)); - var getDefinitionTypesMethod = AccessTools.DeclaredMethod(typeof(IntrospectionPatches), nameof(GetObjectBuilderTypes)); - return instructions.MethodReplacer(getTypesMethod, getDefinitionTypesMethod); - } - - private static Type[] GetObjectBuilderTypes(Assembly assembly) - { - return IntrospectionContext.Global.CollectDerivedTypes(assembly.GetMainModule()) - .ToArray(); - } - - [HarmonyTranspiler, - HarmonyPatch(typeof(MyComponentTypeFactory), nameof(MyComponentTypeFactory.RegisterFromAssembly), typeof(Assembly))] - private static IEnumerable RegisterComponentTypesTranspiler(IEnumerable instructions) - { - var getTypesMethod = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.GetTypes)); - var getDefinitionTypesMethod = AccessTools.DeclaredMethod(typeof(IntrospectionPatches), nameof(GetComponentTypes)); - return instructions.MethodReplacer(getTypesMethod, getDefinitionTypesMethod); - } - - private static Type[] GetComponentTypes(Assembly assembly) - { - return IntrospectionContext.Global.CollectDerivedTypes(assembly.GetMainModule()) - .ToArray(); + 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; - - private static void Register(MyObjectFactory factory, Assembly assembly) - where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class - { - foreach (var type in IntrospectionContext.Global.CollectAttributedTypes(assembly.GetMainModule())) - { - foreach (var attribute in type.GetCustomAttributes()) - { - factory.RegisterDescriptor(attribute, type); - } - } - } [HarmonyPatch] private static class GameAssembliesPatch diff --git a/CringeLauncher/Patches/ModAssemblyLoadContextPatches.cs b/CringeLauncher/Patches/ModAssemblyLoadContextPatches.cs new file mode 100644 index 0000000..2222a32 --- /dev/null +++ b/CringeLauncher/Patches/ModAssemblyLoadContextPatches.cs @@ -0,0 +1,70 @@ +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Loader; +using CringeBootstrap.Abstractions; +using CringeLauncher.Loader; +using HarmonyLib; +using Sandbox.Game.World; +using VRage.Scripting; + +namespace CringeLauncher.Patches; + +[HarmonyPatch] +public static class ModAssemblyLoadContextPatches +{ + private static ModAssemblyLoadContext? _currentSessionContext; + + [HarmonyPatch(typeof(MyScriptCompiler), nameof(MyScriptCompiler.Compile), MethodType.Async)] + [HarmonyTranspiler] + private static IEnumerable CompilerTranspiler(IEnumerable instructions, MethodBase original) + { + var matcher = new CodeMatcher(instructions); + + var load1Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[]), typeof(byte[])]); + var load2Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[])]); + + matcher.SearchForward(i => i.Calls(load1Method)) + .InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target")) + .SetInstruction(CodeInstruction.CallClosure((byte[] assembly, byte[] symbols, MyApiTarget target) => + { + if (target is not MyApiTarget.Mod) return Assembly.Load(assembly, symbols); + ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context"); + return _currentSessionContext.LoadFromStream(new MemoryStream(assembly), new MemoryStream(symbols)); + })); + + matcher.SearchForward(i => i.Calls(load2Method)) + .InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target")) + .SetInstruction(CodeInstruction.CallClosure((byte[] assembly, MyApiTarget target) => + { + if (target is not MyApiTarget.Mod) return Assembly.Load(assembly); + ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context"); + return _currentSessionContext.LoadFromStream(new MemoryStream(assembly)); + })); + + return matcher.Instructions(); + } + + [HarmonyPatch(typeof(MySession), nameof(MySession.Load))] + [HarmonyPatch(typeof(MySession), "LoadMultiplayer")] + [HarmonyPrefix] + private static void LoadPrefix() + { + if (_currentSessionContext is not null) + throw new InvalidOperationException("Previous session context was not disposed"); + + if (AssemblyLoadContext.GetLoadContext(typeof(MySession).Assembly) is not ICoreLoadContext coreContext) + throw new NotSupportedException("Mod loading is not supported in this context"); + + _currentSessionContext = new ModAssemblyLoadContext(coreContext); + } + + [HarmonyPatch(typeof(MySession), nameof(MySession.Unload))] + [HarmonyPostfix] + private static void UnloadPostfix() + { + if (_currentSessionContext is null) return; + + _currentSessionContext.Unload(); + _currentSessionContext = null; + } +} \ No newline at end of file diff --git a/CringeLauncher/Patches/RenderInitPatch.cs b/CringeLauncher/Patches/RenderInitPatch.cs new file mode 100644 index 0000000..490676e --- /dev/null +++ b/CringeLauncher/Patches/RenderInitPatch.cs @@ -0,0 +1,10 @@ +using HarmonyLib; +using SpaceEngineers.Game; + +namespace CringeLauncher.Patches; + +[HarmonyPatch(typeof(SpaceEngineersGame), "InitializeRender")] +public static class RenderInitPatch +{ + private static bool Prefix() => false; +} \ No newline at end of file diff --git a/CringeLauncher/packages.lock.json b/CringeLauncher/packages.lock.json index ac8a9e0..107d8c4 100644 --- a/CringeLauncher/packages.lock.json +++ b/CringeLauncher/packages.lock.json @@ -25,6 +25,28 @@ "resolved": "2.2.1", "contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q==" }, + "Lib.Harmony.Thin": { + "type": "Direct", + "requested": "[2.3.3, )", + "resolved": "2.3.3", + "contentHash": "jsaFv7XnWJnyfyvFbkgIkZtV6tWMteNUcDK3idq+3LwPqpTFNxsOv2eKmj4qqP8QR8UynG1Y9AUaC/+dVruMHg==", + "dependencies": { + "MonoMod.Core": "1.1.0", + "System.Text.Json": "8.0.1" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Direct", + "requested": "[4.11.0, )", + "resolved": "4.11.0", + "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "Microsoft.CodeAnalysis.Common": "[4.11.0]", + "System.Collections.Immutable": "8.0.0", + "System.Reflection.Metadata": "8.0.0" + } + }, "Microsoft.Windows.CsWin32": { "type": "Direct", "requested": "[0.3.106, )", @@ -36,6 +58,28 @@ "Microsoft.Windows.WDK.Win32Metadata": "0.11.4-experimental" } }, + "NLog": { + "type": "Direct", + "requested": "[5.3.4, )", + "resolved": "5.3.4", + "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" + }, + "SpaceEngineersDedicated.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.*, )", + "resolved": "1.205.23", + "contentHash": "J7mF5hY39PzzCZps6vhIRzKiq8vD6Af9TgumTJR068vjEi+BzyeEFhqX+cl2Dd1ngOmsBtGWc5m+vxgTfs5YuA==", + "dependencies": { + "SharpDX": "4.2.0-keen-cringe", + "protobuf-net": "1.0.0" + } + }, + "Steamworks.NET": { + "type": "Direct", + "requested": "[20.1.0, )", + "resolved": "20.1.0", + "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" + }, "System.Diagnostics.PerformanceCounter": { "type": "Direct", "requested": "[8.0.0, )", @@ -88,15 +132,6 @@ "NuGet.Versioning": "6.10.1" } }, - "Lib.Harmony.Thin": { - "type": "Transitive", - "resolved": "2.3.3", - "contentHash": "jsaFv7XnWJnyfyvFbkgIkZtV6tWMteNUcDK3idq+3LwPqpTFNxsOv2eKmj4qqP8QR8UynG1Y9AUaC/+dVruMHg==", - "dependencies": { - "MonoMod.Core": "1.1.0", - "System.Text.Json": "8.0.1" - } - }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "5.0.0", @@ -117,17 +152,6 @@ "System.Reflection.Metadata": "8.0.0" } }, - "Microsoft.CodeAnalysis.CSharp": { - "type": "Transitive", - "resolved": "4.11.0", - "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", - "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "Microsoft.CodeAnalysis.Common": "[4.11.0]", - "System.Collections.Immutable": "8.0.0", - "System.Reflection.Metadata": "8.0.0" - } - }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "8.0.0", @@ -203,15 +227,15 @@ "MonoMod.ILHelpers": "1.0.1" } }, - "NLog": { + "NuGet.Frameworks": { "type": "Transitive", - "resolved": "5.3.4", - "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" + "resolved": "6.11.1", + "contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg==" }, "NuGet.Versioning": { "type": "Transitive", - "resolved": "6.10.1", - "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" + "resolved": "6.11.1", + "contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg==" }, "protobuf-net": { "type": "Transitive", @@ -231,20 +255,6 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.1" } }, - "SpaceEngineersDedicated.ReferenceAssemblies": { - "type": "Transitive", - "resolved": "1.204.18", - "contentHash": "GT7/9CBMx4jjor41zLOOl87YYM/JdJD8xp9ccXyuhP2oUaz25H3ZmCQuGeAuZNENKru1a/7hZrId4PwlMDGoew==", - "dependencies": { - "SharpDX": "4.2.0-keen-cringe", - "protobuf-net": "1.0.0" - } - }, - "Steamworks.NET": { - "type": "Transitive", - "resolved": "20.1.0", - "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" - }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -353,20 +363,26 @@ "cringeplugins": { "type": "Project", "dependencies": { - "CringeBootstrap.Abstractions": "[1.0.0, )", "NLog": "[5.3.4, )", + "NuGet": "[1.0.0, )", + "SharedCringe": "[1.0.0, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", "dnlib": "[4.4.0, )" } }, - "pluginloader": { + "nuget": { "type": "Project", "dependencies": { - "Lib.Harmony.Thin": "[2.3.3, )", - "Microsoft.CodeAnalysis.CSharp": "[4.11.0, )", + "NuGet.Frameworks": "[6.11.1, )", + "NuGet.Versioning": "[6.11.1, )" + } + }, + "sharedcringe": { + "type": "Project", + "dependencies": { + "CringeBootstrap.Abstractions": "[1.0.0, )", "NLog": "[5.3.4, )", - "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )", - "Steamworks.NET": "[20.1.0, )" + "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )" } } }, @@ -382,6 +398,12 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Steamworks.NET": { + "type": "Direct", + "requested": "[20.1.0, )", + "resolved": "20.1.0", + "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" + }, "System.Diagnostics.PerformanceCounter": { "type": "Direct", "requested": "[8.0.0, )", @@ -400,11 +422,6 @@ "System.CodeDom": "8.0.0" } }, - "Steamworks.NET": { - "type": "Transitive", - "resolved": "20.1.0", - "contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ==" - }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "8.0.0", diff --git a/CringePlugins/Abstractions/IRenderComponent.cs b/CringePlugins/Abstractions/IRenderComponent.cs index 67f556b..d679ee8 100644 --- a/CringePlugins/Abstractions/IRenderComponent.cs +++ b/CringePlugins/Abstractions/IRenderComponent.cs @@ -2,5 +2,10 @@ public interface IRenderComponent { + /// + /// Gets called while the dear ImGui frame is being composed + /// void OnFrame(); -} \ No newline at end of file +} + +internal interface IRootRenderComponent : IRenderComponent, IDisposable; \ No newline at end of file diff --git a/CringePlugins/Config/PackagesConfig.cs b/CringePlugins/Config/PackagesConfig.cs new file mode 100644 index 0000000..9d7c8db --- /dev/null +++ b/CringePlugins/Config/PackagesConfig.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; +using CringePlugins.Resolver; +using NuGet; +using NuGet.Models; + +namespace CringePlugins.Config; + +public record PackagesConfig(ImmutableArray Sources, ImmutableArray Packages) +{ + public static PackagesConfig Default { get; } = new([ + new(string.Empty, "https://api.nuget.org/v3/index.json") + ], []); +} \ No newline at end of file diff --git a/CringePlugins/CringePlugins.csproj b/CringePlugins/CringePlugins.csproj index e0e6f91..da11274 100644 --- a/CringePlugins/CringePlugins.csproj +++ b/CringePlugins/CringePlugins.csproj @@ -1,14 +1,16 @@  - net8.0 + net8.0-windows enable enable + true true - + + @@ -25,5 +27,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/CringePlugins/Loader/PluginAssemblyLoadContext.cs b/CringePlugins/Loader/PluginAssemblyLoadContext.cs index c43264b..c587f84 100644 --- a/CringePlugins/Loader/PluginAssemblyLoadContext.cs +++ b/CringePlugins/Loader/PluginAssemblyLoadContext.cs @@ -1,19 +1,18 @@ using System.Reflection; using System.Runtime.Loader; using CringeBootstrap.Abstractions; +using SharedCringe.Loader; namespace CringePlugins.Loader; -internal class PluginAssemblyLoadContext : AssemblyLoadContext +internal class PluginAssemblyLoadContext : DerivedAssemblyLoadContext { - private readonly ICoreLoadContext _parentContext; private readonly string _entrypointPath; private readonly AssemblyDependencyResolver _dependencyResolver; private Assembly? _assembly; - internal PluginAssemblyLoadContext(ICoreLoadContext parentContext, string entrypointPath) : base($"Plugin Context {Path.GetFileNameWithoutExtension(entrypointPath)}", true) + internal PluginAssemblyLoadContext(ICoreLoadContext parentContext, string entrypointPath) : base(parentContext, $"Plugin Context {Path.GetFileNameWithoutExtension(entrypointPath)}") { - _parentContext = parentContext; _entrypointPath = entrypointPath; _dependencyResolver = new(entrypointPath); } @@ -25,7 +24,7 @@ internal class PluginAssemblyLoadContext : AssemblyLoadContext if (_dependencyResolver.ResolveAssemblyToPath(assemblyName) is { } path) return LoadFromAssemblyPath(path); - return _parentContext.ResolveFromAssemblyName(assemblyName); + return base.Load(assemblyName); } protected override nint LoadUnmanagedDll(string unmanagedDllName) @@ -33,6 +32,6 @@ internal class PluginAssemblyLoadContext : AssemblyLoadContext if (_dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName) is { } path) return LoadUnmanagedDllFromPath(path); - return _parentContext.ResolveUnmanagedDll(unmanagedDllName); + return base.LoadUnmanagedDll(unmanagedDllName); } } \ No newline at end of file diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index 9ce9472..21078f3 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -1,38 +1,71 @@ using System.Collections.Immutable; +using System.Runtime.InteropServices; +using System.Text.Json; +using CringePlugins.Config; +using CringePlugins.Resolver; +using CringePlugins.Splash; using NLog; +using NuGet; +using NuGet.Deps; +using NuGet.Frameworks; namespace CringePlugins.Loader; -public class PluginsLifetime +public class PluginsLifetime : ILoadingStage { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private readonly ImmutableArray _plugins; + public string Name => "Loading Plugins"; - public PluginsLifetime() - { - var dir = Directory.CreateDirectory(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CringeLauncher", "plugins")); + private ImmutableArray _plugins = []; + private readonly DirectoryInfo _dir = Directory.CreateDirectory(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CringeLauncher")); + private readonly NuGetFramework _runtimeFramework = NuGetFramework.ParseFolder("net8.0-windows"); - var plugins = ImmutableArray.Empty.ToBuilder(); + public async ValueTask Load(ISplashProgress progress) + { + progress.DefineStepsCount(6); - foreach (var directory in dir.EnumerateDirectories()) + progress.Report("Discovering local plugins"); + + DiscoverLocalPlugins(_dir.CreateSubdirectory("plugins")); + + progress.Report("Loading config"); + + PackagesConfig? packagesConfig = null; + var configPath = Path.Join(_dir.FullName, "packages.json"); + if (File.Exists(configPath)) + await using (var stream = File.OpenRead(configPath)) + packagesConfig = JsonSerializer.Deserialize(stream, NuGetClient.SerializerOptions)!; + + if (packagesConfig == null) { - var files = directory.GetFiles("*.dll"); - - if (files.Length != 1) continue; - - try - { - plugins.Add(new PluginInstance(files[0].FullName)); - } - catch (Exception e) - { - Log.Error(e, "Failed to load plugin {PluginPath}", files[0].FullName); - } + packagesConfig = PackagesConfig.Default; + await using var stream = File.Create(configPath); + await JsonSerializer.SerializeAsync(stream, packagesConfig, NuGetClient.SerializerOptions); } - _plugins = plugins.ToImmutable(); + progress.Report("Resolving packages"); + + var sourceMapping = new PackageSourceMapping(packagesConfig.Sources); + var resolver = new PackageResolver(_runtimeFramework, packagesConfig.Packages, sourceMapping); + + var packages = await resolver.ResolveAsync(); + progress.Report("Downloading packages"); + + var cachedPackages = await resolver.DownloadPackagesAsync(_dir.CreateSubdirectory("cache"), packages, progress); + + progress.Report("Loading plugins"); + + await LoadPlugins(cachedPackages, sourceMapping); + + progress.Report("Registering plugins"); + + RegisterLifetime(); + } + + private void RegisterLifetime() + { foreach (var instance in _plugins) { try @@ -46,4 +79,56 @@ public class PluginsLifetime } } } + + private async Task LoadPlugins(IReadOnlySet packages, PackageSourceMapping sourceMapping) + { + var plugins = _plugins.ToBuilder(); + + foreach (var package in packages) + { + var dir = Path.Join(package.Directory.FullName, "lib", package.ResolvedFramework.GetShortFolderName()); + + await using (var stream = File.Create(Path.Join(dir, $"{package.Package.Id}.deps.json"))) + await DependencyManifestUtility.WriteDependencyManifestAsync(stream, package.Entry, _runtimeFramework, + sourceMapping, + dependency => + packages.First(b => b.Package.Id.Equals(dependency.Id, StringComparison.OrdinalIgnoreCase)) + .Package + .Version); + + LoadComponent(plugins, Path.Join(dir, $"{package.Package.Id}.dll")); + } + + _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(); + } + + private static void LoadComponent(ImmutableArray.Builder plugins, string path) + { + try + { + plugins.Add(new PluginInstance(path)); + } + catch (Exception e) + { + Log.Error(e, "Failed to load plugin {PluginPath}", path); + } + } } \ No newline at end of file diff --git a/CringePlugins/Render/RenderHandler.cs b/CringePlugins/Render/RenderHandler.cs index 1406144..13f6d7c 100644 --- a/CringePlugins/Render/RenderHandler.cs +++ b/CringePlugins/Render/RenderHandler.cs @@ -4,7 +4,7 @@ using NLog; namespace CringePlugins.Render; -public sealed class RenderHandler : IDisposable +public sealed class RenderHandler : IRootRenderComponent { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); @@ -23,7 +23,7 @@ public sealed class RenderHandler : IDisposable _components.Add(new ComponentRegistration(typeof(TComponent), instance)); } - internal void OnFrame() + void IRenderComponent.OnFrame() { foreach (var (instanceType, renderComponent) in _components) { diff --git a/CringePlugins/Resolver/PackageResolver.cs b/CringePlugins/Resolver/PackageResolver.cs new file mode 100644 index 0000000..fbb2043 --- /dev/null +++ b/CringePlugins/Resolver/PackageResolver.cs @@ -0,0 +1,148 @@ +using System.Collections.Immutable; +using System.IO.Compression; +using NuGet; +using NuGet.Frameworks; +using NuGet.Models; +using NuGet.Versioning; + +namespace CringePlugins.Resolver; + +public class PackageResolver(NuGetFramework runtimeFramework, ImmutableArray references, PackageSourceMapping packageSources) +{ + public async Task> ResolveAsync() + { + var order = 0; + var packages = new SortedSet(); + + foreach (var reference in references) + { + var client = await packageSources.GetClientAsync(reference.Id); + + var registrationRoot = await client.GetPackageRegistrationRootAsync(reference.Id); + + NuGetVersion? version = null; + foreach (var page in registrationRoot.Items) + { + version = reference.Range.FindBestMatch(page.Items.Where(b => b.CatalogEntry.PackageTypes is [{ Name: "CringePlugin" }]).Select(b => b.CatalogEntry.Version)); + if (version is not null) + break; + } + + if (version is null) + throw new Exception($"Unable to find version for package {reference.Id}"); + + var package = new Package(order, reference.Id, version, []); // todo resolve dependencies + + if (packages.Add(package)) + continue; + + if (!packages.TryGetValue(package, out var existingPackage)) + throw new Exception($"Duplicate package {package.Id}"); + + if (package.Version < existingPackage.Version) + throw new Exception($"Package reference {package.Id} has lower version {package.Version} than already resolved {existingPackage.Version}"); + + if (package.Version == existingPackage.Version) + continue; + + packages.Remove(existingPackage); + packages.Add(package with + { + Order = ++order + }); + } + + var set = ImmutableHashSet.Empty.ToBuilder(); + foreach (var package in packages) + { + var client = await packageSources.GetClientAsync(package.Id); + + var (catalogEntryUrl, catalogEntry) = await client.GetPackageRegistrationAsync(package.Id, package.Version); + + catalogEntry ??= await client.GetPackageCatalogEntryAsync(catalogEntryUrl); + + var nearestGroup = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups, runtimeFramework, + g => g.TargetFramework); + + if (nearestGroup is null) + throw new Exception($"Unable to find compatible dependency group for package {package.Id}"); + + set.Add(new RemotePackage(package, nearestGroup.TargetFramework, client, catalogEntry)); + } + + return set.ToImmutable(); + } + + public async Task> DownloadPackagesAsync(DirectoryInfo baseDirectory, + IReadOnlySet resolvedPackages, IProgress? progress = null) + { + var packages = ImmutableHashSet.Empty.ToBuilder(); + + var i = 0f; + foreach (var package in resolvedPackages) + { + switch (package) + { + case RemotePackage remotePackage: + { + var dir = new DirectoryInfo(Path.Join(baseDirectory.FullName, package.Package.Id, package.Package.Version.ToString())); + if (!dir.Exists) + { + dir.Create(); + + await using var stream = await remotePackage.Client.GetPackageContentStreamAsync(remotePackage.Package.Id, remotePackage.Package.Version); + 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(); + } +} + +public record CachedPackage(Package Package, NuGetFramework ResolvedFramework, DirectoryInfo Directory, CatalogEntry Entry) : ResolvedPackage(Package, ResolvedFramework, Entry); +public record RemotePackage(Package Package, NuGetFramework ResolvedFramework, NuGetClient Client, CatalogEntry Entry) : ResolvedPackage(Package, ResolvedFramework, Entry); + +public abstract record ResolvedPackage(Package Package, NuGetFramework ResolvedFramework, CatalogEntry Entry); + +public record Package(int Order, string Id, NuGetVersion Version, ImmutableArray Dependencies) : IComparable, IComparable +{ + public int CompareTo(Package? other) + { + if (ReferenceEquals(this, other)) return 0; + if (other is null) return 1; + var orderComparison = Order.CompareTo(other.Order); + if (orderComparison != 0) return orderComparison; + return string.Compare(Id, other.Id, StringComparison.OrdinalIgnoreCase); + } + + public int CompareTo(object? obj) + { + if (obj is null) return 1; + if (ReferenceEquals(this, obj)) return 0; + return obj is Package other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Package)}"); + } + + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Id); + + public virtual bool Equals(Package? other) + { + if (other is null) return false; + return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase); + } +} + +public record PackageReference(string Id, VersionRange Range); \ No newline at end of file diff --git a/CringePlugins/Splash/ISplashProgress.cs b/CringePlugins/Splash/ISplashProgress.cs new file mode 100644 index 0000000..5951f19 --- /dev/null +++ b/CringePlugins/Splash/ISplashProgress.cs @@ -0,0 +1,13 @@ +namespace CringePlugins.Splash; + +public interface ISplashProgress : IProgress, IProgress +{ + void DefineStage(ILoadingStage stage); + void DefineStepsCount(int count); +} + +public interface ILoadingStage +{ + string Name { get; } + ValueTask Load(ISplashProgress progress); +} \ No newline at end of file diff --git a/CringePlugins/Splash/ProgressInfo.cs b/CringePlugins/Splash/ProgressInfo.cs new file mode 100644 index 0000000..891432d --- /dev/null +++ b/CringePlugins/Splash/ProgressInfo.cs @@ -0,0 +1,9 @@ +namespace CringePlugins.Splash; + +public record ProgressInfo(string Text) +{ + public static implicit operator ProgressInfo(string s) => new(s); + public static implicit operator ProgressInfo((string, float) s) => new PercentProgressInfo(s.Item1, s.Item2); +} + +public record PercentProgressInfo(string Text, float Percent = 0) : ProgressInfo(Text); \ No newline at end of file diff --git a/CringePlugins/Splash/Splash.cs b/CringePlugins/Splash/Splash.cs new file mode 100644 index 0000000..5b13456 --- /dev/null +++ b/CringePlugins/Splash/Splash.cs @@ -0,0 +1,29 @@ +namespace CringePlugins.Splash; + +public class Splash : ISplashProgress +{ + private readonly List _loadingStages = []; + + public void Report(ProgressInfo value) + { + } + + public void Report(float value) + { + } + + public void DefineStage(ILoadingStage stage) => _loadingStages.Add(stage); + + public void DefineStepsCount(int count) + { + } + + public void ExecuteLoadingStages() + { + foreach (var loadingStage in _loadingStages) + { + // todo sync context + loadingStage.Load(this).AsTask().GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/CringePlugins/packages.lock.json b/CringePlugins/packages.lock.json index 23ff5bf..18bfc61 100644 --- a/CringePlugins/packages.lock.json +++ b/CringePlugins/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0": { + "net8.0-windows7.0": { "dnlib": { "type": "Direct", "requested": "[4.4.0, )", @@ -23,22 +23,22 @@ "SpaceEngineersDedicated.ReferenceAssemblies": { "type": "Direct", "requested": "[1.*, )", - "resolved": "1.204.18", - "contentHash": "GT7/9CBMx4jjor41zLOOl87YYM/JdJD8xp9ccXyuhP2oUaz25H3ZmCQuGeAuZNENKru1a/7hZrId4PwlMDGoew==", + "resolved": "1.205.23", + "contentHash": "J7mF5hY39PzzCZps6vhIRzKiq8vD6Af9TgumTJR068vjEi+BzyeEFhqX+cl2Dd1ngOmsBtGWc5m+vxgTfs5YuA==", "dependencies": { "SharpDX": "4.2.0-keen-cringe", "protobuf-net": "1.0.0" } }, - "ImGui.NET.DirectX": { + "NuGet.Frameworks": { "type": "Transitive", - "resolved": "1.91.0.1", - "contentHash": "PpW1gQ9g97h6Hm/h/tkSBOmsBYgGwN8wKNmlJomcQFD/zRY1HPkJZz18XRSfRLHPmH2eeh4hhhZv1KHug7dF9g==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "6.11.1", + "contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg==" + }, + "NuGet.Versioning": { + "type": "Transitive", + "resolved": "6.11.1", + "contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg==" }, "protobuf-net": { "type": "Transitive", @@ -50,28 +50,21 @@ "resolved": "4.2.0-keen-cringe", "contentHash": "LaJN3h1Gi1FWVdef2I5WtOH9gwzKCBniH0CragarbkN2QheYY6Lqm+91PcOfp1w/4wdVb+k8Kjv3sO393Tphtw==" }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" - }, - "System.Numerics.Vectors": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, "cringebootstrap.abstractions": { "type": "Project" }, - "cringerender": { + "nuget": { "type": "Project", "dependencies": { - "ImGui.NET.DirectX": "[1.91.0.1, )", + "NuGet.Frameworks": "[6.11.1, )", + "NuGet.Versioning": "[6.11.1, )" + } + }, + "sharedcringe": { + "type": "Project", + "dependencies": { + "CringeBootstrap.Abstractions": "[1.0.0, )", + "NLog": "[5.3.4, )", "SpaceEngineersDedicated.ReferenceAssemblies": "[1.*, )" } } diff --git a/CringePlugins/splash.gif b/CringePlugins/splash.gif new file mode 100644 index 0000000..ad6fb35 Binary files /dev/null and b/CringePlugins/splash.gif differ diff --git a/NuGet/Converters/FrameworkJsonConverter.cs b/NuGet/Converters/FrameworkJsonConverter.cs new file mode 100644 index 0000000..0327247 --- /dev/null +++ b/NuGet/Converters/FrameworkJsonConverter.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Frameworks; + +namespace NuGet.Converters; + +public class FrameworkJsonConverter(FrameworkNameFormat format) : JsonConverter +{ + public override NuGetFramework? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Invalid framework string"); + + var s = reader.GetString()!; + return format switch + { + FrameworkNameFormat.ShortFolderName => NuGetFramework.ParseFolder(s), + FrameworkNameFormat.FrameworkName => NuGetFramework.ParseFrameworkName(s, + DefaultFrameworkNameProvider.Instance), + _ => throw new ArgumentOutOfRangeException() + }; + } + + public override void Write(Utf8JsonWriter writer, NuGetFramework value, JsonSerializerOptions options) + { + writer.WriteStringValue(format switch + { + FrameworkNameFormat.ShortFolderName => value.GetShortFolderName(), + FrameworkNameFormat.FrameworkName => value.DotNetFrameworkName, + _ => throw new ArgumentOutOfRangeException() + }); + } +} + +public enum FrameworkNameFormat +{ + /// + /// The short folder name format (net8.0) + /// + ShortFolderName, + /// + /// Full framework name (.NETCoreApp,Version=v8.0) + /// + FrameworkName +} \ No newline at end of file diff --git a/NuGet/Converters/ManifestPackageKeyJsonConverter.cs b/NuGet/Converters/ManifestPackageKeyJsonConverter.cs new file mode 100644 index 0000000..fb99a0d --- /dev/null +++ b/NuGet/Converters/ManifestPackageKeyJsonConverter.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Deps; + +namespace NuGet.Converters; + +public class ManifestPackageKeyJsonConverter : JsonConverter +{ + public override ManifestPackageKey Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Invalid package key string"); + + return ManifestPackageKey.Parse(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, ManifestPackageKey value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + + public override ManifestPackageKey ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, + JsonSerializerOptions options) => Read(ref reader, typeToConvert, options); + + public override void WriteAsPropertyName(Utf8JsonWriter writer, ManifestPackageKey value, + JsonSerializerOptions options) => Write(writer, value, options); +} \ No newline at end of file diff --git a/NuGet/Converters/ResourceTypeJsonConverter.cs b/NuGet/Converters/ResourceTypeJsonConverter.cs new file mode 100644 index 0000000..edb3929 --- /dev/null +++ b/NuGet/Converters/ResourceTypeJsonConverter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Models; + +namespace NuGet.Converters; + +public class ResourceTypeJsonConverter : JsonConverter +{ + public override ResourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Invalid resource type"); + + return ResourceType.Parse(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, ResourceType value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/NuGet/Converters/VersionJsonConverter.cs b/NuGet/Converters/VersionJsonConverter.cs new file mode 100644 index 0000000..4addc75 --- /dev/null +++ b/NuGet/Converters/VersionJsonConverter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Versioning; + +namespace NuGet.Converters; + +public class VersionJsonConverter : JsonConverter +{ + public override NuGetVersion? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Invalid version string"); + + return NuGetVersion.Parse(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, NuGetVersion value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/NuGet/Converters/VersionRangeJsonConverter.cs b/NuGet/Converters/VersionRangeJsonConverter.cs new file mode 100644 index 0000000..b15c898 --- /dev/null +++ b/NuGet/Converters/VersionRangeJsonConverter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Versioning; + +namespace NuGet.Converters; + +public class VersionRangeJsonConverter : JsonConverter +{ + public override VersionRange? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new JsonException("Invalid version range"); + + return VersionRange.Parse(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, VersionRange value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/NuGet/Deps/DependenciesManifest.cs b/NuGet/Deps/DependenciesManifest.cs new file mode 100644 index 0000000..c74d0c9 --- /dev/null +++ b/NuGet/Deps/DependenciesManifest.cs @@ -0,0 +1,123 @@ +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Converters; +using NuGet.Frameworks; +using NuGet.Models; +using NuGet.Versioning; + +namespace NuGet.Deps; + +public record DependenciesManifest(RuntimeTarget RuntimeTarget, + ImmutableDictionary CompilationOptions, + ImmutableDictionary> Targets, + ImmutableDictionary Libraries); + +public record DependencyLibrary( + LibraryType Type, + string Sha512 = "", + bool Serviceable = false, + ManifestPackageKey? Path = null, + string? HashPath = null); + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum LibraryType +{ + Project, + Package +} + +public record DependencyTarget(ImmutableDictionary? Dependencies, + // key is file path relative to package root + ImmutableDictionary? Runtime, + // key is file path relative to package root + ImmutableDictionary? Native); + +public record Dependency(Version? FileVersion = null); + +public record RuntimeDependency(Version? AssemblyVersion = null, Version? FileVersion = null) : Dependency(FileVersion); + +public record RuntimeTarget([property: JsonPropertyName("name")] NuGetFramework Framework, string Signature = ""); + +[JsonConverter(typeof(ManifestPackageKeyJsonConverter))] +public record ManifestPackageKey(string Id, NuGetVersion Version) +{ + public static ManifestPackageKey Parse(string str) + { + 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}"; +} + +public static class DependencyManifestUtility +{ + private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true, + Converters = + { + new FrameworkJsonConverter(FrameworkNameFormat.FrameworkName), + new VersionJsonConverter() + } + }; + + public static async ValueTask WriteDependencyManifestAsync(Stream stream, CatalogEntry catalogEntry, NuGetFramework targetFramework, + PackageSourceMapping packageSources, Func versionResolver) + { + var runtimeTarget = new RuntimeTarget(targetFramework); + + var targets = ImmutableDictionary.Empty.ToBuilder(); + + await MapCatalogEntry(catalogEntry, targetFramework, packageSources, versionResolver, targets); + + var manifest = new DependenciesManifest(runtimeTarget, ImmutableDictionary.Empty, + ImmutableDictionary>.Empty + .Add(targetFramework, targets.ToImmutable()), + ImmutableDictionary.Empty); + + await JsonSerializer.SerializeAsync(stream, manifest, SerializerOptions); + } + + private static async Task MapCatalogEntry(CatalogEntry catalogEntry, NuGetFramework targetFramework, + PackageSourceMapping packageSources, Func versionResolver, ImmutableDictionary.Builder targets) + { + if (targets.ContainsKey(new(catalogEntry.Id, catalogEntry.Version))) + return; + + var nearest = NuGetFrameworkUtility.GetNearest(catalogEntry.DependencyGroups, targetFramework, + group => group.TargetFramework); + + if (nearest is null) + return; + + targets.Add(new(catalogEntry.Id, catalogEntry.Version), + MapEntry(catalogEntry, nearest, targetFramework, versionResolver)); + + foreach (var dependency in nearest.Dependencies) + { + var client = await packageSources.GetClientAsync(dependency.Id); + var (url, entry) = await client.GetPackageRegistrationAsync(dependency.Id, versionResolver(dependency)); + + entry ??= await client.GetPackageCatalogEntryAsync(url); + + await MapCatalogEntry(entry, targetFramework, packageSources, versionResolver, targets); + } + } + + private static DependencyTarget MapEntry(CatalogEntry entry, DependencyGroup group, NuGetFramework targetFramework, Func versionResolver) + { + return new(group.Dependencies.ToImmutableDictionary(b => b.Id, versionResolver), + entry.PackageEntries.Where(b => b.FullName.StartsWith($"lib/{targetFramework.GetShortFolderName()}/")) + .ToImmutableDictionary(b => b.FullName, _ => new RuntimeDependency()), + entry.PackageEntries.Where(b => + b.FullName.StartsWith($"runtimes/{RuntimeInformation.RuntimeIdentifier}/native/")) + .ToImmutableDictionary(b => b.FullName, _ => new Dependency())); + } +} \ No newline at end of file diff --git a/NuGet/Models/CatalogEntry.cs b/NuGet/Models/CatalogEntry.cs new file mode 100644 index 0000000..b943755 --- /dev/null +++ b/NuGet/Models/CatalogEntry.cs @@ -0,0 +1,9 @@ +using NuGet.Versioning; + +namespace NuGet.Models; + +public record CatalogEntry(string Id, NuGetVersion Version, DependencyGroup[] DependencyGroups, PackageType[]? PackageTypes, + CatalogPackageEntry[] PackageEntries, + bool Serviceable = true); + +public record CatalogPackageEntry(string Name, string FullName, long CompressedLength, long Length); \ No newline at end of file diff --git a/NuGet/Models/Dependency.cs b/NuGet/Models/Dependency.cs new file mode 100644 index 0000000..e1bec5a --- /dev/null +++ b/NuGet/Models/Dependency.cs @@ -0,0 +1,5 @@ +using NuGet.Versioning; + +namespace NuGet.Models; + +public record Dependency(string Id, VersionRange Range); \ No newline at end of file diff --git a/NuGet/Models/DependencyGroup.cs b/NuGet/Models/DependencyGroup.cs new file mode 100644 index 0000000..4c7a3b5 --- /dev/null +++ b/NuGet/Models/DependencyGroup.cs @@ -0,0 +1,5 @@ +using NuGet.Frameworks; + +namespace NuGet.Models; + +public record DependencyGroup(NuGetFramework TargetFramework, Dependency[] Dependencies); \ No newline at end of file diff --git a/NuGet/Models/NuGetIndex.cs b/NuGet/Models/NuGetIndex.cs new file mode 100644 index 0000000..057880d --- /dev/null +++ b/NuGet/Models/NuGetIndex.cs @@ -0,0 +1,5 @@ +using NuGet.Versioning; + +namespace NuGet.Models; + +public record NuGetIndex(NuGetVersion Version, Resource[] Resources); \ No newline at end of file diff --git a/NuGet/Models/PackageType.cs b/NuGet/Models/PackageType.cs new file mode 100644 index 0000000..b1d767f --- /dev/null +++ b/NuGet/Models/PackageType.cs @@ -0,0 +1,3 @@ +namespace NuGet.Models; + +public record PackageType(string Name); \ No newline at end of file diff --git a/NuGet/Models/Registration.cs b/NuGet/Models/Registration.cs new file mode 100644 index 0000000..3e1ce58 --- /dev/null +++ b/NuGet/Models/Registration.cs @@ -0,0 +1,6 @@ +using System.Text.Json.Serialization; + +namespace NuGet.Models; + +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/RegistrationEntry.cs b/NuGet/Models/RegistrationEntry.cs new file mode 100644 index 0000000..f704314 --- /dev/null +++ b/NuGet/Models/RegistrationEntry.cs @@ -0,0 +1,3 @@ +namespace NuGet.Models; + +public record RegistrationEntry(CatalogEntry CatalogEntry); \ No newline at end of file diff --git a/NuGet/Models/RegistrationPage.cs b/NuGet/Models/RegistrationPage.cs new file mode 100644 index 0000000..4626929 --- /dev/null +++ b/NuGet/Models/RegistrationPage.cs @@ -0,0 +1,5 @@ +using NuGet.Versioning; + +namespace NuGet.Models; + +public record RegistrationPage(int Count, NuGetVersion Lower, NuGetVersion Upper, RegistrationEntry[] Items); \ No newline at end of file diff --git a/NuGet/Models/RegistrationRoot.cs b/NuGet/Models/RegistrationRoot.cs new file mode 100644 index 0000000..dd7ff30 --- /dev/null +++ b/NuGet/Models/RegistrationRoot.cs @@ -0,0 +1,3 @@ +namespace NuGet.Models; + +public record RegistrationRoot(int Count, RegistrationPage[] Items); \ No newline at end of file diff --git a/NuGet/Models/Resource.cs b/NuGet/Models/Resource.cs new file mode 100644 index 0000000..6d57df4 --- /dev/null +++ b/NuGet/Models/Resource.cs @@ -0,0 +1,5 @@ +using System.Text.Json.Serialization; + +namespace NuGet.Models; + +public record Resource([property: JsonPropertyName("@id")] string Url, [property: JsonPropertyName("@type")] ResourceType Type, string? Comment); \ No newline at end of file diff --git a/NuGet/Models/ResourceType.cs b/NuGet/Models/ResourceType.cs new file mode 100644 index 0000000..fc3c8cc --- /dev/null +++ b/NuGet/Models/ResourceType.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using NuGet.Converters; +using NuGet.Versioning; + +namespace NuGet.Models; + +[JsonConverter(typeof(ResourceTypeJsonConverter))] +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)..]; + + return NuGetVersion.TryParse(versionStr, out var version) + ? new ResourceType(id, version) + : new ResourceType(id, null); + } + + public override string ToString() => $"{Id}/{Version}"; +} \ No newline at end of file diff --git a/NuGet/NuGet.csproj b/NuGet/NuGet.csproj new file mode 100644 index 0000000..5e23092 --- /dev/null +++ b/NuGet/NuGet.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/NuGet/NuGetClient.cs b/NuGet/NuGetClient.cs new file mode 100644 index 0000000..c7c30dd --- /dev/null +++ b/NuGet/NuGetClient.cs @@ -0,0 +1,73 @@ +using System.Net.Http.Json; +using System.Text.Json; +using NuGet.Converters; +using NuGet.Models; +using NuGet.Versioning; + +namespace NuGet; + +public class NuGetClient +{ + private readonly HttpClient _client; + private readonly Uri _packageBaseAddress; + private readonly Uri _registration; + + public static JsonSerializerOptions SerializerOptions { get; } = new(JsonSerializerDefaults.Web) + { + Converters = + { + new VersionJsonConverter(), + new VersionRangeJsonConverter(), + new FrameworkJsonConverter(FrameworkNameFormat.ShortFolderName), + }, + WriteIndented = true + }; + + private NuGetClient(HttpClient client, Uri packageBaseAddress, Uri registration) + { + _client = client; + _packageBaseAddress = packageBaseAddress; + _registration = registration; + } + + public Task GetPackageContentStreamAsync(string id, NuGetVersion version) + { + return _client.GetStreamAsync(new Uri(_packageBaseAddress, + new Uri($"{id.ToLower()}/{version}.nupkg", UriKind.Relative))); + } + + public Task GetPackageRegistrationAsync(string id, NuGetVersion version) + { + return _client.GetFromJsonAsync( + new Uri(_registration, + new Uri($"{id.ToLower()}/{version}.json", UriKind.Relative)), + SerializerOptions + )!; + } + + public Task GetPackageRegistrationRootAsync(string id) + { + return _client.GetFromJsonAsync( + new Uri(_registration, + new Uri($"{id.ToLower()}/index.json", UriKind.Relative)), + SerializerOptions + )!; + } + + public Task GetPackageCatalogEntryAsync(string url) + { + return _client.GetFromJsonAsync(url, SerializerOptions)!; + } + + public static async Task CreateFromIndexUrlAsync(string indexUrl) + { + var client = new HttpClient(); + + var index = await client.GetFromJsonAsync(indexUrl, SerializerOptions); + + var (packageBaseAddress, _, _) = index!.Resources.First(b => b.Type.Id == "PackageBaseAddress"); + var (registration, _, _) = index!.Resources.First(b => b.Type.Id == "RegistrationsBaseUrl"); + + return new NuGetClient(client, new Uri(packageBaseAddress), new Uri(registration)); + } +} \ No newline at end of file diff --git a/NuGet/PackageSourceMapping.cs b/NuGet/PackageSourceMapping.cs new file mode 100644 index 0000000..5cda169 --- /dev/null +++ b/NuGet/PackageSourceMapping.cs @@ -0,0 +1,16 @@ +using System.Collections.Immutable; + +namespace NuGet; + +public class PackageSourceMapping(ImmutableArray sources) +{ + private readonly ImmutableDictionary> _clients = sources.Select(b => + new KeyValuePair>(b.Pattern, + NuGetClient.CreateFromIndexUrlAsync(b.Url))) + .ToImmutableDictionary(); + + public Task GetClientAsync(string packageId) => + _clients.FirstOrDefault(b => packageId.StartsWith(b.Key)).Value; +} + +public record PackageSource(string Pattern, string Url); \ No newline at end of file diff --git a/SharedCringe/Loader/DerivedAssemblyLoadContext.cs b/SharedCringe/Loader/DerivedAssemblyLoadContext.cs new file mode 100644 index 0000000..ffa7990 --- /dev/null +++ b/SharedCringe/Loader/DerivedAssemblyLoadContext.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.Loader; +using CringeBootstrap.Abstractions; + +namespace SharedCringe.Loader; + +public abstract class DerivedAssemblyLoadContext(ICoreLoadContext parentContext, string name) + : 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/SharedCringe/SharedCringe.csproj b/SharedCringe/SharedCringe.csproj new file mode 100644 index 0000000..bbbbf75 --- /dev/null +++ b/SharedCringe/SharedCringe.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + diff --git a/SharedCringe/packages.lock.json b/SharedCringe/packages.lock.json new file mode 100644 index 0000000..3a1beba --- /dev/null +++ b/SharedCringe/packages.lock.json @@ -0,0 +1,36 @@ +{ + "version": 1, + "dependencies": { + "net8.0": { + "NLog": { + "type": "Direct", + "requested": "[5.3.4, )", + "resolved": "5.3.4", + "contentHash": "gLy7+O1hEYJXIlcTr1/VWjGXrZTQFZzYNO18IWasD64pNwz0BreV+nHLxWKXWZzERRzoKnsk2XYtwLkTVk7J1A==" + }, + "SpaceEngineersDedicated.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.*, )", + "resolved": "1.205.23", + "contentHash": "J7mF5hY39PzzCZps6vhIRzKiq8vD6Af9TgumTJR068vjEi+BzyeEFhqX+cl2Dd1ngOmsBtGWc5m+vxgTfs5YuA==", + "dependencies": { + "SharpDX": "4.2.0-keen-cringe", + "protobuf-net": "1.0.0" + } + }, + "protobuf-net": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw==" + }, + "SharpDX": { + "type": "Transitive", + "resolved": "4.2.0-keen-cringe", + "contentHash": "LaJN3h1Gi1FWVdef2I5WtOH9gwzKCBniH0CragarbkN2QheYY6Lqm+91PcOfp1w/4wdVb+k8Kjv3sO393Tphtw==" + }, + "cringebootstrap.abstractions": { + "type": "Project" + } + } + } +} \ No newline at end of file