diff --git a/Directory.Packages.props b/Directory.Packages.props
index c163cb0bf..ca628f872 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -40,9 +40,9 @@
-
-
-
+
+
+
diff --git a/Ryujinx.sln b/Ryujinx.sln
index bb196cabc..eb4a3bfa9 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -87,6 +87,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.Shared", "src\LibRyujinx\LibRyujinx.Shared.csproj", "{5BBF478C-A520-41E7-9B88-890AD26766B8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{C75176EB-C44E-449A-8077-A48AD94534EE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.NativeSample", "src\LibRyujinx.NativeSample\LibRyujinx.NativeSample.csproj", "{63D2C96B-5194-4592-BC91-30BEB11C06BD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibRyujinx.Build", "src\LibRyujinx.Build\LibRyujinx.Build.csproj", "{028BA6E8-BDB5-4F85-BCA0-EDC559C85F4F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -249,6 +257,22 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C75176EB-C44E-449A-8077-A48AD94534EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C75176EB-C44E-449A-8077-A48AD94534EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C75176EB-C44E-449A-8077-A48AD94534EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C75176EB-C44E-449A-8077-A48AD94534EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {028BA6E8-BDB5-4F85-BCA0-EDC559C85F4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {028BA6E8-BDB5-4F85-BCA0-EDC559C85F4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {028BA6E8-BDB5-4F85-BCA0-EDC559C85F4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {028BA6E8-BDB5-4F85-BCA0-EDC559C85F4F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj b/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj
new file mode 100644
index 000000000..0c44a3456
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ true
+
+
+
+
+
+
diff --git a/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
new file mode 100644
index 000000000..a52264edf
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibRyujinx.Sample
+{
+ internal static class LibRyujinxInterop
+ {
+ private const string dll = "LibRyujinx.Shared.dll";
+
+ [DllImport(dll, EntryPoint = "initialize")]
+ public extern static bool Initialize(IntPtr path);
+
+
+ [DllImport(dll, EntryPoint = "graphics_initialize")]
+ public extern static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration);
+
+ [DllImport(dll, EntryPoint = "device_initialize")]
+ internal extern static bool InitializeDevice();
+
+ [DllImport(dll, EntryPoint = "graphics_initialize_renderer")]
+ internal extern static bool InitializeGraphicsRenderer(GraphicsBackend backend, NativeGraphicsInterop nativeGraphicsInterop);
+
+ [DllImport(dll, EntryPoint = "device_load")]
+ internal extern static bool LoadApplication(IntPtr pathPtr);
+
+ [DllImport(dll, EntryPoint = "graphics_renderer_run_loop")]
+ internal extern static void RunLoop();
+
+ [DllImport(dll, EntryPoint = "graphics_renderer_set_size")]
+ internal extern static void SetRendererSize(int width, int height);
+
+ [DllImport(dll, EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
+ internal extern static void SetSwapBuffersCallback(IntPtr swapBuffers);
+
+ [DllImport(dll, EntryPoint = "graphics_renderer_set_vsync")]
+ internal extern static void SetVsyncState(bool enabled);
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct GraphicsConfiguration
+ {
+ public float ResScale = 1f;
+ public float MaxAnisotropy = -1;
+ public bool FastGpuTime = true;
+ public bool Fast2DCopy = true;
+ public bool EnableMacroJit = false;
+ public bool EnableMacroHLE = true;
+ public bool EnableShaderCache = true;
+ public bool EnableSpirvCompilationOnVulkan = true;
+ public bool EnableTextureRecompression = false;
+ public BackendThreading BackendThreading = BackendThreading.Auto;
+
+ public GraphicsConfiguration()
+ {
+ }
+ }
+ public enum GraphicsBackend
+ {
+ Vulkan,
+ OpenGl
+ }
+ public enum BackendThreading
+ {
+ Auto,
+ Off,
+ On
+ }
+
+ public struct NativeGraphicsInterop
+ {
+ public IntPtr GlGetProcAddress;
+ public IntPtr VkNativeContextLoader;
+ public IntPtr VkCreateSurface;
+ public IntPtr VkRequiredExtensions;
+ public int VkRequiredExtensionsCount;
+ }
+}
diff --git a/src/LibRyujinx.NativeSample/NativeWindow.cs b/src/LibRyujinx.NativeSample/NativeWindow.cs
new file mode 100644
index 000000000..63ac5a0ad
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/NativeWindow.cs
@@ -0,0 +1,115 @@
+using LibRyujinx.Sample;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Windowing.Desktop;
+using OpenTK.Windowing.GraphicsLibraryFramework;
+using System.Runtime.InteropServices;
+
+namespace LibRyujinx.NativeSample
+{
+ internal class NativeWindow : OpenTK.Windowing.Desktop.NativeWindow
+ {
+ private nint del;
+ public delegate void SwapBuffersCallback();
+ public delegate IntPtr GetProcAddress(string name);
+ public delegate IntPtr CreateSurface(IntPtr instance);
+
+ private bool _isVulkan;
+
+ public NativeWindow(NativeWindowSettings nativeWindowSettings) : base(nativeWindowSettings)
+ {
+ _isVulkan = true;
+ }
+
+ internal unsafe void Start(string gamePath)
+ {
+ if (!_isVulkan)
+ {
+ MakeCurrent();
+ }
+
+ var getProcAddress = Marshal.GetFunctionPointerForDelegate(x => GLFW.GetProcAddress(x));
+ var createSurface = Marshal.GetFunctionPointerForDelegate( x =>
+ {
+ VkHandle surface;
+ GLFW.CreateWindowSurface(new VkHandle(x) ,this.WindowPtr, null, out surface);
+
+ return surface.Handle;
+ });
+ var vkExtensions = GLFW.GetRequiredInstanceExtensions();
+
+
+ var pointers = new IntPtr[vkExtensions.Length];
+ for (int i = 0; i < vkExtensions.Length; i++)
+ {
+ pointers[i] = Marshal.StringToHGlobalAnsi(vkExtensions[i]);
+ }
+
+ fixed (IntPtr* ptr = pointers)
+ {
+ var nativeGraphicsInterop = new NativeGraphicsInterop()
+ {
+ GlGetProcAddress = getProcAddress,
+ VkRequiredExtensions = (nint)ptr,
+ VkRequiredExtensionsCount = pointers.Length,
+ VkCreateSurface = createSurface
+ };
+ var success = LibRyujinxInterop.InitializeGraphicsRenderer(_isVulkan ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl, nativeGraphicsInterop);
+ success = LibRyujinxInterop.InitializeDevice();
+
+ var path = Marshal.StringToHGlobalAnsi(gamePath);
+ var loaded = LibRyujinxInterop.LoadApplication(path);
+ LibRyujinxInterop.SetRendererSize(Size.X, Size.Y);
+ Marshal.FreeHGlobal(path);
+ }
+
+ if (!_isVulkan)
+ {
+ Context.MakeNoneCurrent();
+ }
+
+ var thread = new Thread(new ThreadStart(RunLoop));
+ thread.Start();
+ thread.Join();
+
+ foreach(var ptr in pointers)
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+
+ public void RunLoop()
+ {
+ del = Marshal.GetFunctionPointerForDelegate(SwapBuffers);
+ LibRyujinxInterop.SetSwapBuffersCallback(del);
+
+ if (!_isVulkan)
+ {
+ MakeCurrent();
+
+ Context.SwapInterval = 0;
+ }
+
+ Task.Run(async () =>
+ {
+ await Task.Delay(1000);
+
+ LibRyujinxInterop.SetVsyncState(false);
+ });
+
+ LibRyujinxInterop.RunLoop();
+
+ if (!_isVulkan)
+ {
+ Context.MakeNoneCurrent();
+ }
+ }
+
+ private void SwapBuffers()
+ {
+ if (!_isVulkan)
+ {
+ this.Context.SwapBuffers();
+ }
+ }
+ }
+}
diff --git a/src/LibRyujinx.NativeSample/Program.cs b/src/LibRyujinx.NativeSample/Program.cs
new file mode 100644
index 000000000..c27d77883
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/Program.cs
@@ -0,0 +1,33 @@
+using LibRyujinx.Sample;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
+
+namespace LibRyujinx.NativeSample
+{
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length > 0)
+ {
+ var success = LibRyujinxInterop.Initialize(IntPtr.Zero);
+ success = LibRyujinxInterop.InitializeGraphics(new GraphicsConfiguration());
+ var nativeWindowSettings = new NativeWindowSettings()
+ {
+ Size = new Vector2i(800, 600),
+ Title = "Ryujinx",
+ API = ContextAPI.NoAPI,
+ IsEventDriven = true,
+ // This is needed to run on macos
+ Flags = ContextFlags.ForwardCompatible,
+ };
+
+ using var window = new NativeWindow(nativeWindowSettings);
+
+ window.IsVisible = true;
+ window.Start(args[0]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LibRyujinx/LibRyujinx.Device.cs b/src/LibRyujinx/LibRyujinx.Device.cs
new file mode 100644
index 000000000..6ddf9381e
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Device.cs
@@ -0,0 +1,151 @@
+using ARMeilleure.Translation;
+using Ryujinx.Common.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ [UnmanagedCallersOnly(EntryPoint = "device_initialize")]
+ public static bool InitializeDeviceNative()
+ {
+ return InitializeDevice();
+ }
+
+ public static bool InitializeDevice()
+ {
+ if (SwitchDevice == null)
+ {
+ return false;
+ }
+
+ return SwitchDevice.InitializeContext();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_load")]
+ public static bool LoadApplicationNative(IntPtr pathPtr)
+ {
+ if(SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ var path = Marshal.PtrToStringAnsi(pathPtr);
+
+ return LoadApplication(path);
+ }
+
+ public static bool LoadApplication(string path)
+ {
+ var emulationContext = SwitchDevice.EmulationContext;
+
+ if (Directory.Exists(path))
+ {
+ string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
+
+ if (romFsFiles.Length == 0)
+ {
+ romFsFiles = Directory.GetFiles(path, "*.romfs");
+ }
+
+ if (romFsFiles.Length > 0)
+ {
+ Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
+
+ if (!emulationContext.LoadCart(path, romFsFiles[0]))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ }
+ else
+ {
+ Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
+
+ if (!emulationContext.LoadCart(path))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ }
+ }
+ else if (File.Exists(path))
+ {
+ switch (Path.GetExtension(path).ToLowerInvariant())
+ {
+ case ".xci":
+ Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
+
+ if (!emulationContext.LoadXci(path))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ break;
+ case ".nca":
+ Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
+
+ if (!emulationContext.LoadNca(path))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ break;
+ case ".nsp":
+ case ".pfs0":
+ Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
+
+ if (!emulationContext.LoadNsp(path))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ break;
+ default:
+ Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
+ try
+ {
+ if (!emulationContext.LoadProgram(path))
+ {
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
+
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+ break;
+ }
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{path}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
+
+ SwitchDevice.DisposeContext();
+
+ return false;
+ }
+
+ Translator.IsReadyForTranslation.Reset();
+
+ return true;
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs
new file mode 100644
index 000000000..ad16bca69
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Graphics.cs
@@ -0,0 +1,224 @@
+using ARMeilleure.Translation;
+using LibRyujinx.Shared;
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.Graphics.OpenGL;
+using Ryujinx.Graphics.Vulkan;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ private static bool _isActive;
+ private static bool _isStopped;
+ private static CancellationTokenSource _gpuCancellationTokenSource;
+ private static SwapBuffersCallback _swapBuffersCallback;
+ private static NativeGraphicsInterop _nativeGraphicsInterop;
+
+ public delegate void SwapBuffersCallback();
+ public delegate IntPtr GetProcAddress(string name);
+ public delegate IntPtr CreateSurface(IntPtr instance);
+
+
+ public static IRenderer? Renderer { get; set; }
+ public static GraphicsConfiguration GraphicsConfiguration { get; private set; }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_initialize")]
+ public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration)
+ {
+ if(OperatingSystem.IsAndroid())
+ {
+ Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.Android;
+ }
+ return InitializeGraphics(graphicsConfiguration);
+ }
+ public static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration)
+ {
+ GraphicsConfig.ResScale = graphicsConfiguration.ResScale;
+ GraphicsConfig.MaxAnisotropy = graphicsConfiguration.MaxAnisotropy;
+ GraphicsConfig.FastGpuTime = graphicsConfiguration.FastGpuTime;
+ GraphicsConfig.Fast2DCopy = graphicsConfiguration.Fast2DCopy;
+ GraphicsConfig.EnableMacroJit = graphicsConfiguration.EnableMacroJit;
+ GraphicsConfig.EnableMacroHLE = graphicsConfiguration.EnableMacroHLE;
+ GraphicsConfig.EnableShaderCache = graphicsConfiguration.EnableShaderCache;
+ GraphicsConfig.EnableSpirvCompilationOnVulkan = graphicsConfiguration.EnableSpirvCompilationOnVulkan;
+ GraphicsConfig.EnableTextureRecompression = graphicsConfiguration.EnableTextureRecompression;
+
+ GraphicsConfiguration = graphicsConfiguration;
+
+ return true;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_initialize_renderer")]
+ public unsafe static bool InitializeGraphicsRendererNative(GraphicsBackend graphicsBackend, NativeGraphicsInterop nativeGraphicsInterop)
+ {
+ _nativeGraphicsInterop = nativeGraphicsInterop;
+ if (Renderer != null)
+ {
+ return false;
+ }
+
+ List extensions = new List();
+ var size = Marshal.SizeOf();
+ var extPtr = (IntPtr*)nativeGraphicsInterop.VkRequiredExtensions;
+ for (int i = 0; i < nativeGraphicsInterop.VkRequiredExtensionsCount; i++)
+ {
+ var ptr = extPtr[i];
+ extensions.Add(Marshal.PtrToStringAnsi(ptr) ?? string.Empty);
+ }
+
+ CreateSurface createSurfaceFunc = nativeGraphicsInterop.VkCreateSurface == IntPtr.Zero ? default : Marshal.GetDelegateForFunctionPointer(nativeGraphicsInterop.VkCreateSurface);
+
+ return InitializeGraphicsRenderer(graphicsBackend, createSurfaceFunc, extensions.ToArray());
+ }
+
+ public static bool InitializeGraphicsRenderer(GraphicsBackend graphicsBackend, CreateSurface createSurfaceFunc, string[] requiredExtensions)
+ {
+ if (Renderer != null)
+ {
+ return false;
+ }
+
+ if (graphicsBackend == GraphicsBackend.OpenGl)
+ {
+ Renderer = new OpenGLRenderer();
+ }
+ else if (graphicsBackend == GraphicsBackend.Vulkan)
+ {
+ Renderer = new VulkanRenderer(Vk.GetApi(), (instance, vk) => new SurfaceKHR((ulong?)createSurfaceFunc(instance.Handle)),
+ () => requiredExtensions,
+ null);
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")]
+ public static void SetRendererSizeNative(int width, int height)
+ {
+ Renderer?.Window?.SetSize(width, height);
+ }
+
+ public static void SetRendererSize(int width, int height)
+ {
+ Renderer?.Window?.SetSize(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_run_loop")]
+ public static void RunLoopNative()
+ {
+ if (Renderer is OpenGLRenderer)
+ {
+ var proc = Marshal.GetDelegateForFunctionPointer(_nativeGraphicsInterop.GlGetProcAddress);
+ GL.LoadBindings(new OpenTKBindingsContext(x => proc!.Invoke(x)));
+ }
+ RunLoop();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_vsync")]
+ public static void SetVsyncStateNative(bool enabled)
+ {
+ SetVsyncState(enabled);
+ }
+
+ public static void SetVsyncState(bool enabled)
+ {
+ var device = SwitchDevice!.EmulationContext!;
+ device.EnableDeviceVsync = enabled;
+ device.Gpu.Renderer.Window.ChangeVSyncMode(enabled);
+ }
+
+ public static void RunLoop()
+ {
+ if (Renderer == null)
+ {
+ return;
+ }
+ var device = SwitchDevice!.EmulationContext!;
+
+ device.Gpu.Renderer.Initialize(GraphicsDebugLevel.None);
+ _gpuCancellationTokenSource = new CancellationTokenSource();
+
+ device.Gpu.Renderer.RunLoop(() =>
+ {
+ device.Gpu.SetGpuThread();
+ device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
+ Translator.IsReadyForTranslation.Set();
+
+ _isActive = true;
+
+ while (_isActive)
+ {
+ if (_isStopped)
+ {
+ return;
+ }
+
+ if (device.WaitFifo())
+ {
+ device.Statistics.RecordFifoStart();
+ device.ProcessFrame();
+ device.Statistics.RecordFifoEnd();
+ }
+
+ while (device.ConsumeFrameAvailable())
+ {
+ device.PresentFrame(() => _swapBuffersCallback?.Invoke());
+ }
+ }
+ });
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
+ public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback)
+ {
+ _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer(swapBuffersCallback);
+ }
+
+ public static void SetSwapBuffersCallback(SwapBuffersCallback swapBuffersCallback)
+ {
+ _swapBuffersCallback = swapBuffersCallback;
+ }
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct GraphicsConfiguration
+ {
+ public float ResScale = 1f;
+ public float MaxAnisotropy = -1;
+ public bool FastGpuTime = true;
+ public bool Fast2DCopy = true;
+ public bool EnableMacroJit = false;
+ public bool EnableMacroHLE = true;
+ public bool EnableShaderCache;
+ public bool EnableSpirvCompilationOnVulkan = true;
+ public bool EnableTextureRecompression = false;
+ public BackendThreading BackendThreading = BackendThreading.Auto;
+
+ public GraphicsConfiguration()
+ {
+ }
+ }
+
+ public struct NativeGraphicsInterop
+ {
+ public IntPtr GlGetProcAddress;
+ public IntPtr VkNativeContextLoader;
+ public IntPtr VkCreateSurface;
+ public IntPtr VkRequiredExtensions;
+ public int VkRequiredExtensionsCount;
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Input.cs b/src/LibRyujinx/LibRyujinx.Input.cs
new file mode 100644
index 000000000..c301f3502
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Input.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Shared.csproj b/src/LibRyujinx/LibRyujinx.Shared.csproj
new file mode 100644
index 000000000..8bcb26228
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Shared.csproj
@@ -0,0 +1,41 @@
+
+
+
+ net7.0
+ enable
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs
new file mode 100644
index 000000000..6d9a14c05
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.cs
@@ -0,0 +1,151 @@
+// State class for the library
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.HOS;
+using Ryujinx.Input.HLE;
+using Ryujinx.HLE;
+using System;
+using System.Runtime.InteropServices;
+using Ryujinx.Common.Configuration;
+using LibHac.Tools.FsSystem;
+using Ryujinx.Graphics.GAL.Multithreading;
+using Ryujinx.Audio.Backends.Dummy;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.Ui.Common.Configuration;
+using Ryujinx.Common.Logging;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ public static SwitchDevice? SwitchDevice { get; set; }
+
+ [UnmanagedCallersOnly(EntryPoint = "initialize")]
+ public static bool Initialize(IntPtr basePathPtr)
+ {
+ var path = Marshal.PtrToStringAnsi(basePathPtr);
+
+ return Initialize(path);
+ }
+
+ public static bool Initialize(string? basePath)
+ {
+ if (SwitchDevice != null)
+ {
+ return false;
+ }
+
+ try
+ {
+ AppDataManager.Initialize(basePath);
+
+ ConfigurationState.Initialize();
+ LoggerModule.Initialize();
+
+ SwitchDevice = new SwitchDevice();
+
+ Logger.SetEnable(LogLevel.Debug, false);
+ Logger.SetEnable(LogLevel.Stub, true);
+ Logger.SetEnable(LogLevel.Info, true);
+ Logger.SetEnable(LogLevel.Warning, true);
+ Logger.SetEnable(LogLevel.Error, true);
+ Logger.SetEnable(LogLevel.Trace, false);
+ Logger.SetEnable(LogLevel.Guest, true);
+ Logger.SetEnable(LogLevel.AccessLog, false);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ public class SwitchDevice : IDisposable
+ {
+ public VirtualFileSystem VirtualFileSystem { get; set; }
+ public ContentManager ContentManager { get; set; }
+ public AccountManager AccountManager { get; set; }
+ public LibHacHorizonManager LibHacHorizonManager { get; set; }
+ public UserChannelPersistence UserChannelPersistence { get; set; }
+ public InputManager? InputManager { get; set; }
+ public Switch? EmulationContext { get; set; }
+
+ public void Dispose()
+ {
+ VirtualFileSystem?.Dispose();
+ InputManager?.Dispose();
+ EmulationContext?.Dispose();
+ }
+
+ public SwitchDevice()
+ {
+ VirtualFileSystem = VirtualFileSystem.CreateInstance();
+ LibHacHorizonManager = new LibHacHorizonManager();
+
+ LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
+ LibHacHorizonManager.InitializeArpServer();
+ LibHacHorizonManager.InitializeBcatServer();
+ LibHacHorizonManager.InitializeSystemClients();
+
+ ContentManager = new ContentManager(VirtualFileSystem);
+ AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
+ UserChannelPersistence = new UserChannelPersistence();
+ }
+
+ public bool InitializeContext()
+ {
+ if(LibRyujinx.Renderer == null)
+ {
+ return false;
+ }
+
+ var renderer = LibRyujinx.Renderer;
+ BackendThreading threadingMode = LibRyujinx.GraphicsConfiguration.BackendThreading;
+
+ bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
+
+ if (threadedGAL)
+ {
+ renderer = new ThreadedRenderer(renderer);
+ }
+
+ HLEConfiguration configuration = new HLEConfiguration(VirtualFileSystem,
+ LibHacHorizonManager,
+ ContentManager,
+ AccountManager,
+ UserChannelPersistence,
+ renderer,
+ new DummyHardwareDeviceDriver(), //Audio
+ MemoryConfiguration.MemoryConfiguration4GiB,
+ null,
+ SystemLanguage.AmericanEnglish,
+ RegionCode.USA,
+ true,
+ true,
+ true,
+ false,
+ IntegrityCheckLevel.None,
+ 0,
+ 0,
+ "UTC",
+ MemoryManagerMode.HostMappedUnsafe,
+ false,
+ AspectRatio.Fixed16x9,
+ 0,
+ true,
+ "");
+
+ EmulationContext = new Switch(configuration);
+
+ return true;
+ }
+
+ internal void DisposeContext()
+ {
+ EmulationContext?.Dispose();
+ EmulationContext = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LibRyujinx/OpenTKBindingsContext.cs b/src/LibRyujinx/OpenTKBindingsContext.cs
new file mode 100644
index 000000000..203caee8b
--- /dev/null
+++ b/src/LibRyujinx/OpenTKBindingsContext.cs
@@ -0,0 +1,20 @@
+using OpenTK;
+using System;
+
+namespace LibRyujinx.Shared
+{
+ public class OpenTKBindingsContext : IBindingsContext
+ {
+ private readonly Func _getProcAddress;
+
+ public OpenTKBindingsContext(Func getProcAddress)
+ {
+ _getProcAddress = getProcAddress;
+ }
+
+ public IntPtr GetProcAddress(string procName)
+ {
+ return _getProcAddress(procName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/LibRyujinx/rd.xml b/src/LibRyujinx/rd.xml
new file mode 100644
index 000000000..c533ac82a
--- /dev/null
+++ b/src/LibRyujinx/rd.xml
@@ -0,0 +1,661 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file