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