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