using LibKenjinx.Jni; using LibKenjinx.Jni.Identifiers; using LibKenjinx.Jni.Pointers; using LibKenjinx.Jni.Primitives; using LibKenjinx.Jni.References; using LibKenjinx.Jni.Values; using Rxmxnx.PInvoke; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using System; using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Text; namespace LibKenjinx.Android { internal unsafe static class Interop { internal const string BaseClassName = "org/kenjinx/android/KenjinxNative"; 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 = [ ("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)) { if (_classId != null) { 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")) { if (env != null && _classId != null) { 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")) if (env != null && _classId != null) { return JniHelper.CallStaticLongMethod(env.Env, (JClassLocalRef)(_classId.Value.Value), method, values) ?? -1; } } return -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", 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", 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 = 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; } } } }