Archived
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.Primitives;
using LibRyujinx.Jni.References;
using LibRyujinx.Jni.Values;
using Rxmxnx.PInvoke;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@ -18,6 +14,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@ -99,14 +96,8 @@ namespace LibRyujinx
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
[UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")]
internal static int LoadLibrary(JavaVMRef vm, IntPtr unknown)
{
return 0x00010006; //JNI_VERSION_1_6
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId)
[UnmanagedCallersOnly(EntryPoint = "javaInitialize")]
public static bool JniInitialize(IntPtr jpathId)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
PlatformInfo.IsBionic = true;
@ -118,7 +109,7 @@ namespace LibRyujinx
AsyncLogTargetOverflowAction.Block
));
var path = GetStoredString(jpathId);
var path = Marshal.PtrToStringAnsi(jpathId);
var init = Initialize(path);
@ -138,70 +129,71 @@ namespace LibRyujinx
return s;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
public static void JniReloadFileSystem()
[UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
public static void JnaReloadFileSystem()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SwitchDevice?.ReloadFileSystem();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInitialize")]
public static JBoolean JniInitializeDeviceNative(JEnvRef jEnv,
JObjectLocalRef jObj,
JBoolean isHostMapped,
JBoolean useNce,
JInt systemLanguage,
JInt regionCode,
JBoolean enableVsync,
JBoolean enableDockedMode,
JBoolean enablePtc,
JBoolean enableInternetAccess,
JLong timeZoneId,
JBoolean ignoreMissingServices)
[UnmanagedCallersOnly(EntryPoint = "deviceInitialize")]
public static bool JnaDeviceInitialize(bool isHostMapped,
bool useNce,
int systemLanguage,
int regionCode,
bool enableVsync,
bool enableDockedMode,
bool enablePtc,
bool enableInternetAccess,
IntPtr timeZonePtr,
bool ignoreMissingServices)
{
debug_break(4);
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
AudioDriver = new OpenALHardwareDeviceDriver();//new OboeHardwareDeviceDriver();
AudioDriver = new OpenALHardwareDeviceDriver();
var timezone = Marshal.PtrToStringAnsi(timeZonePtr);
return InitializeDevice(isHostMapped,
useNce,
(SystemLanguage)(int)systemLanguage,
(RegionCode)(int)regionCode,
(SystemLanguage)systemLanguage,
(RegionCode)regionCode,
enableVsync,
enableDockedMode,
enablePtc,
enableInternetAccess,
GetStoredString(timeZoneId),
timezone,
ignoreMissingServices);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")]
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFifo")]
public static double JnaGetGameFifo()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
return stats;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")]
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameTime")]
public static double JnaGetGameFrameTime()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
return stats;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")]
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameRate")]
public static double JnaGetGameFrameRate()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
return stats;
}
[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");
if (SwitchDevice?.EmulationContext == null)
@ -214,8 +206,8 @@ namespace LibRyujinx
return LoadApplication(path);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")]
public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")]
public static bool JNALaunchMiiEditApplet()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
@ -226,38 +218,38 @@ namespace LibRyujinx
return LaunchMiiEditApplet();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId)
[UnmanagedCallersOnly(EntryPoint = "deviceGetDlcContentList")]
public static IntPtr JniGetDlcContentListNative(IntPtr pathPtr, long titleId)
{
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")]
public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath)
[UnmanagedCallersOnly(EntryPoint = "deviceGetDlcTitleId")]
public static long JniGetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
{
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")]
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")]
public static void JniSignalEmulationCloseNative()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SignalEmulationClose();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")]
public static void JniCloseEmulationNative()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
CloseEmulation();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JInt type, JInt updateDescriptor)
[UnmanagedCallersOnly(EntryPoint = "deviceLoadDescriptor")]
public static bool JnaLoadApplicationNative(int descriptor, int type, int updateDescriptor)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
@ -268,17 +260,17 @@ namespace LibRyujinx
var stream = OpenFile(descriptor);
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")]
public static JLong JniVerifyFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
[UnmanagedCallersOnly(EntryPoint = "deviceVerifyFirmware")]
public static IntPtr JniVerifyFirmware(int descriptor, bool isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stream = OpenFile(descriptor);
long stringHandle = -1;
IntPtr stringHandle = 0;
try
{
@ -286,19 +278,19 @@ namespace LibRyujinx
if (version != null)
{
stringHandle = storeString(version.VersionString);
stringHandle = Marshal.StringToHGlobalAnsi(version.VersionString);
}
}
catch(Exception _)
{
Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
}
return stringHandle;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInstallFirmware")]
public static void JniInstallFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
[UnmanagedCallersOnly(EntryPoint = "deviceInstallFirmware")]
public static void JniInstallFirmware(int descriptor, bool isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
@ -307,58 +299,47 @@ namespace LibRyujinx
InstallFirmware(stream, isXci);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetInstalledFirmwareVersion")]
public static JLong JniGetInstalledFirmwareVersion(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "deviceGetInstalledFirmwareVersion")]
public static IntPtr JniGetInstalledFirmwareVersion()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var version = GetInstalledFirmwareVersion();
long stringHandle = -1;
IntPtr stringHandle = 0;
if (version != String.Empty)
{
stringHandle = storeString(version);
stringHandle = Marshal.StringToHGlobalAnsi(version);
}
return stringHandle;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
[UnmanagedCallersOnly(EntryPoint = "graphicsInitialize")]
public static bool JnaGraphicsInitialize(float resScale,
float maxAnisotropy,
bool fastGpuTime,
bool fast2DCopy,
bool enableMacroJit,
bool enableMacroHLE,
bool enableShaderCache,
bool enableTextureRecompression,
int backendThreading)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
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;
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)
@ -366,8 +347,8 @@ namespace LibRyujinx
return Encoding.UTF8.GetBytes(s).AsSpan();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
[UnmanagedCallersOnly(EntryPoint = "graphicsSetSurface")]
public static void JniSetSurface(long surfacePtr, long window)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
_surfacePtr = surfacePtr;
@ -376,11 +357,10 @@ namespace LibRyujinx
_surfaceEvent.Set();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitializeRenderer")]
public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv,
JObjectLocalRef jObj,
JArrayLocalRef extensionsArray,
JLong driverHandle)
[UnmanagedCallersOnly(EntryPoint = "graphicsInitializeRenderer")]
public unsafe static bool JnaGraphicsInitializeRenderer(char** extensionsArray,
int extensionsLength,
long driverHandle)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (Renderer != null)
@ -388,37 +368,16 @@ namespace LibRyujinx
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();
var count = getArrayLength(jEnv, extensionsArray);
for (int i = 0; i < count; i++)
for (int i = 0; i < extensionsLength; i++)
{
var obj = getObjectArrayElement(jEnv, extensionsArray, i);
var ext = obj.Transform<JObjectLocalRef, JStringLocalRef>();
extensions.Add(GetString(jEnv, ext));
extensions.Add(Marshal.PtrToStringAnsi((IntPtr)extensionsArray[i]));
}
if ((long)driverHandle != 0)
if (driverHandle != 0)
{
VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle);
VulkanLoader = new VulkanLoader((IntPtr)driverHandle);
}
CreateSurface createSurfaceFunc = instance =>
@ -446,36 +405,29 @@ namespace LibRyujinx
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;
ref JNativeInterface jInterface = ref value.Functions;
IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer;
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);
uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
var array = (char**)Marshal.AllocHGlobal((int)size);
Unsafe.InitBlockUnaligned(array, 0, size);
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")]
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetSize")]
public static void JnaSetRendererSizeNative(int width, int height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
Renderer?.Window?.SetSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererRunLoop")]
public static void JniRunLoopNative()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetSwapBuffersCallback(() =>
@ -487,84 +439,32 @@ namespace LibRyujinx
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_loggingSetEnabled")]
public static void JniSetLoggingEnabledNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt logLevel, JBoolean enabled)
[UnmanagedCallersOnly(EntryPoint = "loggingSetEnabled")]
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")]
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
{
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)
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameInfo")]
public unsafe static void JniGetGameInfo(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
using var stream = OpenFile(fileDescriptor);
var ext = GetStoredString(extension);
var info = GetGameInfo(stream, ext.ToLower());
return GetInfo(jEnv, info);
var ext = Marshal.PtrToStringAnsi(extension);
var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
var i = (GameInfoNative*)infoPtr;
var n = new GameInfoNative(info);
i->TitleId = n.TitleId;
i->TitleName = n.TitleName;
i->Version = n.Version;
i->FileSize = n.FileSize;
i->Icon = n.Icon;
i->Version = n.Version;
i->Developer = n.Developer;
}
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info)
{
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)
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetVsync")]
public static void JnaSetVsyncStateNative(bool enabled)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetVsyncState(enabled);
@ -577,196 +477,197 @@ namespace LibRyujinx
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")]
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
[UnmanagedCallersOnly(EntryPoint = "inputInitialize")]
public static void JnaInitializeInput(int width, int height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
InitializeInput(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")]
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
[UnmanagedCallersOnly(EntryPoint = "inputSetClientSize")]
public static void JnaSetClientSize(int width, int height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetClientSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")]
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y)
[UnmanagedCallersOnly(EntryPoint = "inputSetTouchPoint")]
public static void JnaSetTouchPoint(int x, int y)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetTouchPoint(x, y);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")]
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "inputReleaseTouchPoint")]
public static void JnaReleaseTouchPoint()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
ReleaseTouchPoint();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")]
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "inputUpdate")]
public static void JniUpdateInput()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
UpdateInput();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
[UnmanagedCallersOnly(EntryPoint = "inputSetButtonPressed")]
public static void JnaSetButtonPressed(int button, int id)
{
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")]
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
[UnmanagedCallersOnly(EntryPoint = "inputSetButtonReleased")]
public static void JnaSetButtonReleased(int button, int id)
{
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")]
public static void JniSetAccelerometerData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id)
[UnmanagedCallersOnly(EntryPoint = "inputSetAccelerometerData")]
public static void JniSetAccelerometerData(float x, float y, float z, int id)
{
var accel = new Vector3(x, y, z);
SetAccelerometerData(accel, id);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetGyroData")]
public static void JniSetGyroData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id)
[UnmanagedCallersOnly(EntryPoint = "inputSetGyroData")]
public static void JniSetGyroData(float x, float y, float z, int id)
{
var gryo = new Vector3(x, y, z);
SetGryoData(gryo, id);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
[UnmanagedCallersOnly(EntryPoint = "inputSetStickAxis")]
public static void JnaSetStickAxis(int stick, float x, float y, int id)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetStickAxis((StickInputId)(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")]
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index)
[UnmanagedCallersOnly(EntryPoint = "inputConnectGamepad")]
public static int JnaConnectGamepad(int index)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
return ConnectGamepad(index);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "userGetOpenedUser")]
public static IntPtr JniGetOpenedUser()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetOpenedUser();
var ptr = Marshal.StringToHGlobalAnsi(userId);
return storeString(userId);
return ptr;
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
[UnmanagedCallersOnly(EntryPoint = "userGetUserPicture")]
public static IntPtr JniGetUserPicture(IntPtr userIdPtr)
{
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")]
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
[UnmanagedCallersOnly(EntryPoint = "userSetUserPicture")]
public static void JniGetUserPicture(IntPtr userIdPtr, IntPtr picturePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? "";
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
SetUserPicture(userId, picture);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
[UnmanagedCallersOnly(EntryPoint = "userGetUserName")]
public static IntPtr JniGetUserName(IntPtr userIdPtr)
{
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")]
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
[UnmanagedCallersOnly(EntryPoint = "userSetUserName")]
public static void JniSetUserName(IntPtr userIdPtr, IntPtr userNamePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var userName = GetString(jEnv, userNamePtr) ?? "";
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
SetUserName(userId, userName);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "userGetAllUsers")]
public static IntPtr JniGetAllUsers()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var users = GetAllUsers();
return CreateStringArray(jEnv, users.ToList());
return CreateStringArray(users.ToList());
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
[UnmanagedCallersOnly(EntryPoint = "userAddUser")]
public static void JniAddUser(IntPtr userNamePtr, IntPtr picturePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userName = GetString(jEnv, userNamePtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? "";
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
AddUser(userName, picture);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
[UnmanagedCallersOnly(EntryPoint = "userDeleteUser")]
public static void JniDeleteUser(IntPtr userIdPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
DeleteUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")]
public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "uiHandlerSetup")]
public static void JniSetupUiHandler()
{
SetupUiHandler();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")]
public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "uiHandlerWait")]
public static void JniWaitUiHandler()
{
WaitUiHandler();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")]
public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj)
[UnmanagedCallersOnly(EntryPoint = "uiHandlerStopWait")]
public static void JniStopUiHandlerWait()
{
StopUiHandlerWait();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")]
public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input)
[UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")]
public static void JniSetUiHandlerResponse(bool isOkPressed, long input)
{
SetUiHandlerResponse(isOkPressed, input);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
[UnmanagedCallersOnly(EntryPoint = "userOpenUser")]
public static void JniOpenUser(IntPtr userIdPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? "";
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
OpenUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
[UnmanagedCallersOnly(EntryPoint = "userCloseUser")]
public static void JniCloseUser(IntPtr userIdPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
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");
if (File.Exists(addOnContentMetadataPath))
{
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}");
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}. Available: {dlcContainerList.Count}");
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
{
Logger.Info?.Print(LogClass.Loader, $"DLC Contents Available: {downloadableContentContainer.DownloadableContentNcaList.Count}");
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
if (File.Exists(downloadableContentContainer.ContainerPath))

View File

@ -11,8 +11,8 @@ android {
applicationId "org.ryujinx.android"
minSdk 30
targetSdk 34
versionCode 10030
versionName '1.0.30t'
versionCode 10031
versionName '1.0.31'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -76,6 +76,7 @@ tasks.named("preBuild") {
}
dependencies {
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.14.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
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
abstract class BaseActivity : ComponentActivity() {
companion object{
companion object {
val crashHandler = CrashHandler()
}
}

View File

@ -4,10 +4,12 @@ import java.io.File
import java.lang.Thread.UncaughtExceptionHandler
class CrashHandler : UncaughtExceptionHandler {
var crashLog : String = ""
var crashLog: String = ""
override fun uncaughtException(t: Thread, e: Throwable) {
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) {
companion object{
private fun Create(context: Context, activity: Activity, controller: GameController) : View
{
companion object {
private fun Create(context: Context, controller: GameController): View {
val inflator = LayoutInflater.from(context)
val view = inflator.inflate(R.layout.game_layout, null)
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
@ -44,12 +43,13 @@ class GameController(var activity: Activity) {
return view
}
@Composable
fun Compose(viewModel: MainViewModel) : Unit {
fun Compose(viewModel: MainViewModel): Unit {
AndroidView(
modifier = Modifier.fillMaxSize(), factory = { context ->
val controller = GameController(viewModel.activity)
val c = Create(context, viewModel.activity, controller)
val c = Create(context, controller)
viewModel.activity.lifecycleScope.apply {
viewModel.activity.lifecycleScope.launch {
val events = merge(
@ -70,12 +70,11 @@ class GameController(var activity: Activity) {
}
}
private var ryujinxNative: RyujinxNative
private var controllerView: View? = null
var leftGamePad: GamePad
var rightGamePad: GamePad
var controllerId: Int = -1
val isVisible : Boolean
val isVisible: Boolean
get() {
controllerView?.apply {
return this.isVisible
@ -95,27 +94,25 @@ class GameController(var activity: Activity) {
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
ryujinxNative = RyujinxNative.instance
}
fun setVisible(isVisible: Boolean){
fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if(isVisible)
if (isVisible)
connect()
}
}
fun connect(){
if(controllerId == -1)
controllerId = RyujinxNative.instance.inputConnectGamepad(0)
fun connect() {
if (controllerId == -1)
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if(controllerId == -1)
controllerId = ryujinxNative.inputConnectGamepad(0)
if (controllerId == -1)
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
@ -123,11 +120,11 @@ class GameController(var activity: Activity) {
val action = ev.action
when (action) {
KeyEvent.ACTION_UP -> {
ryujinxNative.inputSetButtonReleased(ev.id, this)
RyujinxNative.jnaInstance.inputSetButtonReleased(ev.id, this)
}
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 -> {
val direction = ev.id
when(direction) {
when (direction) {
GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadRight.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
} else if (ev.xAxis < 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
} else {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadLeft.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadRight.ordinal,
this
)
}
if (ev.yAxis < 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadUp.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
this
)
} else if (ev.yAxis > 0) {
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonPressed(
GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
} else {
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadDown.ordinal,
this
)
RyujinxNative.jnaInstance.inputSetButtonReleased(
GamePadButtonInputId.DpadUp.ordinal,
this
)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this)
RyujinxNative.jnaInstance.inputSetStickAxis(
1,
ev.xAxis,
-ev.yAxis,
this
)
}
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 val nativeWindow: NativeWindow
private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
init {
holder.addCallback(this)
@ -47,21 +45,21 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
if (_width != width || _height != height) {
val window = nativeWindow.requeryWindowHandle()
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
RyujinxNative.jnaInstance.graphicsSetSurface(window, nativeWindow.nativePointer)
nativeWindow.swapInterval = 0;
nativeWindow.swapInterval = 0
}
_width = width
_height = height
_nativeRyujinx.graphicsRendererSetSize(
RyujinxNative.jnaInstance.graphicsRendererSetSize(
width,
height
)
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
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()
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
_nativeRyujinx.graphicsRendererSetSize(
RyujinxNative.jnaInstance.graphicsRendererSetSize(
surfaceHolder.surfaceFrame.width(),
surfaceHolder.surfaceFrame.height()
)
@ -108,12 +106,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
var c = 0
val helper = NativeHelpers.instance
while (_isStarted) {
_nativeRyujinx.inputUpdate()
RyujinxNative.jnaInstance.inputUpdate()
Thread.sleep(1)
showLoading?.apply {
if (value) {
var value = helper.getProgressValue()
val value = helper.getProgressValue()
if (value != -1f)
progress?.apply {
@ -134,9 +132,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
}
c = 0
mainViewModel.updateStats(
_nativeRyujinx.deviceGetGameFifo(),
_nativeRyujinx.deviceGetGameFrameRate(),
_nativeRyujinx.deviceGetGameFrameTime()
RyujinxNative.jnaInstance.deviceGetGameFifo(),
RyujinxNative.jnaInstance.deviceGetGameFrameRate(),
RyujinxNative.jnaInstance.deviceGetGameFrameTime()
)
}
}
@ -148,7 +146,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
thread {
mainViewModel.activity.uiHandler.listen()
}
_nativeRyujinx.graphicsRendererRunLoop()
RyujinxNative.jnaInstance.graphicsRendererRunLoop()
game?.close()
}

View File

@ -2,6 +2,7 @@ package org.ryujinx.android
enum class GamePadButtonInputId {
None,
// Buttons
A,
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>,
finish: () -> Unit
) {
var fPath = path + "/${file.name}";
var fPath = path + "/${file.name}"
var callback: FileCallback? = object : FileCallback() {
override fun onFailed(errorCode: FileCallback.ErrorCode) {
override fun onFailed(errorCode: ErrorCode) {
super.onFailed(errorCode)
File(fPath).delete()
finish()
@ -189,7 +189,6 @@ class Helpers {
dir.mkdirs()
}
ZipInputStream(stream).use { zip ->
var count = 0
while (true) {
val header = zip.nextEntry ?: break
if (!folders.any { header.fileName.startsWith(it) }) {
@ -217,7 +216,7 @@ class Helpers {
}
} finally {
isImporting.value = false
RyujinxNative.instance.deviceReloadFilesystem()
RyujinxNative.jnaInstance.deviceReloadFilesystem()
}
}
}

View File

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

View File

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

View File

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

View File

@ -15,18 +15,20 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
private var controllerId: Int = -1
private val motionGyroOrientation : FloatArray = FloatArray(3)
private val motionAcelOrientation : FloatArray = FloatArray(3)
private val motionGyroOrientation: FloatArray = FloatArray(3)
private val motionAcelOrientation: FloatArray = FloatArray(3)
init {
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
setOrientation90()
var orientationListener = object : OrientationEventListener(activity){
var orientationListener = object : OrientationEventListener(activity) {
override fun onOrientationChanged(orientation: Int) {
when{
when {
isWithinOrientationRange(orientation, 270) -> {
setOrientation270()
}
isWithinOrientationRange(orientation, 90) -> {
setOrientation90()
}
@ -34,10 +36,10 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
}
private fun isWithinOrientationRange(
currentOrientation : Int, targetOrientation : Int, epsilon : Int = 90
) : Boolean {
currentOrientation: Int, targetOrientation: Int, epsilon: Int = 90
): Boolean {
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[2] = -1.0f
}
fun setOrientation90() {
motionGyroOrientation[0] = 1.0f
motionGyroOrientation[1] = -1.0f
@ -59,30 +62,38 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
motionAcelOrientation[2] = -1.0f
}
fun setControllerId(id: Int){
fun setControllerId(id: Int) {
controllerId = id
}
fun register(){
if(isRegistered)
fun register() {
if (isRegistered)
return
gyro?.apply {
sensorManager.registerListener(this@MotionSensorManager, gyro, SensorManager.SENSOR_DELAY_GAME)
sensorManager.registerListener(
this@MotionSensorManager,
gyro,
SensorManager.SENSOR_DELAY_GAME
)
}
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)
isRegistered = false
if (controllerId != -1){
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
if (controllerId != -1) {
RyujinxNative.jnaInstance.inputSetAccelerometerData(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 z = motionAcelOrientation[2] * event.values[2]
RyujinxNative.instance.inputSetAccelerometerData(x, y, z, controllerId)
RyujinxNative.jnaInstance.inputSetAccelerometerData(
x,
y,
z,
controllerId
)
}
Sensor.TYPE_GYROSCOPE -> {
val x = motionGyroOrientation[0] * event.values[1]
val y = motionGyroOrientation[1] * event.values[0]
val z = motionGyroOrientation[2] * event.values[2]
RyujinxNative.instance.inputSetGyroData(x, y, z, controllerId)
RyujinxNative.jnaInstance.inputSetGyroData(x, y, z, controllerId)
}
}
}
else {
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
RyujinxNative.jnaInstance.inputSetAccelerometerData(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 {
val instance = NativeHelpers()
init {
System.loadLibrary("ryujinxjni")
}
@ -27,18 +28,18 @@ class NativeHelpers {
external fun getMaxSwapInterval(nativeWindow: Long): Int
external fun getMinSwapInterval(nativeWindow: Long): Int
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getProgressInfo() : String
external fun getProgressValue() : Float
external fun storeStringJava(string: String) : Long
external fun getStringJava(id: Long) : String
external fun getProgressInfo(): String
external fun getProgressValue(): Float
external fun storeStringJava(string: String): Long
external fun getStringJava(id: Long): String
external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
external fun getUiHandlerRequestType() : Int
external fun getUiHandlerRequestTitle() : Long
external fun getUiHandlerRequestMessage() : Long
external fun getUiHandlerMinLength() : Int
external fun getUiHandlerMaxLength() : Int
external fun getUiHandlerKeyboardMode() : Int
external fun getUiHandlerRequestWatermark() : Long
external fun getUiHandlerRequestInitialText() : Long
external fun getUiHandlerRequestSubtitle() : Long
external fun getUiHandlerRequestType(): Int
external fun getUiHandlerRequestTitle(): Long
external fun getUiHandlerRequestMessage(): Long
external fun getUiHandlerMinLength(): Int
external fun getUiHandlerMaxLength(): Int
external fun getUiHandlerKeyboardMode(): Int
external fun getUiHandlerRequestWatermark(): Long
external fun getUiHandlerRequestInitialText(): Long
external fun getUiHandlerRequestSubtitle(): Long
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,11 @@
package org.ryujinx.android
import com.sun.jna.Library
import com.sun.jna.Native
import org.ryujinx.android.viewmodels.GameInfo
@Suppress("KotlinJniMissingFunction")
class RyujinxNative {
external fun initialize(appPath: Long): Boolean
companion object {
val instance: RyujinxNative = RyujinxNative()
init {
System.loadLibrary("ryujinx")
}
}
external fun deviceInitialize(
interface RyujinxNativeJna : Library {
fun deviceInitialize(
isHostMapped: Boolean, useNce: Boolean,
systemLanguage: Int,
regionCode: Int,
@ -22,60 +13,81 @@ class RyujinxNative {
enableDockedMode: Boolean,
enablePtc: Boolean,
enableInternetAccess: Boolean,
timeZone: Long,
timeZone: String,
ignoreMissingServices: Boolean
): Boolean
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
external fun graphicsInitializeRenderer(
fun graphicsInitialize(
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>,
extensionsLength: Int,
driver: Long
): Boolean
external fun deviceLoad(game: String): Boolean
external fun deviceLaunchMiiEditor(): Boolean
external fun deviceGetGameFrameRate(): Double
external fun deviceGetGameFrameTime(): Double
external fun deviceGetGameFifo(): Double
external fun deviceGetGameInfo(fileDescriptor: Int, extension: Long): GameInfo
external fun deviceGetGameInfoFromPath(path: String): GameInfo
external fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
external fun graphicsRendererSetSize(width: Int, height: Int)
external fun graphicsRendererSetVsync(enabled: Boolean)
external fun graphicsRendererRunLoop()
external fun deviceReloadFilesystem()
external fun inputInitialize(width: Int, height: Int)
external fun inputSetClientSize(width: Int, height: Int)
external fun inputSetTouchPoint(x: Int, y: Int)
external fun inputReleaseTouchPoint()
external fun inputUpdate()
external fun inputSetButtonPressed(button: Int, id: Int)
external fun inputSetButtonReleased(button: Int, id: Int)
external fun inputConnectGamepad(index: Int): Int
external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
external fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
external fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
external fun graphicsSetSurface(surface: Long, window: Long)
external fun deviceCloseEmulation()
external fun deviceSignalEmulationClose()
external fun deviceGetDlcTitleId(path: Long, ncaPath: Long): Long
external fun deviceGetDlcContentList(path: Long, titleId: Long): Array<String>
external fun userGetOpenedUser(): Long
external fun userGetUserPicture(userId: Long): Long
external fun userSetUserPicture(userId: String, picture: String)
external fun userGetUserName(userId: Long): Long
external fun userSetUserName(userId: String, userName: String)
external fun userGetAllUsers(): Array<String>
external fun userAddUser(username: String, picture: String)
external fun userDeleteUser(userId: String)
external fun userOpenUser(userId: Long)
external fun userCloseUser(userId: String)
external fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
external fun deviceGetInstalledFirmwareVersion() : Long
external fun uiHandlerSetup()
external fun uiHandlerWait()
external fun uiHandlerStopWait()
external fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
fun javaInitialize(appPath: String): Boolean
fun deviceLaunchMiiEditor(): Boolean
fun deviceGetGameFrameRate(): Double
fun deviceGetGameFrameTime(): Double
fun deviceGetGameFifo(): Double
fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
fun graphicsRendererSetSize(width: Int, height: Int)
fun graphicsRendererSetVsync(enabled: Boolean)
fun graphicsRendererRunLoop()
fun deviceReloadFilesystem()
fun inputInitialize(width: Int, height: Int)
fun inputSetClientSize(width: Int, height: Int)
fun inputSetTouchPoint(x: Int, y: Int)
fun inputReleaseTouchPoint()
fun inputUpdate()
fun inputSetButtonPressed(button: Int, id: Int)
fun inputSetButtonReleased(button: Int, id: Int)
fun inputConnectGamepad(index: Int): Int
fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
fun graphicsSetSurface(surface: Long, window: Long)
fun deviceCloseEmulation()
fun deviceSignalEmulationClose()
fun userGetOpenedUser(): String
fun userGetUserPicture(userId: String): String
fun userSetUserPicture(userId: String, picture: String)
fun userGetUserName(userId: String): String
fun userSetUserName(userId: String, userName: String)
fun userAddUser(username: String, picture: String)
fun userDeleteUser(userId: String)
fun userOpenUser(userId: String)
fun userCloseUser(userId: String)
fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): String
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
fun deviceGetInstalledFirmwareVersion(): String
fun uiHandlerSetup()
fun uiHandlerWait()
fun uiHandlerStopWait()
fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
fun deviceGetDlcTitleId(path: String, ncaPath: String): String
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
fun userGetAllUsers(): Array<String>
fun deviceGetDlcContentList(path: String, titleId: Long): Array<String>
}
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.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@ -50,13 +51,13 @@ class UiHandler {
var shouldListen = true
init {
RyujinxNative.instance.uiHandlerSetup()
RyujinxNative.jnaInstance.uiHandlerSetup()
}
fun listen() {
showMessage.value = false
while (shouldListen) {
RyujinxNative.instance.uiHandlerWait()
RyujinxNative.jnaInstance.uiHandlerWait()
title =
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
@ -79,7 +80,7 @@ class UiHandler {
fun stop() {
shouldListen = false
RyujinxNative.instance.uiHandlerStopWait()
RyujinxNative.jnaInstance.uiHandlerStopWait()
}
@OptIn(ExperimentalMaterial3Api::class)
@ -96,23 +97,24 @@ class UiHandler {
mutableStateOf("")
}
fun validate() : Boolean{
if(inputText.value.isEmpty()){
fun validate(): Boolean {
if (inputText.value.isEmpty()) {
validation.value = "Must be between ${minLength} and ${maxLength} characters"
}
else{
} else {
return inputText.value.length < minLength || inputText.value.length > maxLength
}
return false;
return false
}
fun getInputType(): KeyboardType {
return when(mode){
return when (mode) {
KeyboardMode.Default -> KeyboardType.Text
KeyboardMode.Numeric -> KeyboardType.Decimal
KeyboardMode.ASCII -> KeyboardType.Ascii
else -> { KeyboardType.Text}
KeyboardMode.Numeric -> KeyboardType.Decimal
KeyboardMode.ASCII -> KeyboardType.Ascii
else -> {
KeyboardType.Text
}
}
}
@ -125,15 +127,15 @@ class UiHandler {
NativeHelpers.instance.storeStringJava(inputListener.value)
}
showMessageListener.value = false
RyujinxNative.instance.uiHandlerSetResponse(true, input)
RyujinxNative.jnaInstance.uiHandlerSetResponse(true, input)
}
if (showMessageListener.value) {
AlertDialog(
BasicAlertDialog(
onDismissRequest = { },
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = false, false)
) {
Column {

View File

@ -21,7 +21,7 @@ class DocumentProvider : DocumentsProvider() {
private val applicationName = "Ryujinx"
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_MIME_TYPES,
DocumentsContract.Root.COLUMN_FLAGS,
@ -32,7 +32,7 @@ class DocumentProvider : DocumentsProvider() {
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_MIME_TYPE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
@ -41,19 +41,19 @@ class DocumentProvider : DocumentsProvider() {
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 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)) {
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
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]
*/
private fun getDocumentId(file : File) : String {
private fun getDocumentId(file: File): String {
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)
cursor.newRow().apply {
add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
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_DOCUMENT_ID, getDocumentId(baseDirectory))
add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
@ -87,22 +90,23 @@ class DocumentProvider : DocumentsProvider() {
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)
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 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)
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 baseName = name.substringBeforeLast('.')
while (file.exists())
@ -111,7 +115,11 @@ class DocumentProvider : DocumentsProvider() {
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 newFile = parentFile.resolveWithoutConflict(displayName)
@ -123,20 +131,20 @@ class DocumentProvider : DocumentsProvider() {
if (!newFile.createNewFile())
throw IOException("Failed to create file")
}
} catch (e : IOException) {
} catch (e: IOException) {
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
}
return getDocumentId(newFile)
}
override fun deleteDocument(documentId : String?) {
override fun deleteDocument(documentId: String?) {
val file = getFile(documentId!!)
if (!file.delete())
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 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)
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
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)
try {
if (!sourceFile.renameTo(destFile))
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}")
}
@ -167,16 +176,16 @@ class DocumentProvider : DocumentsProvider() {
}
private fun copyDocument(
sourceDocumentId : String, sourceParentDocumentId : String,
targetParentDocumentId : String?
) : String? {
sourceDocumentId: String, sourceParentDocumentId: String,
targetParentDocumentId: String?
): String? {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
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 oldFile = getFile(sourceDocumentId)
val newFile = parent.resolveWithoutConflict(oldFile.name)
@ -190,7 +199,7 @@ class DocumentProvider : DocumentsProvider() {
inStream.copyTo(outStream)
}
}
} catch (e : IOException) {
} catch (e: IOException) {
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
}
@ -198,9 +207,9 @@ class DocumentProvider : DocumentsProvider() {
}
override fun moveDocument(
sourceDocumentId : String, sourceParentDocumentId : String?,
targetParentDocumentId : String?
) : String? {
sourceDocumentId: String, sourceParentDocumentId: String?,
targetParentDocumentId: String?
): String? {
try {
val newDocumentId = copyDocument(
sourceDocumentId, sourceParentDocumentId!!,
@ -208,12 +217,12 @@ class DocumentProvider : DocumentsProvider() {
)
removeDocument(sourceDocumentId, sourceParentDocumentId)
return newDocumentId
} catch (e : FileNotFoundException) {
} catch (e: FileNotFoundException) {
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 localFile = file ?: getFile(documentId!!)
@ -232,7 +241,10 @@ class DocumentProvider : DocumentsProvider() {
cursor.newRow().apply {
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_MIME_TYPE, getTypeForFile(localFile))
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
@ -244,14 +256,14 @@ class DocumentProvider : DocumentsProvider() {
return cursor
}
private fun getTypeForFile(file : File) : Any? {
private fun getTypeForFile(file: File): Any? {
return if (file.isDirectory)
DocumentsContract.Document.MIME_TYPE_DIR
else
getTypeForName(file.name)
}
private fun getTypeForName(name : String) : Any? {
private fun getTypeForName(name: String): Any {
val lastDot = name.lastIndexOf('.')
if (lastDot >= 0) {
val extension = name.substring(lastDot + 1)
@ -262,7 +274,11 @@ class DocumentProvider : DocumentsProvider() {
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)
val parent = getFile(parentDocumentId!!)
@ -272,7 +288,11 @@ class DocumentProvider : DocumentsProvider() {
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 accessMode = ParcelFileDescriptor.parseMode(mode)
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.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.reflect.TypeToken
import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative
import java.io.File
@ -40,14 +39,14 @@ class DlcViewModel(val titleId: String) {
val path = file.getAbsolutePath(storageHelper.storage.context)
if (path.isNotEmpty()) {
data?.apply {
val contents = RyujinxNative.instance.deviceGetDlcContentList(
NativeHelpers.instance.storeStringJava(path),
val contents = RyujinxNative.jnaInstance.deviceGetDlcContentList(
path,
titleId.toLong(16)
)
if (contents.isNotEmpty()) {
val contentPath = path
val container = DlcContainerList(contentPath);
val container = DlcContainerList(contentPath)
for (content in contents)
container.dlc_nca_list.add(
@ -90,7 +89,7 @@ class DlcViewModel(val titleId: String) {
val containerPath = container.path
if (!File(containerPath).exists())
continue;
continue
for (dlc in container.dlc_nca_list) {
val enabled = remember {
@ -102,7 +101,10 @@ class DlcViewModel(val titleId: String) {
enabled,
containerPath,
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(
var enabled: Boolean = false,
var titleId: String = "",
var fullPath: String = "")
var fullPath: String = ""
)
data class DlcItem(
var name:String = "",
var name: String = "",
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
var containerPath: 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 androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.file.extension
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative
@ -24,8 +23,8 @@ class GameModel(var file: DocumentFile, val context: Context) {
init {
fileName = file.name
val pid = open()
val ext = NativeHelpers.instance.storeStringJava(file.extension)
val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, ext)
val gameInfo = GameInfo()
RyujinxNative.jnaInstance.deviceGetGameInfo(pid, file.extension, gameInfo)
close()
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")
return descriptor?.fd ?: 0
}
fun openUpdate() : Int {
if(titleId?.isNotEmpty() == true) {
fun openUpdate(): Int {
if (titleId?.isNotEmpty() == true) {
val vm = TitleUpdateViewModel(titleId ?: "")
if(vm.data?.selected?.isNotEmpty() == true){
if (vm.data?.selected?.isNotEmpty() == true) {
val uri = Uri.parse(vm.data?.selected)
val file = DocumentFile.fromSingleUri(context, uri)
if(file?.exists() == true){
if (file?.exists() == true) {
updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
return updateDescriptor ?.fd ?: -1;
return updateDescriptor?.fd ?: -1
}
}
}
return -1;
return -1
}
fun close() {
@ -78,16 +77,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
}
}
class GameInfo {
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{
enum class FileType {
None,
Nsp,
Xci,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,10 +29,11 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
@ -192,7 +193,7 @@ class SettingViews {
)
settingsViewModel.navController.popBackStack()
}) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
})
}) { contentPadding ->
@ -310,7 +311,7 @@ class SettingViews {
}
if (showFirwmareDialog.value) {
AlertDialog(onDismissRequest = {
BasicAlertDialog(onDismissRequest = {
if (firmwareInstallState.value != FirmwareInstallState.Install) {
showFirwmareDialog.value = false
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
@ -552,7 +553,6 @@ class SettingViews {
Button(onClick = {
val storage = MainActivity.StorageHelper
storage?.apply {
val s = this.storage
val callBack = this.onFileSelected
onFileSelected = { requestCode, files ->
run {
@ -578,7 +578,7 @@ class SettingViews {
}
if (showImportWarning.value) {
AlertDialog(onDismissRequest = {
BasicAlertDialog(onDismissRequest = {
showImportWarning.value = false
importFile.value = null
}) {
@ -626,7 +626,7 @@ class SettingViews {
}
if (showImportCompletion.value) {
AlertDialog(onDismissRequest = {
BasicAlertDialog(onDismissRequest = {
showImportCompletion.value = false
importFile.value = null
mainViewModel.userViewModel.refreshUsers()
@ -739,7 +739,7 @@ class SettingViews {
}
if (isDriverSelectorOpen.value) {
AlertDialog(onDismissRequest = {
BasicAlertDialog(onDismissRequest = {
isDriverSelectorOpen.value = false
if (isChanged.value) {
@ -1064,7 +1064,7 @@ class SettingViews {
}
}
BackHandler() {
BackHandler {
settingsViewModel.save(
isHostMapped,
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.shape.CircleShape
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.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@ -43,10 +43,11 @@ class UserViews {
companion object {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
fun Main(viewModel: MainViewModel? = null) {
val reload = remember {
mutableStateOf(true)
}
fun refresh() {
viewModel?.userViewModel?.refreshUsers()
reload.value = true
@ -64,7 +65,7 @@ class UserViews {
IconButton(onClick = {
viewModel?.navController?.popBackStack()
}) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
})
}) { contentPadding ->
@ -133,10 +134,14 @@ class UserViews {
.fillMaxSize()
.padding(4.dp)
) {
if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
if (viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
items(viewModel.userViewModel.userList) { user ->
Image(
bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0)
bitmap = BitmapFactory.decodeByteArray(
user.userPicture,
0,
user.userPicture?.size ?: 0
)
.asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
@ -165,6 +170,6 @@ class UserViews {
@Preview
@Composable
fun Preview() {
UserViews.Main()
Main()
}
}