forked from MeloNX/MeloNX
android - replace jni with jna
This commit is contained in:
parent
8888edde5c
commit
a00c8c909f
@ -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);
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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')
|
||||
|
@ -0,0 +1,7 @@
|
||||
package org.ryujinx.android
|
||||
|
||||
enum class BackendThreading {
|
||||
Auto,
|
||||
Off,
|
||||
On
|
||||
}
|
@ -3,7 +3,7 @@ package org.ryujinx.android
|
||||
import androidx.activity.ComponentActivity
|
||||
|
||||
abstract class BaseActivity : ComponentActivity() {
|
||||
companion object{
|
||||
companion object {
|
||||
val crashHandler = CrashHandler()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package org.ryujinx.android
|
||||
|
||||
enum class GamePadButtonInputId {
|
||||
None,
|
||||
|
||||
// Buttons
|
||||
A,
|
||||
B,
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 = ""
|
||||
)
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 = ""
|
||||
)
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user