diff --git a/Ryujinx.sln b/Ryujinx.sln
index 76ebd573..9235b91c 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -89,6 +89,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx", "src\LibRyujinx\LibRyujinx.csproj", "{D4E40005-CC75-41B2-878E-5A4300197A2C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.NativeSample", "src\LibRyujinx.NativeSample\LibRyujinx.NativeSample.csproj", "{DCB49372-5B6F-4AF4-9A62-D4C2CBB9217B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -255,6 +259,14 @@ Global
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4E40005-CC75-41B2-878E-5A4300197A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4E40005-CC75-41B2-878E-5A4300197A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4E40005-CC75-41B2-878E-5A4300197A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4E40005-CC75-41B2-878E-5A4300197A2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DCB49372-5B6F-4AF4-9A62-D4C2CBB9217B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DCB49372-5B6F-4AF4-9A62-D4C2CBB9217B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DCB49372-5B6F-4AF4-9A62-D4C2CBB9217B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DCB49372-5B6F-4AF4-9A62-D4C2CBB9217B}.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 00000000..ac6f5f17
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ AnyCPU
+
+
+
+
+
+
+
diff --git a/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
new file mode 100644
index 00000000..a5a03dc6
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibRyujinx.Sample
+{
+ internal static class LibRyujinxInterop
+ {
+ private const string dll = "LibRyujinx.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(bool isHostMapped,
+ bool useHypervisor,
+ SystemLanguage systemLanguage,
+ RegionCode regionCode,
+ bool enableVsync,
+ bool enableDockedMode,
+ bool enablePtc,
+ bool enableInternetAccess,
+ IntPtr timeZone,
+ bool ignoreMissingServices);
+
+ [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);
+
+ [DllImport(dll, EntryPoint = "input_initialize")]
+ internal extern static void InitializeInput(int width, int height);
+
+ [DllImport(dll, EntryPoint = "input_set_client_size")]
+ internal extern static void SetClientSize(int width, int height);
+
+ [DllImport(dll, EntryPoint = "input_set_touch_point")]
+ internal extern static void SetTouchPoint(int x, int y);
+
+ [DllImport(dll, EntryPoint = "input_release_touch_point")]
+ internal extern static void ReleaseTouchPoint();
+
+ [DllImport(dll, EntryPoint = "input_update")]
+ internal extern static void UpdateInput();
+
+ [DllImport(dll, EntryPoint = "input_set_button_pressed")]
+ public extern static void SetButtonPressed(GamepadButtonInputId button, IntPtr idPtr);
+
+ [DllImport(dll, EntryPoint = "input_set_button_released")]
+ public extern static void SetButtonReleased(GamepadButtonInputId button, IntPtr idPtr);
+
+ [DllImport(dll, EntryPoint = "input_set_stick_axis")]
+ public extern static void SetStickAxis(StickInputId stick, Vector2 axes, IntPtr idPtr);
+
+ [DllImport(dll, EntryPoint = "input_connect_gamepad")]
+ public extern static IntPtr ConnectGamepad(int index);
+ }
+
+ public enum GraphicsBackend
+ {
+ Vulkan,
+ OpenGl
+ }
+
+ public enum BackendThreading
+ {
+ Auto,
+ Off,
+ On
+ }
+
+ [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 EnableTextureRecompression = false;
+ public BackendThreading BackendThreading = BackendThreading.Auto;
+ public AspectRatio AspectRatio = AspectRatio.Fixed16x9;
+
+ public GraphicsConfiguration()
+ {
+ }
+ }
+ public enum SystemLanguage
+ {
+ Japanese,
+ AmericanEnglish,
+ French,
+ German,
+ Italian,
+ Spanish,
+ Chinese,
+ Korean,
+ Dutch,
+ Portuguese,
+ Russian,
+ Taiwanese,
+ BritishEnglish,
+ CanadianFrench,
+ LatinAmericanSpanish,
+ SimplifiedChinese,
+ TraditionalChinese,
+ BrazilianPortuguese,
+ }
+ public enum RegionCode
+ {
+ Japan,
+ USA,
+ Europe,
+ Australia,
+ China,
+ Korea,
+ Taiwan,
+
+ Min = Japan,
+ Max = Taiwan,
+ }
+
+ public struct NativeGraphicsInterop
+ {
+ public IntPtr GlGetProcAddress;
+ public IntPtr VkNativeContextLoader;
+ public IntPtr VkCreateSurface;
+ public IntPtr VkRequiredExtensions;
+ public int VkRequiredExtensionsCount;
+ }
+
+ public enum AspectRatio
+ {
+ Fixed4x3,
+ Fixed16x9,
+ Fixed16x10,
+ Fixed21x9,
+ Fixed32x9,
+ Stretched
+ }
+
+ ///
+ /// Represent a button from a gamepad.
+ ///
+ public enum GamepadButtonInputId : byte
+ {
+ Unbound,
+ A,
+ B,
+ X,
+ Y,
+ LeftStick,
+ RightStick,
+ LeftShoulder,
+ RightShoulder,
+
+ // Likely axis
+ LeftTrigger,
+ // Likely axis
+ RightTrigger,
+
+ DpadUp,
+ DpadDown,
+ DpadLeft,
+ DpadRight,
+
+ // Special buttons
+
+ Minus,
+ Plus,
+
+ Back = Minus,
+ Start = Plus,
+
+ Guide,
+ Misc1,
+
+ // Xbox Elite paddle
+ Paddle1,
+ Paddle2,
+ Paddle3,
+ Paddle4,
+
+ // PS5 touchpad button
+ Touchpad,
+
+ // Virtual buttons for single joycon
+ SingleLeftTrigger0,
+ SingleRightTrigger0,
+
+ SingleLeftTrigger1,
+ SingleRightTrigger1,
+
+ Count
+ }
+
+ public enum StickInputId : byte
+ {
+ Unbound,
+ Left,
+ Right,
+
+ Count
+ }
+}
diff --git a/src/LibRyujinx.NativeSample/NativeWindow.cs b/src/LibRyujinx.NativeSample/NativeWindow.cs
new file mode 100644
index 00000000..0dd160ea
--- /dev/null
+++ b/src/LibRyujinx.NativeSample/NativeWindow.cs
@@ -0,0 +1,260 @@
+using LibRyujinx.Sample;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+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 _run;
+ private bool _isVulkan;
+ private Vector2 _lastPosition;
+ private bool _mousePressed;
+ private nint _gamepadIdPtr;
+ private string? _gamepadId;
+
+ 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);
+ var timeZone = Marshal.StringToHGlobalAnsi("UTC");
+ success = LibRyujinxInterop.InitializeDevice(true,
+ false,
+ SystemLanguage.AmericanEnglish,
+ RegionCode.USA,
+ true,
+ true,
+ true,
+ false,
+ timeZone,
+ false);
+ LibRyujinxInterop.InitializeInput(ClientSize.X, ClientSize.Y);
+ Marshal.FreeHGlobal(timeZone);
+
+ var path = Marshal.StringToHGlobalAnsi(gamePath);
+ var loaded = LibRyujinxInterop.LoadApplication(path);
+ LibRyujinxInterop.SetRendererSize(Size.X, Size.Y);
+ Marshal.FreeHGlobal(path);
+ }
+
+ _gamepadIdPtr = LibRyujinxInterop.ConnectGamepad(0);
+ _gamepadId = Marshal.PtrToStringAnsi(_gamepadIdPtr);
+
+ if (!_isVulkan)
+ {
+ Context.MakeNoneCurrent();
+ }
+
+ _run = true;
+ var thread = new Thread(new ThreadStart(RunLoop));
+ thread.Start();
+
+ UpdateLoop();
+
+ thread.Join();
+
+ foreach(var ptr in pointers)
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ Marshal.FreeHGlobal(_gamepadIdPtr);
+ }
+
+ 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(true);
+ });*/
+
+ LibRyujinxInterop.RunLoop();
+
+ _run = false;
+
+ if (!_isVulkan)
+ {
+ Context.MakeNoneCurrent();
+ }
+ }
+
+ private void SwapBuffers()
+ {
+ if (!_isVulkan)
+ {
+ this.Context.SwapBuffers();
+ }
+ }
+
+ protected override void OnMouseMove(MouseMoveEventArgs e)
+ {
+ base.OnMouseMove(e);
+ _lastPosition = e.Position;
+ }
+
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseDown(e);
+ if(e.Button == MouseButton.Left)
+ {
+ _mousePressed = true;
+ }
+ }
+
+ protected override void OnResize(ResizeEventArgs e)
+ {
+ base.OnResize(e);
+
+ if (_run)
+ {
+ LibRyujinxInterop.SetRendererSize(e.Width, e.Height);
+ LibRyujinxInterop.SetClientSize(e.Width, e.Height);
+ }
+ }
+
+ protected override void OnMouseUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseUp(e);
+ if (e.Button == MouseButton.Left)
+ {
+ _mousePressed = false;
+ }
+ }
+
+ protected override void OnKeyUp(KeyboardKeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+
+ if (_gamepadIdPtr != IntPtr.Zero)
+ {
+ var key = GetKeyMapping(e.Key);
+
+ LibRyujinxInterop.SetButtonReleased(key, _gamepadIdPtr);
+ }
+ }
+
+ protected override void OnKeyDown(KeyboardKeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+
+ if (_gamepadIdPtr != IntPtr.Zero)
+ {
+ var key = GetKeyMapping(e.Key);
+
+ LibRyujinxInterop.SetButtonPressed(key, _gamepadIdPtr);
+ }
+ }
+
+ public void UpdateLoop()
+ {
+ while(_run)
+ {
+ ProcessWindowEvents(true);
+ NewInputFrame();
+ ProcessWindowEvents(IsEventDriven);
+ if (_mousePressed)
+ {
+ LibRyujinxInterop.SetTouchPoint((int)_lastPosition.X, (int)_lastPosition.Y);
+ }
+ else
+ {
+ LibRyujinxInterop.ReleaseTouchPoint();
+ }
+
+ LibRyujinxInterop.UpdateInput();
+
+ Thread.Sleep(1);
+ }
+ }
+
+ public GamepadButtonInputId GetKeyMapping(Keys key)
+ {
+ if(_keyMapping.TryGetValue(key, out var mapping))
+ {
+ return mapping;
+ }
+
+ return GamepadButtonInputId.Unbound;
+ }
+
+ private Dictionary _keyMapping = new Dictionary()
+ {
+ {Keys.A, GamepadButtonInputId.A },
+ {Keys.S, GamepadButtonInputId.B },
+ {Keys.Z, GamepadButtonInputId.X },
+ {Keys.X, GamepadButtonInputId.Y },
+ {Keys.Equal, GamepadButtonInputId.Plus },
+ {Keys.Minus, GamepadButtonInputId.Minus },
+ {Keys.Q, GamepadButtonInputId.LeftShoulder },
+ {Keys.D1, GamepadButtonInputId.LeftTrigger },
+ {Keys.W, GamepadButtonInputId.RightShoulder },
+ {Keys.D2, GamepadButtonInputId.RightTrigger },
+ {Keys.E, GamepadButtonInputId.LeftStick },
+ {Keys.R, GamepadButtonInputId.RightStick },
+ {Keys.Up, GamepadButtonInputId.DpadUp },
+ {Keys.Down, GamepadButtonInputId.DpadDown },
+ {Keys.Left, GamepadButtonInputId.DpadLeft },
+ {Keys.Right, GamepadButtonInputId.DpadRight },
+ {Keys.U, GamepadButtonInputId.SingleLeftTrigger0 },
+ {Keys.D7, GamepadButtonInputId.SingleLeftTrigger1 },
+ {Keys.O, GamepadButtonInputId.SingleRightTrigger0 },
+ {Keys.D9, GamepadButtonInputId.SingleRightTrigger1 }
+ };
+ }
+}
diff --git a/src/LibRyujinx.NativeSample/Program.cs b/src/LibRyujinx.NativeSample/Program.cs
new file mode 100644
index 00000000..12a7c064
--- /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()
+ {
+ ClientSize = new Vector2i(800, 600),
+ Title = "Ryujinx Native",
+ API = ContextAPI.NoAPI,
+ IsEventDriven = false,
+ // 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/Android/AndroidLogTarget.cs b/src/LibRyujinx/Android/AndroidLogTarget.cs
new file mode 100644
index 00000000..c32cc261
--- /dev/null
+++ b/src/LibRyujinx/Android/AndroidLogTarget.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Logging.Formatters;
+using Ryujinx.Common.Logging.Targets;
+using System;
+
+namespace LibRyujinx
+{
+ public class AndroidLogTarget : ILogTarget
+ {
+ private readonly string _name;
+ private readonly DefaultLogFormatter _formatter;
+
+ string ILogTarget.Name { get => _name; }
+
+ public AndroidLogTarget(string name)
+ {
+ _name = name;
+ _formatter = new DefaultLogFormatter();
+ }
+
+ public void Log(object sender, LogEventArgs args)
+ {
+ Logcat.AndroidLogPrint(GetLogLevel(args.Level), _name, _formatter.Format(args));
+ }
+
+ private static Logcat.LogLevel GetLogLevel(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Debug => Logcat.LogLevel.Debug,
+ LogLevel.Stub => Logcat.LogLevel.Info,
+ LogLevel.Info => Logcat.LogLevel.Info,
+ LogLevel.Warning => Logcat.LogLevel.Warn,
+ LogLevel.Error => Logcat.LogLevel.Error,
+ LogLevel.Guest => Logcat.LogLevel.Info,
+ LogLevel.AccessLog => Logcat.LogLevel.Info,
+ LogLevel.Notice => Logcat.LogLevel.Info,
+ LogLevel.Trace => Logcat.LogLevel.Verbose,
+ _ => throw new NotImplementedException(),
+
+ };
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/LibRyujinx/Android/AndroidUiHandler.cs b/src/LibRyujinx/Android/AndroidUiHandler.cs
new file mode 100644
index 00000000..6d1a6d8b
--- /dev/null
+++ b/src/LibRyujinx/Android/AndroidUiHandler.cs
@@ -0,0 +1,114 @@
+using LibHac.Tools.Fs;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+using Ryujinx.HLE.UI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LibRyujinx.Android
+{
+ internal class AndroidUIHandler : IHostUIHandler, IDisposable
+ {
+ private bool _isDisposed;
+ private bool _isOkPressed;
+ private string? _input;
+ private ManualResetEvent _resetEvent = new ManualResetEvent(false);
+
+ public IHostUITheme HostUITheme => throw new NotImplementedException();
+
+ public IDynamicTextInputHandler CreateDynamicTextInputHandler()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
+ {
+ Interop.UpdateUiHandler(title ?? "",
+ message ?? "",
+ "",
+ 1,
+ 0,
+ 0,
+ KeyboardMode.Default,
+ "",
+ "");
+
+ return _isOkPressed;
+ }
+
+ public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
+ {
+ _input = null;
+ _resetEvent.Reset();
+ Interop.UpdateUiHandler("Software Keyboard",
+ args.HeaderText ?? "",
+ args.GuideText ?? "",
+ 2,
+ args.StringLengthMin,
+ args.StringLengthMax,
+ args.KeyboardMode,
+ args.SubtitleText ?? "",
+ args.InitialText ?? "");
+
+ _resetEvent.WaitOne();
+
+ userText = _input ?? "";
+
+ return _isOkPressed;
+ }
+
+ public bool DisplayMessageDialog(string title, string message)
+ {
+ Interop.UpdateUiHandler(title ?? "",
+ message ?? "",
+ "",
+ 1,
+ 0,
+ 0,
+ KeyboardMode.Default,
+ "",
+ "");
+
+ return _isOkPressed;
+ }
+
+ public bool DisplayMessageDialog(ControllerAppletUIArgs args)
+ {
+ string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
+
+ string message = $"Application requests **{playerCount}** player(s) with:\n\n"
+ + $"**TYPES:** {args.SupportedStyles}\n\n"
+ + $"**PLAYERS:** {string.Join(", ", args.SupportedPlayers)}\n\n"
+ + (args.IsDocked ? "Docked mode set. `Handheld` is also invalid.\n\n" : "")
+ + "_Please reconfigure Input now and then press OK._";
+
+ return DisplayMessageDialog("Controller Applet", message);
+ }
+
+ public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
+ {
+ // throw new NotImplementedException();
+ }
+
+ internal void SetResponse(bool isOkPressed, string input)
+ {
+ if (_isDisposed)
+ return;
+ _isOkPressed = isOkPressed;
+ _input = input;
+ _resetEvent.Set();
+ }
+
+ public void Dispose()
+ {
+ _isDisposed = true;
+ }
+ }
+}
diff --git a/src/LibRyujinx/Android/Interop.cs b/src/LibRyujinx/Android/Interop.cs
new file mode 100644
index 00000000..590ad88e
--- /dev/null
+++ b/src/LibRyujinx/Android/Interop.cs
@@ -0,0 +1,232 @@
+using LibRyujinx.Jni;
+using LibRyujinx.Jni.Identifiers;
+using LibRyujinx.Jni.Pointers;
+using LibRyujinx.Jni.Primitives;
+using LibRyujinx.Jni.References;
+using LibRyujinx.Jni.Values;
+using Rxmxnx.PInvoke;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace LibRyujinx.Android
+{
+ internal unsafe static class Interop
+ {
+ internal const string BaseClassName = "org/ryujinx/android/RyujinxNative";
+
+ private static JGlobalRef? _classId;
+ private static ConcurrentDictionary<(string method, string descriptor), JMethodId> _methodCache = new ConcurrentDictionary<(string method, string descriptor), JMethodId>();
+ private static (string name, string descriptor)[] _methods = new[]
+ {
+ ("test", "()V"),
+ ("updateUiHandler", "(JJJIIIIJJ)V"),
+ ("frameEnded", "()V"),
+ ("updateProgress", "(JF)V"),
+ ("getSurfacePtr", "()J"),
+ ("getWindowHandle", "()J")
+ };
+
+ internal static void Initialize(JEnvRef jniEnv)
+ {
+ var vm = JniHelper.GetVirtualMachine(jniEnv);
+ if (_classId == null)
+ {
+ var className = new ReadOnlySpan(Encoding.UTF8.GetBytes(BaseClassName));
+ using (IReadOnlyFixedMemory.IDisposable cName = className.GetUnsafeValPtr()
+ .GetUnsafeFixedContext(className.Length))
+ {
+ _classId = JniHelper.GetGlobalClass(jniEnv, cName);
+ if (_classId == null)
+ {
+ Logger.Info?.Print(LogClass.Application, $"Class Id {BaseClassName} not found");
+ return;
+ }
+ }
+ }
+
+ foreach (var x in _methods)
+ {
+ CacheMethod(jniEnv, x.name, x.descriptor);
+ }
+
+ JniEnv._jvm = vm;
+ }
+
+ private static void CacheMethod(JEnvRef jEnv, string name, string descriptor)
+ {
+ if (!_methodCache.TryGetValue((name, descriptor), out var method))
+ {
+ var methodName = new ReadOnlySpan(Encoding.UTF8.GetBytes(name));
+ var descriptorId = new ReadOnlySpan(Encoding.UTF8.GetBytes(descriptor));
+ using (IReadOnlyFixedMemory.IDisposable mName = methodName.GetUnsafeValPtr()
+ .GetUnsafeFixedContext(methodName.Length))
+ using (IReadOnlyFixedMemory.IDisposable dName = descriptorId.GetUnsafeValPtr()
+ .GetUnsafeFixedContext(descriptorId.Length))
+ {
+ var methodId = JniHelper.GetStaticMethodId(jEnv, (JClassLocalRef)(_classId.Value.Value), mName, dName);
+ if (methodId == null)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Java Method Id {name} not found");
+ return;
+ }
+
+ method = methodId.Value;
+ _methodCache[(name, descriptor)] = method;
+ }
+ }
+ }
+
+ private static void CallVoidMethod(string name, string descriptor, params JValue[] values)
+ {
+ using var env = JniEnv.Create();
+ if (_methodCache.TryGetValue((name, descriptor), out var method))
+ {
+ if (descriptor.EndsWith("V"))
+ {
+ JniHelper.CallStaticVoidMethod(env.Env, (JClassLocalRef)(_classId.Value.Value), method, values);
+ }
+ }
+ }
+
+ private static JLong CallLongMethod(string name, string descriptor, params JValue[] values)
+ {
+ using var env = JniEnv.Create();
+ if (_methodCache.TryGetValue((name, descriptor), out var method))
+ {
+ if (descriptor.EndsWith("J"))
+ return JniHelper.CallStaticLongMethod(env.Env, (JClassLocalRef)(_classId.Value.Value), method, values) ?? (JLong)(-1);
+ }
+
+ return (JLong)(-1);
+ }
+
+ public static void Test()
+ {
+ CallVoidMethod("test", "()V");
+ }
+
+ public static void FrameEnded(double time)
+ {
+ CallVoidMethod("frameEnded", "()V");
+ }
+
+ public static void UpdateProgress(string info, float progress)
+ {
+ using var infoPtr = new TempNativeString(info);
+ CallVoidMethod("updateProgress", "(JF)V", new JValue[]
+ {
+ JValue.Create(infoPtr.AsBytes()),
+ JValue.Create(progress.AsBytes())
+ });
+ }
+
+ public static JLong GetSurfacePtr()
+ {
+ return CallLongMethod("getSurfacePtr", "()J");
+ }
+
+ public static JLong GetWindowsHandle()
+ {
+ return CallLongMethod("getWindowHandle", "()J");
+ }
+
+ public static void UpdateUiHandler(string newTitle,
+ string newMessage,
+ string newWatermark,
+ int newType,
+ int min,
+ int max,
+ KeyboardMode nMode,
+ string newSubtitle,
+ string newInitialText)
+ {
+ using var titlePointer = new TempNativeString(newTitle);
+ using var messagePointer = new TempNativeString(newMessage);
+ using var watermarkPointer = new TempNativeString(newWatermark);
+ using var subtitlePointer = new TempNativeString(newSubtitle);
+ using var newInitialPointer = new TempNativeString(newInitialText);
+ CallVoidMethod("updateUiHandler", "(JJJIIIIJJ)V", new JValue[]
+ {
+ JValue.Create(titlePointer.AsBytes()),
+ JValue.Create(messagePointer.AsBytes()),
+ JValue.Create(watermarkPointer.AsBytes()),
+ JValue.Create(newType.AsBytes()),
+ JValue.Create(min.AsBytes()),
+ JValue.Create(max.AsBytes()),
+ JValue.Create(nMode.AsBytes()),
+ JValue.Create(subtitlePointer.AsBytes()),
+ JValue.Create(newInitialPointer.AsBytes())
+ });
+ }
+
+ private class TempNativeString : IDisposable
+ {
+ private JLong _jPointer;
+
+ public TempNativeString(string value)
+ {
+ Pointer = Marshal.StringToHGlobalAuto(value);
+ JPointer = (JLong)Pointer;
+ }
+
+ public nint Pointer { get; private set; }
+ public JLong JPointer { get => _jPointer; private set => _jPointer = value; }
+
+ public Span AsBytes()
+ {
+ return _jPointer.AsBytes();
+ }
+
+ public void Dispose()
+ {
+ if (Pointer != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(Pointer);
+ }
+ Pointer = IntPtr.Zero;
+ }
+ }
+
+ private class JniEnv : IDisposable
+ {
+ internal static JavaVMRef? _jvm;
+ private readonly JEnvRef _env;
+ private readonly bool _newAttach;
+
+ public JEnvRef Env => _env;
+
+ private JniEnv(JEnvRef env, bool newAttach)
+ {
+ _env = env;
+ _newAttach = newAttach;
+ }
+
+ public void Dispose()
+ {
+ if(_newAttach)
+ {
+ JniHelper.Detach(_jvm!.Value);
+ }
+ }
+
+ public static JniEnv? Create()
+ {
+ bool newAttach = false;
+ ReadOnlySpan threadName = "JvmCall"u8;
+ var env = _jvm == null ? default : JniHelper.Attach(_jvm.Value, threadName.GetUnsafeValPtr().GetUnsafeFixedContext(threadName.Length),
+ out newAttach);
+
+ return env != null ? new JniEnv(env.Value, newAttach) : null;
+ }
+ }
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Delegates.cs b/src/LibRyujinx/Android/Jni/Delegates.cs
new file mode 100644
index 00000000..a0d86c53
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Delegates.cs
@@ -0,0 +1,357 @@
+using LibRyujinx.Jni.Identifiers;
+using LibRyujinx.Jni.Pointers;
+using LibRyujinx.Jni.Primitives;
+using LibRyujinx.Jni.References;
+using LibRyujinx.Jni.Values;
+using System;
+
+using Rxmxnx.PInvoke;
+
+namespace LibRyujinx.Jni;
+
+internal delegate Int32 GetVersionDelegate(JEnvRef env);
+
+internal delegate JResult RegisterNativesDelegate(JEnvRef env, JClassLocalRef jClass,
+ ReadOnlyValPtr methods0, Int32 nMethods);
+
+internal delegate JResult UnregisterNativesDelegate(JEnvRef env, JClassLocalRef jClass);
+internal delegate JResult MonitorEnterDelegate(JEnvRef env, JObjectLocalRef jClass);
+internal delegate JResult MonitorExitDelegate(JEnvRef env, JObjectLocalRef jClass);
+internal delegate JResult GetVirtualMachineDelegate(JEnvRef env, out JavaVMRef jvm);
+internal delegate JResult DestroyVirtualMachineDelegate(JavaVMRef vm);
+internal delegate JResult AttachCurrentThreadDelegate(JavaVMRef vm, out JEnvRef env, in JavaVMAttachArgs args);
+internal delegate JResult DetachCurrentThreadDelegate(JavaVMRef vm);
+internal delegate JResult GetEnvDelegate(JavaVMRef vm, out JEnvRef env, Int32 version);
+internal delegate JResult AttachCurrentThreadAsDaemonDelegate(JavaVMRef vm, out JEnvRef env, in JavaVMAttachArgs args);
+
+internal delegate JResult GetCreatedVirtualMachinesDelegate(ValPtr buffer0, Int32 bufferLength,
+ out Int32 totalVms);
+
+internal delegate JStringLocalRef NewStringDelegate(JEnvRef env, ReadOnlyValPtr chars0, Int32 length);
+internal delegate Int32 GetStringLengthDelegate(JEnvRef env, JStringLocalRef jString);
+
+internal delegate ReadOnlyValPtr
+ GetStringCharsDelegate(JEnvRef env, JStringLocalRef jString, out JBoolean isCopy);
+
+internal delegate void ReleaseStringCharsDelegate(JEnvRef env, JStringLocalRef jString, ReadOnlyValPtr chars0);
+internal delegate JStringLocalRef NewStringUtfDelegate(JEnvRef env, ReadOnlyValPtr utf8Chars0);
+internal delegate Int32 GetStringUtfLengthDelegate(JEnvRef env, JStringLocalRef jString);
+
+internal delegate ReadOnlyValPtr GetStringUtfCharsDelegate(JEnvRef env, JStringLocalRef jString,
+ out JBoolean isCopy);
+
+internal delegate void ReleaseStringUtfCharsDelegate(JEnvRef env, JStringLocalRef jString,
+ ReadOnlyValPtr utf8Chars0);
+
+internal delegate void GetStringRegionDelegate(JEnvRef env, JStringLocalRef jString, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetStringUtfRegionDelegate(JEnvRef env, JStringLocalRef jString, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate ReadOnlyValPtr GetStringCriticalDelegate(JEnvRef env, JStringLocalRef jString,
+ out JBoolean isCopy);
+
+internal delegate void ReleaseStringCriticalDelegate(JEnvRef env, JStringLocalRef jString, ReadOnlyValPtr chars0);
+
+internal delegate JObjectLocalRef CallStaticObjectMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JBoolean CallStaticBooleanMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JByte CallStaticByteMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JChar CallStaticCharMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JShort CallStaticShortMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JInt CallStaticIntMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JLong CallStaticLongMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JFloat CallStaticFloatMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JDouble CallStaticDoubleMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate void CallStaticVoidMethodADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JObjectLocalRef GetStaticObjectFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JBoolean GetStaticBooleanFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JByte GetStaticByteFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JChar GetStaticCharFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JShort GetStaticShortFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JInt GetStaticIntFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JLong GetStaticLongFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JFloat GetStaticFloatFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+internal delegate JDouble GetStaticDoubleFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField);
+
+internal delegate void SetStaticObjectFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField,
+ JObjectLocalRef value);
+
+internal delegate void SetStaticBooleanFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField,
+ JBoolean value);
+
+internal delegate void SetStaticByteFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JByte value);
+internal delegate void SetStaticCharFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JChar value);
+internal delegate void SetStaticShortFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JShort value);
+internal delegate void SetStaticIntFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JInt value);
+internal delegate void SetStaticLongFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JLong value);
+internal delegate void SetStaticFloatFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JFloat value);
+internal delegate void SetStaticDoubleFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId jField, JDouble value);
+internal delegate JMethodId FromReflectedMethodDelegate(JEnvRef env, JObjectLocalRef method);
+internal delegate JFieldId FromReflectedFieldDelegate(JEnvRef env, JObjectLocalRef field);
+internal delegate JResult PushLocalFrameDelegate(JEnvRef env, Int32 capacity);
+internal delegate JObjectLocalRef PopLocalFrameDelegate(JEnvRef env, JObjectLocalRef result);
+internal delegate JGlobalRef NewGlobalRefDelegate(JEnvRef env, JObjectLocalRef localRef);
+internal delegate void DeleteGlobalRefDelegate(JEnvRef env, JGlobalRef globalRef);
+internal delegate void DeleteLocalRefDelegate(JEnvRef env, JObjectLocalRef localRef);
+internal delegate JBoolean IsSameObjectDelegate(JEnvRef env, JObjectLocalRef obj1, JObjectLocalRef obj2);
+internal delegate JObjectLocalRef NewLocalRefDelegate(JEnvRef env, JObjectLocalRef objRef);
+internal delegate JResult EnsureLocalCapacityDelegate(JEnvRef env, Int32 capacity);
+internal delegate JWeakRef NewWeakGlobalRefDelegate(JEnvRef env, JObjectLocalRef obj);
+internal delegate void DeleteWeakGlobalRefDelegate(JEnvRef env, JWeakRef jWeak);
+internal delegate JReferenceType GetObjectRefTypeDelegate(JEnvRef env, JObjectLocalRef obj);
+
+internal delegate JObjectLocalRef CallNonVirtualObjectMethodADelegate(JEnvRef env, JObjectLocalRef obj,
+ JClassLocalRef jClass, JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JBoolean CallNonVirtualBooleanMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JByte CallNonVirtualByteMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JChar CallNonVirtualCharMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JShort CallNonVirtualShortMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JInt CallNonVirtualIntMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JLong CallNonVirtualLongMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JFloat CallNonVirtualFloatMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JDouble CallNonVirtualDoubleMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate void CallNonVirtualVoidMethodADelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass,
+ JMethodId jMethod, ReadOnlyValPtr args0);
+
+internal delegate JObjectLocalRef CallObjectMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JBoolean CallBooleanMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JByte CallByteMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JChar CallCharMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JShort CallShortMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JInt CallIntMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JLong CallLongMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JFloat CallFloatMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JDouble CallDoubleMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate void CallVoidMethodADelegate(JEnvRef env, JObjectLocalRef obj, JMethodId jMethod,
+ ReadOnlyValPtr args0);
+
+internal delegate JObjectLocalRef GetObjectFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JBoolean GetBooleanFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JByte GetByteFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JChar GetCharFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JShort GetShortFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JInt GetIntFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JLong GetLongFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JFloat GetFloatFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate JDouble GetDoubleFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField);
+internal delegate void SetObjectFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JObjectLocalRef value);
+internal delegate void SetBooleanFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JBoolean value);
+internal delegate void SetByteFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JByte value);
+internal delegate void SetCharFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JChar value);
+internal delegate void SetShortFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JShort value);
+internal delegate void SetIntFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JInt value);
+internal delegate void SetLongFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JLong value);
+internal delegate void SetFloatFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JFloat value);
+internal delegate void SetDoubleFieldDelegate(JEnvRef env, JObjectLocalRef obj, JFieldId jField, JDouble value);
+internal delegate JResult ThrowDelegate(JEnvRef env, JThrowableLocalRef obj);
+internal delegate JResult ThrowNewDelegate(JEnvRef env, JClassLocalRef jClass, ReadOnlyValPtr messageChars0);
+internal delegate JThrowableLocalRef ExceptionOccurredDelegate(JEnvRef env);
+internal delegate void ExceptionDescribeDelegate(JEnvRef env);
+internal delegate void ExceptionClearDelegate(JEnvRef env);
+internal delegate void FatalErrorDelegate(JEnvRef env, ReadOnlyValPtr messageChars0);
+internal delegate JBoolean ExceptionCheckDelegate(JEnvRef env);
+internal delegate JObjectLocalRef NewDirectByteBufferDelegate(JEnvRef env, IntPtr address, Int64 capacity);
+internal delegate IntPtr GetDirectBufferAddressDelegate(JEnvRef env, JObjectLocalRef buffObj);
+internal delegate Int64 GetDirectBufferCapacityDelegate(JEnvRef env, JObjectLocalRef buffObj);
+
+internal delegate JClassLocalRef DefineClassDelegate(JEnvRef env, ReadOnlyValPtr nameChars0,
+ JObjectLocalRef loader, IntPtr binaryData, Int32 len);
+
+internal delegate JClassLocalRef FindClassDelegate(JEnvRef env, ReadOnlyValPtr nameChars0);
+
+internal delegate JObjectLocalRef ToReflectedMethodDelegate(JEnvRef env, JClassLocalRef jClass, JMethodId methodId,
+ JBoolean isStatic);
+
+internal delegate JClassLocalRef GetSuperclassDelegate(JEnvRef env, JClassLocalRef sub);
+internal delegate JBoolean IsAssignableFromDelegate(JEnvRef env, JClassLocalRef sub, JClassLocalRef sup);
+internal delegate JClassLocalRef GetObjectClassDelegate(JEnvRef env, JObjectLocalRef obj);
+internal delegate JBoolean IsInstanceOfDelegate(JEnvRef env, JObjectLocalRef obj, JClassLocalRef jClass);
+
+internal delegate JObjectLocalRef ToReflectedFieldDelegate(JEnvRef env, JClassLocalRef jClass, JFieldId fieldId,
+ JBoolean isStatic);
+
+internal delegate JMethodId GetMethodIdDelegate(JEnvRef env, JClassLocalRef jClass, ReadOnlyValPtr nameChars0,
+ ReadOnlyValPtr signatureChars0);
+
+internal delegate JFieldId GetFieldIdDelegate(JEnvRef env, JClassLocalRef jClass, ReadOnlyValPtr nameChars0,
+ ReadOnlyValPtr signatureChars0);
+
+internal delegate JMethodId GetStaticMethodIdDelegate(JEnvRef env, JClassLocalRef jClass,
+ ReadOnlyValPtr nameChars0, ReadOnlyValPtr signatureChars0);
+
+internal delegate JFieldId GetStaticFieldIdDelegate(JEnvRef env, JClassLocalRef jClass, ReadOnlyValPtr nameChars0,
+ ReadOnlyValPtr signatureChars0);
+
+internal delegate Int32 GetArrayLengthDelegate(JEnvRef env, JArrayLocalRef array);
+
+internal delegate JArrayLocalRef NewObjectArrayDelegate(JEnvRef env, Int32 length, JClassLocalRef jClass,
+ JObjectLocalRef init);
+
+internal delegate JObjectLocalRef GetObjectArrayElementDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 index);
+
+internal delegate void SetObjectArrayElementDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 index,
+ JObjectLocalRef obj);
+
+internal delegate JArrayLocalRef NewBooleanArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewByteArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewCharArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewShortArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewIntArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewLongArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewFloatArrayDelegate(JEnvRef env, Int32 length);
+internal delegate JArrayLocalRef NewDoubleArrayDelegate(JEnvRef env, Int32 length);
+
+internal delegate ValPtr GetBooleanArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ out JBoolean isCopy);
+
+internal delegate ValPtr GetByteArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef, out JBoolean isCopy);
+internal delegate ValPtr GetCharArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef, out JBoolean isCopy);
+
+internal delegate ValPtr GetShortArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ out JBoolean isCopy);
+
+internal delegate ValPtr GetIntArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef, out JBoolean isCopy);
+internal delegate ValPtr GetLongArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef, out JBoolean isCopy);
+
+internal delegate ValPtr GetFloatArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ out JBoolean isCopy);
+
+internal delegate ValPtr GetDoubleArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ out JBoolean isCopy);
+
+internal delegate void ReleaseBooleanArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseByteArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseCharArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseShortArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseIntArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseLongArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseFloatArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void ReleaseDoubleArrayElementsDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ReadOnlyValPtr elements0, JReleaseMode mode);
+
+internal delegate void GetBooleanArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex,
+ Int32 length, ValPtr buffer0);
+
+internal delegate void GetByteArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetCharArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetShortArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetIntArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetLongArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetFloatArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ValPtr buffer0);
+
+internal delegate void GetDoubleArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex,
+ Int32 length, ValPtr buffer0);
+
+internal delegate void SetBooleanArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex,
+ Int32 length, ReadOnlyValPtr buffer0);
+
+internal delegate void SetByteArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetCharArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetShortArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetIntArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetLongArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetFloatArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex, Int32 length,
+ ReadOnlyValPtr buffer0);
+
+internal delegate void SetDoubleArrayRegionDelegate(JEnvRef env, JArrayLocalRef arrayRef, Int32 startIndex,
+ Int32 length, ReadOnlyValPtr buffer0);
+
+internal delegate ValPtr GetPrimitiveArrayCriticalDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ out JBoolean isCopy);
+
+internal delegate void ReleasePrimitiveArrayCriticalDelegate(JEnvRef env, JArrayLocalRef arrayRef,
+ ValPtr elements, JReleaseMode mode);
+
+internal delegate JObjectLocalRef NewObjectADelegate(JEnvRef env, JClassLocalRef jClass, JMethodId jMethod,
+ ReadOnlyValPtr arg0);
diff --git a/src/LibRyujinx/Android/Jni/Identifiers/JFieldId.cs b/src/LibRyujinx/Android/Jni/Identifiers/JFieldId.cs
new file mode 100644
index 00000000..1a776943
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Identifiers/JFieldId.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.Identifiers
+{
+ public readonly struct JFieldId : IEquatable
+ {
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JFieldId other)
+ => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj)
+ => obj is JFieldId other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static Boolean operator ==(JFieldId a, JFieldId b) => a.Equals(b);
+ public static Boolean operator !=(JFieldId a, JFieldId b) => !a.Equals(b);
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Identifiers/JMethodId.cs b/src/LibRyujinx/Android/Jni/Identifiers/JMethodId.cs
new file mode 100644
index 00000000..657ffce5
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Identifiers/JMethodId.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.Identifiers
+{
+ public readonly struct JMethodId : IEquatable
+ {
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JMethodId other)
+ => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj)
+ => obj is JMethodId other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static Boolean operator ==(JMethodId a, JMethodId b) => a.Equals(b);
+ public static Boolean operator !=(JMethodId a, JMethodId b) => !a.Equals(b);
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/JReferenceType.cs b/src/LibRyujinx/Android/Jni/JReferenceType.cs
new file mode 100644
index 00000000..bd5c64c8
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/JReferenceType.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace LibRyujinx.Jni
+{
+ public enum JReferenceType : Int32
+ {
+ InvalidRefType = 0,
+ LocalRefType = 1,
+ GlobalRefType = 2,
+ WeakGlobalRefType = 3
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/JReleaseMode.cs b/src/LibRyujinx/Android/Jni/JReleaseMode.cs
new file mode 100644
index 00000000..c273a28c
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/JReleaseMode.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace LibRyujinx.Jni
+{
+ public enum JReleaseMode : Int32
+ {
+ Free = 0,
+ Commit = 1,
+ Abort = 2,
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/JResult.cs b/src/LibRyujinx/Android/Jni/JResult.cs
new file mode 100644
index 00000000..bcfd470a
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/JResult.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace LibRyujinx.Jni
+{
+ public enum JResult : Int32
+ {
+ Ok = 0,
+ Error = -1,
+ DetachedThreadError = -2,
+ VersionError = -3,
+ MemoryError = -4,
+ ExitingVMError = -5,
+ InvalidArgumentsError = -6,
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/JniHelper.cs b/src/LibRyujinx/Android/Jni/JniHelper.cs
new file mode 100644
index 00000000..f9d4f147
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/JniHelper.cs
@@ -0,0 +1,251 @@
+using LibRyujinx.Jni.Identifiers;
+using LibRyujinx.Jni.Pointers;
+using LibRyujinx.Jni.Primitives;
+using LibRyujinx.Jni.References;
+using LibRyujinx.Jni.Values;
+using System;
+
+using Rxmxnx.PInvoke;
+
+namespace LibRyujinx.Jni
+{
+ internal static class JniHelper
+ {
+ public const Int32 JniVersion = 0x00010006; //JNI_VERSION_1_6;
+
+ public static JEnvRef? Attach(JavaVMRef javaVm, IReadOnlyFixedMemory threadName, out Boolean newAttach)
+ {
+ ref JavaVMValue value = ref javaVm.VirtualMachine;
+ ref JInvokeInterface jInvoke = ref value.Functions;
+
+ IntPtr getEnvPtr = jInvoke.GetEnvPointer;
+ GetEnvDelegate getEnv = getEnvPtr.GetUnsafeDelegate()!;
+
+ if (getEnv(javaVm, out JEnvRef jEnv, JniHelper.JniVersion) == JResult.Ok)
+ {
+ newAttach = false;
+ return jEnv;
+ }
+
+ JavaVMAttachArgs args = new() { Version = JniHelper.JniVersion, Name = threadName.ValuePointer, };
+ IntPtr attachCurrentThreadPtr = jInvoke.AttachCurrentThreadPointer;
+ AttachCurrentThreadDelegate attachCurrentThread =
+ attachCurrentThreadPtr.GetUnsafeDelegate()!;
+
+ newAttach = true;
+ return attachCurrentThread(javaVm, out jEnv, in args) == JResult.Ok ? jEnv : null;
+ }
+ public static JEnvRef? AttachDaemon(JavaVMRef javaVm, IReadOnlyFixedMemory daemonName)
+ {
+ ref JavaVMValue value = ref javaVm.VirtualMachine;
+ ref JInvokeInterface jInvoke = ref value.Functions;
+
+ JavaVMAttachArgs args = new() { Version = JniHelper.JniVersion, Name = daemonName.ValuePointer, };
+ IntPtr attachCurrentThreadAsDaemonPtr = jInvoke.AttachCurrentThreadAsDaemonPointer;
+ AttachCurrentThreadAsDaemonDelegate attachCurrentThreadAsDaemon =
+ attachCurrentThreadAsDaemonPtr.GetUnsafeDelegate()!;
+
+ return attachCurrentThreadAsDaemon(javaVm, out JEnvRef jEnv, in args) == JResult.Ok ? jEnv : null;
+ }
+ public static void Detach(JavaVMRef javaVm)
+ {
+ ref JavaVMValue value = ref javaVm.VirtualMachine;
+ ref JInvokeInterface jInvoke = ref value.Functions;
+
+ IntPtr detachCurrentThreadPtr = jInvoke.DetachCurrentThreadPointer;
+ DetachCurrentThreadDelegate detachCurrentThread =
+ detachCurrentThreadPtr.GetUnsafeDelegate()!;
+
+ detachCurrentThread(javaVm);
+ }
+ public static JGlobalRef? GetGlobalClass(JEnvRef jEnv, IReadOnlyFixedMemory className)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr findClassPtr = jInterface.FindClassPointer;
+ FindClassDelegate findClass = findClassPtr.GetUnsafeDelegate()!;
+ JClassLocalRef jClass = findClass(jEnv, className.ValuePointer);
+
+ if (JniHelper.ExceptionCheck(jEnv))
+ return default;
+
+ IntPtr newGlobalRefPtr = jInterface.NewGlobalRefPointer;
+ NewGlobalRefDelegate newGlobalRef = newGlobalRefPtr.GetUnsafeDelegate()!;
+
+ JGlobalRef jGlobal = newGlobalRef(jEnv, (JObjectLocalRef)jClass);
+ JniHelper.RemoveLocal(jEnv, (JObjectLocalRef)jClass);
+
+ return !JniHelper.ExceptionCheck(jEnv) ? jGlobal : null;
+ }
+ public static void RemoveLocal(JEnvRef jEnv, JObjectLocalRef jObject)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr deleteLocalRefPtr = jInterface.DeleteLocalRefPointer;
+ DeleteLocalRefDelegate deleteLocalRef = deleteLocalRefPtr.GetUnsafeDelegate()!;
+
+ deleteLocalRef(jEnv, jObject);
+ }
+ public static void RemoveGlobal(JEnvRef jEnv, JGlobalRef jObject)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr deleteGlobalRefPtr = jInterface.DeleteGlobalRefPointer;
+ DeleteGlobalRefDelegate deleteGlobalRef = deleteGlobalRefPtr.GetUnsafeDelegate()!;
+
+ deleteGlobalRef(jEnv, jObject);
+ }
+ public static void RemoveWeakGlobal(JEnvRef jEnv, JWeakRef jObject)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr deleteWeakGlobalRefPtr = jInterface.DeleteWeakGlobalRefPointer;
+ DeleteWeakGlobalRefDelegate deleteWeakGlobalRef =
+ deleteWeakGlobalRefPtr.GetUnsafeDelegate()!;
+
+ deleteWeakGlobalRef(jEnv, jObject);
+ }
+ public static JStringLocalRef? CreateString(JEnvRef jEnv, String textValue)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr newStringPtr = jInterface.NewStringPointer;
+ NewStringDelegate newString = newStringPtr.GetUnsafeDelegate()!;
+ using IReadOnlyFixedMemory.IDisposable ctx = textValue.AsMemory().GetFixedContext();
+ JStringLocalRef jString = newString(jEnv, ctx.ValuePointer, ctx.Values.Length);
+
+ return !JniHelper.ExceptionCheck(jEnv) ? jString : null;
+ }
+ public static JWeakRef? CreateWeakGlobal(JEnvRef jEnv, JObjectLocalRef jObject)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr newWeakGlobalRefPtr = jInterface.NewWeakGlobalRefPointer;
+ NewWeakGlobalRefDelegate newWeakGlobalRef = newWeakGlobalRefPtr.GetUnsafeDelegate()!;
+ JWeakRef jWeak = newWeakGlobalRef(jEnv, jObject);
+
+ return !JniHelper.ExceptionCheck(jEnv) ? jWeak : null;
+ }
+ private static Boolean ExceptionCheck(JEnvRef jEnv)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr exceptionCheckPtr = jInterface.ExceptionCheckPointer;
+ ExceptionCheckDelegate exceptionCheck = exceptionCheckPtr.GetUnsafeDelegate()!;
+
+ if (!exceptionCheck(jEnv))
+ return false;
+ IntPtr exceptionDescribePtr = jInterface.ExceptionDescribePointer;
+ IntPtr exceptionClearPtr = jInterface.ExceptionClearPointer;
+
+ ExceptionDescribeDelegate exceptionDescribe =
+ exceptionDescribePtr.GetUnsafeDelegate()!;
+ ExceptionClearDelegate exceptionClear = exceptionClearPtr.GetUnsafeDelegate()!;
+
+ exceptionDescribe(jEnv);
+ exceptionClear(jEnv);
+ return true;
+ }
+ public static JavaVMRef? GetVirtualMachine(JEnvRef jEnv)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr getJavaVmPtr = jInterface.GetJavaVMPointer;
+ GetVirtualMachineDelegate getJavaVm = getJavaVmPtr.GetUnsafeDelegate()!;
+ return getJavaVm(jEnv, out JavaVMRef javaVm) == JResult.Ok ? javaVm : null;
+ }
+ public static Boolean? IsValidGlobalWeak(JEnvRef jEnv, JWeakRef jWeak)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr isSameObjectPtr = jInterface.IsSameObjectPointer;
+ IsSameObjectDelegate isSameObject = isSameObjectPtr.GetUnsafeDelegate()!;
+ JBoolean result = isSameObject(jEnv, (JObjectLocalRef)jWeak, default);
+ return !JniHelper.ExceptionCheck(jEnv) ? !result : null;
+ }
+ public static JMethodId? GetMethodId(JEnvRef jEnv, JClassLocalRef jClass, IReadOnlyFixedMemory methodName,
+ IReadOnlyFixedMemory descriptor)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr getMethodIdPtr = jInterface.GetMethodIdPointer;
+ GetMethodIdDelegate getMethodId = getMethodIdPtr.GetUnsafeDelegate()!;
+ JMethodId methodId = getMethodId(jEnv, jClass, methodName.ValuePointer, descriptor.ValuePointer);
+ return !JniHelper.ExceptionCheck(jEnv) ? methodId : null;
+ }
+ public static JMethodId? GetStaticMethodId(JEnvRef jEnv, JClassLocalRef jClass,
+ IReadOnlyFixedMemory methodName, IReadOnlyFixedMemory descriptor)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr getStaticMethodIdPtr = jInterface.GetStaticMethodIdPointer;
+ GetStaticMethodIdDelegate getStaticMethodId =
+ getStaticMethodIdPtr.GetUnsafeDelegate()!;
+ JMethodId jMethodId = getStaticMethodId(jEnv, jClass, methodName.ValuePointer, descriptor.ValuePointer);
+ return !JniHelper.ExceptionCheck(jEnv) ? jMethodId : null;
+ }
+ public static void CallStaticVoidMethod(JEnvRef jEnv, JClassLocalRef jClass, JMethodId jMethodId,
+ params JValue[] args)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr callStaticVoidMethodPtr = jInterface.CallStaticVoidMethodAPointer;
+ CallStaticVoidMethodADelegate callStaticVoidMethod =
+ callStaticVoidMethodPtr.GetUnsafeDelegate()!;
+
+ using IReadOnlyFixedMemory.IDisposable fArgs = args.AsMemory().GetFixedContext();
+ callStaticVoidMethod(jEnv, jClass, jMethodId, fArgs.ValuePointer);
+ }
+ public static JObjectLocalRef? CallStaticObjectMethod(JEnvRef jEnv, JClassLocalRef jClass, JMethodId jMethodId,
+ params JValue[] args)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr callStaticObjectMethodPtr = jInterface.CallStaticObjectMethodAPointer;
+ CallStaticObjectMethodADelegate callStaticObjectMethod =
+ callStaticObjectMethodPtr.GetUnsafeDelegate()!;
+
+ using IReadOnlyFixedMemory.IDisposable fArgs = args.AsMemory().GetFixedContext();
+ JObjectLocalRef jObject = callStaticObjectMethod(jEnv, jClass, jMethodId, fArgs.ValuePointer);
+ return !JniHelper.ExceptionCheck(jEnv) ? jObject : null;
+ }
+ public static JLong? CallStaticLongMethod(JEnvRef jEnv, JClassLocalRef jClass, JMethodId jMethodId,
+ params JValue[] args)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr callStaticLongMethodPtr = jInterface.CallStaticLongMethodAPointer;
+ CallStaticLongMethodADelegate callStaticLongMethod =
+ callStaticLongMethodPtr.GetUnsafeDelegate()!;
+
+ using IReadOnlyFixedMemory.IDisposable fArgs = args.AsMemory().GetFixedContext();
+ JLong jLong = callStaticLongMethod(jEnv, jClass, jMethodId, fArgs.ValuePointer);
+ return !JniHelper.ExceptionCheck(jEnv) ? jLong : null;
+ }
+ public static void CallVoidMethod(JEnvRef jEnv, JObjectLocalRef jObject, JMethodId jMethodId, params JValue[] args)
+ {
+ ref readonly JEnvValue value = ref jEnv.Environment;
+ ref JNativeInterface jInterface = ref value.Functions;
+
+ IntPtr callVoidMethodPtr = jInterface.CallVoidMethodAPointer;
+ CallVoidMethodADelegate callVoidMethod = callVoidMethodPtr.GetUnsafeDelegate()!;
+
+ using IReadOnlyFixedMemory.IDisposable fArgs = args.AsMemory().GetFixedContext();
+ callVoidMethod(jEnv, jObject, jMethodId, fArgs.ValuePointer);
+ }
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Pointers/JEnvRef.cs b/src/LibRyujinx/Android/Jni/Pointers/JEnvRef.cs
new file mode 100644
index 00000000..20b07ad3
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Pointers/JEnvRef.cs
@@ -0,0 +1,37 @@
+using LibRyujinx.Jni.Values;
+
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Pointers
+{
+ public readonly struct JEnvRef : IEquatable
+ {
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ public JEnvRef(IntPtr val)
+ {
+ _value = val;
+ }
+
+ #region Operators
+ public static Boolean operator ==(JEnvRef a, JEnvRef b) => a._value.Equals(b._value);
+ public static Boolean operator !=(JEnvRef a, JEnvRef b) => !a._value.Equals(b._value);
+ #endregion
+
+ #region Public Properties
+ internal readonly ref JEnvValue Environment => ref this._value.GetUnsafeReference();
+ #endregion
+
+ #region Public Methods
+ public Boolean Equals(JEnvRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Overrided Methods
+ public override Boolean Equals(Object obj) => obj is JEnvRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Pointers/JavaVMRef.cs b/src/LibRyujinx/Android/Jni/Pointers/JavaVMRef.cs
new file mode 100644
index 00000000..80fc468f
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Pointers/JavaVMRef.cs
@@ -0,0 +1,31 @@
+using LibRyujinx.Jni.Values;
+
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Pointers;
+
+public readonly struct JavaVMRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Operators
+ public static Boolean operator ==(JavaVMRef a, JavaVMRef b) => a._value.Equals(b._value);
+ public static Boolean operator !=(JavaVMRef a, JavaVMRef b) => !a._value.Equals(b._value);
+ #endregion
+
+ #region Public Properties
+ internal readonly ref JavaVMValue VirtualMachine => ref this._value.GetUnsafeReference();
+ #endregion
+
+ #region Public Methods
+ public Boolean Equals(JavaVMRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Overrided Methods
+ public override Boolean Equals(Object obj) => obj is JavaVMRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JBoolean.cs b/src/LibRyujinx/Android/Jni/Primitives/JBoolean.cs
new file mode 100644
index 00000000..5f131559
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JBoolean.cs
@@ -0,0 +1,67 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives;
+
+public readonly struct JBoolean : IComparable, IEquatable
+{
+ internal static readonly Type Type = typeof(JBoolean);
+ private const Byte trueByte = 1;
+ private const Byte falseByte = 0;
+
+ public static readonly CString Signature = (CString)"Z";
+
+ private readonly Byte _value;
+ private Boolean Value => this._value == JBoolean.trueByte;
+
+ private JBoolean(Boolean value) => this._value = value ? JBoolean.trueByte : JBoolean.falseByte;
+
+ #region Operators
+ public static implicit operator JBoolean(Boolean value) => new(value);
+ public static implicit operator Boolean(JBoolean jValue) => jValue._value == 1;
+ public static JBoolean operator !(JBoolean a) => new(!a.Value);
+ public static JBoolean operator |(JBoolean a, JBoolean b) => new(a.Value || b.Value);
+ public static JBoolean operator |(Boolean a, JBoolean b) => new(a || b.Value);
+ public static JBoolean operator |(JBoolean a, Boolean b) => new(a.Value || b);
+ public static JBoolean operator &(JBoolean a, JBoolean b) => new(a.Value && b.Value);
+ public static JBoolean operator &(Boolean a, JBoolean b) => new(a && b.Value);
+ public static JBoolean operator &(JBoolean a, Boolean b) => new(a.Value && b);
+ public static Boolean operator ==(JBoolean a, JBoolean b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Boolean a, JBoolean b) => a.Equals(b._value);
+ public static Boolean operator ==(JBoolean a, Boolean b) => a._value.Equals(b);
+ public static Boolean operator !=(JBoolean a, JBoolean b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Boolean a, JBoolean b) => !a.Equals(b._value);
+ public static Boolean operator !=(JBoolean a, Boolean b) => !a._value.Equals(b);
+ public static Boolean operator >(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Boolean a, JBoolean b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JBoolean a, Boolean b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Boolean a, JBoolean b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JBoolean a, Boolean b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Boolean a, JBoolean b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JBoolean a, Boolean b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JBoolean a, JBoolean b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Boolean a, JBoolean b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JBoolean a, Boolean b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Boolean other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JBoolean other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj)
+ => obj is JBoolean jvalue ? this.CompareTo(jvalue) :
+ obj is Boolean value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Boolean other) => this._value.Equals(other);
+ public Boolean Equals(JBoolean other) => this._value.Equals(other._value);
+ public String ToString(IFormatProvider formatProvider) => this._value.ToString(formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj)
+ => obj is JBoolean jvalue ? this.Equals(jvalue) :
+ obj is Boolean value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JByte.cs b/src/LibRyujinx/Android/Jni/Primitives/JByte.cs
new file mode 100644
index 00000000..18ed5092
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JByte.cs
@@ -0,0 +1,74 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JByte : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JByte);
+
+ public static readonly CString Signature = (CString)"B";
+
+ private readonly SByte _value;
+
+ private JByte(SByte value) => this._value = value;
+ private JByte(Int32 value) => this._value = Convert.ToSByte(value);
+
+ #region Operators
+ public static implicit operator JByte(SByte value) => new(value);
+ public static implicit operator SByte(JByte jValue) => jValue._value;
+ public static JByte operator +(JByte a) => a;
+ public static JByte operator ++(JByte a) => new(a._value + 1);
+ public static JByte operator -(JByte a) => new(-a._value);
+ public static JByte operator --(JByte a) => new(a._value - 1);
+ public static JByte operator +(JByte a, JByte b) => new(a._value + b._value);
+ public static JByte operator +(SByte a, JByte b) => new(a + b._value);
+ public static JByte operator +(JByte a, SByte b) => new(a._value + b);
+ public static JByte operator -(JByte a, JByte b) => new(a._value - b._value);
+ public static JByte operator -(SByte a, JByte b) => new(a - b._value);
+ public static JByte operator -(JByte a, SByte b) => new(a._value - b);
+ public static JByte operator *(JByte a, JByte b) => new(a._value * b._value);
+ public static JByte operator *(SByte a, JByte b) => new(a * b._value);
+ public static JByte operator *(JByte a, SByte b) => new(a._value * b);
+ public static JByte operator /(JByte a, JByte b) => new(a._value / b._value);
+ public static JByte operator /(SByte a, JByte b) => new(a / b._value);
+ public static JByte operator /(JByte a, SByte b) => new(a._value / b);
+ public static JByte operator %(JByte a, JByte b) => new(a._value % b._value);
+ public static JByte operator %(SByte a, JByte b) => new(a % b._value);
+ public static JByte operator %(JByte a, SByte b) => new(a._value % b);
+ public static Boolean operator ==(JByte a, JByte b) => a._value.Equals(b._value);
+ public static Boolean operator ==(SByte a, JByte b) => a.Equals(b._value);
+ public static Boolean operator ==(JByte a, SByte b) => a._value.Equals(b);
+ public static Boolean operator !=(JByte a, JByte b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(SByte a, JByte b) => !a.Equals(b._value);
+ public static Boolean operator !=(JByte a, SByte b) => !a._value.Equals(b);
+ public static Boolean operator >(JByte a, JByte b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(SByte a, JByte b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JByte a, SByte b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JByte a, JByte b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(SByte a, JByte b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JByte a, SByte b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JByte a, JByte b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(SByte a, JByte b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JByte a, SByte b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JByte a, JByte b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(SByte a, JByte b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JByte a, SByte b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(SByte other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JByte other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JByte jValue ? this.CompareTo(jValue) : obj is SByte value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(SByte other) => this._value.Equals(other);
+ public Boolean Equals(JByte other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JByte jvalue ? this.Equals(jvalue) : obj is SByte value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JChar.cs b/src/LibRyujinx/Android/Jni/Primitives/JChar.cs
new file mode 100644
index 00000000..310acf40
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JChar.cs
@@ -0,0 +1,56 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JChar : IComparable, IEquatable
+ {
+ internal static readonly Type Type = typeof(JChar);
+
+ public static readonly CString Signature = (CString)"C";
+
+ private readonly Char _value;
+
+ private JChar(Char value) => this._value = value;
+
+ #region Operators
+ public static implicit operator JChar(Char value) => new(value);
+ public static explicit operator JChar(Int16 value) => new((Char)value);
+ public static implicit operator Char(JChar jValue) => jValue._value;
+ public static explicit operator Int16(JChar jValue) => (Int16)jValue._value;
+ public static Boolean operator ==(JChar a, JChar b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Char a, JChar b) => a.Equals(b._value);
+ public static Boolean operator ==(JChar a, Char b) => a._value.Equals(b);
+ public static Boolean operator !=(JChar a, JChar b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Char a, JChar b) => !a.Equals(b._value);
+ public static Boolean operator !=(JChar a, Char b) => !a._value.Equals(b);
+ public static Boolean operator >(JChar a, JChar b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Char a, JChar b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JChar a, Char b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JChar a, JChar b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Char a, JChar b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JChar a, Char b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JChar a, JChar b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Char a, JChar b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JChar a, Char b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JChar a, JChar b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Char a, JChar b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JChar a, Char b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Char other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JChar other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JChar jvalue ? this.CompareTo(jvalue) : obj is Char value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Char other) => this._value.Equals(other);
+ public Boolean Equals(JChar other) => this._value.Equals(other._value);
+ public String ToString(IFormatProvider formatProvider) => this._value.ToString(formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JChar jvalue ? this.Equals(jvalue) : obj is Char value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JDouble.cs b/src/LibRyujinx/Android/Jni/Primitives/JDouble.cs
new file mode 100644
index 00000000..5fe0c2b5
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JDouble.cs
@@ -0,0 +1,70 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JDouble : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JDouble);
+
+ public static readonly CString Signature = (CString)"D";
+
+ private readonly Double _value;
+
+ private JDouble(Double value) => this._value = value;
+
+ #region Operators
+ public static implicit operator JDouble(Double value) => new(value);
+ public static implicit operator Double(JDouble jValue) => jValue._value;
+ public static JDouble operator +(JDouble a) => a;
+ public static JDouble operator ++(JDouble a) => new(a._value + 1);
+ public static JDouble operator -(JDouble a) => new(-a._value);
+ public static JDouble operator --(JDouble a) => new(a._value - 1);
+ public static JDouble operator +(JDouble a, JDouble b) => new(a._value + b._value);
+ public static JDouble operator +(Double a, JDouble b) => new(a + b._value);
+ public static JDouble operator +(JDouble a, Double b) => new(a._value + b);
+ public static JDouble operator -(JDouble a, JDouble b) => new(a._value - b._value);
+ public static JDouble operator -(Double a, JDouble b) => new(a - b._value);
+ public static JDouble operator -(JDouble a, Double b) => new(a._value - b);
+ public static JDouble operator *(JDouble a, JDouble b) => new(a._value * b._value);
+ public static JDouble operator *(Double a, JDouble b) => new(a * b._value);
+ public static JDouble operator *(JDouble a, Double b) => new(a._value * b);
+ public static JDouble operator /(JDouble a, JDouble b) => new(a._value / b._value);
+ public static JDouble operator /(Double a, JDouble b) => new(a / b._value);
+ public static JDouble operator /(JDouble a, Double b) => new(a._value / b);
+ public static Boolean operator ==(JDouble a, JDouble b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Double a, JDouble b) => a.Equals(b._value);
+ public static Boolean operator ==(JDouble a, Double b) => a._value.Equals(b);
+ public static Boolean operator !=(JDouble a, JDouble b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Double a, JDouble b) => !a.Equals(b._value);
+ public static Boolean operator !=(JDouble a, Double b) => !a._value.Equals(b);
+ public static Boolean operator >(JDouble a, JDouble b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Double a, JDouble b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JDouble a, Double b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JDouble a, JDouble b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Double a, JDouble b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JDouble a, Double b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JDouble a, JDouble b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Double a, JDouble b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JDouble a, Double b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JDouble a, JDouble b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Double a, JDouble b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JDouble a, Double b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Double other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JDouble other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JDouble jvalue ? this.CompareTo(jvalue) : obj is Double value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Double other) => this._value.Equals(other);
+ public Boolean Equals(JDouble other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JDouble jvalue ? this.Equals(jvalue) : obj is Double value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JFloat.cs b/src/LibRyujinx/Android/Jni/Primitives/JFloat.cs
new file mode 100644
index 00000000..0cd81879
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JFloat.cs
@@ -0,0 +1,70 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JFloat : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JFloat);
+
+ public static readonly CString Signature = (CString)"F";
+
+ private readonly Single _value;
+
+ private JFloat(Single value) => this._value = value;
+
+ #region Operators
+ public static implicit operator JFloat(Single value) => new(value);
+ public static implicit operator Single(JFloat jValue) => jValue._value;
+ public static JFloat operator +(JFloat a) => a;
+ public static JFloat operator ++(JFloat a) => new(a._value + 1);
+ public static JFloat operator -(JFloat a) => new(-a._value);
+ public static JFloat operator --(JFloat a) => new(a._value - 1);
+ public static JFloat operator +(JFloat a, JFloat b) => new(a._value + b._value);
+ public static JFloat operator +(Single a, JFloat b) => new(a + b._value);
+ public static JFloat operator +(JFloat a, Single b) => new(a._value + b);
+ public static JFloat operator -(JFloat a, JFloat b) => new(a._value - b._value);
+ public static JFloat operator -(Single a, JFloat b) => new(a - b._value);
+ public static JFloat operator -(JFloat a, Single b) => new(a._value - b);
+ public static JFloat operator *(JFloat a, JFloat b) => new(a._value * b._value);
+ public static JFloat operator *(Single a, JFloat b) => new(a * b._value);
+ public static JFloat operator *(JFloat a, Single b) => new(a._value * b);
+ public static JFloat operator /(JFloat a, JFloat b) => new(a._value / b._value);
+ public static JFloat operator /(Single a, JFloat b) => new(a / b._value);
+ public static JFloat operator /(JFloat a, Single b) => new(a._value / b);
+ public static Boolean operator ==(JFloat a, JFloat b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Single a, JFloat b) => a.Equals(b._value);
+ public static Boolean operator ==(JFloat a, Single b) => a._value.Equals(b);
+ public static Boolean operator !=(JFloat a, JFloat b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Single a, JFloat b) => !a.Equals(b._value);
+ public static Boolean operator !=(JFloat a, Single b) => !a._value.Equals(b);
+ public static Boolean operator >(JFloat a, JFloat b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Single a, JFloat b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JFloat a, Single b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JFloat a, JFloat b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Single a, JFloat b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JFloat a, Single b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JFloat a, JFloat b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Single a, JFloat b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JFloat a, Single b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JFloat a, JFloat b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Single a, JFloat b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JFloat a, Single b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Single other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JFloat other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JFloat jvalue ? this.CompareTo(jvalue) : obj is Single value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Single other) => this._value.Equals(other);
+ public Boolean Equals(JFloat other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JFloat jvalue ? this.Equals(jvalue) : obj is Single value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JInt.cs b/src/LibRyujinx/Android/Jni/Primitives/JInt.cs
new file mode 100644
index 00000000..8dfcc783
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JInt.cs
@@ -0,0 +1,73 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JInt : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JInt);
+
+ public static readonly CString Signature = (CString)"I";
+
+ private readonly Int32 _value;
+
+ private JInt(Int32 value) => this._value = value;
+
+ #region Operators
+ public static implicit operator JInt(Int32 value) => new(value);
+ public static implicit operator Int32(JInt jValue) => jValue._value;
+ public static JInt operator +(JInt a) => a;
+ public static JInt operator ++(JInt a) => new(a._value + 1);
+ public static JInt operator -(JInt a) => new(-a._value);
+ public static JInt operator --(JInt a) => new(a._value - 1);
+ public static JInt operator +(JInt a, JInt b) => new(a._value + b._value);
+ public static JInt operator +(Int32 a, JInt b) => new(a + b._value);
+ public static JInt operator +(JInt a, Int32 b) => new(a._value + b);
+ public static JInt operator -(JInt a, JInt b) => new(a._value - b._value);
+ public static JInt operator -(Int32 a, JInt b) => new(a - b._value);
+ public static JInt operator -(JInt a, Int32 b) => new(a._value - b);
+ public static JInt operator *(JInt a, JInt b) => new(a._value * b._value);
+ public static JInt operator *(Int32 a, JInt b) => new(a * b._value);
+ public static JInt operator *(JInt a, Int32 b) => new(a._value * b);
+ public static JInt operator /(JInt a, JInt b) => new(a._value / b._value);
+ public static JInt operator /(Int32 a, JInt b) => new(a / b._value);
+ public static JInt operator /(JInt a, Int32 b) => new(a._value / b);
+ public static JInt operator %(JInt a, JInt b) => new(a._value % b._value);
+ public static JInt operator %(Int32 a, JInt b) => new(a % b._value);
+ public static JInt operator %(JInt a, Int32 b) => new(a._value % b);
+ public static Boolean operator ==(JInt a, JInt b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Int32 a, JInt b) => a.Equals(b._value);
+ public static Boolean operator ==(JInt a, Int32 b) => a._value.Equals(b);
+ public static Boolean operator !=(JInt a, JInt b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Int32 a, JInt b) => !a.Equals(b._value);
+ public static Boolean operator !=(JInt a, Int32 b) => !a._value.Equals(b);
+ public static Boolean operator >(JInt a, JInt b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Int32 a, JInt b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JInt a, Int32 b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JInt a, JInt b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Int32 a, JInt b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JInt a, Int32 b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JInt a, JInt b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Int32 a, JInt b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JInt a, Int32 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JInt a, JInt b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Int32 a, JInt b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JInt a, Int32 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Int32 other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JInt other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JInt jValue ? this.CompareTo(jValue) : obj is Int32 value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Int32 other) => this._value.Equals(other);
+ public Boolean Equals(JInt other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JInt jvalue ? this.Equals(jvalue) : obj is Int32 value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JLong.cs b/src/LibRyujinx/Android/Jni/Primitives/JLong.cs
new file mode 100644
index 00000000..d8088391
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JLong.cs
@@ -0,0 +1,73 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JLong : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JLong);
+
+ public static readonly CString Signature = (CString)"J";
+
+ private readonly Int64 _value;
+
+ private JLong(Int64 value) => this._value = value;
+
+ #region Operators
+ public static implicit operator JLong(Int64 value) => new(value);
+ public static implicit operator Int64(JLong jValue) => jValue._value;
+ public static JLong operator +(JLong a) => a;
+ public static JLong operator ++(JLong a) => new(a._value + 1);
+ public static JLong operator -(JLong a) => new(-a._value);
+ public static JLong operator --(JLong a) => new(a._value - 1);
+ public static JLong operator +(JLong a, JLong b) => new(a._value + b._value);
+ public static JLong operator +(Int64 a, JLong b) => new(a + b._value);
+ public static JLong operator +(JLong a, Int64 b) => new(a._value + b);
+ public static JLong operator -(JLong a, JLong b) => new(a._value - b._value);
+ public static JLong operator -(Int64 a, JLong b) => new(a - b._value);
+ public static JLong operator -(JLong a, Int64 b) => new(a._value - b);
+ public static JLong operator *(JLong a, JLong b) => new(a._value * b._value);
+ public static JLong operator *(Int64 a, JLong b) => new(a * b._value);
+ public static JLong operator *(JLong a, Int64 b) => new(a._value * b);
+ public static JLong operator /(JLong a, JLong b) => new(a._value / b._value);
+ public static JLong operator /(Int64 a, JLong b) => new(a / b._value);
+ public static JLong operator /(JLong a, Int64 b) => new(a._value / b);
+ public static JLong operator %(JLong a, JLong b) => new(a._value % b._value);
+ public static JLong operator %(Int64 a, JLong b) => new(a % b._value);
+ public static JLong operator %(JLong a, Int64 b) => new(a._value % b);
+ public static Boolean operator ==(JLong a, JLong b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Int64 a, JLong b) => a.Equals(b._value);
+ public static Boolean operator ==(JLong a, Int64 b) => a._value.Equals(b);
+ public static Boolean operator !=(JLong a, JLong b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Int64 a, JLong b) => !a.Equals(b._value);
+ public static Boolean operator !=(JLong a, Int64 b) => !a._value.Equals(b);
+ public static Boolean operator >(JLong a, JLong b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Int64 a, JLong b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JLong a, Int64 b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JLong a, JLong b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Int64 a, JLong b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JLong a, Int64 b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JLong a, JLong b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Int64 a, JLong b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JLong a, Int64 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JLong a, JLong b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Int64 a, JLong b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JLong a, Int64 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Int64 other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JLong other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JLong jvalue ? this.CompareTo(jvalue) : obj is Int64 value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Int64 other) => this._value.Equals(other);
+ public Boolean Equals(JLong other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JLong jvalue ? this.Equals(jvalue) : obj is Int64 value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Primitives/JShort.cs b/src/LibRyujinx/Android/Jni/Primitives/JShort.cs
new file mode 100644
index 00000000..de959a48
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Primitives/JShort.cs
@@ -0,0 +1,74 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Primitives
+{
+ public readonly struct JShort : IComparable, IEquatable, IFormattable
+ {
+ internal static readonly Type Type = typeof(JShort);
+
+ public static readonly CString Signature = (CString)"S";
+
+ private readonly Int16 _value;
+
+ private JShort(Int16 value) => this._value = value;
+ private JShort(Int32 value) => this._value = Convert.ToInt16(value);
+
+ #region Operators
+ public static implicit operator JShort(Int16 value) => new(value);
+ public static implicit operator Int16(JShort jValue) => jValue._value;
+ public static JShort operator +(JShort a) => a;
+ public static JShort operator ++(JShort a) => new(a._value + 1);
+ public static JShort operator -(JShort a) => new(-a._value);
+ public static JShort operator --(JShort a) => new(a._value - 1);
+ public static JShort operator +(JShort a, JShort b) => new(a._value + b._value);
+ public static JShort operator +(Int16 a, JShort b) => new(a + b._value);
+ public static JShort operator +(JShort a, Int16 b) => new(a._value + b);
+ public static JShort operator -(JShort a, JShort b) => new(a._value - b._value);
+ public static JShort operator -(Int16 a, JShort b) => new(a - b._value);
+ public static JShort operator -(JShort a, Int16 b) => new(a._value - b);
+ public static JShort operator *(JShort a, JShort b) => new(a._value * b._value);
+ public static JShort operator *(Int16 a, JShort b) => new(a * b._value);
+ public static JShort operator *(JShort a, Int16 b) => new(a._value * b);
+ public static JShort operator /(JShort a, JShort b) => new(a._value / b._value);
+ public static JShort operator /(Int16 a, JShort b) => new(a / b._value);
+ public static JShort operator /(JShort a, Int16 b) => new(a._value / b);
+ public static JShort operator %(JShort a, JShort b) => new(a._value % b._value);
+ public static JShort operator %(Int16 a, JShort b) => new(a % b._value);
+ public static JShort operator %(JShort a, Int16 b) => new(a._value % b);
+ public static Boolean operator ==(JShort a, JShort b) => a._value.Equals(b._value);
+ public static Boolean operator ==(Int16 a, JShort b) => a.Equals(b._value);
+ public static Boolean operator ==(JShort a, Int16 b) => a._value.Equals(b);
+ public static Boolean operator !=(JShort a, JShort b) => !a._value.Equals(b._value);
+ public static Boolean operator !=(Int16 a, JShort b) => !a.Equals(b._value);
+ public static Boolean operator !=(JShort a, Int16 b) => !a._value.Equals(b);
+ public static Boolean operator >(JShort a, JShort b) => a._value.CompareTo(b._value) > 0;
+ public static Boolean operator >(Int16 a, JShort b) => a.CompareTo(b._value) > 0;
+ public static Boolean operator >(JShort a, Int16 b) => a._value.CompareTo(b) > 0;
+ public static Boolean operator <(JShort a, JShort b) => a._value.CompareTo(b._value) < 0;
+ public static Boolean operator <(Int16 a, JShort b) => a.CompareTo(b._value) < 0;
+ public static Boolean operator <(JShort a, Int16 b) => a._value.CompareTo(b) < 0;
+ public static Boolean operator >=(JShort a, JShort b) => a._value.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(Int16 a, JShort b) => a.CompareTo(b._value) > 0 || a.Equals(b._value);
+ public static Boolean operator >=(JShort a, Int16 b) => a._value.CompareTo(b) > 0 || a._value.Equals(b);
+ public static Boolean operator <=(JShort a, JShort b) => a._value.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(Int16 a, JShort b) => a.CompareTo(b._value) < 0 || a.Equals(b._value);
+ public static Boolean operator <=(JShort a, Int16 b) => a._value.CompareTo(b) < 0 || a._value.Equals(b);
+ #endregion
+
+ #region Public Methods
+ public Int32 CompareTo(Int16 other) => this._value.CompareTo(other);
+ public Int32 CompareTo(JShort other) => this._value.CompareTo(other._value);
+ public Int32 CompareTo(Object obj) => obj is JShort jvalue ? this.CompareTo(jvalue) : obj is Int16 value ? this.CompareTo(value) : this._value.CompareTo(obj);
+ public Boolean Equals(Int16 other) => this._value.Equals(other);
+ public Boolean Equals(JShort other) => this._value.Equals(other._value);
+ public String ToString(String format, IFormatProvider formatProvider) => this._value.ToString(format, formatProvider);
+ #endregion
+
+ #region Overrided Methods
+ public override String ToString() => this._value.ToString();
+ public override Boolean Equals(Object obj) => obj is JShort jvalue ? this.Equals(jvalue) : obj is Int16 value ? this.Equals(value) : this._value.Equals(obj);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JArrayLocalRef.cs b/src/LibRyujinx/Android/Jni/References/JArrayLocalRef.cs
new file mode 100644
index 00000000..73e9ecd9
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JArrayLocalRef.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JArrayLocalRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JArrayLocalRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj) => obj is JArrayLocalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JArrayLocalRef a) => a._value;
+ public static Boolean operator ==(JArrayLocalRef a, JArrayLocalRef b) => a.Equals(b);
+ public static Boolean operator !=(JArrayLocalRef a, JArrayLocalRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JClassLocalRef.cs b/src/LibRyujinx/Android/Jni/References/JClassLocalRef.cs
new file mode 100644
index 00000000..4f7bab33
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JClassLocalRef.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JClassLocalRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+#pragma warning restore 0649
+
+ private JClassLocalRef(JObjectLocalRef value) => this._value = value;
+
+ #region Public Methods
+ public Boolean Equals(JClassLocalRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj) => obj is JClassLocalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JClassLocalRef a) => a._value;
+ public static explicit operator JClassLocalRef(JObjectLocalRef a) => new(a);
+ public static Boolean operator ==(JClassLocalRef a, JClassLocalRef b) => a.Equals(b);
+ public static Boolean operator !=(JClassLocalRef a, JClassLocalRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JGlobalRef.cs b/src/LibRyujinx/Android/Jni/References/JGlobalRef.cs
new file mode 100644
index 00000000..6fbd8fad
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JGlobalRef.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JGlobalRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+
+ public JObjectLocalRef Value => _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JGlobalRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj) => obj is JGlobalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JGlobalRef a) => a._value;
+ public static Boolean operator ==(JGlobalRef a, JGlobalRef b) => a.Equals(b);
+ public static Boolean operator !=(JGlobalRef a, JGlobalRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JObjectLocalRef.cs b/src/LibRyujinx/Android/Jni/References/JObjectLocalRef.cs
new file mode 100644
index 00000000..241cb0be
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JObjectLocalRef.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References
+{
+ public readonly struct JObjectLocalRef : IEquatable
+ {
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JObjectLocalRef other)
+ => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj)
+ => obj is JObjectLocalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static Boolean operator ==(JObjectLocalRef a, JObjectLocalRef b) => a.Equals(b);
+ public static Boolean operator !=(JObjectLocalRef a, JObjectLocalRef b) => !a.Equals(b);
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JStringLocalRef.cs b/src/LibRyujinx/Android/Jni/References/JStringLocalRef.cs
new file mode 100644
index 00000000..dcc5d9e2
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JStringLocalRef.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JStringLocalRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JStringLocalRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj)
+ => obj is JStringLocalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JStringLocalRef a) => a._value;
+ public static Boolean operator ==(JStringLocalRef a, JStringLocalRef b) => a.Equals(b);
+ public static Boolean operator !=(JStringLocalRef a, JStringLocalRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JThrowableLocalRef.cs b/src/LibRyujinx/Android/Jni/References/JThrowableLocalRef.cs
new file mode 100644
index 00000000..a27ba231
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JThrowableLocalRef.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JThrowableLocalRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JThrowableLocalRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj)
+ => obj is JThrowableLocalRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JThrowableLocalRef a) => a._value;
+ public static Boolean operator ==(JThrowableLocalRef a, JThrowableLocalRef b) => a.Equals(b);
+ public static Boolean operator !=(JThrowableLocalRef a, JThrowableLocalRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/References/JWeakRef.cs b/src/LibRyujinx/Android/Jni/References/JWeakRef.cs
new file mode 100644
index 00000000..1a045006
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/References/JWeakRef.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.References;
+
+public readonly struct JWeakRef : IEquatable
+{
+#pragma warning disable 0649
+ private readonly JObjectLocalRef _value;
+#pragma warning restore 0649
+
+ #region Public Methods
+ public Boolean Equals(JWeakRef other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Override Methods
+ public override Boolean Equals([NotNullWhen(true)] Object obj) => obj is JWeakRef other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+
+ #region Operators
+ public static explicit operator JObjectLocalRef(JWeakRef a) => a._value;
+ public static Boolean operator ==(JWeakRef a, JWeakRef b) => a.Equals(b);
+ public static Boolean operator !=(JWeakRef a, JWeakRef b) => !a.Equals(b);
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JEnvValue.cs b/src/LibRyujinx/Android/Jni/Values/JEnvValue.cs
new file mode 100644
index 00000000..966fe73a
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JEnvValue.cs
@@ -0,0 +1,30 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Values
+{
+ internal readonly struct JEnvValue : IEquatable
+ {
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Operators
+ public static Boolean operator ==(JEnvValue a, JEnvValue b) => a._value.Equals(b._value);
+ public static Boolean operator !=(JEnvValue a, JEnvValue b) => !a._value.Equals(b._value);
+ #endregion
+
+ #region Public Properties
+ internal readonly ref JNativeInterface Functions => ref this._value.GetUnsafeReference();
+ #endregion
+
+ #region Public Methods
+ public Boolean Equals(JEnvValue other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Overrided Methods
+ public override Boolean Equals(Object obj) => obj is JEnvValue other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JInvokeInterface.cs b/src/LibRyujinx/Android/Jni/Values/JInvokeInterface.cs
new file mode 100644
index 00000000..4d9ba71f
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JInvokeInterface.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibRyujinx.Jni.Values
+{
+ [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "This struct is created only by binary operations.")]
+ public readonly struct JInvokeInterface
+ {
+#pragma warning disable 0169
+ private readonly IntPtr _reserved0;
+ private readonly IntPtr _reserved1;
+ private readonly IntPtr _reserved2;
+#pragma warning restore 0169
+ internal IntPtr DestroyJavaVMPointer { get; init; }
+ internal IntPtr AttachCurrentThreadPointer { get; init; }
+ internal IntPtr DetachCurrentThreadPointer { get; init; }
+ internal IntPtr GetEnvPointer { get; init; }
+ internal IntPtr AttachCurrentThreadAsDaemonPointer { get; init; }
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JNativeInterface.cs b/src/LibRyujinx/Android/Jni/Values/JNativeInterface.cs
new file mode 100644
index 00000000..35886bf2
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JNativeInterface.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace LibRyujinx.Jni.Values;
+
+[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members",
+ Justification = "This struct is created only by binary operations.")]
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct JNativeInterface
+{
+#pragma warning disable 0169
+ private readonly IntPtr _reserved0;
+ private readonly IntPtr _reserved1;
+ private readonly IntPtr _reserved2;
+ private readonly IntPtr _reserved3;
+#pragma warning restore 0169
+ internal readonly IntPtr GetVersionPointer { get; init; }
+ internal readonly IntPtr DefineClassPointer { get; init; }
+ internal readonly IntPtr FindClassPointer { get; init; }
+ internal readonly IntPtr FromReflectedMethodPointer { get; init; }
+ internal readonly IntPtr FromReflectedFieldPointer { get; init; }
+ internal readonly IntPtr ToReflectedMethodPointer { get; init; }
+ internal readonly IntPtr GetSuperclassPointer { get; init; }
+ internal readonly IntPtr IsAssignableFromPointer { get; init; }
+ internal readonly IntPtr ToReflectedFieldPointer { get; init; }
+ internal readonly IntPtr ThrowPointer { get; init; }
+ internal readonly IntPtr ThrowNewPointer { get; init; }
+ internal readonly IntPtr ExceptionOccurredPointer { get; init; }
+ internal readonly IntPtr ExceptionDescribePointer { get; init; }
+ internal readonly IntPtr ExceptionClearPointer { get; init; }
+ internal readonly IntPtr FatalErrorPointer { get; init; }
+ internal readonly IntPtr PushLocalFramePointer { get; init; }
+ internal readonly IntPtr PopLocalFramePointer { get; init; }
+ internal readonly IntPtr NewGlobalRefPointer { get; init; }
+ internal readonly IntPtr DeleteGlobalRefPointer { get; init; }
+ internal readonly IntPtr DeleteLocalRefPointer { get; init; }
+ internal readonly IntPtr IsSameObjectPointer { get; init; }
+ internal readonly IntPtr NewLocalRefPointer { get; init; }
+ internal readonly IntPtr EnsureLocalCapacityPointer { get; init; }
+ internal readonly IntPtr AllocObjectPointer { get; init; }
+ internal readonly IntPtr NewObjectPointer { get; init; }
+ internal readonly IntPtr NewObjectVPointer { get; init; }
+ internal readonly IntPtr NewObjectAPointer { get; init; }
+ internal readonly IntPtr GetObjectClassPointer { get; init; }
+ internal readonly IntPtr IsInstanceOfPointer { get; init; }
+ internal readonly IntPtr GetMethodIdPointer { get; init; }
+ private readonly IntPtr CallObjectMethodPointer { get; init; }
+ private readonly IntPtr CallObjectMethodVPointer { get; init; }
+ internal readonly IntPtr CallObjectMethodAPointer { get; init; }
+ private readonly IntPtr CallBooleanMethodPointer { get; init; }
+ private readonly IntPtr CallBooleanMethodVPointer { get; init; }
+ internal readonly IntPtr CallBooleanMethodAPointer { get; init; }
+ private readonly IntPtr CallByteMethodPointer { get; init; }
+ private readonly IntPtr CallByteMethodVPointer { get; init; }
+ internal readonly IntPtr CallByteMethodAPointer { get; init; }
+ private readonly IntPtr CallCharMethodPointer { get; init; }
+ private readonly IntPtr CallCharMethodVPointer { get; init; }
+ internal readonly IntPtr CallCharMethodAPointer { get; init; }
+ private readonly IntPtr CallShortMethodPointer { get; init; }
+ private readonly IntPtr CallShortMethodVPointer { get; init; }
+ internal readonly IntPtr CallShortMethodAPointer { get; init; }
+ private readonly IntPtr CallIntMethodPointer { get; init; }
+ private readonly IntPtr CallIntMethodVPointer { get; init; }
+ internal readonly IntPtr CallIntMethodAPointer { get; init; }
+ private readonly IntPtr CallLongMethodPointer { get; init; }
+ private readonly IntPtr CallLongMethodVPointer { get; init; }
+ internal readonly IntPtr CallLongMethodAPointer { get; init; }
+ private readonly IntPtr CallFloatMethodPointer { get; init; }
+ private readonly IntPtr CallFloatMethodVPointer { get; init; }
+ internal readonly IntPtr CallFloatMethodAPointer { get; init; }
+ private readonly IntPtr CallDoubleMethodPointer { get; init; }
+ private readonly IntPtr CallDoubleMethodVPointer { get; init; }
+ internal readonly IntPtr CallDoubleMethodAPointer { get; init; }
+ private readonly IntPtr CallVoidMethodPointer { get; init; }
+ private readonly IntPtr CallVoidMethodVPointer { get; init; }
+ internal readonly IntPtr CallVoidMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualObjectMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualObjectMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualObjectMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualBooleanMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualBooleanMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualBooleanMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualByteMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualByteMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualByteMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualCharMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualCharMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualCharMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualShortMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualShortMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualShortMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualIntMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualIntMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualIntMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualLongMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualLongMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualLongMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualFloatMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualFloatMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualFloatMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualDoubleMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualDoubleMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualDoubleMethodAPointer { get; init; }
+ private readonly IntPtr CallNonVirtualVoidMethodPointer { get; init; }
+ private readonly IntPtr CallNonVirtualVoidMethodVPointer { get; init; }
+ internal readonly IntPtr CallNonVirtualVoidMethodAPointer { get; init; }
+ internal readonly IntPtr GetFieldIdPointer { get; init; }
+ internal readonly IntPtr GetObjectFieldPointer { get; init; }
+ internal readonly IntPtr GetBooleanFieldPointer { get; init; }
+ internal readonly IntPtr GetByteFieldPointer { get; init; }
+ internal readonly IntPtr GetCharFieldPointer { get; init; }
+ internal readonly IntPtr GetShortFieldPointer { get; init; }
+ internal readonly IntPtr GetIntFieldPointer { get; init; }
+ internal readonly IntPtr GetLongFieldPointer { get; init; }
+ internal readonly IntPtr GetFloatFieldPointer { get; init; }
+ internal readonly IntPtr GetDoubleFieldPointer { get; init; }
+ internal readonly IntPtr SetObjectFieldPointer { get; init; }
+ internal readonly IntPtr SetBooleanFieldPointer { get; init; }
+ internal readonly IntPtr SetByteFieldPointer { get; init; }
+ internal readonly IntPtr SetCharFieldPointer { get; init; }
+ internal readonly IntPtr SetShortFieldPointer { get; init; }
+ internal readonly IntPtr SetIntFieldPointer { get; init; }
+ internal readonly IntPtr SetLongFieldPointer { get; init; }
+ internal readonly IntPtr SetFloatFieldPointer { get; init; }
+ internal readonly IntPtr SetDoubleFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticMethodIdPointer { get; init; }
+ private readonly IntPtr CallStaticObjectMethodPointer { get; init; }
+ private readonly IntPtr CallStaticObjectMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticObjectMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticBooleanMethodPointer { get; init; }
+ private readonly IntPtr CallStaticBooleanMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticBooleanMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticByteMethodPointer { get; init; }
+ private readonly IntPtr CallStaticByteMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticByteMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticCharMethodPointer { get; init; }
+ private readonly IntPtr CallStaticCharMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticCharMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticShortMethodPointer { get; init; }
+ private readonly IntPtr CallStaticShortMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticShortMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticIntMethodPointer { get; init; }
+ private readonly IntPtr CallStaticIntMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticIntMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticLongMethodPointer { get; init; }
+ private readonly IntPtr CallStaticLongMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticLongMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticFloatMethodPointer { get; init; }
+ private readonly IntPtr CallStaticFloatMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticFloatMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticDoubleMethodPointer { get; init; }
+ private readonly IntPtr CallStaticDoubleMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticDoubleMethodAPointer { get; init; }
+ private readonly IntPtr CallStaticVoidMethodPointer { get; init; }
+ private readonly IntPtr CallStaticVoidMethodVPointer { get; init; }
+ internal readonly IntPtr CallStaticVoidMethodAPointer { get; init; }
+ internal readonly IntPtr GetStaticFieldIdPointer { get; init; }
+ internal readonly IntPtr GetStaticObjectFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticBooleanFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticByteFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticCharFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticShortFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticIntFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticLongFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticFloatFieldPointer { get; init; }
+ internal readonly IntPtr GetStaticDoubleFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticObjectFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticBooleanFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticByteFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticCharFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticShortFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticIntFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticLongFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticFloatFieldPointer { get; init; }
+ internal readonly IntPtr SetStaticDoubleFieldPointer { get; init; }
+ internal readonly IntPtr NewStringPointer { get; init; }
+ internal readonly IntPtr GetStringLengthPointer { get; init; }
+ internal readonly IntPtr GetStringCharsPointer { get; init; }
+ internal readonly IntPtr ReleaseStringCharsPointer { get; init; }
+ internal readonly IntPtr NewStringUtfPointer { get; init; }
+ internal readonly IntPtr GetStringUtfLengthPointer { get; init; }
+ internal readonly IntPtr GetStringUtfCharsPointer { get; init; }
+ internal readonly IntPtr ReleaseStringUtfCharsPointer { get; init; }
+ internal readonly IntPtr GetArrayLengthPointer { get; init; }
+ internal readonly IntPtr NewObjectArrayPointer { get; init; }
+ internal readonly IntPtr GetObjectArrayElementPointer { get; init; }
+ internal readonly IntPtr SetObjectArrayElementPointer { get; init; }
+ internal readonly IntPtr NewBooleanArrayPointer { get; init; }
+ internal readonly IntPtr NewByteArrayPointer { get; init; }
+ internal readonly IntPtr NewCharArrayPointer { get; init; }
+ internal readonly IntPtr NewShortArrayPointer { get; init; }
+ internal readonly IntPtr NewIntArrayPointer { get; init; }
+ internal readonly IntPtr NewLongArrayPointer { get; init; }
+ internal readonly IntPtr NewFloatArrayPointer { get; init; }
+ internal readonly IntPtr NewDoubleArrayPointer { get; init; }
+ internal readonly IntPtr GetBooleanArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetByteArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetCharArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetShortArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetIntArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetLongArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetFloatArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetDoubleArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseBooleanArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseByteArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseCharArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseShortArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseIntArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseLongArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseFloatArrayElementsPointer { get; init; }
+ internal readonly IntPtr ReleaseDoubleArrayElementsPointer { get; init; }
+ internal readonly IntPtr GetBooleanArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetByteArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetCharArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetShortArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetIntArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetLongArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetFloatArrayRegionPointer { get; init; }
+ internal readonly IntPtr GetDoubleArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetBooleanArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetByteArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetCharArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetShortArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetIntArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetLongArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetFloatArrayRegionPointer { get; init; }
+ internal readonly IntPtr SetDoubleArrayRegionPointer { get; init; }
+ internal readonly IntPtr RegisterNativesPointer { get; init; }
+ internal readonly IntPtr UnregisterNativesPointer { get; init; }
+ internal readonly IntPtr MonitorEnterPointer { get; init; }
+ internal readonly IntPtr MonitorExitPointer { get; init; }
+ internal readonly IntPtr GetJavaVMPointer { get; init; }
+ internal readonly IntPtr GetStringRegionPointer { get; init; }
+ internal readonly IntPtr GetStringUtfRegionPointer { get; init; }
+ internal readonly IntPtr GetPrimitiveArrayCriticalPointer { get; init; }
+ internal readonly IntPtr ReleasePrimitiveArrayCriticalPointer { get; init; }
+ internal readonly IntPtr GetStringCriticalPointer { get; init; }
+ internal readonly IntPtr ReleaseStringCriticalPointer { get; init; }
+ internal readonly IntPtr NewWeakGlobalRefPointer { get; init; }
+ internal readonly IntPtr DeleteWeakGlobalRefPointer { get; init; }
+ internal readonly IntPtr ExceptionCheckPointer { get; init; }
+ internal readonly IntPtr NewDirectByteBufferPointer { get; init; }
+ internal readonly IntPtr GetDirectBufferAddressPointer { get; init; }
+ internal readonly IntPtr GetDirectBufferCapacityPointer { get; init; }
+ internal readonly IntPtr GetObjectRefTypePointer { get; init; }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JNativeMethod.cs b/src/LibRyujinx/Android/Jni/Values/JNativeMethod.cs
new file mode 100644
index 00000000..cf5c512e
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JNativeMethod.cs
@@ -0,0 +1,11 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Values;
+
+public readonly struct JNativeMethod
+{
+ internal ReadOnlyValPtr Name { get; init; }
+ internal ReadOnlyValPtr Signature { get; init; }
+ internal IntPtr Pointer { get; init; }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JValue.cs b/src/LibRyujinx/Android/Jni/Values/JValue.cs
new file mode 100644
index 00000000..45d07788
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JValue.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Runtime.CompilerServices;
+
+using LibRyujinx.Jni.References;
+
+using Rxmxnx.PInvoke;
+
+namespace LibRyujinx.Jni.Values;
+
+internal readonly struct JValue
+{
+ private delegate Boolean IsDefaultDelegate(in JValue value);
+
+ private static readonly Int32 size = NativeUtilities.SizeOf();
+
+ private static readonly IsDefaultDelegate isDefault = JValue.GetIsDefault();
+
+#pragma warning disable 0649
+#pragma warning disable 0169
+ private readonly Byte _value1;
+ private readonly Byte _value2;
+ private readonly Int16 _value3;
+ private readonly Int32 _value4;
+#pragma warning restore 0169
+#pragma warning restore 0649
+
+ public Boolean IsDefault => JValue.isDefault(this);
+
+ public static JValue Create(in ReadOnlySpan source)
+ {
+ Byte[] result = new Byte[JValue.size];
+ for (Int32 i = 0; i < source.Length; i++)
+ result[i] = source[i];
+ return result.ToValue();
+ }
+
+ private static IsDefaultDelegate GetIsDefault() => Environment.Is64BitProcess ? JValue.DefaultLong : JValue.Default;
+
+ private static Boolean Default(in JValue jValue)
+ => jValue._value1 + jValue._value2 + jValue._value3 == default && jValue._value4 == default;
+
+ private static Boolean DefaultLong(in JValue jValue)
+ => Unsafe.AsRef(in jValue).Transform() == default;
+
+ public static explicit operator JValue(JObjectLocalRef a) => JValue.Create(NativeUtilities.AsBytes(in a));
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JavaVMAttachArgs.cs b/src/LibRyujinx/Android/Jni/Values/JavaVMAttachArgs.cs
new file mode 100644
index 00000000..75bc87fb
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JavaVMAttachArgs.cs
@@ -0,0 +1,13 @@
+using LibRyujinx.Jni.References;
+
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Values;
+
+public readonly struct JavaVMAttachArgs
+{
+ internal Int32 Version { get; init; }
+ internal ReadOnlyValPtr Name { get; init; }
+ internal JObjectLocalRef Group { get; init; }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JavaVMInitArgs.cs b/src/LibRyujinx/Android/Jni/Values/JavaVMInitArgs.cs
new file mode 100644
index 00000000..424dc3bb
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JavaVMInitArgs.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace LibRyujinx.Jni.Values
+{
+ public readonly struct JavaVMInitArgs
+ {
+ internal Int32 Version { get; init; }
+ internal Int32 OptionsLenght { get; init; }
+ internal IntPtr Options { get; init; }
+ internal Boolean IgnoreUnrecognized { get; init; }
+ }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JavaVMOption.cs b/src/LibRyujinx/Android/Jni/Values/JavaVMOption.cs
new file mode 100644
index 00000000..84f9c969
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JavaVMOption.cs
@@ -0,0 +1,10 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Values;
+
+public readonly struct JavaVMOption
+{
+ internal ReadOnlyValPtr Name { get; init; }
+ internal IntPtr ExtraInfo { get; init; }
+}
diff --git a/src/LibRyujinx/Android/Jni/Values/JavaVMValue.cs b/src/LibRyujinx/Android/Jni/Values/JavaVMValue.cs
new file mode 100644
index 00000000..6767f6db
--- /dev/null
+++ b/src/LibRyujinx/Android/Jni/Values/JavaVMValue.cs
@@ -0,0 +1,29 @@
+using Rxmxnx.PInvoke;
+using System;
+
+namespace LibRyujinx.Jni.Values;
+
+internal readonly struct JavaVMValue : IEquatable
+{
+#pragma warning disable 0649
+ private readonly IntPtr _value;
+#pragma warning restore 0649
+
+ #region Operators
+ public static Boolean operator ==(JavaVMValue a, JavaVMValue b) => a._value.Equals(b._value);
+ public static Boolean operator !=(JavaVMValue a, JavaVMValue b) => !a._value.Equals(b._value);
+ #endregion
+
+ #region Public Properties
+ internal readonly ref JInvokeInterface Functions => ref this._value.GetUnsafeReference();
+ #endregion
+
+ #region Public Methods
+ public Boolean Equals(JavaVMValue other) => this._value.Equals(other._value);
+ #endregion
+
+ #region Overrided Methods
+ public override Boolean Equals(Object obj) => obj is JavaVMValue other && this.Equals(other);
+ public override Int32 GetHashCode() => this._value.GetHashCode();
+ #endregion
+}
diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs
new file mode 100644
index 00000000..7e1070f8
--- /dev/null
+++ b/src/LibRyujinx/Android/JniExportedMethods.cs
@@ -0,0 +1,566 @@
+using LibRyujinx.Android;
+using LibRyujinx.Jni.Pointers;
+using Ryujinx.Audio.Backends.OpenAL;
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Logging.Targets;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.Input;
+using Silk.NET.Core.Loader;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.KHR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ private static long _surfacePtr;
+ private static long _window = 0;
+
+ public static VulkanLoader? VulkanLoader { get; private set; }
+
+ [DllImport("libryujinxjni")]
+ internal extern static void setRenderingThread();
+
+ [DllImport("libryujinxjni")]
+ internal extern static void debug_break(int code);
+
+ [DllImport("libryujinxjni")]
+ internal extern static void setCurrentTransform(long native_window, int transform);
+
+ public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
+
+ [UnmanagedCallersOnly(EntryPoint = "javaInitialize")]
+ public unsafe static bool JniInitialize(IntPtr jpathId, IntPtr jniEnv)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ PlatformInfo.IsBionic = true;
+
+ Logger.AddTarget(
+ new AsyncLogTargetWrapper(
+ new AndroidLogTarget("RyujinxLog"),
+ 1000,
+ AsyncLogTargetOverflowAction.Block
+ ));
+
+ var path = Marshal.PtrToStringAnsi(jpathId);
+
+ var init = Initialize(path);
+
+ Interop.Initialize(new JEnvRef(jniEnv));
+
+ Interop.Test();
+
+ return init;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
+ public static void JnaReloadFileSystem()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SwitchDevice?.ReloadFileSystem();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceInitialize")]
+ public static bool JnaDeviceInitialize(bool isHostMapped,
+ bool useNce,
+ int systemLanguage,
+ int regionCode,
+ bool enableVsync,
+ bool enableDockedMode,
+ bool enablePtc,
+ bool enableInternetAccess,
+ IntPtr timeZonePtr,
+ bool ignoreMissingServices)
+ {
+ debug_break(4);
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ AudioDriver = new OpenALHardwareDeviceDriver();
+
+ var timezone = Marshal.PtrToStringAnsi(timeZonePtr);
+ return InitializeDevice(isHostMapped,
+ useNce,
+ (SystemLanguage)systemLanguage,
+ (RegionCode)regionCode,
+ enableVsync,
+ enableDockedMode,
+ enablePtc,
+ enableInternetAccess,
+ timezone,
+ ignoreMissingServices);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFifo")]
+ public static double JnaGetGameFifo()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
+
+ return stats;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameTime")]
+ public static double JnaGetGameFrameTime()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
+
+ return stats;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameRate")]
+ public static double JnaGetGameFrameRate()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
+
+ return stats;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")]
+ public static bool JNALaunchMiiEditApplet()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ if (SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ return LaunchMiiEditApplet();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetDlcContentList")]
+ public static IntPtr JniGetDlcContentListNative(IntPtr pathPtr, long titleId)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var list = GetDlcContentList(Marshal.PtrToStringAnsi(pathPtr) ?? "", (ulong)titleId);
+
+ return CreateStringArray(list);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetDlcTitleId")]
+ public static long JniGetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ return Marshal.StringToHGlobalAnsi(GetDlcTitleId(Marshal.PtrToStringAnsi(pathPtr) ?? "", Marshal.PtrToStringAnsi(ncaPath) ?? ""));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")]
+ public static void JniSignalEmulationCloseNative()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SignalEmulationClose();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")]
+ public static void JniCloseEmulationNative()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ CloseEmulation();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceLoadDescriptor")]
+ public static bool JnaLoadApplicationNative(int descriptor, int type, int updateDescriptor)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ if (SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ var stream = OpenFile(descriptor);
+ var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
+
+ return LoadApplication(stream, (FileType)type, update);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceVerifyFirmware")]
+ public static IntPtr JniVerifyFirmware(int descriptor, bool isXci)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+
+ var stream = OpenFile(descriptor);
+
+ IntPtr stringHandle = 0;
+ string? version = "0.0";
+
+ try
+ {
+ version = VerifyFirmware(stream, isXci)?.VersionString;
+ }
+ catch (Exception _)
+ {
+ Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
+ }
+
+ if (version != null)
+ {
+ stringHandle = Marshal.StringToHGlobalAnsi(version);
+ }
+
+ return stringHandle;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceInstallFirmware")]
+ public static void JniInstallFirmware(int descriptor, bool isXci)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+
+ var stream = OpenFile(descriptor);
+
+ InstallFirmware(stream, isXci);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetInstalledFirmwareVersion")]
+ public static IntPtr JniGetInstalledFirmwareVersion()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+
+ var version = GetInstalledFirmwareVersion() ?? "0.0";
+ return Marshal.StringToHGlobalAnsi(version);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphicsInitialize")]
+ public static bool JnaGraphicsInitialize(float resScale,
+ float maxAnisotropy,
+ bool fastGpuTime,
+ bool fast2DCopy,
+ bool enableMacroJit,
+ bool enableMacroHLE,
+ bool enableShaderCache,
+ bool enableTextureRecompression,
+ int backendThreading)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SearchPathContainer.Platform = UnderlyingPlatform.Android;
+ return InitializeGraphics(new GraphicsConfiguration()
+ {
+ ResScale = resScale,
+ MaxAnisotropy = maxAnisotropy,
+ FastGpuTime = fastGpuTime,
+ Fast2DCopy = fast2DCopy,
+ EnableMacroJit = enableMacroJit,
+ EnableMacroHLE = enableMacroHLE,
+ EnableShaderCache = enableShaderCache,
+ EnableTextureRecompression = enableTextureRecompression,
+ BackendThreading = (BackendThreading)backendThreading
+ });
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphicsInitializeRenderer")]
+ public unsafe static bool JnaGraphicsInitializeRenderer(char** extensionsArray,
+ int extensionsLength,
+ long driverHandle)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ if (Renderer != null)
+ {
+ return false;
+ }
+
+ List extensions = new();
+
+ for (int i = 0; i < extensionsLength; i++)
+ {
+ extensions.Add(Marshal.PtrToStringAnsi((IntPtr)extensionsArray[i]));
+ }
+
+ if (driverHandle != 0)
+ {
+ VulkanLoader = new VulkanLoader((IntPtr)driverHandle);
+ }
+
+ CreateSurface createSurfaceFunc = instance =>
+ {
+ _surfacePtr = Interop.GetSurfacePtr();
+ _window = Interop.GetWindowsHandle();
+
+ var api = VulkanLoader?.GetApi() ?? Vk.GetApi();
+ if (api.TryGetInstanceExtension(new Instance(instance), out KhrAndroidSurface surfaceExtension))
+ {
+ var createInfo = new AndroidSurfaceCreateInfoKHR
+ {
+ SType = StructureType.AndroidSurfaceCreateInfoKhr,
+ Window = (nint*)_surfacePtr,
+ };
+
+ var result = surfaceExtension.CreateAndroidSurface(new Instance(instance), createInfo, null, out var surface);
+
+ return (nint)surface.Handle;
+ }
+
+ return IntPtr.Zero;
+ };
+
+ return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetSize")]
+ public static void JnaSetRendererSizeNative(int width, int height)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ Renderer?.Window?.SetSize(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphicsRendererRunLoop")]
+ public static void JniRunLoopNative()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetSwapBuffersCallback(() =>
+ {
+ var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime();
+ Interop.FrameEnded(time);
+ });
+ RunLoop();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "loggingSetEnabled")]
+ public static void JniSetLoggingEnabledNative(int logLevel, bool enabled)
+ {
+ Logger.SetEnable((LogLevel)logLevel, enabled);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "loggingEnabledGraphicsLog")]
+ public static void JniSetLoggingEnabledGraphicsLog(bool enabled)
+ {
+ _enableGraphicsLogging = enabled;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "deviceGetGameInfo")]
+ public unsafe static void JniGetGameInfo(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ using var stream = OpenFile(fileDescriptor);
+ var ext = Marshal.PtrToStringAnsi(extension);
+ var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
+ var i = (GameInfoNative*)infoPtr;
+ var n = new GameInfoNative(info);
+ i->TitleId = n.TitleId;
+ i->TitleName = n.TitleName;
+ i->Version = n.Version;
+ i->FileSize = n.FileSize;
+ i->Icon = n.Icon;
+ i->Version = n.Version;
+ i->Developer = n.Developer;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetVsync")]
+ public static void JnaSetVsyncStateNative(bool enabled)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetVsyncState(enabled);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputInitialize")]
+ public static void JnaInitializeInput(int width, int height)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ InitializeInput(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetClientSize")]
+ public static void JnaSetClientSize(int width, int height)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetClientSize(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetTouchPoint")]
+ public static void JnaSetTouchPoint(int x, int y)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetTouchPoint(x, y);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputReleaseTouchPoint")]
+ public static void JnaReleaseTouchPoint()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ ReleaseTouchPoint();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputUpdate")]
+ public static void JniUpdateInput()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ UpdateInput();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetButtonPressed")]
+ public static void JnaSetButtonPressed(int button, int id)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetButtonPressed((GamepadButtonInputId)button, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetButtonReleased")]
+ public static void JnaSetButtonReleased(int button, int id)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetButtonReleased((GamepadButtonInputId)button, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetAccelerometerData")]
+ public static void JniSetAccelerometerData(float x, float y, float z, int id)
+ {
+ var accel = new Vector3(x, y, z);
+ SetAccelerometerData(accel, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetGyroData")]
+ public static void JniSetGyroData(float x, float y, float z, int id)
+ {
+ var gryo = new Vector3(x, y, z);
+ SetGryoData(gryo, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputSetStickAxis")]
+ public static void JnaSetStickAxis(int stick, float x, float y, int id)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ SetStickAxis((StickInputId)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "inputConnectGamepad")]
+ public static int JnaConnectGamepad(int index)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ return ConnectGamepad(index);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userGetOpenedUser")]
+ public static IntPtr JniGetOpenedUser()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = GetOpenedUser();
+ var ptr = Marshal.StringToHGlobalAnsi(userId);
+
+ return ptr;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userGetUserPicture")]
+ public static IntPtr JniGetUserPicture(IntPtr userIdPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ return Marshal.StringToHGlobalAnsi(GetUserPicture(userId));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userSetUserPicture")]
+ public static void JniGetUserPicture(IntPtr userIdPtr, IntPtr picturePtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+ var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
+
+ SetUserPicture(userId, picture);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userGetUserName")]
+ public static IntPtr JniGetUserName(IntPtr userIdPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ return Marshal.StringToHGlobalAnsi(GetUserName(userId));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userSetUserName")]
+ public static void JniSetUserName(IntPtr userIdPtr, IntPtr userNamePtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+ var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
+
+ SetUserName(userId, userName);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userGetAllUsers")]
+ public static IntPtr JniGetAllUsers()
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var users = GetAllUsers();
+
+ return CreateStringArray(users.ToList());
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userAddUser")]
+ public static void JniAddUser(IntPtr userNamePtr, IntPtr picturePtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
+ var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
+
+ AddUser(userName, picture);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userDeleteUser")]
+ public static void JniDeleteUser(IntPtr userIdPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ DeleteUser(userId);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "uiHandlerSetup")]
+ public static void JniSetupUiHandler()
+ {
+ SetupUiHandler();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")]
+ public static void JniSetUiHandlerResponse(bool isOkPressed, IntPtr input)
+ {
+ SetUiHandlerResponse(isOkPressed, Marshal.PtrToStringAnsi(input) ?? "");
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userOpenUser")]
+ public static void JniOpenUser(IntPtr userIdPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ OpenUser(userId);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "userCloseUser")]
+ public static void JniCloseUser(IntPtr userIdPtr)
+ {
+ Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ CloseUser(userId);
+ }
+ }
+
+ internal static partial class Logcat
+ {
+ [LibraryImport("liblog", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void __android_log_print(LogLevel level, string? tag, string format, string args, IntPtr ptr);
+
+ internal static void AndroidLogPrint(LogLevel level, string? tag, string message) =>
+ __android_log_print(level, tag, "%s", message, IntPtr.Zero);
+
+ internal enum LogLevel
+ {
+ Unknown = 0x00,
+ Default = 0x01,
+ Verbose = 0x02,
+ Debug = 0x03,
+ Info = 0x04,
+ Warn = 0x05,
+ Error = 0x06,
+ Fatal = 0x07,
+ Silent = 0x08,
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Device.cs b/src/LibRyujinx/LibRyujinx.Device.cs
new file mode 100644
index 00000000..f92f2de2
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Device.cs
@@ -0,0 +1,235 @@
+using LibHac.Ncm;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Microsoft.Win32.SafeHandles;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.SystemState;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ public static bool InitializeDevice(bool isHostMapped,
+ bool useHypervisor,
+ SystemLanguage systemLanguage,
+ RegionCode regionCode,
+ bool enableVsync,
+ bool enableDockedMode,
+ bool enablePtc,
+ bool enableInternetAccess,
+ string? timeZone,
+ bool ignoreMissingServices)
+ {
+ if (SwitchDevice == null)
+ {
+ return false;
+ }
+
+ return SwitchDevice.InitializeContext(isHostMapped,
+ useHypervisor,
+ systemLanguage,
+ regionCode,
+ enableVsync,
+ enableDockedMode,
+ enablePtc,
+ enableInternetAccess,
+ timeZone,
+ ignoreMissingServices);
+ }
+
+ public static void InstallFirmware(Stream stream, bool isXci)
+ {
+ SwitchDevice?.ContentManager.InstallFirmware(stream, isXci);
+ }
+
+ public static string GetInstalledFirmwareVersion()
+ {
+ var version = SwitchDevice?.ContentManager.GetCurrentFirmwareVersion();
+
+ if (version != null)
+ {
+ return version.VersionString;
+ }
+
+ return String.Empty;
+ }
+
+ public static SystemVersion? VerifyFirmware(Stream stream, bool isXci)
+ {
+ return SwitchDevice?.ContentManager?.VerifyFirmwarePackage(stream, isXci) ?? null;
+ }
+
+ public static bool LoadApplication(Stream stream, FileType type, Stream? updateStream = null)
+ {
+ var emulationContext = SwitchDevice.EmulationContext;
+ return type switch
+ {
+ FileType.None => false,
+ FileType.Nsp => emulationContext?.LoadNsp(stream, 0, updateStream) ?? false,
+ FileType.Xci => emulationContext?.LoadXci(stream, 0, updateStream) ?? false,
+ FileType.Nro => emulationContext?.LoadProgram(stream, true, "") ?? false,
+ };
+ }
+
+ public static bool LaunchMiiEditApplet()
+ {
+ string contentPath = SwitchDevice.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
+
+ return LoadApplication(contentPath);
+ }
+
+ 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;
+ }
+
+ return true;
+ }
+
+ public static void SignalEmulationClose()
+ {
+ _isStopped = true;
+ _isActive = false;
+ }
+
+ public static void CloseEmulation()
+ {
+ if (SwitchDevice == null)
+ return;
+
+ _npadManager?.Dispose();
+ _npadManager = null;
+
+ _touchScreenManager?.Dispose();
+ _touchScreenManager = null;
+
+ SwitchDevice!.InputManager?.Dispose();
+ SwitchDevice!.InputManager = null;
+ _inputManager = null;
+
+ if (Renderer != null)
+ {
+ _gpuDoneEvent.WaitOne();
+ _gpuDoneEvent.Dispose();
+ _gpuDoneEvent = null;
+ SwitchDevice?.DisposeContext();
+ Renderer = null;
+ }
+ }
+
+ private static FileStream OpenFile(int descriptor)
+ {
+ var safeHandle = new SafeFileHandle(descriptor, false);
+
+ return new FileStream(safeHandle, FileAccess.ReadWrite);
+ }
+
+ public enum FileType
+ {
+ None,
+ Nsp,
+ Xci,
+ Nro
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs
new file mode 100644
index 00000000..bfe07713
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Graphics.cs
@@ -0,0 +1,248 @@
+using LibRyujinx.Android;
+using LibRyujinx.Shared;
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Multithreading;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.Graphics.Gpu.Shader;
+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;
+ private static ManualResetEvent _gpuDoneEvent;
+ private static bool _enableGraphicsLogging;
+
+ 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; }
+
+ 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.EnableTextureRecompression = graphicsConfiguration.EnableTextureRecompression;
+
+ GraphicsConfiguration = graphicsConfiguration;
+
+ return true;
+ }
+
+ 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(createSurfaceFunc == null ? null : (ulong?)createSurfaceFunc(instance.Handle)),
+ () => requiredExtensions,
+ null);
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void SetRendererSize(int width, int height)
+ {
+ Renderer?.Window?.SetSize(width, height);
+ }
+
+ 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!;
+ _gpuDoneEvent = new ManualResetEvent(true);
+
+ device.Gpu.Renderer.Initialize(_enableGraphicsLogging ? GraphicsDebugLevel.All : GraphicsDebugLevel.None);
+
+ _gpuCancellationTokenSource = new CancellationTokenSource();
+
+ device.Gpu.ShaderCacheStateChanged += LoadProgressStateChangedHandler;
+ device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += LoadProgressStateChangedHandler;
+
+ try
+ {
+ device.Gpu.Renderer.RunLoop(() =>
+ {
+ _gpuDoneEvent.Reset();
+ device.Gpu.SetGpuThread();
+ device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
+
+ _isActive = true;
+
+ if (Ryujinx.Common.PlatformInfo.IsBionic)
+ {
+ setRenderingThread();
+ }
+
+ while (_isActive)
+ {
+ if (_isStopped)
+ {
+ break;
+ }
+
+ if (device.WaitFifo())
+ {
+ device.Statistics.RecordFifoStart();
+ device.ProcessFrame();
+ device.Statistics.RecordFifoEnd();
+ }
+
+ while (device.ConsumeFrameAvailable())
+ {
+ device.PresentFrame(() =>
+ {
+ if (device.Gpu.Renderer is ThreadedRenderer threaded && threaded.BaseRenderer is VulkanRenderer vulkanRenderer)
+ {
+ setCurrentTransform(_window, (int)vulkanRenderer.CurrentTransform);
+ }
+ _swapBuffersCallback?.Invoke();
+ });
+ }
+ }
+
+ if (device.Gpu.Renderer is ThreadedRenderer threaded)
+ {
+ threaded.FlushThreadedCommands();
+ }
+
+ _gpuDoneEvent.Set();
+ });
+ }
+ finally
+ {
+ device.Gpu.ShaderCacheStateChanged -= LoadProgressStateChangedHandler;
+ device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= LoadProgressStateChangedHandler;
+ }
+ }
+
+ private static void LoadProgressStateChangedHandler(T state, int current, int total) where T : Enum
+ {
+ void SetInfo(string status, float value)
+ {
+ if(Ryujinx.Common.PlatformInfo.IsBionic)
+ {
+ Interop.UpdateProgress(status, value);
+ }
+ }
+ var status = $"{current} / {total}";
+ var progress = current / (float)total;
+ if (float.IsNaN(progress))
+ progress = 0;
+
+ switch (state)
+ {
+ case LoadState ptcState:
+ if (float.IsNaN((progress)))
+ progress = 0;
+
+ switch (ptcState)
+ {
+ case LoadState.Unloaded:
+ case LoadState.Loading:
+ SetInfo($"Loading PTC {status}", progress);
+ break;
+ case LoadState.Loaded:
+ SetInfo($"PTC Loaded", -1);
+ break;
+ }
+ break;
+ case ShaderCacheState shaderCacheState:
+ switch (shaderCacheState)
+ {
+ case ShaderCacheState.Start:
+ case ShaderCacheState.Loading:
+ SetInfo($"Compiling Shaders {status}", progress);
+ break;
+ case ShaderCacheState.Packaging:
+ SetInfo($"Packaging Shaders {status}", progress);
+ break;
+ case ShaderCacheState.Loaded:
+ SetInfo($"Shaders Loaded", -1);
+ break;
+ }
+ break;
+ default:
+ throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
+ }
+ }
+
+ 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 = true;
+ public bool EnableTextureRecompression = false;
+ public BackendThreading BackendThreading = BackendThreading.Auto;
+ public AspectRatio AspectRatio = AspectRatio.Fixed16x9;
+
+ 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 00000000..390eeb54
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Input.cs
@@ -0,0 +1,525 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+using Ryujinx.Input;
+using Ryujinx.Input.HLE;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
+using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
+using StickInputId = Ryujinx.Input.StickInputId;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ private static VirtualGamepadDriver? _gamepadDriver;
+ private static VirtualTouchScreen? _virtualTouchScreen;
+ private static VirtualTouchScreenDriver? _touchScreenDriver;
+ private static TouchScreenManager? _touchScreenManager;
+ private static InputManager? _inputManager;
+ private static NpadManager? _npadManager;
+ private static InputConfig[] _configs;
+
+ public static void InitializeInput(int width, int height)
+ {
+ if(SwitchDevice!.InputManager != null)
+ {
+ throw new InvalidOperationException("Input is already initialized");
+ }
+
+ _gamepadDriver = new VirtualGamepadDriver(4);
+ _configs = new InputConfig[4];
+ _virtualTouchScreen = new VirtualTouchScreen();
+ _touchScreenDriver = new VirtualTouchScreenDriver(_virtualTouchScreen);
+ _inputManager = new InputManager(null, _gamepadDriver);
+ _inputManager.SetMouseDriver(_touchScreenDriver);
+ _npadManager = _inputManager.CreateNpadManager();
+
+ SwitchDevice!.InputManager = _inputManager;
+
+ _touchScreenManager = _inputManager.CreateTouchScreenManager();
+ _touchScreenManager.Initialize(SwitchDevice!.EmulationContext);
+
+ _npadManager.Initialize(SwitchDevice.EmulationContext, new List(), false, false);
+
+ _virtualTouchScreen.ClientSize = new Size(width, height);
+ }
+
+ public static void SetClientSize(int width, int height)
+ {
+ _virtualTouchScreen!.ClientSize = new Size(width, height);
+ }
+
+ public static void SetTouchPoint(int x, int y)
+ {
+ _virtualTouchScreen?.SetPosition(x, y);
+ }
+
+ public static void ReleaseTouchPoint()
+ {
+ _virtualTouchScreen?.ReleaseTouch();
+ }
+
+ public static void SetButtonPressed(GamepadButtonInputId button, int id)
+ {
+ _gamepadDriver?.SetButtonPressed(button, id);
+ }
+
+ public static void SetButtonReleased(GamepadButtonInputId button, int id)
+ {
+ _gamepadDriver?.SetButtonReleased(button, id);
+ }
+
+ public static void SetAccelerometerData(Vector3 accel, int id)
+ {
+ _gamepadDriver?.SetAccelerometerData(accel, id);
+ }
+
+ public static void SetGryoData(Vector3 gyro, int id)
+ {
+ _gamepadDriver?.SetGryoData(gyro, id);
+ }
+
+ public static void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId)
+ {
+ _gamepadDriver?.SetStickAxis(stick, axes, deviceId);
+ }
+
+ public static int ConnectGamepad(int index)
+ {
+ var gamepad = _gamepadDriver?.GetGamepad(index);
+ if (gamepad != null)
+ {
+ var config = CreateDefaultInputConfig();
+
+ config.Id = gamepad.Id;
+ config.PlayerIndex = (PlayerIndex)index;
+
+ _configs[index] = config;
+ }
+
+ _npadManager?.ReloadConfiguration(_configs.Where(x => x != null).ToList(), false, false);
+
+ return int.TryParse(gamepad?.Id, out var idInt) ? idInt : -1;
+ }
+
+ private static InputConfig CreateDefaultInputConfig()
+ {
+ return new StandardControllerInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.GamepadSDL2,
+ Id = null,
+ ControllerType = ControllerType.ProController,
+ DeadzoneLeft = 0.1f,
+ DeadzoneRight = 0.1f,
+ RangeLeft = 1.0f,
+ RangeRight = 1.0f,
+ TriggerThreshold = 0.5f,
+ LeftJoycon = new LeftJoyconCommonConfig
+ {
+ DpadUp = ConfigGamepadInputId.DpadUp,
+ DpadDown = ConfigGamepadInputId.DpadDown,
+ DpadLeft = ConfigGamepadInputId.DpadLeft,
+ DpadRight = ConfigGamepadInputId.DpadRight,
+ ButtonMinus = ConfigGamepadInputId.Minus,
+ ButtonL = ConfigGamepadInputId.LeftShoulder,
+ ButtonZl = ConfigGamepadInputId.LeftTrigger,
+ ButtonSl = ConfigGamepadInputId.Unbound,
+ ButtonSr = ConfigGamepadInputId.Unbound,
+ },
+
+ LeftJoyconStick = new JoyconConfigControllerStick
+ {
+ Joystick = ConfigStickInputId.Left,
+ StickButton = ConfigGamepadInputId.LeftStick,
+ InvertStickX = false,
+ InvertStickY = false,
+ Rotate90CW = false,
+ },
+
+ RightJoycon = new RightJoyconCommonConfig
+ {
+ ButtonA = ConfigGamepadInputId.A,
+ ButtonB = ConfigGamepadInputId.B,
+ ButtonX = ConfigGamepadInputId.X,
+ ButtonY = ConfigGamepadInputId.Y,
+ ButtonPlus = ConfigGamepadInputId.Plus,
+ ButtonR = ConfigGamepadInputId.RightShoulder,
+ ButtonZr = ConfigGamepadInputId.RightTrigger,
+ ButtonSl = ConfigGamepadInputId.Unbound,
+ ButtonSr = ConfigGamepadInputId.Unbound,
+ },
+
+ RightJoyconStick = new JoyconConfigControllerStick
+ {
+ Joystick = ConfigStickInputId.Right,
+ StickButton = ConfigGamepadInputId.RightStick,
+ InvertStickX = false,
+ InvertStickY = false,
+ Rotate90CW = false,
+ },
+
+ Motion = new StandardMotionConfigController
+ {
+ MotionBackend = MotionInputBackendType.GamepadDriver,
+ EnableMotion = true,
+ Sensitivity = 100,
+ GyroDeadzone = 1,
+ },
+ Rumble = new RumbleConfigController
+ {
+ StrongRumble = 1f,
+ WeakRumble = 1f,
+ EnableRumble = false
+ }
+ };
+ }
+
+ public static void UpdateInput()
+ {
+ _npadManager?.Update(GraphicsConfiguration.AspectRatio.ToFloat());
+
+ if(!_touchScreenManager!.Update(true, _virtualTouchScreen!.IsButtonPressed(MouseButton.Button1), GraphicsConfiguration.AspectRatio.ToFloat()))
+ {
+ SwitchDevice!.EmulationContext?.Hid.Touchscreen.Update();
+ }
+ }
+ }
+
+ public class VirtualTouchScreen : IMouse
+ {
+ public Size ClientSize { get; set; }
+
+ public bool[] Buttons { get; }
+
+ public VirtualTouchScreen()
+ {
+ Buttons = new bool[2];
+ }
+
+ public Vector2 CurrentPosition { get; private set; }
+ public Vector2 Scroll { get; private set; }
+ public string Id => "0";
+ public string Name => "AvaloniaMouse";
+
+ public bool IsConnected => true;
+ public GamepadFeaturesFlag Features => throw new NotImplementedException();
+
+ public void Dispose()
+ {
+
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetPosition(int x, int y)
+ {
+ CurrentPosition = new Vector2(x, y);
+
+ Buttons[0] = true;
+ }
+
+ public void ReleaseTouch()
+ {
+ Buttons[0] = false;
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Vector2 GetPosition()
+ {
+ return CurrentPosition;
+ }
+
+ public Vector2 GetScroll()
+ {
+ return Scroll;
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotImplementedException();
+ }
+
+ public (float, float) GetStick(Ryujinx.Input.StickInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsButtonPressed(MouseButton button)
+ {
+ return Buttons[0];
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class VirtualTouchScreenDriver : IGamepadDriver
+ {
+ private readonly VirtualTouchScreen _virtualTouchScreen;
+
+ public VirtualTouchScreenDriver(VirtualTouchScreen virtualTouchScreen)
+ {
+ _virtualTouchScreen = virtualTouchScreen;
+ }
+
+ public string DriverName => "VirtualTouchDriver";
+
+ public ReadOnlySpan GamepadsIds => new[] { "0" };
+
+
+ public event Action OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ public void Dispose()
+ {
+
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ return _virtualTouchScreen;
+ }
+ }
+
+ public class VirtualGamepadDriver : IGamepadDriver
+ {
+ private readonly int _controllerCount;
+
+ public ReadOnlySpan GamepadsIds => _gamePads.Keys.Select(x => x.ToString()).ToArray();
+
+ public string DriverName => "Virtual";
+
+ public event Action OnGamepadConnected;
+ public event Action OnGamepadDisconnected;
+
+ private Dictionary _gamePads;
+
+ public VirtualGamepadDriver(int controllerCount)
+ {
+ _gamePads = new Dictionary();
+ for (int joystickIndex = 0; joystickIndex < controllerCount; joystickIndex++)
+ {
+ HandleJoyStickConnected(joystickIndex);
+ }
+
+ _controllerCount = controllerCount;
+ }
+
+ private void HandleJoyStickConnected(int joystickDeviceId)
+ {
+ _gamePads[joystickDeviceId] = new VirtualGamepad(this, joystickDeviceId);
+ OnGamepadConnected?.Invoke(joystickDeviceId.ToString());
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Simulate a full disconnect when disposing
+ var ids = GamepadsIds;
+ foreach (string id in ids)
+ {
+ OnGamepadDisconnected?.Invoke(id);
+ }
+
+ _gamePads.Clear();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ return _gamePads[int.Parse(id)];
+ }
+
+ public IGamepad GetGamepad(int index)
+ {
+ return _gamePads[index];
+ }
+
+ public void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId)
+ {
+ if(_gamePads.TryGetValue(deviceId, out var gamePad))
+ {
+ gamePad.StickInputs[(int)stick] = axes;
+ }
+ }
+
+ public void SetButtonPressed(GamepadButtonInputId button, int deviceId)
+ {
+ if (_gamePads.TryGetValue(deviceId, out var gamePad))
+ {
+ gamePad.ButtonInputs[(int)button] = true;
+ }
+ }
+
+ public void SetButtonReleased(GamepadButtonInputId button, int deviceId)
+ {
+ if (_gamePads.TryGetValue(deviceId, out var gamePad))
+ {
+ gamePad.ButtonInputs[(int)button] = false;
+ }
+ }
+
+ public void SetAccelerometerData(Vector3 accel, int deviceId)
+ {
+ if (_gamePads.TryGetValue(deviceId, out var gamePad))
+ {
+ gamePad.Accelerometer = accel;
+ }
+ }
+
+ public void SetGryoData(Vector3 gyro, int deviceId)
+ {
+ if (_gamePads.TryGetValue(deviceId, out var gamePad))
+ {
+ gamePad.Gyro = gyro;
+ }
+ }
+ }
+
+ public class VirtualGamepad : IGamepad
+ {
+ private readonly VirtualGamepadDriver _driver;
+
+ private bool[] _buttonInputs;
+
+ private Vector2[] _stickInputs;
+
+ public VirtualGamepad(VirtualGamepadDriver driver, int id)
+ {
+ _buttonInputs = new bool[(int)GamepadButtonInputId.Count];
+ _stickInputs = new Vector2[(int)StickInputId.Count];
+ _driver = driver;
+ Id = id.ToString();
+ IdInt = id;
+ }
+
+ public void Dispose() { }
+
+ public GamepadFeaturesFlag Features { get; } = GamepadFeaturesFlag.Motion;
+ public string Id { get; }
+
+ internal readonly int IdInt;
+
+ public string Name => Id;
+ public bool IsConnected { get; }
+ public Vector2[] StickInputs { get => _stickInputs; set => _stickInputs = value; }
+ public bool[] ButtonInputs { get => _buttonInputs; set => _buttonInputs = value; }
+ public Vector3 Accelerometer { get; internal set; }
+ public Vector3 Gyro { get; internal set; }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ return _buttonInputs[(int)inputId];
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ var v = _stickInputs[(int)inputId];
+
+ return (v.X, v.Y);
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ if (inputId == MotionInputId.Accelerometer)
+ return Accelerometer;
+ else if (inputId == MotionInputId.Gyroscope)
+ return RadToDegree(Gyro);
+ return new Vector3();
+ }
+
+ private static Vector3 RadToDegree(Vector3 rad)
+ {
+ return rad * (180 / MathF.PI);
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ //throw new System.NotImplementedException();
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ //throw new System.NotImplementedException();
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ //throw new System.NotImplementedException();
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ GamepadStateSnapshot result = default;
+
+ foreach (var button in Enum.GetValues())
+ {
+ // Do not touch state of button already pressed
+ if (button != GamepadButtonInputId.Count && !result.IsPressed(button))
+ {
+ result.SetPressed(button, IsPressed(button));
+ }
+ }
+
+ (float leftStickX, float leftStickY) = GetStick(StickInputId.Left);
+ (float rightStickX, float rightStickY) = GetStick(StickInputId.Right);
+
+ result.SetStick(StickInputId.Left, leftStickX, leftStickY);
+ result.SetStick(StickInputId.Right, rightStickX, rightStickY);
+
+ return result;
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ return new GamepadStateSnapshot();
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.Native.cs b/src/LibRyujinx/LibRyujinx.Native.cs
new file mode 100644
index 00000000..d4a9f6d9
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.Native.cs
@@ -0,0 +1,455 @@
+using LibRyujinx.Shared;
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.OpenGL;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.Input;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ private unsafe static IntPtr CreateStringArray(List strings)
+ {
+ uint size = (uint)(Marshal.SizeOf() * (strings.Count + 1));
+ var array = (char**)Marshal.AllocHGlobal((int)size);
+ Unsafe.InitBlockUnaligned(array, 0, size);
+
+ for (int i = 0; i < strings.Count; i++)
+ {
+ array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
+ }
+
+ return (nint)array;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_initialize")]
+ public static bool InitializeDeviceNative(bool isHostMapped,
+ bool useHypervisor,
+ SystemLanguage systemLanguage,
+ RegionCode regionCode,
+ bool enableVsync,
+ bool enableDockedMode,
+ bool enablePtc,
+ bool enableInternetAccess,
+ IntPtr timeZone,
+ bool ignoreMissingServices)
+ {
+ return InitializeDevice(isHostMapped,
+ useHypervisor,
+ systemLanguage,
+ regionCode,
+ enableVsync,
+ enableDockedMode,
+ enablePtc,
+ enableInternetAccess,
+ Marshal.PtrToStringAnsi(timeZone),
+ ignoreMissingServices);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_reload_file_system")]
+ public static void ReloadFileSystemNative()
+ {
+ SwitchDevice?.ReloadFileSystem();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_load")]
+ public static bool LoadApplicationNative(IntPtr pathPtr)
+ {
+ if (SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ var path = Marshal.PtrToStringAnsi(pathPtr);
+
+ return LoadApplication(path);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_install_firmware")]
+ public static void InstallFirmwareNative(int descriptor, bool isXci)
+ {
+ var stream = OpenFile(descriptor);
+
+ InstallFirmware(stream, isXci);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_installed_firmware_version")]
+ public static IntPtr GetInstalledFirmwareVersionNative()
+ {
+ var result = GetInstalledFirmwareVersion();
+ return Marshal.StringToHGlobalAnsi(result);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "initialize")]
+ public static bool InitializeNative(IntPtr basePathPtr)
+ {
+ var path = Marshal.PtrToStringAnsi(basePathPtr);
+
+ var res = Initialize(path);
+
+ InitializeAudio();
+
+ return res;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_initialize")]
+ public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration)
+ {
+ if (OperatingSystem.IsIOS())
+ {
+ // Yes, macOS not iOS
+ Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.MacOS;
+ }
+ return InitializeGraphics(graphicsConfiguration);
+ }
+
+ [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());
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")]
+ public static void SetRendererSizeNative(int width, int height)
+ {
+ SetRendererSize(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);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
+ public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback)
+ {
+ _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer(swapBuffersCallback);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "get_game_info")]
+ public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
+ {
+ var extension = Marshal.PtrToStringAnsi(extensionPtr);
+ var stream = OpenFile(descriptor);
+
+ var gameInfo = GetGameInfo(stream, extension ?? "");
+
+ return gameInfo == null ? default : new GameInfoNative(gameInfo.FileSize, gameInfo.TitleName, gameInfo.TitleId, gameInfo.Developer, gameInfo.Version, gameInfo.Icon);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_initialize")]
+ public static void InitializeInputNative(int width, int height)
+ {
+ InitializeInput(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_client_size")]
+ public static void SetClientSizeNative(int width, int height)
+ {
+ SetClientSize(width, height);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_touch_point")]
+ public static void SetTouchPointNative(int x, int y)
+ {
+ SetTouchPoint(x, y);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_release_touch_point")]
+ public static void ReleaseTouchPointNative()
+ {
+ ReleaseTouchPoint();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_update")]
+ public static void UpdateInputNative()
+ {
+ UpdateInput();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_button_pressed")]
+ public static void SetButtonPressedNative(GamepadButtonInputId button, int id)
+ {
+ SetButtonPressed(button, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_button_released")]
+ public static void SetButtonReleasedNative(GamepadButtonInputId button, int id)
+ {
+ SetButtonReleased(button, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_accelerometer_data")]
+ public static void SetAccelerometerDataNative(Vector3 accel, int id)
+ {
+ SetAccelerometerData(accel, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_gyro_data")]
+ public static void SetGryoDataNative(Vector3 gyro, int id)
+ {
+ SetGryoData(gyro, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_set_stick_axis")]
+ public static void SetStickAxisNative(StickInputId stick, Vector2 axes, int id)
+ {
+ SetStickAxis(stick, axes, id);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "input_connect_gamepad")]
+ public static IntPtr ConnectGamepadNative(int index)
+ {
+ return ConnectGamepad(index);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_game_fifo")]
+ public static double GetGameInfoNative()
+ {
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
+
+ return stats;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_game_frame_time")]
+ public static double GetGameFrameTimeNative()
+ {
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
+
+ return stats;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_game_frame_rate")]
+ public static double GetGameFrameRateNative()
+ {
+ var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
+
+ return stats;
+ }
+ [UnmanagedCallersOnly(EntryPoint = "device_launch_mii_editor")]
+ public static bool LaunchMiiEditAppletNative()
+ {
+ if (SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ return LaunchMiiEditApplet();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_dlc_content_list")]
+ public static IntPtr GetDlcContentListNative(IntPtr pathPtr, long titleId)
+ {
+ var list = GetDlcContentList(Marshal.PtrToStringAnsi(pathPtr) ?? "", (ulong)titleId);
+
+ return CreateStringArray(list);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_dlc_title_id")]
+ public static long GetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
+ {
+ return Marshal.StringToHGlobalAnsi(GetDlcTitleId(Marshal.PtrToStringAnsi(pathPtr) ?? "", Marshal.PtrToStringAnsi(ncaPath) ?? ""));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_signal_emulation_close")]
+ public static void SignalEmulationCloseNative()
+ {
+ SignalEmulationClose();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_close_emulation")]
+ public static void CloseEmulationNative()
+ {
+ CloseEmulation();
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_load_descriptor")]
+ public static bool LoadApplicationNative(int descriptor, int type, int updateDescriptor)
+ {
+ if (SwitchDevice?.EmulationContext == null)
+ {
+ return false;
+ }
+
+ var stream = OpenFile(descriptor);
+ var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
+
+ return LoadApplication(stream, (FileType)type, update);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_verify_firmware")]
+ public static IntPtr VerifyFirmwareNative(int descriptor, bool isXci)
+ {
+ var stream = OpenFile(descriptor);
+
+ IntPtr stringHandle = 0;
+ string? version = "0.0";
+
+ try
+ {
+ version = VerifyFirmware(stream, isXci)?.VersionString;
+ }
+ catch (Exception _)
+ {
+ Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
+ }
+
+ if (version != null)
+ {
+ stringHandle = Marshal.StringToHGlobalAnsi(version);
+ }
+
+ return stringHandle;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "logging_set_enabled")]
+ public static void SetLoggingEnabledNative(int logLevel, bool enabled)
+ {
+ Logger.SetEnable((LogLevel)logLevel, enabled);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "logging_enabled_graphics_log")]
+ public static void SetLoggingEnabledGraphicsLogNative(bool enabled)
+ {
+ _enableGraphicsLogging = enabled;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "device_get_game_info")]
+ public unsafe static void GetGameInfoNative(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
+ {
+ using var stream = OpenFile(fileDescriptor);
+ var ext = Marshal.PtrToStringAnsi(extension);
+ var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
+ var i = (GameInfoNative*)infoPtr;
+ var n = new GameInfoNative(info);
+ i->TitleId = n.TitleId;
+ i->TitleName = n.TitleName;
+ i->Version = n.Version;
+ i->FileSize = n.FileSize;
+ i->Icon = n.Icon;
+ i->Version = n.Version;
+ i->Developer = n.Developer;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_get_opened_user")]
+ public static IntPtr GetOpenedUserNative()
+ {
+ var userId = GetOpenedUser();
+ var ptr = Marshal.StringToHGlobalAnsi(userId);
+
+ return ptr;
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_get_user_picture")]
+ public static IntPtr GetUserPictureNative(IntPtr userIdPtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ return Marshal.StringToHGlobalAnsi(GetUserPicture(userId));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_set_user_picture")]
+ public static void SetUserPictureNative(IntPtr userIdPtr, IntPtr picturePtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+ var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
+
+ SetUserPicture(userId, picture);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_get_user_name")]
+ public static IntPtr GetUserNameNative(IntPtr userIdPtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ return Marshal.StringToHGlobalAnsi(GetUserName(userId));
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_set_user_name")]
+ public static void SetUserNameNative(IntPtr userIdPtr, IntPtr userNamePtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+ var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
+
+ SetUserName(userId, userName);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_get_all_users")]
+ public static IntPtr GetAllUsersNative()
+ {
+ var users = GetAllUsers();
+
+ return CreateStringArray(users.ToList());
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_add_user")]
+ public static void AddUserNative(IntPtr userNamePtr, IntPtr picturePtr)
+ {
+ var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
+ var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
+
+ AddUser(userName, picture);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_delete_user")]
+ public static void DeleteUserNative(IntPtr userIdPtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ DeleteUser(userId);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_open_user")]
+ public static void OpenUserNative(IntPtr userIdPtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ OpenUser(userId);
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "user_close_user")]
+ public static void CloseUserNative(IntPtr userIdPtr)
+ {
+ var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
+
+ CloseUser(userId);
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.User.cs b/src/LibRyujinx/LibRyujinx.User.cs
new file mode 100644
index 00000000..f78ee22b
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.User.cs
@@ -0,0 +1,82 @@
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System;
+using System.Linq;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ public static string GetOpenedUser()
+ {
+ var lastProfile = SwitchDevice?.AccountManager.LastOpenedUser;
+
+ return lastProfile?.UserId.ToString() ?? "";
+ }
+
+ public static string GetUserPicture(string userId)
+ {
+ var uid = new UserId(userId);
+
+ var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
+
+ if (user == null)
+ return "";
+
+ var pic = user.Image;
+
+ return pic != null ? Convert.ToBase64String(pic) : "";
+ }
+
+ public static void SetUserPicture(string userId, string picture)
+ {
+ var uid = new UserId(userId);
+
+ SwitchDevice?.AccountManager.SetUserImage(uid, Convert.FromBase64String(picture));
+ }
+
+ public static string GetUserName(string userId)
+ {
+ var uid = new UserId(userId);
+
+ var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
+
+ return user?.Name ?? "";
+ }
+
+ public static void SetUserName(string userId, string name)
+ {
+ var uid = new UserId(userId);
+
+ SwitchDevice?.AccountManager.SetUserName(uid, name);
+ }
+
+ public static string[] GetAllUsers()
+ {
+ return SwitchDevice?.AccountManager.GetAllUsers().Select(x => x.UserId.ToString()).ToArray() ??
+ Array.Empty();
+ }
+
+ public static void AddUser(string userName, string picture)
+ {
+ SwitchDevice?.AccountManager.AddUser(userName, Convert.FromBase64String(picture));
+ }
+
+ public static void DeleteUser(string userId)
+ {
+ var uid = new UserId(userId);
+ SwitchDevice?.AccountManager.DeleteUser(uid);
+ }
+
+ public static void OpenUser(string userId)
+ {
+ var uid = new UserId(userId);
+ SwitchDevice?.AccountManager.OpenUser(uid);
+ }
+
+ public static void CloseUser(string userId)
+ {
+ var uid = new UserId(userId);
+ SwitchDevice?.AccountManager.CloseUser(uid);
+ }
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs
new file mode 100644
index 00000000..1074a047
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.cs
@@ -0,0 +1,852 @@
+// 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;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Backends.SDL2;
+using System.IO;
+using LibHac.Common.Keys;
+using LibHac.Common;
+using LibHac.Ns;
+using LibHac.Tools.Fs;
+using LibHac.Tools.FsSystem.NcaUtils;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Fs;
+using Path = System.IO.Path;
+using LibHac;
+using OpenTK.Audio.OpenAL;
+using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.Common.Utilities;
+using System.Globalization;
+using Ryujinx.UI.Common.Configuration.System;
+using Ryujinx.Common.Logging.Targets;
+using System.Collections.Generic;
+using System.Text;
+using Ryujinx.HLE.UI;
+using LibRyujinx.Android;
+
+namespace LibRyujinx
+{
+ public static partial class LibRyujinx
+ {
+ internal static IHardwareDeviceDriver AudioDriver { get; set; } = new DummyHardwareDeviceDriver();
+
+ private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+ public static SwitchDevice? SwitchDevice { get; set; }
+
+ public static bool Initialize(string? basePath)
+ {
+ if (SwitchDevice != null)
+ {
+ return false;
+ }
+
+ try
+ {
+ AppDataManager.Initialize(basePath);
+
+ ConfigurationState.Initialize();
+ LoggerModule.Initialize();
+
+ string logDir = Path.Combine(AppDataManager.BaseDirPath, "Logs");
+ FileStream logFile = FileLogTarget.PrepareLogFile(logDir);
+ Logger.AddTarget(new AsyncLogTargetWrapper(
+ new FileLogTarget("file", logFile),
+ 1000,
+ AsyncLogTargetOverflowAction.Block
+ ));
+
+ Logger.Notice.Print(LogClass.Application, "Initializing...");
+ Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}");
+
+ SwitchDevice = new SwitchDevice();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ return false;
+ }
+
+ OpenALLibraryNameContainer.OverridePath = "libopenal.so";
+
+ return true;
+ }
+
+ public static void InitializeAudio()
+ {
+ AudioDriver = new SDL2HardwareDeviceDriver();
+ }
+
+ public static GameStats GetGameStats()
+ {
+ if (SwitchDevice?.EmulationContext == null)
+ return new GameStats();
+
+ var context = SwitchDevice.EmulationContext;
+
+ return new GameStats
+ {
+ Fifo = context.Statistics.GetFifoPercent(),
+ GameFps = context.Statistics.GetGameFrameRate(),
+ GameTime = context.Statistics.GetGameFrameTime()
+ };
+ }
+
+
+ public static GameInfo? GetGameInfo(string? file)
+ {
+ if (string.IsNullOrWhiteSpace(file))
+ {
+ return new GameInfo();
+ }
+
+ Logger.Info?.Print(LogClass.Application, $"Getting game info for file: {file}");
+
+ using var stream = File.Open(file, FileMode.Open);
+
+ return GetGameInfo(stream, new FileInfo(file).Extension.Remove('.'));
+ }
+
+ public static GameInfo? GetGameInfo(Stream gameStream, string extension)
+ {
+ if (SwitchDevice == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized.");
+ return null;
+ }
+ GameInfo gameInfo = GetDefaultInfo(gameStream);
+
+ const Language TitleLanguage = Language.AmericanEnglish;
+
+ BlitStruct controlHolder = new(1);
+
+ try
+ {
+ try
+ {
+ if (extension == "nsp" || extension == "pfs0" || extension == "xci")
+ {
+ IFileSystem pfs;
+
+ bool isExeFs = false;
+
+ if (extension == "xci")
+ {
+ Xci xci = new(SwitchDevice.VirtualFileSystem.KeySet, gameStream.AsStorage());
+
+ pfs = xci.OpenPartition(XciPartitionType.Secure);
+ }
+ else
+ {
+ var pfsTemp = new PartitionFileSystem();
+ pfsTemp.Initialize(gameStream.AsStorage()).ThrowIfFailure();
+ pfs = pfsTemp;
+
+ // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
+ bool hasMainNca = false;
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
+ {
+ if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
+ {
+ using UniqueRef ncaFile = new();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(SwitchDevice.VirtualFileSystem.KeySet, ncaFile.Get.AsStorage());
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ // Some main NCAs don't have a data partition, so check if the partition exists before opening it
+ if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
+ {
+ hasMainNca = true;
+
+ break;
+ }
+ }
+ else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
+ {
+ isExeFs = true;
+ }
+ }
+
+ if (!hasMainNca && !isExeFs)
+ {
+ return null;
+ }
+ }
+
+ if (isExeFs)
+ {
+ using UniqueRef npdmFile = new();
+
+ Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
+
+ if (ResultFs.PathNotFound.Includes(result))
+ {
+ Npdm npdm = new(npdmFile.Get.AsStream());
+
+ gameInfo.TitleName = npdm.TitleName;
+ gameInfo.TitleId = npdm.Aci0.TitleId.ToString("x16");
+ }
+ }
+ else
+ {
+ GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id);
+
+ gameInfo.TitleId = id;
+
+ if (controlFs == null)
+ {
+ Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}");
+ return null;
+ }
+
+ // Check if there is an update available.
+ if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs))
+ {
+ // Replace the original ControlFs by the updated one.
+ controlFs = updatedControlFs;
+ }
+
+ ReadControlData(controlFs, controlHolder.ByteSpan);
+
+ GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
+
+ // Read the icon from the ControlFS and store it as a byte array
+ try
+ {
+ using UniqueRef icon = new();
+
+ controlFs?.OpenFile(ref icon.Ref, $"/icon_{TitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using MemoryStream stream = new();
+
+ icon.Get.AsStream().CopyTo(stream);
+ gameInfo.Icon = stream.ToArray();
+ }
+ catch (HorizonResultException)
+ {
+ foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
+ {
+ if (entry.Name == "control.nacp")
+ {
+ continue;
+ }
+
+ using var icon = new UniqueRef();
+
+ controlFs?.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ using MemoryStream stream = new();
+
+ icon.Get.AsStream().CopyTo(stream);
+ gameInfo.Icon = stream.ToArray();
+
+ if (gameInfo.Icon != null)
+ {
+ break;
+ }
+ }
+
+ }
+ }
+ }
+ else if (extension == "nro")
+ {
+ BinaryReader reader = new(gameStream);
+
+ byte[] Read(long position, int size)
+ {
+ gameStream.Seek(position, SeekOrigin.Begin);
+
+ return reader.ReadBytes(size);
+ }
+
+ gameStream.Seek(24, SeekOrigin.Begin);
+
+ int assetOffset = reader.ReadInt32();
+
+ if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
+ {
+ byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
+
+ long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
+ long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
+
+ ulong nacpOffset = reader.ReadUInt64();
+ ulong nacpSize = reader.ReadUInt64();
+
+ // Reads and stores game icon as byte array
+ if (iconSize > 0)
+ {
+ gameInfo.Icon = Read(assetOffset + iconOffset, (int)iconSize);
+ }
+
+ // Read the NACP data
+ Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
+
+ GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
+ }
+ }
+ }
+ catch (MissingKeyException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
+ }
+ catch (InvalidDataException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. {exception}");
+ }
+ catch (Exception exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The gameStream encountered was not of a valid type. Error: {exception}");
+
+ return null;
+ }
+ }
+ catch (IOException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, exception.Message);
+ }
+
+ void ReadControlData(IFileSystem? controlFs, Span outProperty)
+ {
+ using UniqueRef controlFile = new();
+
+ controlFs?.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
+ }
+
+ void GetGameInformation(ref ApplicationControlProperty controlData, out string? titleName, out string titleId, out string? publisher, out string? version)
+ {
+ _ = Enum.TryParse(TitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
+
+ if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
+ {
+ titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
+ publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
+ }
+ else
+ {
+ titleName = null;
+ publisher = null;
+ }
+
+ if (string.IsNullOrWhiteSpace(titleName))
+ {
+ foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
+ {
+ if (!controlTitle.NameString.IsEmpty())
+ {
+ titleName = controlTitle.NameString.ToString();
+
+ break;
+ }
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(publisher))
+ {
+ foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
+ {
+ if (!controlTitle.PublisherString.IsEmpty())
+ {
+ publisher = controlTitle.PublisherString.ToString();
+
+ break;
+ }
+ }
+ }
+
+ if (controlData.PresenceGroupId != 0)
+ {
+ titleId = controlData.PresenceGroupId.ToString("x16");
+ }
+ else if (controlData.SaveDataOwnerId != 0)
+ {
+ titleId = controlData.SaveDataOwnerId.ToString();
+ }
+ else if (controlData.AddOnContentBaseId != 0)
+ {
+ titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
+ }
+ else
+ {
+ titleId = "0000000000000000";
+ }
+
+ version = controlData.DisplayVersionString.ToString();
+ }
+
+ void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem? controlFs, out string? titleId)
+ {
+ if (SwitchDevice == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized.");
+
+ controlFs = null;
+ titleId = null;
+ return;
+ }
+ (_, _, Nca? controlNca) = GetGameData(SwitchDevice.VirtualFileSystem, pfs, 0);
+
+ if (controlNca == null)
+ {
+ Logger.Warning?.Print(LogClass.Application, "Control NCA is null. Unable to load control FS.");
+ }
+
+ // Return the ControlFS
+ controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+ titleId = controlNca?.Header.TitleId.ToString("x16");
+ }
+
+ (Nca? mainNca, Nca? patchNca, Nca? controlNca) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
+ {
+ Nca? mainNca = null;
+ Nca? patchNca = null;
+ Nca? controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef();
+
+ Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}");
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
+
+ if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
+ {
+ patchNca = nca;
+ }
+ else
+ {
+ mainNca = nca;
+ }
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (mainNca, patchNca, controlNca);
+ }
+
+ bool IsUpdateApplied(string? titleId, out IFileSystem? updatedControlFs)
+ {
+ updatedControlFs = null;
+
+ string? updatePath = "(unknown)";
+
+ if (SwitchDevice?.VirtualFileSystem == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "SwitchDevice was not initialized.");
+ return false;
+ }
+
+ try
+ {
+ (Nca? patchNca, Nca? controlNca) = GetGameUpdateData(SwitchDevice.VirtualFileSystem, titleId, 0, out updatePath);
+
+ if (patchNca != null && controlNca != null)
+ {
+ updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
+
+ return true;
+ }
+ }
+ catch (InvalidDataException)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
+ }
+ catch (MissingKeyException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
+ }
+
+ return false;
+ }
+
+ (Nca? patch, Nca? control) GetGameUpdateData(VirtualFileSystem fileSystem, string? titleId, int programIndex, out string? updatePath)
+ {
+ updatePath = null;
+
+ if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+ {
+ // Clear the program index part.
+ titleIdBase &= ~0xFUL;
+
+ // Load update information if exists.
+ string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+
+ if (File.Exists(titleUpdateMetadataPath))
+ {
+ updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
+
+ if (File.Exists(updatePath))
+ {
+ FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
+ PartitionFileSystem nsp = new();
+ nsp.Initialize(file.AsStorage()).ThrowIfFailure();
+
+ return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
+ }
+ }
+ }
+
+ return (null, null);
+ }
+
+ (Nca? patchNca, Nca? controlNca) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
+ {
+ Nca? patchNca = null;
+ Nca? controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef();
+
+ pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
+ {
+ break;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ patchNca = nca;
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (patchNca, controlNca);
+ }
+
+ return gameInfo;
+ }
+
+ private static GameInfo GetDefaultInfo(Stream gameStream)
+ {
+ return new GameInfo
+ {
+ FileSize = gameStream.Length * 0.000000000931,
+ TitleName = "Unknown",
+ TitleId = "0000000000000000",
+ Developer = "Unknown",
+ Version = "0",
+ Icon = null
+ };
+ }
+
+ public static string GetDlcTitleId(string path, string ncaPath)
+ {
+ if (File.Exists(path))
+ {
+ using FileStream containerFile = File.OpenRead(path);
+
+ PartitionFileSystem partitionFileSystem = new();
+ partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
+
+ SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
+
+ using UniqueRef ncaFile = new();
+
+ partitionFileSystem.OpenFile(ref ncaFile.Ref, ncaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), ncaPath);
+ if (nca != null)
+ {
+ return nca.Header.TitleId.ToString("X16");
+ }
+ }
+
+ return string.Empty;
+ }
+
+
+ private static Nca TryOpenNca(IStorage ncaStorage, string containerPath)
+ {
+ try
+ {
+ return new Nca(SwitchDevice.VirtualFileSystem.KeySet, ncaStorage);
+ }
+ catch (Exception ex)
+ {
+ }
+
+ return null;
+ }
+
+ public static List GetDlcContentList(string path, ulong titleId)
+ {
+ if (!File.Exists(path))
+ return new List();
+
+ using FileStream containerFile = File.OpenRead(path);
+
+ PartitionFileSystem partitionFileSystem = new();
+ partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
+
+ SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
+ List paths = new List();
+
+ foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
+ {
+ using var ncaFile = new UniqueRef();
+
+ partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
+ if (nca == null)
+ {
+ continue;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.PublicData)
+ {
+ if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != titleId)
+ {
+ break;
+ }
+
+ paths.Add(fileEntry.FullPath);
+ }
+ }
+
+ return paths;
+ }
+
+ public static void SetupUiHandler()
+ {
+ if (SwitchDevice is { } switchDevice)
+ {
+ switchDevice.HostUiHandler = new AndroidUIHandler();
+ }
+ }
+
+ public static void SetUiHandlerResponse(bool isOkPressed, string input)
+ {
+ if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler)
+ {
+ uiHandler.SetResponse(isOkPressed, input);
+ }
+ }
+ }
+
+ public class SwitchDevice : IDisposable
+ {
+ private readonly SystemVersion _firmwareVersion;
+ 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 IHostUIHandler? HostUiHandler { get; set; }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+
+ 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();
+
+ _firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
+
+ if (_firmwareVersion != null)
+ {
+ Logger.Notice.Print(LogClass.Application, $"System Firmware Version: {_firmwareVersion.VersionString}");
+ }
+ else
+ {
+ Logger.Notice.Print(LogClass.Application, $"System Firmware not installed");
+ }
+ }
+
+ public bool InitializeContext(bool isHostMapped,
+ bool useHypervisor,
+ SystemLanguage systemLanguage,
+ RegionCode regionCode,
+ bool enableVsync,
+ bool enableDockedMode,
+ bool enablePtc,
+ bool enableInternetAccess,
+ string? timeZone,
+ bool ignoreMissingServices)
+ {
+ 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,
+ LibRyujinx.AudioDriver, //Audio
+ MemoryConfiguration.MemoryConfiguration4GiB,
+ HostUiHandler,
+ systemLanguage,
+ regionCode,
+ enableVsync,
+ enableDockedMode,
+ enablePtc,
+ enableInternetAccess,
+ IntegrityCheckLevel.None,
+ 0,
+ 0,
+ timeZone,
+ isHostMapped ? MemoryManagerMode.HostMappedUnsafe : MemoryManagerMode.SoftwarePageTable,
+ ignoreMissingServices,
+ LibRyujinx.GraphicsConfiguration.AspectRatio,
+ 100,
+ useHypervisor,
+ "",
+ Ryujinx.Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
+
+ EmulationContext = new Switch(configuration);
+
+ return true;
+ }
+
+ internal void ReloadFileSystem()
+ {
+ VirtualFileSystem.ReloadKeySet();
+ ContentManager = new ContentManager(VirtualFileSystem);
+ AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
+ }
+
+ internal void DisposeContext()
+ {
+ EmulationContext?.Dispose();
+ EmulationContext?.DisposeGpu();
+ EmulationContext = null;
+ LibRyujinx.Renderer = null;
+ }
+ }
+
+ public class GameInfo
+ {
+ public double FileSize;
+ public string? TitleName;
+ public string? TitleId;
+ public string? Developer;
+ public string? Version;
+ public byte[]? Icon;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct GameInfoNative
+ {
+ public double FileSize;
+ public char* TitleName;
+ public char* TitleId;
+ public char* Developer;
+ public char* Version;
+ public char* Icon;
+
+ public GameInfoNative()
+ {
+
+ }
+
+ public GameInfoNative(double fileSize, string? titleName, string? titleId, string? developer, string? version, byte[]? icon)
+ {
+ FileSize = fileSize;
+ TitleId = (char*)Marshal.StringToHGlobalAnsi(titleId);
+ Version = (char*)Marshal.StringToHGlobalAnsi(version);
+ Developer = (char*)Marshal.StringToHGlobalAnsi(developer);
+ TitleName = (char*)Marshal.StringToHGlobalAnsi(titleName);
+
+ if (icon != null)
+ {
+ Icon = (char*)Marshal.StringToHGlobalAnsi(Convert.ToBase64String(icon));
+ }
+ else
+ {
+ Icon = (char*)0;
+ }
+ }
+
+ public GameInfoNative(GameInfo info) : this(info.FileSize, info.TitleName, info.TitleId, info.Developer, info.Version, info.Icon){}
+ }
+
+ public class GameStats
+ {
+ public double Fifo;
+ public double GameFps;
+ public double GameTime;
+ }
+}
diff --git a/src/LibRyujinx/LibRyujinx.csproj b/src/LibRyujinx/LibRyujinx.csproj
new file mode 100644
index 00000000..45ad7d35
--- /dev/null
+++ b/src/LibRyujinx/LibRyujinx.csproj
@@ -0,0 +1,76 @@
+
+
+ net9.0
+ linux-bionic-arm64;ios-arm64
+ enable
+ lld
+ $(DefineConstants);FORCE_EXTERNAL_BASE_DIR
+
+
+ true
+ true
+
+ Shared
+ true
+ true
+ true
+ false
+ armv8.2-a
+
+
+ true
+ Speed
+
+
+
+
+
+
+
+
+ $(XcodeSelect)
+ $([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/LibRyujinx/OpenTKBindingsContext.cs b/src/LibRyujinx/OpenTKBindingsContext.cs
new file mode 100644
index 00000000..203caee8
--- /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/VulkanLoader.cs b/src/LibRyujinx/VulkanLoader.cs
new file mode 100644
index 00000000..7a5f9546
--- /dev/null
+++ b/src/LibRyujinx/VulkanLoader.cs
@@ -0,0 +1,98 @@
+using Ryujinx.Common.Logging;
+using Silk.NET.Core.Contexts;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LibRyujinx
+{
+ public class VulkanLoader : IDisposable
+ {
+ private delegate IntPtr GetInstanceProcAddress(IntPtr instance, IntPtr name);
+ private delegate IntPtr GetDeviceProcAddress(IntPtr device, IntPtr name);
+
+ private IntPtr _loadedLibrary = IntPtr.Zero;
+ private GetInstanceProcAddress _getInstanceProcAddr;
+ private GetDeviceProcAddress _getDeviceProcAddr;
+
+ public void Dispose()
+ {
+ if (_loadedLibrary != IntPtr.Zero)
+ {
+ NativeLibrary.Free(_loadedLibrary);
+ _loadedLibrary = IntPtr.Zero;
+ }
+ }
+
+ public VulkanLoader(IntPtr driver)
+ {
+ _loadedLibrary = driver;
+
+ if (_loadedLibrary != IntPtr.Zero)
+ {
+ var instanceGetProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetInstanceProcAddr");
+ var deviceProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetDeviceProcAddr");
+
+ _getInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(instanceGetProc);
+ _getDeviceProcAddr = Marshal.GetDelegateForFunctionPointer(deviceProc);
+ }
+ }
+
+ public unsafe Vk GetApi()
+ {
+
+ if (_loadedLibrary == IntPtr.Zero)
+ {
+ return Vk.GetApi();
+ }
+ var ctx = new MultiNativeContext(new INativeContext[1]);
+ var ret = new Vk(ctx);
+ ctx.Contexts[0] = new LamdaNativeContext
+ (
+ x =>
+ {
+ var xPtr = Marshal.StringToHGlobalAnsi(x);
+ byte* xp = (byte*)xPtr;
+ try
+ {
+ nint ptr = default;
+ ptr = _getInstanceProcAddr(ret.CurrentInstance.GetValueOrDefault().Handle, xPtr);
+
+ if (ptr == default)
+ {
+ ptr = _getInstanceProcAddr(IntPtr.Zero, xPtr);
+
+ if (ptr == default)
+ {
+ var currentDevice = ret.CurrentDevice.GetValueOrDefault().Handle;
+ if (currentDevice != IntPtr.Zero)
+ {
+ ptr = _getDeviceProcAddr(currentDevice, xPtr);
+ }
+
+ if (ptr == default)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Failed to get function pointer: {x}");
+ }
+
+ }
+ }
+
+ return ptr;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(xPtr);
+ }
+ }
+ );
+ return ret;
+ }
+ }
+}
diff --git a/src/LibRyujinx/rd.xml b/src/LibRyujinx/rd.xml
new file mode 100644
index 00000000..2faf502a
--- /dev/null
+++ b/src/LibRyujinx/rd.xml
@@ -0,0 +1,536 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file