1
0
forked from MeloNX/MeloNX

android - replace jni with jna

This commit is contained in:
Emmanuel Hansen 2024-05-09 16:15:41 +00:00
parent 8888edde5c
commit a00c8c909f
39 changed files with 870 additions and 818 deletions

View File

@ -1,9 +1,5 @@
using LibRyujinx.Jni;
using LibRyujinx.Jni.Pointers; using LibRyujinx.Jni.Pointers;
using LibRyujinx.Jni.Primitives;
using LibRyujinx.Jni.References; using LibRyujinx.Jni.References;
using LibRyujinx.Jni.Values;
using Rxmxnx.PInvoke;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@ -18,6 +14,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -99,14 +96,8 @@ namespace LibRyujinx
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance); public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
[UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")] [UnmanagedCallersOnly(EntryPoint = "javaInitialize")]
internal static int LoadLibrary(JavaVMRef vm, IntPtr unknown) public static bool JniInitialize(IntPtr jpathId)
{
return 0x00010006; //JNI_VERSION_1_6
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
PlatformInfo.IsBionic = true; PlatformInfo.IsBionic = true;
@ -118,7 +109,7 @@ namespace LibRyujinx
AsyncLogTargetOverflowAction.Block AsyncLogTargetOverflowAction.Block
)); ));
var path = GetStoredString(jpathId); var path = Marshal.PtrToStringAnsi(jpathId);
var init = Initialize(path); var init = Initialize(path);
@ -138,70 +129,71 @@ namespace LibRyujinx
return s; return s;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")] [UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
public static void JniReloadFileSystem() public static void JnaReloadFileSystem()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SwitchDevice?.ReloadFileSystem(); SwitchDevice?.ReloadFileSystem();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInitialize")] [UnmanagedCallersOnly(EntryPoint = "deviceInitialize")]
public static JBoolean JniInitializeDeviceNative(JEnvRef jEnv, public static bool JnaDeviceInitialize(bool isHostMapped,
JObjectLocalRef jObj, bool useNce,
JBoolean isHostMapped, int systemLanguage,
JBoolean useNce, int regionCode,
JInt systemLanguage, bool enableVsync,
JInt regionCode, bool enableDockedMode,
JBoolean enableVsync, bool enablePtc,
JBoolean enableDockedMode, bool enableInternetAccess,
JBoolean enablePtc, IntPtr timeZonePtr,
JBoolean enableInternetAccess, bool ignoreMissingServices)
JLong timeZoneId,
JBoolean ignoreMissingServices)
{ {
debug_break(4);
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
AudioDriver = new OpenALHardwareDeviceDriver();//new OboeHardwareDeviceDriver(); AudioDriver = new OpenALHardwareDeviceDriver();
var timezone = Marshal.PtrToStringAnsi(timeZonePtr);
return InitializeDevice(isHostMapped, return InitializeDevice(isHostMapped,
useNce, useNce,
(SystemLanguage)(int)systemLanguage, (SystemLanguage)systemLanguage,
(RegionCode)(int)regionCode, (RegionCode)regionCode,
enableVsync, enableVsync,
enableDockedMode, enableDockedMode,
enablePtc, enablePtc,
enableInternetAccess, enableInternetAccess,
GetStoredString(timeZoneId), timezone,
ignoreMissingServices); ignoreMissingServices);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")] [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFifo")]
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj) public static double JnaGetGameFifo()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0; var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
return stats; return stats;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")] [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameTime")]
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj) public static double JnaGetGameFrameTime()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0; var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
return stats; return stats;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")] [UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameRate")]
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj) public static double JnaGetGameFrameRate()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0; var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
return stats; return stats;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")] [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")]
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr) public static bool JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null) if (SwitchDevice?.EmulationContext == null)
@ -214,8 +206,8 @@ namespace LibRyujinx
return LoadApplication(path); return LoadApplication(path);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")] [UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")]
public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj) public static bool JNALaunchMiiEditApplet()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null) if (SwitchDevice?.EmulationContext == null)
@ -226,38 +218,38 @@ namespace LibRyujinx
return LaunchMiiEditApplet(); return LaunchMiiEditApplet();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")] [UnmanagedCallersOnly(EntryPoint = "deviceGetDlcContentList")]
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId) public static IntPtr JniGetDlcContentListNative(IntPtr pathPtr, long titleId)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId); var list = GetDlcContentList(Marshal.PtrToStringAnsi(pathPtr) ?? "", (ulong)titleId);
return CreateStringArray(jEnv, list); return CreateStringArray(list);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")] [UnmanagedCallersOnly(EntryPoint = "deviceGetDlcTitleId")]
public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath) public static long JniGetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
return storeString(GetDlcTitleId(GetStoredString(pathPtr), GetStoredString(ncaPath))); return Marshal.StringToHGlobalAnsi(GetDlcTitleId(Marshal.PtrToStringAnsi(pathPtr) ?? "", Marshal.PtrToStringAnsi(ncaPath) ?? ""));
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")] [UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")]
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniSignalEmulationCloseNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SignalEmulationClose(); SignalEmulationClose();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")] [UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")]
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniCloseEmulationNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
CloseEmulation(); CloseEmulation();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")] [UnmanagedCallersOnly(EntryPoint = "deviceLoadDescriptor")]
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JInt type, JInt updateDescriptor) public static bool JnaLoadApplicationNative(int descriptor, int type, int updateDescriptor)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null) if (SwitchDevice?.EmulationContext == null)
@ -268,17 +260,17 @@ namespace LibRyujinx
var stream = OpenFile(descriptor); var stream = OpenFile(descriptor);
var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor); var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
return LoadApplication(stream, (FileType)(int)type, update); return LoadApplication(stream, (FileType)type, update);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceVerifyFirmware")] [UnmanagedCallersOnly(EntryPoint = "deviceVerifyFirmware")]
public static JLong JniVerifyFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci) public static IntPtr JniVerifyFirmware(int descriptor, bool isXci)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stream = OpenFile(descriptor); var stream = OpenFile(descriptor);
long stringHandle = -1; IntPtr stringHandle = 0;
try try
{ {
@ -286,19 +278,19 @@ namespace LibRyujinx
if (version != null) if (version != null)
{ {
stringHandle = storeString(version.VersionString); stringHandle = Marshal.StringToHGlobalAnsi(version.VersionString);
} }
} }
catch(Exception _) catch(Exception _)
{ {
Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
} }
return stringHandle; return stringHandle;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInstallFirmware")] [UnmanagedCallersOnly(EntryPoint = "deviceInstallFirmware")]
public static void JniInstallFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci) public static void JniInstallFirmware(int descriptor, bool isXci)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
@ -307,58 +299,47 @@ namespace LibRyujinx
InstallFirmware(stream, isXci); InstallFirmware(stream, isXci);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetInstalledFirmwareVersion")] [UnmanagedCallersOnly(EntryPoint = "deviceGetInstalledFirmwareVersion")]
public static JLong JniGetInstalledFirmwareVersion(JEnvRef jEnv, JObjectLocalRef jObj) public static IntPtr JniGetInstalledFirmwareVersion()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var version = GetInstalledFirmwareVersion(); var version = GetInstalledFirmwareVersion();
long stringHandle = -1; IntPtr stringHandle = 0;
if (version != String.Empty) if (version != String.Empty)
{ {
stringHandle = storeString(version); stringHandle = Marshal.StringToHGlobalAnsi(version);
} }
return stringHandle; return stringHandle;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")] [UnmanagedCallersOnly(EntryPoint = "graphicsInitialize")]
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject) 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"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
JEnvValue value = jEnv.Environment;
ref JNativeInterface jInterface = ref value.Functions;
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
IntPtr getIntFieldPtr = jInterface.GetIntFieldPointer;
IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer;
IntPtr getFloatFieldPtr = jInterface.GetFloatFieldPointer;
IntPtr getBooleanFieldPtr = jInterface.GetBooleanFieldPointer;
var getObjectClass = getObjectClassPtr.GetUnsafeDelegate<GetObjectClassDelegate>();
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
var getIntField = getIntFieldPtr.GetUnsafeDelegate<GetIntFieldDelegate>();
var getBooleanField = getBooleanFieldPtr.GetUnsafeDelegate<GetBooleanFieldDelegate>();
var getFloatField = getFloatFieldPtr.GetUnsafeDelegate<GetFloatFieldDelegate>();
var jobject = getObjectClass(jEnv, graphicObject);
GraphicsConfiguration graphicsConfiguration = new()
{
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))),
EnableMacroJit = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroJit"), GetCCharSequence("Z"))),
EnableTextureRecompression = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableTextureRecompression"), GetCCharSequence("Z"))),
Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))),
FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))),
ResScale = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("ResScale"), GetCCharSequence("F"))),
MaxAnisotropy = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("MaxAnisotropy"), GetCCharSequence("F"))),
BackendThreading = (BackendThreading)(int)getIntField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("BackendThreading"), GetCCharSequence("I")))
};
SearchPathContainer.Platform = UnderlyingPlatform.Android; SearchPathContainer.Platform = UnderlyingPlatform.Android;
return InitializeGraphics(graphicsConfiguration); return InitializeGraphics(new GraphicsConfiguration()
{
ResScale = resScale,
MaxAnisotropy = maxAnisotropy,
FastGpuTime = fastGpuTime,
Fast2DCopy = fast2DCopy,
EnableMacroJit = enableMacroJit,
EnableMacroHLE = enableMacroHLE,
EnableShaderCache = enableShaderCache,
EnableTextureRecompression = enableTextureRecompression,
BackendThreading = (BackendThreading)backendThreading
});
} }
private static CCharSequence GetCCharSequence(string s) private static CCharSequence GetCCharSequence(string s)
@ -366,8 +347,8 @@ namespace LibRyujinx
return Encoding.UTF8.GetBytes(s).AsSpan(); return Encoding.UTF8.GetBytes(s).AsSpan();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")] [UnmanagedCallersOnly(EntryPoint = "graphicsSetSurface")]
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window) public static void JniSetSurface(long surfacePtr, long window)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
_surfacePtr = surfacePtr; _surfacePtr = surfacePtr;
@ -376,11 +357,10 @@ namespace LibRyujinx
_surfaceEvent.Set(); _surfaceEvent.Set();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitializeRenderer")] [UnmanagedCallersOnly(EntryPoint = "graphicsInitializeRenderer")]
public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv, public unsafe static bool JnaGraphicsInitializeRenderer(char** extensionsArray,
JObjectLocalRef jObj, int extensionsLength,
JArrayLocalRef extensionsArray, long driverHandle)
JLong driverHandle)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (Renderer != null) if (Renderer != null)
@ -388,37 +368,16 @@ namespace LibRyujinx
return false; return false;
} }
JEnvValue value = jEnv.Environment;
ref JNativeInterface jInterface = ref value.Functions;
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer;
IntPtr getArrayLengthPtr = jInterface.GetArrayLengthPointer;
IntPtr getObjectArrayElementPtr = jInterface.GetObjectArrayElementPointer;
IntPtr getObjectFieldPtr = jInterface.GetObjectFieldPointer;
var getObjectClass = getObjectClassPtr.GetUnsafeDelegate<GetObjectClassDelegate>();
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
var getArrayLength = getArrayLengthPtr.GetUnsafeDelegate<GetArrayLengthDelegate>();
var getObjectArrayElement = getObjectArrayElementPtr.GetUnsafeDelegate<GetObjectArrayElementDelegate>();
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
var getObjectField = getObjectFieldPtr.GetUnsafeDelegate<GetObjectFieldDelegate>();
List<string?> extensions = new(); List<string?> extensions = new();
var count = getArrayLength(jEnv, extensionsArray); for (int i = 0; i < extensionsLength; i++)
for (int i = 0; i < count; i++)
{ {
var obj = getObjectArrayElement(jEnv, extensionsArray, i); extensions.Add(Marshal.PtrToStringAnsi((IntPtr)extensionsArray[i]));
var ext = obj.Transform<JObjectLocalRef, JStringLocalRef>();
extensions.Add(GetString(jEnv, ext));
} }
if ((long)driverHandle != 0) if (driverHandle != 0)
{ {
VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle); VulkanLoader = new VulkanLoader((IntPtr)driverHandle);
} }
CreateSurface createSurfaceFunc = instance => CreateSurface createSurfaceFunc = instance =>
@ -446,36 +405,29 @@ namespace LibRyujinx
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray()); return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
} }
private static JArrayLocalRef CreateStringArray(JEnvRef jEnv, List<string> strings) private unsafe static IntPtr CreateStringArray(List<string> strings)
{ {
JEnvValue value = jEnv.Environment; uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
ref JNativeInterface jInterface = ref value.Functions; var array = (char**)Marshal.AllocHGlobal((int)size);
IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer; Unsafe.InitBlockUnaligned(array, 0, size);
IntPtr findClassPtr = jInterface.FindClassPointer;
IntPtr setObjectArrayElementPtr = jInterface.SetObjectArrayElementPointer;
var newObjectArray = newObjectArrayPtr.GetUnsafeDelegate<NewObjectArrayDelegate>();
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
var setObjectArrayElement = setObjectArrayElementPtr.GetUnsafeDelegate<SetObjectArrayElementDelegate>();
var array = newObjectArray(jEnv, strings.Count, findClass(jEnv, GetCCharSequence("java/lang/String")), CreateString(jEnv, "")._value);
for (int i = 0; i < strings.Count; i++) for (int i = 0; i < strings.Count; i++)
{ {
setObjectArrayElement(jEnv, array, i, CreateString(jEnv, strings[i])._value); array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
} }
return array; return (nint)array;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")] [UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetSize")]
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) public static void JnaSetRendererSizeNative(int width, int height)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
Renderer?.Window?.SetSize(width, height); Renderer?.Window?.SetSize(width, height);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")] [UnmanagedCallersOnly(EntryPoint = "graphicsRendererRunLoop")]
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniRunLoopNative()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetSwapBuffersCallback(() => SetSwapBuffersCallback(() =>
@ -487,84 +439,32 @@ namespace LibRyujinx
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_loggingSetEnabled")] [UnmanagedCallersOnly(EntryPoint = "loggingSetEnabled")]
public static void JniSetLoggingEnabledNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt logLevel, JBoolean enabled) public static void JniSetLoggingEnabledNative(int logLevel, bool enabled)
{ {
Logger.SetEnable((LogLevel)(int)logLevel, enabled); Logger.SetEnable((LogLevel)logLevel, enabled);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")] [UnmanagedCallersOnly(EntryPoint = "deviceGetGameInfo")]
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path) public unsafe static void JniGetGameInfo(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var info = GetGameInfo(GetString(jEnv, path));
return GetInfo(jEnv, info);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JLong extension)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
using var stream = OpenFile(fileDescriptor); using var stream = OpenFile(fileDescriptor);
var ext = GetStoredString(extension); var ext = Marshal.PtrToStringAnsi(extension);
var info = GetGameInfo(stream, ext.ToLower()); var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
return GetInfo(jEnv, info); 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;
} }
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info) [UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetVsync")]
{ public static void JnaSetVsyncStateNative(bool enabled)
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
JEnvValue value = jEnv.Environment;
ref JNativeInterface jInterface = ref value.Functions;
IntPtr findClassPtr = jInterface.FindClassPointer;
IntPtr newGlobalRefPtr = jInterface.NewGlobalRefPointer;
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
IntPtr getMethodPtr = jInterface.GetMethodIdPointer;
IntPtr newObjectPtr = jInterface.NewObjectPointer;
IntPtr setObjectFieldPtr = jInterface.SetObjectFieldPointer;
IntPtr setDoubleFieldPtr = jInterface.SetDoubleFieldPointer;
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
var newGlobalRef = newGlobalRefPtr.GetUnsafeDelegate<NewGlobalRefDelegate>();
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
var getMethod = getMethodPtr.GetUnsafeDelegate<GetMethodIdDelegate>();
var newObject = newObjectPtr.GetUnsafeDelegate<NewObjectDelegate>();
var setObjectField = setObjectFieldPtr.GetUnsafeDelegate<SetObjectFieldDelegate>();
var setDoubleField = setDoubleFieldPtr.GetUnsafeDelegate<SetDoubleFieldDelegate>();
var javaClass = findClass(jEnv, javaClassName);
var newGlobal = newGlobalRef(jEnv, javaClass._value);
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
var newObj = newObject(jEnv, javaClass, constructor, 0);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Icon"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, Convert.ToBase64String(info?.Icon ?? Array.Empty<byte>()))._value);
setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d);
return newObj;
}
private static JStringLocalRef CreateString(JEnvRef jEnv, string? s)
{
s ??= string.Empty;
var ptr = Marshal.StringToHGlobalAnsi(s);
var str = createString(jEnv, ptr);
Marshal.FreeHGlobal(ptr);
return str;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")]
public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetVsyncState(enabled); SetVsyncState(enabled);
@ -577,196 +477,197 @@ namespace LibRyujinx
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback); _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")] [UnmanagedCallersOnly(EntryPoint = "inputInitialize")]
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) public static void JnaInitializeInput(int width, int height)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
InitializeInput(width, height); InitializeInput(width, height);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")] [UnmanagedCallersOnly(EntryPoint = "inputSetClientSize")]
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) public static void JnaSetClientSize(int width, int height)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetClientSize(width, height); SetClientSize(width, height);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")] [UnmanagedCallersOnly(EntryPoint = "inputSetTouchPoint")]
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y) public static void JnaSetTouchPoint(int x, int y)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetTouchPoint(x, y); SetTouchPoint(x, y);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")] [UnmanagedCallersOnly(EntryPoint = "inputReleaseTouchPoint")]
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj) public static void JnaReleaseTouchPoint()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
ReleaseTouchPoint(); ReleaseTouchPoint();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")] [UnmanagedCallersOnly(EntryPoint = "inputUpdate")]
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniUpdateInput()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
UpdateInput(); UpdateInput();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")] [UnmanagedCallersOnly(EntryPoint = "inputSetButtonPressed")]
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) public static void JnaSetButtonPressed(int button, int id)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetButtonPressed((GamepadButtonInputId)(int)button, id); SetButtonPressed((GamepadButtonInputId)button, id);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")] [UnmanagedCallersOnly(EntryPoint = "inputSetButtonReleased")]
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) public static void JnaSetButtonReleased(int button, int id)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetButtonReleased((GamepadButtonInputId)(int)button, id); SetButtonReleased((GamepadButtonInputId)button, id);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetAccelerometerData")] [UnmanagedCallersOnly(EntryPoint = "inputSetAccelerometerData")]
public static void JniSetAccelerometerData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) public static void JniSetAccelerometerData(float x, float y, float z, int id)
{ {
var accel = new Vector3(x, y, z); var accel = new Vector3(x, y, z);
SetAccelerometerData(accel, id); SetAccelerometerData(accel, id);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetGyroData")] [UnmanagedCallersOnly(EntryPoint = "inputSetGyroData")]
public static void JniSetGyroData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) public static void JniSetGyroData(float x, float y, float z, int id)
{ {
var gryo = new Vector3(x, y, z); var gryo = new Vector3(x, y, z);
SetGryoData(gryo, id); SetGryoData(gryo, id);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")] [UnmanagedCallersOnly(EntryPoint = "inputSetStickAxis")]
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id) public static void JnaSetStickAxis(int stick, float x, float y, int id)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id); SetStickAxis((StickInputId)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")] [UnmanagedCallersOnly(EntryPoint = "inputConnectGamepad")]
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index) public static int JnaConnectGamepad(int index)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
return ConnectGamepad(index); return ConnectGamepad(index);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")] [UnmanagedCallersOnly(EntryPoint = "userGetOpenedUser")]
public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj) public static IntPtr JniGetOpenedUser()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetOpenedUser(); var userId = GetOpenedUser();
var ptr = Marshal.StringToHGlobalAnsi(userId);
return storeString(userId); return ptr;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")] [UnmanagedCallersOnly(EntryPoint = "userGetUserPicture")]
public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) public static IntPtr JniGetUserPicture(IntPtr userIdPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
return storeString(GetUserPicture(userId)); return Marshal.StringToHGlobalAnsi(GetUserPicture(userId));
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")] [UnmanagedCallersOnly(EntryPoint = "userSetUserPicture")]
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr) public static void JniGetUserPicture(IntPtr userIdPtr, IntPtr picturePtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? ""; var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
SetUserPicture(userId, picture); SetUserPicture(userId, picture);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")] [UnmanagedCallersOnly(EntryPoint = "userGetUserName")]
public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) public static IntPtr JniGetUserName(IntPtr userIdPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
return storeString(GetUserName(userId)); return Marshal.StringToHGlobalAnsi(GetUserName(userId));
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")] [UnmanagedCallersOnly(EntryPoint = "userSetUserName")]
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr) public static void JniSetUserName(IntPtr userIdPtr, IntPtr userNamePtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var userName = GetString(jEnv, userNamePtr) ?? ""; var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
SetUserName(userId, userName); SetUserName(userId, userName);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")] [UnmanagedCallersOnly(EntryPoint = "userGetAllUsers")]
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj) public static IntPtr JniGetAllUsers()
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var users = GetAllUsers(); var users = GetAllUsers();
return CreateStringArray(jEnv, users.ToList()); return CreateStringArray(users.ToList());
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")] [UnmanagedCallersOnly(EntryPoint = "userAddUser")]
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr) public static void JniAddUser(IntPtr userNamePtr, IntPtr picturePtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userName = GetString(jEnv, userNamePtr) ?? ""; var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? ""; var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
AddUser(userName, picture); AddUser(userName, picture);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")] [UnmanagedCallersOnly(EntryPoint = "userDeleteUser")]
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) public static void JniDeleteUser(IntPtr userIdPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
DeleteUser(userId); DeleteUser(userId);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")] [UnmanagedCallersOnly(EntryPoint = "uiHandlerSetup")]
public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniSetupUiHandler()
{ {
SetupUiHandler(); SetupUiHandler();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")] [UnmanagedCallersOnly(EntryPoint = "uiHandlerWait")]
public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniWaitUiHandler()
{ {
WaitUiHandler(); WaitUiHandler();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")] [UnmanagedCallersOnly(EntryPoint = "uiHandlerStopWait")]
public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj) public static void JniStopUiHandlerWait()
{ {
StopUiHandlerWait(); StopUiHandlerWait();
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")] [UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")]
public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input) public static void JniSetUiHandlerResponse(bool isOkPressed, long input)
{ {
SetUiHandlerResponse(isOkPressed, input); SetUiHandlerResponse(isOkPressed, input);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")] [UnmanagedCallersOnly(EntryPoint = "userOpenUser")]
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr) public static void JniOpenUser(IntPtr userIdPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
OpenUser(userId); OpenUser(userId);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")] [UnmanagedCallersOnly(EntryPoint = "userCloseUser")]
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) public static void JniCloseUser(IntPtr userIdPtr)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? ""; var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
CloseUser(userId); CloseUser(userId);
} }

View File

@ -121,10 +121,13 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json"); string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json");
if (File.Exists(addOnContentMetadataPath)) if (File.Exists(addOnContentMetadataPath))
{ {
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}");
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer); List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}. Available: {dlcContainerList.Count}");
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
{ {
Logger.Info?.Print(LogClass.Loader, $"DLC Contents Available: {downloadableContentContainer.DownloadableContentNcaList.Count}");
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{ {
if (File.Exists(downloadableContentContainer.ContainerPath)) if (File.Exists(downloadableContentContainer.ContainerPath))

View File

@ -11,8 +11,8 @@ android {
applicationId "org.ryujinx.android" applicationId "org.ryujinx.android"
minSdk 30 minSdk 30
targetSdk 34 targetSdk 34
versionCode 10030 versionCode 10031
versionName '1.0.30t' versionName '1.0.31'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -76,6 +76,7 @@ tasks.named("preBuild") {
} }
dependencies { dependencies {
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.14.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
implementation platform('androidx.compose:compose-bom:2024.05.00') implementation platform('androidx.compose:compose-bom:2024.05.00')

View File

@ -0,0 +1,7 @@
package org.ryujinx.android
enum class BackendThreading {
Auto,
Off,
On
}

View File

@ -3,7 +3,7 @@ package org.ryujinx.android
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
abstract class BaseActivity : ComponentActivity() { abstract class BaseActivity : ComponentActivity() {
companion object{ companion object {
val crashHandler = CrashHandler() val crashHandler = CrashHandler()
} }
} }

View File

@ -4,10 +4,12 @@ import java.io.File
import java.lang.Thread.UncaughtExceptionHandler import java.lang.Thread.UncaughtExceptionHandler
class CrashHandler : UncaughtExceptionHandler { class CrashHandler : UncaughtExceptionHandler {
var crashLog : String = "" var crashLog: String = ""
override fun uncaughtException(t: Thread, e: Throwable) { override fun uncaughtException(t: Thread, e: Throwable) {
crashLog += e.toString() + "\n" crashLog += e.toString() + "\n"
File(MainActivity.AppPath + "${File.separator}Logs${File.separator}crash.log").writeText(crashLog) File(MainActivity.AppPath + "${File.separator}Logs${File.separator}crash.log").writeText(
crashLog
)
} }
} }

View File

@ -34,9 +34,8 @@ typealias GamePadConfig = RadialGamePadConfig
class GameController(var activity: Activity) { class GameController(var activity: Activity) {
companion object{ companion object {
private fun Create(context: Context, activity: Activity, controller: GameController) : View private fun Create(context: Context, controller: GameController): View {
{
val inflator = LayoutInflater.from(context) val inflator = LayoutInflater.from(context)
val view = inflator.inflate(R.layout.game_layout, null) val view = inflator.inflate(R.layout.game_layout, null)
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad) view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
@ -44,12 +43,13 @@ class GameController(var activity: Activity) {
return view return view
} }
@Composable @Composable
fun Compose(viewModel: MainViewModel) : Unit { fun Compose(viewModel: MainViewModel): Unit {
AndroidView( AndroidView(
modifier = Modifier.fillMaxSize(), factory = { context -> modifier = Modifier.fillMaxSize(), factory = { context ->
val controller = GameController(viewModel.activity) val controller = GameController(viewModel.activity)
val c = Create(context, viewModel.activity, controller) val c = Create(context, controller)
viewModel.activity.lifecycleScope.apply { viewModel.activity.lifecycleScope.apply {
viewModel.activity.lifecycleScope.launch { viewModel.activity.lifecycleScope.launch {
val events = merge( val events = merge(
@ -70,12 +70,11 @@ class GameController(var activity: Activity) {
} }
} }
private var ryujinxNative: RyujinxNative
private var controllerView: View? = null private var controllerView: View? = null
var leftGamePad: GamePad var leftGamePad: GamePad
var rightGamePad: GamePad var rightGamePad: GamePad
var controllerId: Int = -1 var controllerId: Int = -1
val isVisible : Boolean val isVisible: Boolean
get() { get() {
controllerView?.apply { controllerView?.apply {
return this.isVisible return this.isVisible
@ -95,27 +94,25 @@ class GameController(var activity: Activity) {
leftGamePad.gravityY = 1f leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f rightGamePad.gravityY = 1f
ryujinxNative = RyujinxNative.instance
} }
fun setVisible(isVisible: Boolean){ fun setVisible(isVisible: Boolean) {
controllerView?.apply { controllerView?.apply {
this.isVisible = isVisible this.isVisible = isVisible
if(isVisible) if (isVisible)
connect() connect()
} }
} }
fun connect(){ fun connect() {
if(controllerId == -1) if (controllerId == -1)
controllerId = RyujinxNative.instance.inputConnectGamepad(0) controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
} }
private fun handleEvent(ev: Event) { private fun handleEvent(ev: Event) {
if(controllerId == -1) if (controllerId == -1)
controllerId = ryujinxNative.inputConnectGamepad(0) controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
controllerId.apply { controllerId.apply {
when (ev) { when (ev) {
@ -123,11 +120,11 @@ class GameController(var activity: Activity) {
val action = ev.action val action = ev.action
when (action) { when (action) {
KeyEvent.ACTION_UP -> { KeyEvent.ACTION_UP -> {
ryujinxNative.inputSetButtonReleased(ev.id, this) RyujinxNative.jnaInstance.inputSetButtonReleased(ev.id, this)
} }
KeyEvent.ACTION_DOWN -> { KeyEvent.ACTION_DOWN -> {
ryujinxNative.inputSetButtonPressed(ev.id, this) RyujinxNative.jnaInstance.inputSetButtonPressed(ev.id, this)
} }
} }
} }
@ -135,36 +132,82 @@ class GameController(var activity: Activity) {
is Event.Direction -> { is Event.Direction -> {
val direction = ev.id val direction = ev.id
when(direction) { when (direction) {
GamePadButtonInputId.DpadUp.ordinal -> { GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) { if (ev.xAxis > 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this) GamePadButtonInputId.DpadRight.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
} else if (ev.xAxis < 0) { } else if (ev.xAxis < 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this) GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
} else { } else {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this) GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
} }
if (ev.yAxis < 0) { if (ev.yAxis < 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this) GamePadButtonInputId.DpadUp.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
this
)
} else if (ev.yAxis > 0) { } else if (ev.yAxis > 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this) GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
} else { } else {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this) GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
} }
} }
GamePadButtonInputId.LeftStick.ordinal -> { GamePadButtonInputId.LeftStick.ordinal -> {
ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this) RyujinxNative.jnaInstance.inputSetStickAxis(
1,
ev.xAxis,
-ev.yAxis,
this
)
} }
GamePadButtonInputId.RightStick.ordinal -> { GamePadButtonInputId.RightStick.ordinal -> {
ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this) RyujinxNative.jnaInstance.inputSetStickAxis(
2,
ev.xAxis,
-ev.yAxis,
this
)
} }
} }
} }

View File

@ -27,8 +27,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
private var _isStarted: Boolean = false private var _isStarted: Boolean = false
private val nativeWindow: NativeWindow private val nativeWindow: NativeWindow
private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
init { init {
holder.addCallback(this) holder.addCallback(this)
@ -47,21 +45,21 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
if (_width != width || _height != height) { if (_width != width || _height != height) {
val window = nativeWindow.requeryWindowHandle() val window = nativeWindow.requeryWindowHandle()
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer) RyujinxNative.jnaInstance.graphicsSetSurface(window, nativeWindow.nativePointer)
nativeWindow.swapInterval = 0; nativeWindow.swapInterval = 0
} }
_width = width _width = width
_height = height _height = height
_nativeRyujinx.graphicsRendererSetSize( RyujinxNative.jnaInstance.graphicsRendererSetSize(
width, width,
height height
) )
if (_isStarted) { if (_isStarted) {
_nativeRyujinx.inputSetClientSize(width, height) RyujinxNative.jnaInstance.inputSetClientSize(width, height)
} }
} }
@ -86,14 +84,14 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
_isStarted = true _isStarted = true
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel; game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
_nativeRyujinx.inputInitialize(width, height) RyujinxNative.jnaInstance.inputInitialize(width, height)
val id = mainViewModel.physicalControllerManager?.connect() val id = mainViewModel.physicalControllerManager?.connect()
mainViewModel.motionSensorManager?.setControllerId(id ?: -1) mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
_nativeRyujinx.graphicsRendererSetSize( RyujinxNative.jnaInstance.graphicsRendererSetSize(
surfaceHolder.surfaceFrame.width(), surfaceHolder.surfaceFrame.width(),
surfaceHolder.surfaceFrame.height() surfaceHolder.surfaceFrame.height()
) )
@ -108,12 +106,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
var c = 0 var c = 0
val helper = NativeHelpers.instance val helper = NativeHelpers.instance
while (_isStarted) { while (_isStarted) {
_nativeRyujinx.inputUpdate() RyujinxNative.jnaInstance.inputUpdate()
Thread.sleep(1) Thread.sleep(1)
showLoading?.apply { showLoading?.apply {
if (value) { if (value) {
var value = helper.getProgressValue() val value = helper.getProgressValue()
if (value != -1f) if (value != -1f)
progress?.apply { progress?.apply {
@ -134,9 +132,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
} }
c = 0 c = 0
mainViewModel.updateStats( mainViewModel.updateStats(
_nativeRyujinx.deviceGetGameFifo(), RyujinxNative.jnaInstance.deviceGetGameFifo(),
_nativeRyujinx.deviceGetGameFrameRate(), RyujinxNative.jnaInstance.deviceGetGameFrameRate(),
_nativeRyujinx.deviceGetGameFrameTime() RyujinxNative.jnaInstance.deviceGetGameFrameTime()
) )
} }
} }
@ -148,7 +146,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
thread { thread {
mainViewModel.activity.uiHandler.listen() mainViewModel.activity.uiHandler.listen()
} }
_nativeRyujinx.graphicsRendererRunLoop() RyujinxNative.jnaInstance.graphicsRendererRunLoop()
game?.close() game?.close()
} }

View File

@ -2,6 +2,7 @@ package org.ryujinx.android
enum class GamePadButtonInputId { enum class GamePadButtonInputId {
None, None,
// Buttons // Buttons
A, A,
B, B,

View File

@ -1,22 +0,0 @@
package org.ryujinx.android
import android.R.bool
class GraphicsConfiguration {
var ResScale = 1f
var MaxAnisotropy = -1f
var FastGpuTime: Boolean = true
var Fast2DCopy: Boolean = true
var EnableMacroJit: Boolean = false
var EnableMacroHLE: Boolean = true
var EnableShaderCache: Boolean = true
var EnableTextureRecompression: Boolean = false
var BackendThreading: Int = org.ryujinx.android.BackendThreading.Auto.ordinal
}
enum class BackendThreading
{
Auto,
Off,
On
}

View File

@ -80,9 +80,9 @@ class Helpers {
currentProgressName: MutableState<String>, currentProgressName: MutableState<String>,
finish: () -> Unit finish: () -> Unit
) { ) {
var fPath = path + "/${file.name}"; var fPath = path + "/${file.name}"
var callback: FileCallback? = object : FileCallback() { var callback: FileCallback? = object : FileCallback() {
override fun onFailed(errorCode: FileCallback.ErrorCode) { override fun onFailed(errorCode: ErrorCode) {
super.onFailed(errorCode) super.onFailed(errorCode)
File(fPath).delete() File(fPath).delete()
finish() finish()
@ -189,7 +189,6 @@ class Helpers {
dir.mkdirs() dir.mkdirs()
} }
ZipInputStream(stream).use { zip -> ZipInputStream(stream).use { zip ->
var count = 0
while (true) { while (true) {
val header = zip.nextEntry ?: break val header = zip.nextEntry ?: break
if (!folders.any { header.fileName.startsWith(it) }) { if (!folders.any { header.fileName.startsWith(it) }) {
@ -217,7 +216,7 @@ class Helpers {
} }
} finally { } finally {
isImporting.value = false isImporting.value = false
RyujinxNative.instance.deviceReloadFilesystem() RyujinxNative.jnaInstance.deviceReloadFilesystem()
} }
} }
} }

View File

@ -20,7 +20,7 @@ import compose.icons.CssGgIcons
import compose.icons.cssggicons.Games import compose.icons.cssggicons.Games
class Icons { class Icons {
companion object{ companion object {
/// Icons exported from https://www.composables.com/icons /// Icons exported from https://www.composables.com/icons
@Composable @Composable
fun listView(color: Color): ImageVector { fun listView(color: Color): ImageVector {
@ -216,6 +216,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun applets(color: Color): ImageVector { fun applets(color: Color): ImageVector {
return remember { return remember {
@ -331,6 +332,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun playArrow(color: Color): ImageVector { fun playArrow(color: Color): ImageVector {
return remember { return remember {
@ -372,6 +374,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun folderOpen(color: Color): ImageVector { fun folderOpen(color: Color): ImageVector {
return remember { return remember {
@ -437,6 +440,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun gameUpdate(): ImageVector { fun gameUpdate(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
@ -514,6 +518,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun download(): ImageVector { fun download(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
@ -583,6 +588,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun vSync(): ImageVector { fun vSync(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
@ -653,6 +659,7 @@ class Icons {
}.build() }.build()
} }
} }
@Composable @Composable
fun videoGame(): ImageVector { fun videoGame(): ImageVector {
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
@ -753,7 +760,7 @@ class Icons {
@Preview @Preview
@Composable @Composable
fun Preview(){ fun Preview() {
IconButton(modifier = Modifier.padding(4.dp), onClick = { IconButton(modifier = Modifier.padding(4.dp), onClick = {
}) { }) {
Icon( Icon(

View File

@ -9,11 +9,12 @@ import java.net.URLConnection
class Logging(private var viewModel: MainViewModel) { class Logging(private var viewModel: MainViewModel) {
val logPath = MainActivity.AppPath + "/Logs" val logPath = MainActivity.AppPath + "/Logs"
init{
init {
File(logPath).mkdirs() File(logPath).mkdirs()
} }
fun requestExport(){ fun requestExport() {
val files = File(logPath).listFiles() val files = File(logPath).listFiles()
files?.apply { files?.apply {
val zipExportPath = MainActivity.AppPath + "/log.zip" val zipExportPath = MainActivity.AppPath + "/log.zip"
@ -22,7 +23,7 @@ class Logging(private var viewModel: MainViewModel) {
if (files.isNotEmpty()) { if (files.isNotEmpty()) {
val zipFile = ZipFile(zipExportPath) val zipFile = ZipFile(zipExportPath)
for (file in files) { for (file in files) {
if(file.isFile) { if (file.isFile) {
zipFile.addFile(file) zipFile.addFile(file)
count++ count++
} }
@ -30,13 +31,17 @@ class Logging(private var viewModel: MainViewModel) {
zipFile.close() zipFile.close()
} }
if (count > 0) { if (count > 0) {
val zip =File (zipExportPath) val zip = File(zipExportPath)
val uri = FileProvider.getUriForFile(viewModel.activity, viewModel.activity.packageName + ".fileprovider", zip) val uri = FileProvider.getUriForFile(
viewModel.activity,
viewModel.activity.packageName + ".fileprovider",
zip
)
val intent = Intent(Intent.ACTION_SEND) val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri) intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name)) intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, "Share logs"); val chooser = Intent.createChooser(intent, "Share logs")
viewModel.activity.startActivity(chooser) viewModel.activity.startActivity(chooser)
} else { } else {
File(zipExportPath).delete() File(zipExportPath).delete()
@ -45,7 +50,7 @@ class Logging(private var viewModel: MainViewModel) {
} }
fun clearLogs() { fun clearLogs() {
if(File(logPath).exists()){ if (File(logPath).exists()) {
File(logPath).deleteRecursively() File(logPath).deleteRecursively()
} }

View File

@ -42,7 +42,7 @@ class MainActivity : BaseActivity() {
fun frameEnded(gameTime: Long) { fun frameEnded(gameTime: Long) {
mainViewModel?.activity?.apply { mainViewModel?.activity?.apply {
if (isActive && QuickSettings(this).enablePerformanceMode) { if (isActive && QuickSettings(this).enablePerformanceMode) {
mainViewModel?.performanceManager?.setTurboMode(true); mainViewModel?.performanceManager?.setTurboMode(true)
} }
} }
mainViewModel?.gameHost?.hideProgressIndicator() mainViewModel?.gameHost?.hideProgressIndicator()
@ -65,40 +65,40 @@ class MainActivity : BaseActivity() {
val appPath: String = AppPath val appPath: String = AppPath
var quickSettings = QuickSettings(this) var quickSettings = QuickSettings(this)
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Debug.ordinal, LogLevel.Debug.ordinal,
quickSettings.enableDebugLogs quickSettings.enableDebugLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Info.ordinal, LogLevel.Info.ordinal,
quickSettings.enableInfoLogs quickSettings.enableInfoLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Stub.ordinal, LogLevel.Stub.ordinal,
quickSettings.enableStubLogs quickSettings.enableStubLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Warning.ordinal, LogLevel.Warning.ordinal,
quickSettings.enableWarningLogs quickSettings.enableWarningLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Error.ordinal, LogLevel.Error.ordinal,
quickSettings.enableErrorLogs quickSettings.enableErrorLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.AccessLog.ordinal, LogLevel.AccessLog.ordinal,
quickSettings.enableAccessLogs quickSettings.enableAccessLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Guest.ordinal, LogLevel.Guest.ordinal,
quickSettings.enableGuestLogs quickSettings.enableGuestLogs
) )
RyujinxNative.instance.loggingSetEnabled( RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.Trace.ordinal, LogLevel.Trace.ordinal,
quickSettings.enableTraceLogs quickSettings.enableTraceLogs
) )
val success = val success =
RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath)) RyujinxNative.jnaInstance.javaInitialize(appPath)
uiHandler = UiHandler() uiHandler = UiHandler()
_isInit = success _isInit = success

View File

@ -15,18 +15,20 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
private var controllerId: Int = -1 private var controllerId: Int = -1
private val motionGyroOrientation : FloatArray = FloatArray(3) private val motionGyroOrientation: FloatArray = FloatArray(3)
private val motionAcelOrientation : FloatArray = FloatArray(3) private val motionAcelOrientation: FloatArray = FloatArray(3)
init { init {
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
setOrientation90() setOrientation90()
var orientationListener = object : OrientationEventListener(activity){ var orientationListener = object : OrientationEventListener(activity) {
override fun onOrientationChanged(orientation: Int) { override fun onOrientationChanged(orientation: Int) {
when{ when {
isWithinOrientationRange(orientation, 270) -> { isWithinOrientationRange(orientation, 270) -> {
setOrientation270() setOrientation270()
} }
isWithinOrientationRange(orientation, 90) -> { isWithinOrientationRange(orientation, 90) -> {
setOrientation90() setOrientation90()
} }
@ -34,8 +36,8 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
} }
private fun isWithinOrientationRange( private fun isWithinOrientationRange(
currentOrientation : Int, targetOrientation : Int, epsilon : Int = 90 currentOrientation: Int, targetOrientation: Int, epsilon: Int = 90
) : Boolean { ): Boolean {
return currentOrientation > targetOrientation - epsilon return currentOrientation > targetOrientation - epsilon
&& currentOrientation < targetOrientation + epsilon && currentOrientation < targetOrientation + epsilon
} }
@ -50,6 +52,7 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
motionAcelOrientation[1] = -1.0f motionAcelOrientation[1] = -1.0f
motionAcelOrientation[2] = -1.0f motionAcelOrientation[2] = -1.0f
} }
fun setOrientation90() { fun setOrientation90() {
motionGyroOrientation[0] = 1.0f motionGyroOrientation[0] = 1.0f
motionGyroOrientation[1] = -1.0f motionGyroOrientation[1] = -1.0f
@ -59,30 +62,38 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
motionAcelOrientation[2] = -1.0f motionAcelOrientation[2] = -1.0f
} }
fun setControllerId(id: Int){ fun setControllerId(id: Int) {
controllerId = id controllerId = id
} }
fun register(){ fun register() {
if(isRegistered) if (isRegistered)
return return
gyro?.apply { gyro?.apply {
sensorManager.registerListener(this@MotionSensorManager, gyro, SensorManager.SENSOR_DELAY_GAME) sensorManager.registerListener(
this@MotionSensorManager,
gyro,
SensorManager.SENSOR_DELAY_GAME
)
} }
accelerometer?.apply { accelerometer?.apply {
sensorManager.registerListener(this@MotionSensorManager, accelerometer, SensorManager.SENSOR_DELAY_GAME) sensorManager.registerListener(
this@MotionSensorManager,
accelerometer,
SensorManager.SENSOR_DELAY_GAME
)
} }
isRegistered = true; isRegistered = true
} }
fun unregister(){ fun unregister() {
sensorManager.unregisterListener(this) sensorManager.unregisterListener(this)
isRegistered = false isRegistered = false
if (controllerId != -1){ if (controllerId != -1) {
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId) RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId) RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
} }
} }
@ -96,20 +107,25 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
val y = motionAcelOrientation[1] * event.values[0] val y = motionAcelOrientation[1] * event.values[0]
val z = motionAcelOrientation[2] * event.values[2] val z = motionAcelOrientation[2] * event.values[2]
RyujinxNative.instance.inputSetAccelerometerData(x, y, z, controllerId) RyujinxNative.jnaInstance.inputSetAccelerometerData(
x,
y,
z,
controllerId
)
} }
Sensor.TYPE_GYROSCOPE -> { Sensor.TYPE_GYROSCOPE -> {
val x = motionGyroOrientation[0] * event.values[1] val x = motionGyroOrientation[0] * event.values[1]
val y = motionGyroOrientation[1] * event.values[0] val y = motionGyroOrientation[1] * event.values[0]
val z = motionGyroOrientation[2] * event.values[2] val z = motionGyroOrientation[2] * event.values[2]
RyujinxNative.instance.inputSetGyroData(x, y, z, controllerId) RyujinxNative.jnaInstance.inputSetGyroData(x, y, z, controllerId)
} }
} }
} }
else { else {
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId) RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId) RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
} }
} }

View File

@ -6,6 +6,7 @@ class NativeHelpers {
companion object { companion object {
val instance = NativeHelpers() val instance = NativeHelpers()
init { init {
System.loadLibrary("ryujinxjni") System.loadLibrary("ryujinxjni")
} }
@ -27,18 +28,18 @@ class NativeHelpers {
external fun getMaxSwapInterval(nativeWindow: Long): Int external fun getMaxSwapInterval(nativeWindow: Long): Int
external fun getMinSwapInterval(nativeWindow: Long): Int external fun getMinSwapInterval(nativeWindow: Long): Int
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getProgressInfo() : String external fun getProgressInfo(): String
external fun getProgressValue() : Float external fun getProgressValue(): Float
external fun storeStringJava(string: String) : Long external fun storeStringJava(string: String): Long
external fun getStringJava(id: Long) : String external fun getStringJava(id: Long): String
external fun setIsInitialOrientationFlipped(isFlipped: Boolean) external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
external fun getUiHandlerRequestType() : Int external fun getUiHandlerRequestType(): Int
external fun getUiHandlerRequestTitle() : Long external fun getUiHandlerRequestTitle(): Long
external fun getUiHandlerRequestMessage() : Long external fun getUiHandlerRequestMessage(): Long
external fun getUiHandlerMinLength() : Int external fun getUiHandlerMinLength(): Int
external fun getUiHandlerMaxLength() : Int external fun getUiHandlerMaxLength(): Int
external fun getUiHandlerKeyboardMode() : Int external fun getUiHandlerKeyboardMode(): Int
external fun getUiHandlerRequestWatermark() : Long external fun getUiHandlerRequestWatermark(): Long
external fun getUiHandlerRequestInitialText() : Long external fun getUiHandlerRequestInitialText(): Long
external fun getUiHandlerRequestSubtitle() : Long external fun getUiHandlerRequestSubtitle(): Long
} }

View File

@ -4,35 +4,35 @@ import android.view.SurfaceView
class NativeWindow(val surface: SurfaceView) { class NativeWindow(val surface: SurfaceView) {
var nativePointer: Long var nativePointer: Long
var nativeHelpers: NativeHelpers = NativeHelpers.instance private val nativeHelpers: NativeHelpers = NativeHelpers.instance
private var _swapInterval : Int = 0 private var _swapInterval: Int = 0
var maxSwapInterval : Int = 0 var maxSwapInterval: Int = 0
get() { get() {
return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer) return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer)
} }
var minSwapInterval : Int = 0 var minSwapInterval: Int = 0
get() { get() {
return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer) return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer)
} }
var swapInterval : Int var swapInterval: Int
get() { get() {
return _swapInterval return _swapInterval
} }
set(value) { set(value) {
if(nativePointer == -1L || nativeHelpers.setSwapInterval(nativePointer, value) == 0) if (nativePointer == -1L || nativeHelpers.setSwapInterval(nativePointer, value) == 0)
_swapInterval = value _swapInterval = value
} }
init { init {
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface); nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
swapInterval = maxOf(1, minSwapInterval) swapInterval = maxOf(1, minSwapInterval)
} }
fun requeryWindowHandle() : Long { fun requeryWindowHandle(): Long {
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface) nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
swapInterval = swapInterval swapInterval = swapInterval

View File

@ -15,10 +15,10 @@ import java.io.RandomAccessFile
class PerformanceMonitor { class PerformanceMonitor {
val numberOfCores = Runtime.getRuntime().availableProcessors() val numberOfCores = Runtime.getRuntime().availableProcessors()
fun getFrequencies() : List<Double> { fun getFrequencies(): List<Double> {
val frequencies = mutableListOf<Double>() val frequencies = mutableListOf<Double>()
for (i in 0..<numberOfCores){ for (i in 0..<numberOfCores) {
var freq = 0.0; var freq = 0.0
try { try {
val reader = RandomAccessFile( val reader = RandomAccessFile(
"/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_cur_freq", "/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_cur_freq",
@ -27,8 +27,7 @@ class PerformanceMonitor {
val f = reader.readLine() val f = reader.readLine()
reader.close() reader.close()
freq = f.toDouble() / 1000.0 freq = f.toDouble() / 1000.0
} } catch (e: Exception) {
catch (e:Exception){
} }
@ -38,14 +37,14 @@ class PerformanceMonitor {
return frequencies.toList() return frequencies.toList()
} }
fun getMemoryUsage() : List<Int> { fun getMemoryUsage(): List<Int> {
val mem = mutableListOf<Int>() val mem = mutableListOf<Int>()
MainActivity.mainViewModel?.activity?.apply { MainActivity.mainViewModel?.activity?.apply {
val actManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager val actManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo() val memInfo = ActivityManager.MemoryInfo()
actManager.getMemoryInfo(memInfo) actManager.getMemoryInfo(memInfo)
val availMemory = memInfo.availMem.toDouble()/(1024*1024) val availMemory = memInfo.availMem.toDouble() / (1024 * 1024)
val totalMemory= memInfo.totalMem.toDouble()/(1024*1024) val totalMemory = memInfo.totalMem.toDouble() / (1024 * 1024)
mem.add((totalMemory - availMemory).toInt()) mem.add((totalMemory - availMemory).toInt())
mem.add(totalMemory.toInt()) mem.add(totalMemory.toInt())
@ -55,11 +54,11 @@ class PerformanceMonitor {
@Composable @Composable
fun RenderUsage() { fun RenderUsage() {
LazyColumn{ LazyColumn {
val frequencies = getFrequencies() val frequencies = getFrequencies()
val mem = getMemoryUsage() val mem = getMemoryUsage()
for (i in 0..<numberOfCores){ for (i in 0..<numberOfCores) {
item { item {
Row { Row {
Text(modifier = Modifier.padding(2.dp), text = "CPU ${i}") Text(modifier = Modifier.padding(2.dp), text = "CPU ${i}")
@ -69,7 +68,7 @@ class PerformanceMonitor {
} }
} }
if(mem.isNotEmpty()) { if (mem.isNotEmpty()) {
item { item {
Row { Row {
Text(modifier = Modifier.padding(2.dp), text = "Used") Text(modifier = Modifier.padding(2.dp), text = "Used")

View File

@ -7,25 +7,23 @@ import org.ryujinx.android.viewmodels.QuickSettings
class PhysicalControllerManager(val activity: MainActivity) { class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1 private var controllerId: Int = -1
private var ryujinxNative: RyujinxNative = RyujinxNative.instance
fun onKeyEvent(event: KeyEvent) : Boolean{ fun onKeyEvent(event: KeyEvent): Boolean {
val id = getGamePadButtonInputId(event.keyCode) val id = getGamePadButtonInputId(event.keyCode)
if(id != GamePadButtonInputId.None) { if (id != GamePadButtonInputId.None) {
val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0 val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0
if (/*controllerId != -1 &&*/ isNotFallback) { if (/*controllerId != -1 &&*/ isNotFallback) {
when (event.action) { when (event.action) {
KeyEvent.ACTION_UP -> { KeyEvent.ACTION_UP -> {
ryujinxNative.inputSetButtonReleased(id.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonReleased(id.ordinal, controllerId)
} }
KeyEvent.ACTION_DOWN -> { KeyEvent.ACTION_DOWN -> {
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonPressed(id.ordinal, controllerId)
} }
} }
return true return true
} } else if (!isNotFallback) {
else if(!isNotFallback){
return true return true
} }
} }
@ -34,45 +32,91 @@ class PhysicalControllerManager(val activity: MainActivity) {
} }
fun onMotionEvent(ev: MotionEvent) { fun onMotionEvent(ev: MotionEvent) {
if(true) { if (true) {
if(ev.action == MotionEvent.ACTION_MOVE) { if (ev.action == MotionEvent.ACTION_MOVE) {
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X) val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y) val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z) val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ) val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId) RyujinxNative.jnaInstance.inputSetStickAxis(
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId) 1,
leftStickX,
-leftStickY,
controllerId
)
RyujinxNative.jnaInstance.inputSetStickAxis(
2,
rightStickX,
-rightStickY,
controllerId
)
ev.device?.apply { ev.device?.apply {
if(sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD){ if (sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD) {
// Controller uses HAT // Controller uses HAT
val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X) val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X)
val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y) val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y)
if(dPadVert == 0.0f){ if (dPadVert == 0.0f) {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, controllerId) GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
} }
if(dPadHor == 0.0f){ if (dPadHor == 0.0f) {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, controllerId) GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
} }
if(dPadVert < 0.0f){ if (dPadVert < 0.0f) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, controllerId) GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
} }
if(dPadHor < 0.0f){ if (dPadHor < 0.0f) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonPressed(
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, controllerId) GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
} }
if(dPadVert > 0.0f){ if (dPadVert > 0.0f) {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, controllerId) GamePadButtonInputId.DpadUp.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadDown.ordinal,
controllerId
)
} }
if(dPadHor > 0.0f){ if (dPadHor > 0.0f) {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, controllerId) RyujinxNative.jnaInstance.inputSetButtonReleased(
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, controllerId) GamePadButtonInputId.DpadLeft.ordinal,
controllerId
)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadRight.ordinal,
controllerId
)
} }
} }
} }
@ -80,12 +124,12 @@ class PhysicalControllerManager(val activity: MainActivity) {
} }
} }
fun connect() : Int { fun connect(): Int {
controllerId = ryujinxNative.inputConnectGamepad(0) controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
return controllerId return controllerId
} }
fun disconnect(){ fun disconnect() {
controllerId = -1 controllerId = -1
} }

View File

@ -9,11 +9,12 @@ class RyujinxApplication : Application() {
instance = this instance = this
} }
fun getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir fun getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
companion object { companion object {
lateinit var instance : RyujinxApplication lateinit var instance: RyujinxApplication
private set private set
val context : Context get() = instance.applicationContext val context: Context get() = instance.applicationContext
} }
} }

View File

@ -1,20 +1,11 @@
package org.ryujinx.android package org.ryujinx.android
import com.sun.jna.Library
import com.sun.jna.Native
import org.ryujinx.android.viewmodels.GameInfo import org.ryujinx.android.viewmodels.GameInfo
@Suppress("KotlinJniMissingFunction") interface RyujinxNativeJna : Library {
class RyujinxNative { fun deviceInitialize(
external fun initialize(appPath: Long): Boolean
companion object {
val instance: RyujinxNative = RyujinxNative()
init {
System.loadLibrary("ryujinx")
}
}
external fun deviceInitialize(
isHostMapped: Boolean, useNce: Boolean, isHostMapped: Boolean, useNce: Boolean,
systemLanguage: Int, systemLanguage: Int,
regionCode: Int, regionCode: Int,
@ -22,60 +13,81 @@ class RyujinxNative {
enableDockedMode: Boolean, enableDockedMode: Boolean,
enablePtc: Boolean, enablePtc: Boolean,
enableInternetAccess: Boolean, enableInternetAccess: Boolean,
timeZone: Long, timeZone: String,
ignoreMissingServices: Boolean ignoreMissingServices: Boolean
): Boolean ): Boolean
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean fun graphicsInitialize(
external fun graphicsInitializeRenderer( rescale: Float = 1f,
maxAnisotropy: Float = 1f,
fastGpuTime: Boolean = true,
fast2DCopy: Boolean = true,
enableMacroJit: Boolean = false,
enableMacroHLE: Boolean = true,
enableShaderCache: Boolean = true,
enableTextureRecompression: Boolean = false,
backendThreading: Int = BackendThreading.Auto.ordinal
): Boolean
fun graphicsInitializeRenderer(
extensions: Array<String>, extensions: Array<String>,
extensionsLength: Int,
driver: Long driver: Long
): Boolean ): Boolean
external fun deviceLoad(game: String): Boolean fun javaInitialize(appPath: String): Boolean
external fun deviceLaunchMiiEditor(): Boolean fun deviceLaunchMiiEditor(): Boolean
external fun deviceGetGameFrameRate(): Double fun deviceGetGameFrameRate(): Double
external fun deviceGetGameFrameTime(): Double fun deviceGetGameFrameTime(): Double
external fun deviceGetGameFifo(): Double fun deviceGetGameFifo(): Double
external fun deviceGetGameInfo(fileDescriptor: Int, extension: Long): GameInfo fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
external fun deviceGetGameInfoFromPath(path: String): GameInfo fun graphicsRendererSetSize(width: Int, height: Int)
external fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean fun graphicsRendererSetVsync(enabled: Boolean)
external fun graphicsRendererSetSize(width: Int, height: Int) fun graphicsRendererRunLoop()
external fun graphicsRendererSetVsync(enabled: Boolean) fun deviceReloadFilesystem()
external fun graphicsRendererRunLoop() fun inputInitialize(width: Int, height: Int)
external fun deviceReloadFilesystem() fun inputSetClientSize(width: Int, height: Int)
external fun inputInitialize(width: Int, height: Int) fun inputSetTouchPoint(x: Int, y: Int)
external fun inputSetClientSize(width: Int, height: Int) fun inputReleaseTouchPoint()
external fun inputSetTouchPoint(x: Int, y: Int) fun inputUpdate()
external fun inputReleaseTouchPoint() fun inputSetButtonPressed(button: Int, id: Int)
external fun inputUpdate() fun inputSetButtonReleased(button: Int, id: Int)
external fun inputSetButtonPressed(button: Int, id: Int) fun inputConnectGamepad(index: Int): Int
external fun inputSetButtonReleased(button: Int, id: Int) fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
external fun inputConnectGamepad(index: Int): Int fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int) fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
external fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int) fun graphicsSetSurface(surface: Long, window: Long)
external fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int) fun deviceCloseEmulation()
external fun graphicsSetSurface(surface: Long, window: Long) fun deviceSignalEmulationClose()
external fun deviceCloseEmulation() fun userGetOpenedUser(): String
external fun deviceSignalEmulationClose() fun userGetUserPicture(userId: String): String
external fun deviceGetDlcTitleId(path: Long, ncaPath: Long): Long fun userSetUserPicture(userId: String, picture: String)
external fun deviceGetDlcContentList(path: Long, titleId: Long): Array<String> fun userGetUserName(userId: String): String
external fun userGetOpenedUser(): Long fun userSetUserName(userId: String, userName: String)
external fun userGetUserPicture(userId: Long): Long fun userAddUser(username: String, picture: String)
external fun userSetUserPicture(userId: String, picture: String) fun userDeleteUser(userId: String)
external fun userGetUserName(userId: Long): Long fun userOpenUser(userId: String)
external fun userSetUserName(userId: String, userName: String) fun userCloseUser(userId: String)
external fun userGetAllUsers(): Array<String> fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
external fun userAddUser(username: String, picture: String) fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): String
external fun userDeleteUser(userId: String) fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
external fun userOpenUser(userId: Long) fun deviceGetInstalledFirmwareVersion(): String
external fun userCloseUser(userId: String) fun uiHandlerSetup()
external fun loggingSetEnabled(logLevel: Int, enabled: Boolean) fun uiHandlerWait()
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long fun uiHandlerStopWait()
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean) fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
external fun deviceGetInstalledFirmwareVersion() : Long fun deviceGetDlcTitleId(path: String, ncaPath: String): String
external fun uiHandlerSetup() fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
external fun uiHandlerWait() fun userGetAllUsers(): Array<String>
external fun uiHandlerStopWait() fun deviceGetDlcContentList(path: String, titleId: Long): Array<String>
external fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long) }
class RyujinxNative {
companion object {
val jnaInstance: RyujinxNativeJna = Native.load(
"ryujinx",
RyujinxNativeJna::class.java
)
}
} }

View File

@ -14,6 +14,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -50,13 +51,13 @@ class UiHandler {
var shouldListen = true var shouldListen = true
init { init {
RyujinxNative.instance.uiHandlerSetup() RyujinxNative.jnaInstance.uiHandlerSetup()
} }
fun listen() { fun listen() {
showMessage.value = false showMessage.value = false
while (shouldListen) { while (shouldListen) {
RyujinxNative.instance.uiHandlerWait() RyujinxNative.jnaInstance.uiHandlerWait()
title = title =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle()) NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
@ -79,7 +80,7 @@ class UiHandler {
fun stop() { fun stop() {
shouldListen = false shouldListen = false
RyujinxNative.instance.uiHandlerStopWait() RyujinxNative.jnaInstance.uiHandlerStopWait()
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -96,23 +97,24 @@ class UiHandler {
mutableStateOf("") mutableStateOf("")
} }
fun validate() : Boolean{ fun validate(): Boolean {
if(inputText.value.isEmpty()){ if (inputText.value.isEmpty()) {
validation.value = "Must be between ${minLength} and ${maxLength} characters" validation.value = "Must be between ${minLength} and ${maxLength} characters"
} } else {
else{
return inputText.value.length < minLength || inputText.value.length > maxLength return inputText.value.length < minLength || inputText.value.length > maxLength
} }
return false; return false
} }
fun getInputType(): KeyboardType { fun getInputType(): KeyboardType {
return when(mode){ return when (mode) {
KeyboardMode.Default -> KeyboardType.Text KeyboardMode.Default -> KeyboardType.Text
KeyboardMode.Numeric -> KeyboardType.Decimal KeyboardMode.Numeric -> KeyboardType.Decimal
KeyboardMode.ASCII -> KeyboardType.Ascii KeyboardMode.ASCII -> KeyboardType.Ascii
else -> { KeyboardType.Text} else -> {
KeyboardType.Text
}
} }
} }
@ -125,15 +127,15 @@ class UiHandler {
NativeHelpers.instance.storeStringJava(inputListener.value) NativeHelpers.instance.storeStringJava(inputListener.value)
} }
showMessageListener.value = false showMessageListener.value = false
RyujinxNative.instance.uiHandlerSetResponse(true, input) RyujinxNative.jnaInstance.uiHandlerSetResponse(true, input)
} }
if (showMessageListener.value) { if (showMessageListener.value) {
AlertDialog( BasicAlertDialog(
onDismissRequest = { },
modifier = Modifier modifier = Modifier
.wrapContentWidth() .wrapContentWidth()
.wrapContentHeight(), .wrapContentHeight(),
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = false, false) properties = DialogProperties(dismissOnBackPress = false, false)
) { ) {
Column { Column {

View File

@ -21,7 +21,7 @@ class DocumentProvider : DocumentsProvider() {
private val applicationName = "Ryujinx" private val applicationName = "Ryujinx"
companion object { companion object {
private val DEFAULT_ROOT_PROJECTION : Array<String> = arrayOf( private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
DocumentsContract.Root.COLUMN_ROOT_ID, DocumentsContract.Root.COLUMN_ROOT_ID,
DocumentsContract.Root.COLUMN_MIME_TYPES, DocumentsContract.Root.COLUMN_MIME_TYPES,
DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.COLUMN_FLAGS,
@ -32,7 +32,7 @@ class DocumentProvider : DocumentsProvider() {
DocumentsContract.Root.COLUMN_AVAILABLE_BYTES DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
) )
private val DEFAULT_DOCUMENT_PROJECTION : Array<String> = arrayOf( private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_DISPLAY_NAME,
@ -41,19 +41,19 @@ class DocumentProvider : DocumentsProvider() {
DocumentsContract.Document.COLUMN_SIZE DocumentsContract.Document.COLUMN_SIZE
) )
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".providers" const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".providers"
const val ROOT_ID : String = "root" const val ROOT_ID: String = "root"
} }
override fun onCreate() : Boolean { override fun onCreate(): Boolean {
return true return true
} }
/** /**
* @return The [File] that corresponds to the document ID supplied by [getDocumentId] * @return The [File] that corresponds to the document ID supplied by [getDocumentId]
*/ */
private fun getFile(documentId : String) : File { private fun getFile(documentId: String): File {
if (documentId.startsWith(ROOT_ID)) { if (documentId.startsWith(ROOT_ID)) {
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
@ -66,17 +66,20 @@ class DocumentProvider : DocumentsProvider() {
/** /**
* @return A unique ID for the provided [File] * @return A unique ID for the provided [File]
*/ */
private fun getDocumentId(file : File) : String { private fun getDocumentId(file: File): String {
return "$ROOT_ID/${file.toRelativeString(baseDirectory)}" return "$ROOT_ID/${file.toRelativeString(baseDirectory)}"
} }
override fun queryRoots(projection : Array<out String>?) : Cursor { override fun queryRoots(projection: Array<out String>?): Cursor {
val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION) val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
cursor.newRow().apply { cursor.newRow().apply {
add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID) add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
add(DocumentsContract.Root.COLUMN_SUMMARY, null) add(DocumentsContract.Root.COLUMN_SUMMARY, null)
add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD) add(
DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
)
add(DocumentsContract.Root.COLUMN_TITLE, applicationName) add(DocumentsContract.Root.COLUMN_TITLE, applicationName)
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*") add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
@ -87,22 +90,23 @@ class DocumentProvider : DocumentsProvider() {
return cursor return cursor
} }
override fun queryDocument(documentId : String?, projection : Array<out String>?) : Cursor { override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
return includeFile(cursor, documentId, null) return includeFile(cursor, documentId, null)
} }
override fun isChildDocument(parentDocumentId : String?, documentId : String?) : Boolean { override fun isChildDocument(parentDocumentId: String?, documentId: String?): Boolean {
return documentId?.startsWith(parentDocumentId!!) ?: false return documentId?.startsWith(parentDocumentId!!) ?: false
} }
/** /**
* @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file * @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file
*/ */
fun File.resolveWithoutConflict(name : String) : File { fun File.resolveWithoutConflict(name: String): File {
var file = resolve(name) var file = resolve(name)
if (file.exists()) { if (file.exists()) {
var noConflictId = 1 // Makes sure two files don't have the same name by adding a number to the end var noConflictId =
1 // Makes sure two files don't have the same name by adding a number to the end
val extension = name.substringAfterLast('.') val extension = name.substringAfterLast('.')
val baseName = name.substringBeforeLast('.') val baseName = name.substringBeforeLast('.')
while (file.exists()) while (file.exists())
@ -111,7 +115,11 @@ class DocumentProvider : DocumentsProvider() {
return file return file
} }
override fun createDocument(parentDocumentId : String?, mimeType : String?, displayName : String) : String? { override fun createDocument(
parentDocumentId: String?,
mimeType: String?,
displayName: String
): String {
val parentFile = getFile(parentDocumentId!!) val parentFile = getFile(parentDocumentId!!)
val newFile = parentFile.resolveWithoutConflict(displayName) val newFile = parentFile.resolveWithoutConflict(displayName)
@ -123,20 +131,20 @@ class DocumentProvider : DocumentsProvider() {
if (!newFile.createNewFile()) if (!newFile.createNewFile())
throw IOException("Failed to create file") throw IOException("Failed to create file")
} }
} catch (e : IOException) { } catch (e: IOException) {
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
} }
return getDocumentId(newFile) return getDocumentId(newFile)
} }
override fun deleteDocument(documentId : String?) { override fun deleteDocument(documentId: String?) {
val file = getFile(documentId!!) val file = getFile(documentId!!)
if (!file.delete()) if (!file.delete())
throw FileNotFoundException("Couldn't delete document with ID '$documentId'") throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
} }
override fun removeDocument(documentId : String, parentDocumentId : String?) { override fun removeDocument(documentId: String, parentDocumentId: String?) {
val parent = getFile(parentDocumentId!!) val parent = getFile(parentDocumentId!!)
val file = getFile(documentId) val file = getFile(documentId)
@ -148,18 +156,19 @@ class DocumentProvider : DocumentsProvider() {
} }
} }
override fun renameDocument(documentId : String?, displayName : String?) : String? { override fun renameDocument(documentId: String?, displayName: String?): String {
if (displayName == null) if (displayName == null)
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
val sourceFile = getFile(documentId!!) val sourceFile = getFile(documentId!!)
val sourceParentFile = sourceFile.parentFile ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") val sourceParentFile = sourceFile.parentFile
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
val destFile = sourceParentFile.resolve(displayName) val destFile = sourceParentFile.resolve(displayName)
try { try {
if (!sourceFile.renameTo(destFile)) if (!sourceFile.renameTo(destFile))
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
} catch (e : Exception) { } catch (e: Exception) {
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
} }
@ -167,16 +176,16 @@ class DocumentProvider : DocumentsProvider() {
} }
private fun copyDocument( private fun copyDocument(
sourceDocumentId : String, sourceParentDocumentId : String, sourceDocumentId: String, sourceParentDocumentId: String,
targetParentDocumentId : String? targetParentDocumentId: String?
) : String? { ): String? {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
return copyDocument(sourceDocumentId, targetParentDocumentId) return copyDocument(sourceDocumentId, targetParentDocumentId)
} }
override fun copyDocument(sourceDocumentId : String, targetParentDocumentId : String?) : String? { override fun copyDocument(sourceDocumentId: String, targetParentDocumentId: String?): String {
val parent = getFile(targetParentDocumentId!!) val parent = getFile(targetParentDocumentId!!)
val oldFile = getFile(sourceDocumentId) val oldFile = getFile(sourceDocumentId)
val newFile = parent.resolveWithoutConflict(oldFile.name) val newFile = parent.resolveWithoutConflict(oldFile.name)
@ -190,7 +199,7 @@ class DocumentProvider : DocumentsProvider() {
inStream.copyTo(outStream) inStream.copyTo(outStream)
} }
} }
} catch (e : IOException) { } catch (e: IOException) {
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}") throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
} }
@ -198,9 +207,9 @@ class DocumentProvider : DocumentsProvider() {
} }
override fun moveDocument( override fun moveDocument(
sourceDocumentId : String, sourceParentDocumentId : String?, sourceDocumentId: String, sourceParentDocumentId: String?,
targetParentDocumentId : String? targetParentDocumentId: String?
) : String? { ): String? {
try { try {
val newDocumentId = copyDocument( val newDocumentId = copyDocument(
sourceDocumentId, sourceParentDocumentId!!, sourceDocumentId, sourceParentDocumentId!!,
@ -208,12 +217,12 @@ class DocumentProvider : DocumentsProvider() {
) )
removeDocument(sourceDocumentId, sourceParentDocumentId) removeDocument(sourceDocumentId, sourceParentDocumentId)
return newDocumentId return newDocumentId
} catch (e : FileNotFoundException) { } catch (e: FileNotFoundException) {
throw FileNotFoundException("Couldn't move document '$sourceDocumentId'") throw FileNotFoundException("Couldn't move document '$sourceDocumentId'")
} }
} }
private fun includeFile(cursor : MatrixCursor, documentId : String?, file : File?) : MatrixCursor { private fun includeFile(cursor: MatrixCursor, documentId: String?, file: File?): MatrixCursor {
val localDocumentId = documentId ?: file?.let { getDocumentId(it) } val localDocumentId = documentId ?: file?.let { getDocumentId(it) }
val localFile = file ?: getFile(documentId!!) val localFile = file ?: getFile(documentId!!)
@ -232,7 +241,10 @@ class DocumentProvider : DocumentsProvider() {
cursor.newRow().apply { cursor.newRow().apply {
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, if (localFile == baseDirectory) applicationName else localFile.name) add(
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
if (localFile == baseDirectory) applicationName else localFile.name
)
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
@ -244,14 +256,14 @@ class DocumentProvider : DocumentsProvider() {
return cursor return cursor
} }
private fun getTypeForFile(file : File) : Any? { private fun getTypeForFile(file: File): Any? {
return if (file.isDirectory) return if (file.isDirectory)
DocumentsContract.Document.MIME_TYPE_DIR DocumentsContract.Document.MIME_TYPE_DIR
else else
getTypeForName(file.name) getTypeForName(file.name)
} }
private fun getTypeForName(name : String) : Any? { private fun getTypeForName(name: String): Any {
val lastDot = name.lastIndexOf('.') val lastDot = name.lastIndexOf('.')
if (lastDot >= 0) { if (lastDot >= 0) {
val extension = name.substring(lastDot + 1) val extension = name.substring(lastDot + 1)
@ -262,7 +274,11 @@ class DocumentProvider : DocumentsProvider() {
return "application/octect-stream" return "application/octect-stream"
} }
override fun queryChildDocuments(parentDocumentId : String?, projection : Array<out String>?, sortOrder : String?) : Cursor { override fun queryChildDocuments(
parentDocumentId: String?,
projection: Array<out String>?,
sortOrder: String?
): Cursor {
var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION) var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
val parent = getFile(parentDocumentId!!) val parent = getFile(parentDocumentId!!)
@ -272,7 +288,11 @@ class DocumentProvider : DocumentsProvider() {
return cursor return cursor
} }
override fun openDocument(documentId : String?, mode : String?, signal : CancellationSignal?) : ParcelFileDescriptor { override fun openDocument(
documentId: String?,
mode: String?,
signal: CancellationSignal?
): ParcelFileDescriptor {
val file = documentId?.let { getFile(it) } val file = documentId?.let { getFile(it) }
val accessMode = ParcelFileDescriptor.parseMode(mode) val accessMode = ParcelFileDescriptor.parseMode(mode)
return ParcelFileDescriptor.open(file, accessMode) return ParcelFileDescriptor.open(file, accessMode)

View File

@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat

View File

@ -11,7 +11,6 @@ import com.anggrayudi.storage.file.getAbsolutePath
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import java.io.File import java.io.File
@ -40,14 +39,14 @@ class DlcViewModel(val titleId: String) {
val path = file.getAbsolutePath(storageHelper.storage.context) val path = file.getAbsolutePath(storageHelper.storage.context)
if (path.isNotEmpty()) { if (path.isNotEmpty()) {
data?.apply { data?.apply {
val contents = RyujinxNative.instance.deviceGetDlcContentList( val contents = RyujinxNative.jnaInstance.deviceGetDlcContentList(
NativeHelpers.instance.storeStringJava(path), path,
titleId.toLong(16) titleId.toLong(16)
) )
if (contents.isNotEmpty()) { if (contents.isNotEmpty()) {
val contentPath = path val contentPath = path
val container = DlcContainerList(contentPath); val container = DlcContainerList(contentPath)
for (content in contents) for (content in contents)
container.dlc_nca_list.add( container.dlc_nca_list.add(
@ -90,7 +89,7 @@ class DlcViewModel(val titleId: String) {
val containerPath = container.path val containerPath = container.path
if (!File(containerPath).exists()) if (!File(containerPath).exists())
continue; continue
for (dlc in container.dlc_nca_list) { for (dlc in container.dlc_nca_list) {
val enabled = remember { val enabled = remember {
@ -102,7 +101,10 @@ class DlcViewModel(val titleId: String) {
enabled, enabled,
containerPath, containerPath,
dlc.fullPath, dlc.fullPath,
NativeHelpers.instance.getStringJava(RyujinxNative.instance.deviceGetDlcTitleId(NativeHelpers.instance.storeStringJava(containerPath), NativeHelpers.instance.storeStringJava(dlc.fullPath))) RyujinxNative.jnaInstance.deviceGetDlcTitleId(
containerPath,
dlc.fullPath
)
) )
) )
} }
@ -143,11 +145,13 @@ data class DlcContainerList(
data class DlcContainer( data class DlcContainer(
var enabled: Boolean = false, var enabled: Boolean = false,
var titleId: String = "", var titleId: String = "",
var fullPath: String = "") var fullPath: String = ""
)
data class DlcItem( data class DlcItem(
var name:String = "", var name: String = "",
var isEnabled: MutableState<Boolean> = mutableStateOf(false), var isEnabled: MutableState<Boolean> = mutableStateOf(false),
var containerPath: String = "", var containerPath: String = "",
var fullPath: String = "", var fullPath: String = "",
var titleId: String = "") var titleId: String = ""
)

View File

@ -0,0 +1,20 @@
package org.ryujinx.android.viewmodels;
import com.sun.jna.Structure;
import java.util.List;
public class GameInfo extends Structure {
public double FileSize = 0.0;
public String TitleName;
public String TitleId;
public String Developer;
public String Version;
public String Icon;
@Override
protected List<String> getFieldOrder() {
return List.of("FileSize", "TitleName", "TitleId", "Developer", "Version", "Icon");
}
}

View File

@ -5,7 +5,6 @@ import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.file.extension import com.anggrayudi.storage.file.extension
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
@ -24,8 +23,8 @@ class GameModel(var file: DocumentFile, val context: Context) {
init { init {
fileName = file.name fileName = file.name
val pid = open() val pid = open()
val ext = NativeHelpers.instance.storeStringJava(file.extension) val gameInfo = GameInfo()
val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, ext) RyujinxNative.jnaInstance.deviceGetGameInfo(pid, file.extension, gameInfo)
close() close()
fileSize = gameInfo.FileSize fileSize = gameInfo.FileSize
@ -46,28 +45,28 @@ class GameModel(var file: DocumentFile, val context: Context) {
} }
} }
fun open() : Int { fun open(): Int {
descriptor = context.contentResolver.openFileDescriptor(file.uri, "rw") descriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
return descriptor?.fd ?: 0 return descriptor?.fd ?: 0
} }
fun openUpdate() : Int { fun openUpdate(): Int {
if(titleId?.isNotEmpty() == true) { if (titleId?.isNotEmpty() == true) {
val vm = TitleUpdateViewModel(titleId ?: "") val vm = TitleUpdateViewModel(titleId ?: "")
if(vm.data?.selected?.isNotEmpty() == true){ if (vm.data?.selected?.isNotEmpty() == true) {
val uri = Uri.parse(vm.data?.selected) val uri = Uri.parse(vm.data?.selected)
val file = DocumentFile.fromSingleUri(context, uri) val file = DocumentFile.fromSingleUri(context, uri)
if(file?.exists() == true){ if (file?.exists() == true) {
updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw") updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
return updateDescriptor ?.fd ?: -1; return updateDescriptor?.fd ?: -1
} }
} }
} }
return -1; return -1
} }
fun close() { fun close() {
@ -78,16 +77,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
} }
} }
class GameInfo { enum class FileType {
var FileSize = 0.0
var TitleName: String? = null
var TitleId: String? = null
var Developer: String? = null
var Version: String? = null
var Icon: String? = null
}
enum class FileType{
None, None,
Nsp, Nsp,
Xci, Xci,

View File

@ -46,21 +46,23 @@ class HomeViewModel(
} }
} }
fun filter(query : String){ fun filter(query: String) {
gameList.clear() gameList.clear()
gameList.addAll(loadedCache.filter { it.titleName != null && it.titleName!!.isNotEmpty() && (query.trim() gameList.addAll(loadedCache.filter {
it.titleName != null && it.titleName!!.isNotEmpty() && (query.trim()
.isEmpty() || it.titleName!!.lowercase(Locale.getDefault()) .isEmpty() || it.titleName!!.lowercase(Locale.getDefault())
.contains(query)) }) .contains(query))
})
} }
fun requestReload(){ fun requestReload() {
shouldReload = true shouldReload = true
} }
fun reloadGameList() { private fun reloadGameList() {
var storage = activity?.storageHelper ?: return activity?.storageHelper ?: return
if(isLoading) if (isLoading)
return return
val folder = gameFolderPath ?: return val folder = gameFolderPath ?: return
@ -70,13 +72,12 @@ class HomeViewModel(
thread { thread {
try { try {
loadedCache.clear() loadedCache.clear()
val files = mutableListOf<GameModel>()
for (file in folder.search(false, DocumentFileType.FILE)) { for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro") if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro")
activity.let { activity.let {
val item = GameModel(file, it) val item = GameModel(file, it)
if(item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true) { if (item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true && item.titleName != "Unknown") {
loadedCache.add(item) loadedCache.add(item)
gameList.add(item) gameList.add(item)
} }

View File

@ -8,7 +8,6 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import org.ryujinx.android.GameController import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost import org.ryujinx.android.GameHost
import org.ryujinx.android.GraphicsConfiguration
import org.ryujinx.android.Logging import org.ryujinx.android.Logging
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.MotionSensorManager import org.ryujinx.android.MotionSensorManager
@ -55,24 +54,19 @@ class MainViewModel(val activity: MainActivity) {
} }
fun closeGame() { fun closeGame() {
RyujinxNative.instance.deviceSignalEmulationClose() RyujinxNative.jnaInstance.deviceSignalEmulationClose()
gameHost?.close() gameHost?.close()
RyujinxNative.instance.deviceCloseEmulation() RyujinxNative.jnaInstance.deviceCloseEmulation()
motionSensorManager?.unregister() motionSensorManager?.unregister()
physicalControllerManager?.disconnect() physicalControllerManager?.disconnect()
motionSensorManager?.setControllerId(-1) motionSensorManager?.setControllerId(-1)
} }
fun refreshFirmwareVersion() { fun refreshFirmwareVersion() {
var handle = RyujinxNative.instance.deviceGetInstalledFirmwareVersion() firmwareVersion = RyujinxNative.jnaInstance.deviceGetInstalledFirmwareVersion()
if (handle != -1L) {
firmwareVersion = NativeHelpers.instance.getStringJava(handle)
}
} }
fun loadGame(game: GameModel): Boolean { fun loadGame(game: GameModel): Boolean {
val nativeRyujinx = RyujinxNative.instance
val descriptor = game.open() val descriptor = game.open()
if (descriptor == 0) if (descriptor == 0)
@ -85,12 +79,12 @@ class MainViewModel(val activity: MainActivity) {
val settings = QuickSettings(activity) val settings = QuickSettings(activity)
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply { var success = RyujinxNative.jnaInstance.graphicsInitialize(
EnableShaderCache = settings.enableShaderCache enableShaderCache = settings.enableShaderCache,
EnableTextureRecompression = settings.enableTextureRecompression enableTextureRecompression = settings.enableTextureRecompression,
ResScale = settings.resScale rescale = settings.resScale,
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
}) )
if (!success) if (!success)
return false return false
@ -139,8 +133,11 @@ class MainViewModel(val activity: MainActivity) {
} }
success = nativeRyujinx.graphicsInitializeRenderer( val extensions = nativeInterop.VkRequiredExtensions
nativeInterop.VkRequiredExtensions!!,
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
extensions!!,
extensions.size,
driverHandle driverHandle
) )
if (!success) if (!success)
@ -151,7 +148,7 @@ class MainViewModel(val activity: MainActivity) {
semaphore.acquire() semaphore.acquire()
launchOnUiThread { launchOnUiThread {
// We are only able to initialize the emulation context on the main thread // We are only able to initialize the emulation context on the main thread
success = nativeRyujinx.deviceInitialize( success = RyujinxNative.jnaInstance.deviceInitialize(
settings.isHostMapped, settings.isHostMapped,
settings.useNce, settings.useNce,
SystemLanguage.AmericanEnglish.ordinal, SystemLanguage.AmericanEnglish.ordinal,
@ -160,7 +157,7 @@ class MainViewModel(val activity: MainActivity) {
settings.enableDocked, settings.enableDocked,
settings.enablePtc, settings.enablePtc,
false, false,
NativeHelpers.instance.storeStringJava("UTC"), "UTC",
settings.ignoreMissingServices settings.ignoreMissingServices
) )
@ -173,28 +170,24 @@ class MainViewModel(val activity: MainActivity) {
if (!success) if (!success)
return false return false
success = nativeRyujinx.deviceLoadDescriptor(descriptor, game.type.ordinal, update) success =
RyujinxNative.jnaInstance.deviceLoadDescriptor(descriptor, game.type.ordinal, update)
if (!success) return success
return false
return true
} }
fun loadMiiEditor(): Boolean { fun loadMiiEditor(): Boolean {
val nativeRyujinx = RyujinxNative.instance
gameModel = null gameModel = null
isMiiEditorLaunched = true isMiiEditorLaunched = true
val settings = QuickSettings(activity) val settings = QuickSettings(activity)
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply { var success = RyujinxNative.jnaInstance.graphicsInitialize(
EnableShaderCache = settings.enableShaderCache enableShaderCache = settings.enableShaderCache,
EnableTextureRecompression = settings.enableTextureRecompression enableTextureRecompression = settings.enableTextureRecompression,
ResScale = settings.resScale rescale = settings.resScale,
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
}) )
if (!success) if (!success)
return false return false
@ -243,8 +236,11 @@ class MainViewModel(val activity: MainActivity) {
} }
success = nativeRyujinx.graphicsInitializeRenderer( val extensions = nativeInterop.VkRequiredExtensions
nativeInterop.VkRequiredExtensions!!,
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
extensions!!,
extensions.size,
driverHandle driverHandle
) )
if (!success) if (!success)
@ -255,7 +251,7 @@ class MainViewModel(val activity: MainActivity) {
semaphore.acquire() semaphore.acquire()
launchOnUiThread { launchOnUiThread {
// We are only able to initialize the emulation context on the main thread // We are only able to initialize the emulation context on the main thread
success = nativeRyujinx.deviceInitialize( success = RyujinxNative.jnaInstance.deviceInitialize(
settings.isHostMapped, settings.isHostMapped,
settings.useNce, settings.useNce,
SystemLanguage.AmericanEnglish.ordinal, SystemLanguage.AmericanEnglish.ordinal,
@ -264,7 +260,7 @@ class MainViewModel(val activity: MainActivity) {
settings.enableDocked, settings.enableDocked,
settings.enablePtc, settings.enablePtc,
false, false,
NativeHelpers.instance.storeStringJava("UTC"), "UTC",
settings.ignoreMissingServices settings.ignoreMissingServices
) )
@ -277,12 +273,9 @@ class MainViewModel(val activity: MainActivity) {
if (!success) if (!success)
return false return false
success = nativeRyujinx.deviceLaunchMiiEditor() success = RyujinxNative.jnaInstance.deviceLaunchMiiEditor()
if (!success) return success
return false
return true
} }
fun clearPptcCache(titleId: String) { fun clearPptcCache(titleId: String) {

View File

@ -12,7 +12,6 @@ import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.getAbsolutePath
import org.ryujinx.android.LogLevel import org.ryujinx.android.LogLevel
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import java.io.File import java.io.File
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -28,7 +27,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
sharedPref = getPreferences() sharedPref = getPreferences()
previousFolderCallback = activity.storageHelper!!.onFolderSelected previousFolderCallback = activity.storageHelper!!.onFolderSelected
previousFileCallback = activity.storageHelper!!.onFileSelected previousFileCallback = activity.storageHelper!!.onFileSelected
activity.storageHelper!!.onFolderSelected = { requestCode, folder -> activity.storageHelper!!.onFolderSelected = { _, folder ->
run { run {
val p = folder.getAbsolutePath(activity) val p = folder.getAbsolutePath(activity)
val editor = sharedPref.edit() val editor = sharedPref.edit()
@ -146,18 +145,24 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
editor.apply() editor.apply()
activity.storageHelper!!.onFolderSelected = previousFolderCallback activity.storageHelper!!.onFolderSelected = previousFolderCallback
RyujinxNative.instance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value) RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value) RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Stub.ordinal, enableStubLogs.value) RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Stub.ordinal, enableStubLogs.value)
RyujinxNative.instance.loggingSetEnabled(LogLevel.Warning.ordinal, enableWarningLogs.value) RyujinxNative.jnaInstance.loggingSetEnabled(
RyujinxNative.instance.loggingSetEnabled(LogLevel.Error.ordinal, enableErrorLogs.value) LogLevel.Warning.ordinal,
RyujinxNative.instance.loggingSetEnabled(LogLevel.AccessLog.ordinal, enableAccessLogs.value) enableWarningLogs.value
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, enableGuestLogs.value) )
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value) RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Error.ordinal, enableErrorLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(
LogLevel.AccessLog.ordinal,
enableAccessLogs.value
)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Guest.ordinal, enableGuestLogs.value)
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value)
} }
fun openGameFolder() { fun openGameFolder() {
val path = sharedPref?.getString("gameFolder", "") ?: "" val path = sharedPref.getString("gameFolder", "") ?: ""
if (path.isEmpty()) if (path.isEmpty())
activity.storageHelper?.storage?.openFolderPicker() activity.storageHelper?.storage?.openFolderPicker()
@ -169,13 +174,13 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
} }
fun importProdKeys() { fun importProdKeys() {
activity.storageHelper!!.onFileSelected = { requestCode, files -> activity.storageHelper!!.onFileSelected = { _, files ->
run { run {
activity.storageHelper!!.onFileSelected = previousFileCallback activity.storageHelper!!.onFileSelected = previousFileCallback
val file = files.firstOrNull() val file = files.firstOrNull()
file?.apply { file?.apply {
if (name == "prod.keys") { if (name == "prod.keys") {
val outputFile = File(MainActivity.AppPath + "/system"); val outputFile = File(MainActivity.AppPath + "/system")
outputFile.delete() outputFile.delete()
thread { thread {
@ -183,9 +188,6 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
activity, activity,
outputFile, outputFile,
callback = object : FileCallback() { callback = object : FileCallback() {
override fun onCompleted(result: Any) {
super.onCompleted(result)
}
}) })
} }
} }
@ -209,14 +211,13 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
val descriptor = val descriptor =
activity.contentResolver.openFileDescriptor(file.uri, "rw") activity.contentResolver.openFileDescriptor(file.uri, "rw")
descriptor?.use { d -> descriptor?.use { d ->
val version = RyujinxNative.instance.deviceVerifyFirmware( selectedFirmwareVersion =
RyujinxNative.jnaInstance.deviceVerifyFirmware(
d.fd, d.fd,
extension == "xci" extension == "xci"
) )
selectedFirmwareFile = file selectedFirmwareFile = file
if (version != -1L) { if (selectedFirmwareVersion.isEmpty()) {
selectedFirmwareVersion =
NativeHelpers.instance.getStringJava(version)
installState.value = FirmwareInstallState.Query installState.value = FirmwareInstallState.Query
} else { } else {
installState.value = FirmwareInstallState.Cancelled installState.value = FirmwareInstallState.Cancelled
@ -246,7 +247,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
installState.value = FirmwareInstallState.Install installState.value = FirmwareInstallState.Install
thread { thread {
try { try {
RyujinxNative.instance.deviceInstallFirmware( RyujinxNative.jnaInstance.deviceInstallFirmware(
d.fd, d.fd,
extension == "xci" extension == "xci"
) )

View File

@ -53,8 +53,11 @@ class TitleUpdateViewModel(val titleId: String) {
if (requestCode == UpdateRequestCode) { if (requestCode == UpdateRequestCode) {
val file = files.firstOrNull() val file = files.firstOrNull()
file?.apply { file?.apply {
if(file.extension == "nsp"){ if (file.extension == "nsp") {
storageHelper.storage.context.contentResolver.takePersistableUriPermission(file.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) storageHelper.storage.context.contentResolver.takePersistableUriPermission(
file.uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
currentPaths.add(file.uri.toString()) currentPaths.add(file.uri.toString())
} }
} }
@ -72,7 +75,7 @@ class TitleUpdateViewModel(val titleId: String) {
currentPaths.forEach { currentPaths.forEach {
val uri = Uri.parse(it) val uri = Uri.parse(it)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri) val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if(file?.exists() == true){ if (file?.exists() == true) {
existingPaths.add(it) existingPaths.add(it)
} }
} }
@ -108,20 +111,19 @@ class TitleUpdateViewModel(val titleId: String) {
currentPaths.forEach { currentPaths.forEach {
val uri = Uri.parse(it) val uri = Uri.parse(it)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri) val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if(file?.exists() == true){ if (file?.exists() == true) {
savedUpdates.add(it) savedUpdates.add(it)
} }
} }
metadata.paths = savedUpdates metadata.paths = savedUpdates
if(selected.isNotEmpty()){ if (selected.isNotEmpty()) {
val uri = Uri.parse(selected) val uri = Uri.parse(selected)
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri) val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
if(file?.exists() == true){ if (file?.exists() == true) {
metadata.selected = selected metadata.selected = selected
} }
} } else {
else {
metadata.selected = selected metadata.selected = selected
} }

View File

@ -1,6 +1,5 @@
package org.ryujinx.android.viewmodels package org.ryujinx.android.viewmodels
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import java.util.Base64 import java.util.Base64
@ -14,52 +13,45 @@ class UserViewModel {
fun refreshUsers() { fun refreshUsers() {
userList.clear() userList.clear()
val native = RyujinxNative.instance
val helper = NativeHelpers.instance
val decoder = Base64.getDecoder() val decoder = Base64.getDecoder()
openedUser = UserModel() openedUser = UserModel()
openedUser.id = helper.getStringJava(native.userGetOpenedUser()) openedUser.id = RyujinxNative.jnaInstance.userGetOpenedUser()
if (openedUser.id.isNotEmpty()) { if (openedUser.id.isNotEmpty()) {
openedUser.username = openedUser.username = RyujinxNative.jnaInstance.userGetUserName(openedUser.id)
helper.getStringJava(native.userGetUserName(helper.storeStringJava(openedUser.id)))
openedUser.userPicture = decoder.decode( openedUser.userPicture = decoder.decode(
helper.getStringJava( RyujinxNative.jnaInstance.userGetUserPicture(
native.userGetUserPicture( openedUser.id
helper.storeStringJava(openedUser.id)
)
) )
) )
} }
val users = native.userGetAllUsers() val users = RyujinxNative.jnaInstance.userGetAllUsers()
for (user in users) { for (user in users) {
userList.add( userList.add(
UserModel( UserModel(
user, user,
helper.getStringJava(native.userGetUserName(helper.storeStringJava(user))), RyujinxNative.jnaInstance.userGetUserName(user),
decoder.decode( decoder.decode(
helper.getStringJava( RyujinxNative.jnaInstance.userGetUserPicture(user)
native.userGetUserPicture(
helper.storeStringJava(user)
)
)
) )
) )
) )
} }
} }
fun openUser(userModel: UserModel){ fun openUser(userModel: UserModel) {
val native = RyujinxNative.instance RyujinxNative.jnaInstance.userOpenUser(userModel.id)
val helper = NativeHelpers.instance
native.userOpenUser(helper.storeStringJava(userModel.id))
refreshUsers() refreshUsers()
} }
} }
data class UserModel(var id : String = "", var username: String = "", var userPicture: ByteArray? = null) { data class UserModel(
var id: String = "",
var username: String = "",
var userPicture: ByteArray? = null
) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@ -18,7 +18,7 @@ class VulkanDriverViewModel(val activity: MainActivity) {
const val DriverFolder: String = "drivers" const val DriverFolder: String = "drivers"
} }
private fun getAppPath() : String { private fun getAppPath(): String {
var appPath = var appPath =
MainActivity.AppPath MainActivity.AppPath
appPath += "/" appPath += "/"
@ -26,18 +26,18 @@ class VulkanDriverViewModel(val activity: MainActivity) {
return appPath return appPath
} }
fun ensureDriverPath() : File { fun ensureDriverPath(): File {
val driverPath = getAppPath() + DriverFolder val driverPath = getAppPath() + DriverFolder
val driverFolder = File(driverPath) val driverFolder = File(driverPath)
if(!driverFolder.exists()) if (!driverFolder.exists())
driverFolder.mkdirs() driverFolder.mkdirs()
return driverFolder return driverFolder
} }
fun getAvailableDrivers() : MutableList<DriverMetadata> { fun getAvailableDrivers(): MutableList<DriverMetadata> {
val driverFolder = ensureDriverPath() val driverFolder = ensureDriverPath()
val folders = driverFolder.walkTopDown() val folders = driverFolder.walkTopDown()
@ -45,10 +45,10 @@ class VulkanDriverViewModel(val activity: MainActivity) {
val drivers = mutableListOf<DriverMetadata>() val drivers = mutableListOf<DriverMetadata>()
val selectedDriverFile = File(driverFolder.absolutePath + "/selected") val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
if(selectedDriverFile.exists()){ if (selectedDriverFile.exists()) {
selected = selectedDriverFile.readText() selected = selectedDriverFile.readText()
if(!File(selected).exists()) { if (!File(selected).exists()) {
selected = "" selected = ""
saveSelected() saveSelected()
} }
@ -56,13 +56,13 @@ class VulkanDriverViewModel(val activity: MainActivity) {
val gson = Gson() val gson = Gson()
for (folder in folders){ for (folder in folders) {
if(folder.isDirectory && folder.parent == driverFolder.absolutePath){ if (folder.isDirectory && folder.parent == driverFolder.absolutePath) {
val meta = File(folder.absolutePath + "/meta.json") val meta = File(folder.absolutePath + "/meta.json")
if(meta.exists()){ if (meta.exists()) {
val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java) val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java)
if(metadata.name.isNotEmpty()) { if (metadata.name.isNotEmpty()) {
val driver = folder.absolutePath + "/${metadata.libraryName}" val driver = folder.absolutePath + "/${metadata.libraryName}"
metadata.driverPath = driver metadata.driverPath = driver
if (File(driver).exists()) if (File(driver).exists())
@ -82,10 +82,10 @@ class VulkanDriverViewModel(val activity: MainActivity) {
selectedDriverFile.writeText(selected) selectedDriverFile.writeText(selected)
} }
fun removeSelected(){ fun removeSelected() {
if(selected.isNotEmpty()){ if (selected.isNotEmpty()) {
val sel = File(selected) val sel = File(selected)
if(sel.exists()) { if (sel.exists()) {
sel.parentFile?.deleteRecursively() sel.parentFile?.deleteRecursively()
} }
selected = "" selected = ""
@ -102,12 +102,11 @@ class VulkanDriverViewModel(val activity: MainActivity) {
onFileSelected = { requestCode, files -> onFileSelected = { requestCode, files ->
run { run {
onFileSelected = callBack onFileSelected = callBack
if(requestCode == DriverRequestCode) if (requestCode == DriverRequestCode) {
{
val file = files.firstOrNull() val file = files.firstOrNull()
file?.apply { file?.apply {
val path = file.getAbsolutePath(storage.context) val path = file.getAbsolutePath(storage.context)
if (path.isNotEmpty()){ if (path.isNotEmpty()) {
val name = file.name?.removeSuffix("." + file.extension) ?: "" val name = file.name?.removeSuffix("." + file.extension) ?: ""
val driverFolder = ensureDriverPath() val driverFolder = ensureDriverPath()
val extractionFolder = File(driverFolder.absolutePath + "/${name}") val extractionFolder = File(driverFolder.absolutePath + "/${name}")
@ -117,14 +116,18 @@ class VulkanDriverViewModel(val activity: MainActivity) {
zip.entries().asSequence().forEach { entry -> zip.entries().asSequence().forEach { entry ->
zip.getInputStream(entry).use { input -> zip.getInputStream(entry).use { input ->
val filePath = extractionFolder.absolutePath + File.separator + entry.name val filePath =
extractionFolder.absolutePath + File.separator + entry.name
if (!entry.isDirectory) { if (!entry.isDirectory) {
File(filePath).delete() File(filePath).delete()
val bos = BufferedOutputStream(FileOutputStream(filePath)) val bos =
BufferedOutputStream(FileOutputStream(filePath))
val bytesIn = ByteArray(4096) val bytesIn = ByteArray(4096)
var read: Int var read: Int
while (input.read(bytesIn).also { read = it } != -1) { while (input.read(bytesIn)
.also { read = it } != -1
) {
bos.write(bytesIn, 0, read) bos.write(bytesIn, 0, read)
} }
bos.close() bos.close()
@ -144,7 +147,8 @@ class VulkanDriverViewModel(val activity: MainActivity) {
} }
} }
} }
openFilePicker(DriverRequestCode, openFilePicker(
DriverRequestCode,
filterMimeTypes = arrayOf("application/zip") filterMimeTypes = arrayOf("application/zip")
) )
} }
@ -152,14 +156,14 @@ class VulkanDriverViewModel(val activity: MainActivity) {
} }
data class DriverMetadata( data class DriverMetadata(
var schemaVersion : Int = 0, var schemaVersion: Int = 0,
var name : String = "", var name: String = "",
var description : String = "", var description: String = "",
var author : String = "", var author: String = "",
var packageVersion : String = "", var packageVersion: String = "",
var vendor : String = "", var vendor: String = "",
var driverVersion : String = "", var driverVersion: String = "",
var minApi : Int = 0, var minApi: Int = 0,
var libraryName : String = "", var libraryName: String = "",
var driverPath : String = "" var driverPath: String = ""
) )

View File

@ -50,13 +50,19 @@ class DlcViews {
Column(modifier = Modifier.padding(16.dp)) { Column(modifier = Modifier.padding(16.dp)) {
Column { Column {
Row(modifier = Modifier Row(
modifier = Modifier
.padding(8.dp) .padding(8.dp)
.fillMaxWidth(), .fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween) { horizontalArrangement = Arrangement.SpaceBetween
Text(text = "DLC for ${name}", textAlign = TextAlign.Center, modifier = Modifier.align( ) {
Text(
text = "DLC for ${name}",
textAlign = TextAlign.Center,
modifier = Modifier.align(
Alignment.CenterVertically Alignment.CenterVertically
)) )
)
} }
Surface( Surface(
modifier = Modifier modifier = Modifier
@ -65,17 +71,20 @@ class DlcViews {
shape = MaterialTheme.shapes.medium shape = MaterialTheme.shapes.medium
) { ) {
if(refresh.value) { if (refresh.value) {
dlcList.clear() dlcList.clear()
dlcList.addAll(viewModel.getDlc()) dlcList.addAll(viewModel.getDlc())
refresh.value = false refresh.value = false
} }
LazyColumn(modifier = Modifier LazyColumn(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(400.dp)){ .height(400.dp)
) {
items(dlcList) { dlcItem -> items(dlcList) { dlcItem ->
dlcItem.apply { dlcItem.apply {
Row(modifier = Modifier Row(
modifier = Modifier
.padding(8.dp) .padding(8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
@ -94,7 +103,8 @@ class DlcViews {
viewModel.remove(dlcItem) viewModel.remove(dlcItem)
refresh.value = true refresh.value = true
}) { }) {
Icon(Icons.Filled.Delete, Icon(
Icons.Filled.Delete,
contentDescription = "remove" contentDescription = "remove"
) )
} }

View File

@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -80,8 +81,6 @@ class GameViews {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
GameStats(mainViewModel) GameStats(mainViewModel)
val ryujinxNative = RyujinxNative.instance
val showController = remember { val showController = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController) mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
} }
@ -128,19 +127,19 @@ class GameViews {
when (event.type) { when (event.type) {
PointerEventType.Press -> { PointerEventType.Press -> {
ryujinxNative.inputSetTouchPoint( RyujinxNative.jnaInstance.inputSetTouchPoint(
position.x.roundToInt(), position.x.roundToInt(),
position.y.roundToInt() position.y.roundToInt()
) )
} }
PointerEventType.Release -> { PointerEventType.Release -> {
ryujinxNative.inputReleaseTouchPoint() RyujinxNative.jnaInstance.inputReleaseTouchPoint()
} }
PointerEventType.Move -> { PointerEventType.Move -> {
ryujinxNative.inputSetTouchPoint( RyujinxNative.jnaInstance.inputSetTouchPoint(
position.x.roundToInt(), position.x.roundToInt(),
position.y.roundToInt() position.y.roundToInt()
) )
@ -209,7 +208,7 @@ class GameViews {
IconButton(modifier = Modifier.padding(4.dp), onClick = { IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false showMore.value = false
showController.value = !showController.value showController.value = !showController.value
ryujinxNative.inputReleaseTouchPoint() RyujinxNative.jnaInstance.inputReleaseTouchPoint()
mainViewModel.controller?.setVisible(showController.value) mainViewModel.controller?.setVisible(showController.value)
}) { }) {
Icon( Icon(
@ -220,7 +219,7 @@ class GameViews {
IconButton(modifier = Modifier.padding(4.dp), onClick = { IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false showMore.value = false
enableVsync.value = !enableVsync.value enableVsync.value = !enableVsync.value
RyujinxNative.instance.graphicsRendererSetVsync( RyujinxNative.jnaInstance.graphicsRendererSetVsync(
enableVsync.value enableVsync.value
) )
}) { }) {
@ -262,10 +261,12 @@ class GameViews {
if (progressValue.value > -1) if (progressValue.value > -1)
LinearProgressIndicator( LinearProgressIndicator(
progress = {
progressValue.value
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp), .padding(top = 16.dp),
progress = progressValue.value
) )
else else
LinearProgressIndicator( LinearProgressIndicator(
@ -279,7 +280,7 @@ class GameViews {
} }
if (showBackNotice.value) { if (showBackNotice.value) {
AlertDialog(onDismissRequest = { showBackNotice.value = false }) { BasicAlertDialog(onDismissRequest = { showBackNotice.value = false }) {
Column { Column {
Surface( Surface(
modifier = Modifier modifier = Modifier

View File

@ -3,8 +3,6 @@ package org.ryujinx.android.views
import android.content.res.Resources import android.content.res.Resources
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
@ -38,25 +36,22 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardElevation
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -69,14 +64,10 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.HorizontalAlignmentLine
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.anggrayudi.storage.extension.launchOnUiThread import com.anggrayudi.storage.extension.launchOnUiThread
import org.ryujinx.android.R import org.ryujinx.android.R
@ -377,7 +368,7 @@ class HomeViews {
} }
if (showLoading.value) { if (showLoading.value) {
AlertDialog(onDismissRequest = { }) { BasicAlertDialog(onDismissRequest = { }) {
Card( Card(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
@ -401,7 +392,7 @@ class HomeViews {
} }
} }
if (openTitleUpdateDialog.value) { if (openTitleUpdateDialog.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
openTitleUpdateDialog.value = false openTitleUpdateDialog.value = false
}) { }) {
Surface( Surface(
@ -419,7 +410,7 @@ class HomeViews {
} }
} }
if (openDlcDialog.value) { if (openDlcDialog.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
openDlcDialog.value = false openDlcDialog.value = false
}) { }) {
Surface( Surface(
@ -451,14 +442,13 @@ class HomeViews {
thread { thread {
showLoading.value = true showLoading.value = true
val success = val success =
viewModel.mainViewModel?.loadGame(viewModel.mainViewModel.selected!!) viewModel.mainViewModel.loadGame(viewModel.mainViewModel.selected!!)
?: false
if (success) { if (success) {
launchOnUiThread { launchOnUiThread {
viewModel.mainViewModel?.navigateToGame() viewModel.mainViewModel.navigateToGame()
} }
} else { } else {
viewModel.mainViewModel?.selected!!.close() viewModel.mainViewModel.selected!!.close()
} }
showLoading.value = false showLoading.value = false
} }
@ -487,7 +477,7 @@ class HomeViews {
}, onClick = { }, onClick = {
showAppMenu.value = false showAppMenu.value = false
viewModel.mainViewModel?.clearPptcCache( viewModel.mainViewModel?.clearPptcCache(
viewModel.mainViewModel?.selected?.titleId ?: "" viewModel.mainViewModel.selected?.titleId ?: ""
) )
}) })
DropdownMenuItem(text = { DropdownMenuItem(text = {
@ -495,7 +485,7 @@ class HomeViews {
}, onClick = { }, onClick = {
showAppMenu.value = false showAppMenu.value = false
viewModel.mainViewModel?.purgeShaderCache( viewModel.mainViewModel?.purgeShaderCache(
viewModel.mainViewModel?.selected?.titleId ?: "" viewModel.mainViewModel.selected?.titleId ?: ""
) )
}) })
DropdownMenuItem(text = { DropdownMenuItem(text = {
@ -548,7 +538,7 @@ class HomeViews {
onClick = { onClick = {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false showAppActions.value = false
viewModel.mainViewModel?.apply { viewModel.mainViewModel.apply {
selected = null selected = null
} }
selectedModel.value = null selectedModel.value = null
@ -639,7 +629,7 @@ class HomeViews {
onClick = { onClick = {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false showAppActions.value = false
viewModel.mainViewModel?.apply { viewModel.mainViewModel.apply {
selected = null selected = null
} }
selectedModel.value = null selectedModel.value = null

View File

@ -16,7 +16,7 @@ class MainView {
NavHost(navController = navController, startDestination = "home") { NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) } composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
composable("user") { UserViews.Main(mainViewModel, navController) } composable("user") { UserViews.Main(mainViewModel) }
composable("game") { GameViews.Main() } composable("game") { GameViews.Main() }
composable("settings") { composable("settings") {
SettingViews.Main( SettingViews.Main(

View File

@ -29,10 +29,11 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -192,7 +193,7 @@ class SettingViews {
) )
settingsViewModel.navController.popBackStack() settingsViewModel.navController.popBackStack()
}) { }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back") Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
} }
}) })
}) { contentPadding -> }) { contentPadding ->
@ -310,7 +311,7 @@ class SettingViews {
} }
if (showFirwmareDialog.value) { if (showFirwmareDialog.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
if (firmwareInstallState.value != FirmwareInstallState.Install) { if (firmwareInstallState.value != FirmwareInstallState.Install) {
showFirwmareDialog.value = false showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState) settingsViewModel.clearFirmwareSelection(firmwareInstallState)
@ -552,7 +553,6 @@ class SettingViews {
Button(onClick = { Button(onClick = {
val storage = MainActivity.StorageHelper val storage = MainActivity.StorageHelper
storage?.apply { storage?.apply {
val s = this.storage
val callBack = this.onFileSelected val callBack = this.onFileSelected
onFileSelected = { requestCode, files -> onFileSelected = { requestCode, files ->
run { run {
@ -578,7 +578,7 @@ class SettingViews {
} }
if (showImportWarning.value) { if (showImportWarning.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
showImportWarning.value = false showImportWarning.value = false
importFile.value = null importFile.value = null
}) { }) {
@ -626,7 +626,7 @@ class SettingViews {
} }
if (showImportCompletion.value) { if (showImportCompletion.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
showImportCompletion.value = false showImportCompletion.value = false
importFile.value = null importFile.value = null
mainViewModel.userViewModel.refreshUsers() mainViewModel.userViewModel.refreshUsers()
@ -739,7 +739,7 @@ class SettingViews {
} }
if (isDriverSelectorOpen.value) { if (isDriverSelectorOpen.value) {
AlertDialog(onDismissRequest = { BasicAlertDialog(onDismissRequest = {
isDriverSelectorOpen.value = false isDriverSelectorOpen.value = false
if (isChanged.value) { if (isChanged.value) {
@ -1064,7 +1064,7 @@ class SettingViews {
} }
} }
BackHandler() { BackHandler {
settingsViewModel.save( settingsViewModel.save(
isHostMapped, isHostMapped,
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices, useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,

View File

@ -17,7 +17,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -43,10 +43,11 @@ class UserViews {
companion object { companion object {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) { fun Main(viewModel: MainViewModel? = null) {
val reload = remember { val reload = remember {
mutableStateOf(true) mutableStateOf(true)
} }
fun refresh() { fun refresh() {
viewModel?.userViewModel?.refreshUsers() viewModel?.userViewModel?.refreshUsers()
reload.value = true reload.value = true
@ -64,7 +65,7 @@ class UserViews {
IconButton(onClick = { IconButton(onClick = {
viewModel?.navController?.popBackStack() viewModel?.navController?.popBackStack()
}) { }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back") Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
} }
}) })
}) { contentPadding -> }) { contentPadding ->
@ -133,10 +134,14 @@ class UserViews {
.fillMaxSize() .fillMaxSize()
.padding(4.dp) .padding(4.dp)
) { ) {
if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) { if (viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
items(viewModel.userViewModel.userList) { user -> items(viewModel.userViewModel.userList) { user ->
Image( Image(
bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0) bitmap = BitmapFactory.decodeByteArray(
user.userPicture,
0,
user.userPicture?.size ?: 0
)
.asImageBitmap(), .asImageBitmap(),
contentDescription = "selected image", contentDescription = "selected image",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
@ -165,6 +170,6 @@ class UserViews {
@Preview @Preview
@Composable @Composable
fun Preview() { fun Preview() {
UserViews.Main() Main()
} }
} }