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.Pointers;
|
||||||
using LibRyujinx.Jni.Primitives;
|
|
||||||
using LibRyujinx.Jni.References;
|
using LibRyujinx.Jni.References;
|
||||||
using LibRyujinx.Jni.Values;
|
|
||||||
using Rxmxnx.PInvoke;
|
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
@ -18,6 +14,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -99,14 +96,8 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
|
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")]
|
[UnmanagedCallersOnly(EntryPoint = "javaInitialize")]
|
||||||
internal static int LoadLibrary(JavaVMRef vm, IntPtr unknown)
|
public static bool JniInitialize(IntPtr jpathId)
|
||||||
{
|
|
||||||
return 0x00010006; //JNI_VERSION_1_6
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
|
|
||||||
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId)
|
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
PlatformInfo.IsBionic = true;
|
PlatformInfo.IsBionic = true;
|
||||||
@ -118,7 +109,7 @@ namespace LibRyujinx
|
|||||||
AsyncLogTargetOverflowAction.Block
|
AsyncLogTargetOverflowAction.Block
|
||||||
));
|
));
|
||||||
|
|
||||||
var path = GetStoredString(jpathId);
|
var path = Marshal.PtrToStringAnsi(jpathId);
|
||||||
|
|
||||||
var init = Initialize(path);
|
var init = Initialize(path);
|
||||||
|
|
||||||
@ -138,70 +129,71 @@ namespace LibRyujinx
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
|
||||||
public static void JniReloadFileSystem()
|
public static void JnaReloadFileSystem()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SwitchDevice?.ReloadFileSystem();
|
SwitchDevice?.ReloadFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInitialize")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceInitialize")]
|
||||||
public static JBoolean JniInitializeDeviceNative(JEnvRef jEnv,
|
public static bool JnaDeviceInitialize(bool isHostMapped,
|
||||||
JObjectLocalRef jObj,
|
bool useNce,
|
||||||
JBoolean isHostMapped,
|
int systemLanguage,
|
||||||
JBoolean useNce,
|
int regionCode,
|
||||||
JInt systemLanguage,
|
bool enableVsync,
|
||||||
JInt regionCode,
|
bool enableDockedMode,
|
||||||
JBoolean enableVsync,
|
bool enablePtc,
|
||||||
JBoolean enableDockedMode,
|
bool enableInternetAccess,
|
||||||
JBoolean enablePtc,
|
IntPtr timeZonePtr,
|
||||||
JBoolean enableInternetAccess,
|
bool ignoreMissingServices)
|
||||||
JLong timeZoneId,
|
|
||||||
JBoolean ignoreMissingServices)
|
|
||||||
{
|
{
|
||||||
|
debug_break(4);
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
AudioDriver = new OpenALHardwareDeviceDriver();//new OboeHardwareDeviceDriver();
|
AudioDriver = new OpenALHardwareDeviceDriver();
|
||||||
|
|
||||||
|
var timezone = Marshal.PtrToStringAnsi(timeZonePtr);
|
||||||
return InitializeDevice(isHostMapped,
|
return InitializeDevice(isHostMapped,
|
||||||
useNce,
|
useNce,
|
||||||
(SystemLanguage)(int)systemLanguage,
|
(SystemLanguage)systemLanguage,
|
||||||
(RegionCode)(int)regionCode,
|
(RegionCode)regionCode,
|
||||||
enableVsync,
|
enableVsync,
|
||||||
enableDockedMode,
|
enableDockedMode,
|
||||||
enablePtc,
|
enablePtc,
|
||||||
enableInternetAccess,
|
enableInternetAccess,
|
||||||
GetStoredString(timeZoneId),
|
timezone,
|
||||||
ignoreMissingServices);
|
ignoreMissingServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFifo")]
|
||||||
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static double JnaGetGameFifo()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
|
var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameTime")]
|
||||||
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static double JnaGetGameFrameTime()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
|
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameFrameRate")]
|
||||||
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static double JnaGetGameFrameRate()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
|
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")]
|
||||||
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
|
public static bool JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (SwitchDevice?.EmulationContext == null)
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
@ -214,8 +206,8 @@ namespace LibRyujinx
|
|||||||
return LoadApplication(path);
|
return LoadApplication(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")]
|
||||||
public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static bool JNALaunchMiiEditApplet()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (SwitchDevice?.EmulationContext == null)
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
@ -226,38 +218,38 @@ namespace LibRyujinx
|
|||||||
return LaunchMiiEditApplet();
|
return LaunchMiiEditApplet();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetDlcContentList")]
|
||||||
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId)
|
public static IntPtr JniGetDlcContentListNative(IntPtr pathPtr, long titleId)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId);
|
var list = GetDlcContentList(Marshal.PtrToStringAnsi(pathPtr) ?? "", (ulong)titleId);
|
||||||
|
|
||||||
return CreateStringArray(jEnv, list);
|
return CreateStringArray(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetDlcTitleId")]
|
||||||
public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath)
|
public static long JniGetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
return storeString(GetDlcTitleId(GetStoredString(pathPtr), GetStoredString(ncaPath)));
|
return Marshal.StringToHGlobalAnsi(GetDlcTitleId(Marshal.PtrToStringAnsi(pathPtr) ?? "", Marshal.PtrToStringAnsi(ncaPath) ?? ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceSignalEmulationClose")]
|
||||||
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniSignalEmulationCloseNative()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SignalEmulationClose();
|
SignalEmulationClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceCloseEmulation")]
|
||||||
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniCloseEmulationNative()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
CloseEmulation();
|
CloseEmulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceLoadDescriptor")]
|
||||||
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JInt type, JInt updateDescriptor)
|
public static bool JnaLoadApplicationNative(int descriptor, int type, int updateDescriptor)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (SwitchDevice?.EmulationContext == null)
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
@ -268,17 +260,17 @@ namespace LibRyujinx
|
|||||||
var stream = OpenFile(descriptor);
|
var stream = OpenFile(descriptor);
|
||||||
var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
|
var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
|
||||||
|
|
||||||
return LoadApplication(stream, (FileType)(int)type, update);
|
return LoadApplication(stream, (FileType)type, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceVerifyFirmware")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceVerifyFirmware")]
|
||||||
public static JLong JniVerifyFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
public static IntPtr JniVerifyFirmware(int descriptor, bool isXci)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
|
||||||
var stream = OpenFile(descriptor);
|
var stream = OpenFile(descriptor);
|
||||||
|
|
||||||
long stringHandle = -1;
|
IntPtr stringHandle = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -286,19 +278,19 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
if (version != null)
|
if (version != null)
|
||||||
{
|
{
|
||||||
stringHandle = storeString(version.VersionString);
|
stringHandle = Marshal.StringToHGlobalAnsi(version.VersionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception _)
|
catch(Exception _)
|
||||||
{
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringHandle;
|
return stringHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceInstallFirmware")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceInstallFirmware")]
|
||||||
public static void JniInstallFirmware(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
public static void JniInstallFirmware(int descriptor, bool isXci)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
|
||||||
@ -307,58 +299,47 @@ namespace LibRyujinx
|
|||||||
InstallFirmware(stream, isXci);
|
InstallFirmware(stream, isXci);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetInstalledFirmwareVersion")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetInstalledFirmwareVersion")]
|
||||||
public static JLong JniGetInstalledFirmwareVersion(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static IntPtr JniGetInstalledFirmwareVersion()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
|
||||||
var version = GetInstalledFirmwareVersion();
|
var version = GetInstalledFirmwareVersion();
|
||||||
long stringHandle = -1;
|
IntPtr stringHandle = 0;
|
||||||
|
|
||||||
if (version != String.Empty)
|
if (version != String.Empty)
|
||||||
{
|
{
|
||||||
stringHandle = storeString(version);
|
stringHandle = Marshal.StringToHGlobalAnsi(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringHandle;
|
return stringHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsInitialize")]
|
||||||
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
|
public static bool JnaGraphicsInitialize(float resScale,
|
||||||
|
float maxAnisotropy,
|
||||||
|
bool fastGpuTime,
|
||||||
|
bool fast2DCopy,
|
||||||
|
bool enableMacroJit,
|
||||||
|
bool enableMacroHLE,
|
||||||
|
bool enableShaderCache,
|
||||||
|
bool enableTextureRecompression,
|
||||||
|
int backendThreading)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
JEnvValue value = jEnv.Environment;
|
|
||||||
ref JNativeInterface jInterface = ref value.Functions;
|
|
||||||
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
|
|
||||||
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
|
|
||||||
IntPtr getIntFieldPtr = jInterface.GetIntFieldPointer;
|
|
||||||
IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer;
|
|
||||||
IntPtr getFloatFieldPtr = jInterface.GetFloatFieldPointer;
|
|
||||||
IntPtr getBooleanFieldPtr = jInterface.GetBooleanFieldPointer;
|
|
||||||
|
|
||||||
var getObjectClass = getObjectClassPtr.GetUnsafeDelegate<GetObjectClassDelegate>();
|
|
||||||
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
|
|
||||||
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
|
|
||||||
var getIntField = getIntFieldPtr.GetUnsafeDelegate<GetIntFieldDelegate>();
|
|
||||||
var getBooleanField = getBooleanFieldPtr.GetUnsafeDelegate<GetBooleanFieldDelegate>();
|
|
||||||
var getFloatField = getFloatFieldPtr.GetUnsafeDelegate<GetFloatFieldDelegate>();
|
|
||||||
|
|
||||||
var jobject = getObjectClass(jEnv, graphicObject);
|
|
||||||
|
|
||||||
GraphicsConfiguration graphicsConfiguration = new()
|
|
||||||
{
|
|
||||||
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
|
|
||||||
EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))),
|
|
||||||
EnableMacroJit = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroJit"), GetCCharSequence("Z"))),
|
|
||||||
EnableTextureRecompression = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableTextureRecompression"), GetCCharSequence("Z"))),
|
|
||||||
Fast2DCopy = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("Fast2DCopy"), GetCCharSequence("Z"))),
|
|
||||||
FastGpuTime = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("FastGpuTime"), GetCCharSequence("Z"))),
|
|
||||||
ResScale = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("ResScale"), GetCCharSequence("F"))),
|
|
||||||
MaxAnisotropy = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("MaxAnisotropy"), GetCCharSequence("F"))),
|
|
||||||
BackendThreading = (BackendThreading)(int)getIntField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("BackendThreading"), GetCCharSequence("I")))
|
|
||||||
};
|
|
||||||
SearchPathContainer.Platform = UnderlyingPlatform.Android;
|
SearchPathContainer.Platform = UnderlyingPlatform.Android;
|
||||||
return InitializeGraphics(graphicsConfiguration);
|
return InitializeGraphics(new GraphicsConfiguration()
|
||||||
|
{
|
||||||
|
ResScale = resScale,
|
||||||
|
MaxAnisotropy = maxAnisotropy,
|
||||||
|
FastGpuTime = fastGpuTime,
|
||||||
|
Fast2DCopy = fast2DCopy,
|
||||||
|
EnableMacroJit = enableMacroJit,
|
||||||
|
EnableMacroHLE = enableMacroHLE,
|
||||||
|
EnableShaderCache = enableShaderCache,
|
||||||
|
EnableTextureRecompression = enableTextureRecompression,
|
||||||
|
BackendThreading = (BackendThreading)backendThreading
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CCharSequence GetCCharSequence(string s)
|
private static CCharSequence GetCCharSequence(string s)
|
||||||
@ -366,8 +347,8 @@ namespace LibRyujinx
|
|||||||
return Encoding.UTF8.GetBytes(s).AsSpan();
|
return Encoding.UTF8.GetBytes(s).AsSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsSetSurface")]
|
||||||
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
|
public static void JniSetSurface(long surfacePtr, long window)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
_surfacePtr = surfacePtr;
|
_surfacePtr = surfacePtr;
|
||||||
@ -376,11 +357,10 @@ namespace LibRyujinx
|
|||||||
_surfaceEvent.Set();
|
_surfaceEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitializeRenderer")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsInitializeRenderer")]
|
||||||
public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv,
|
public unsafe static bool JnaGraphicsInitializeRenderer(char** extensionsArray,
|
||||||
JObjectLocalRef jObj,
|
int extensionsLength,
|
||||||
JArrayLocalRef extensionsArray,
|
long driverHandle)
|
||||||
JLong driverHandle)
|
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (Renderer != null)
|
if (Renderer != null)
|
||||||
@ -388,37 +368,16 @@ namespace LibRyujinx
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JEnvValue value = jEnv.Environment;
|
|
||||||
ref JNativeInterface jInterface = ref value.Functions;
|
|
||||||
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
|
|
||||||
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
|
|
||||||
IntPtr getLongFieldPtr = jInterface.GetLongFieldPointer;
|
|
||||||
IntPtr getArrayLengthPtr = jInterface.GetArrayLengthPointer;
|
|
||||||
IntPtr getObjectArrayElementPtr = jInterface.GetObjectArrayElementPointer;
|
|
||||||
IntPtr getObjectFieldPtr = jInterface.GetObjectFieldPointer;
|
|
||||||
|
|
||||||
var getObjectClass = getObjectClassPtr.GetUnsafeDelegate<GetObjectClassDelegate>();
|
|
||||||
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
|
|
||||||
var getArrayLength = getArrayLengthPtr.GetUnsafeDelegate<GetArrayLengthDelegate>();
|
|
||||||
var getObjectArrayElement = getObjectArrayElementPtr.GetUnsafeDelegate<GetObjectArrayElementDelegate>();
|
|
||||||
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
|
|
||||||
var getObjectField = getObjectFieldPtr.GetUnsafeDelegate<GetObjectFieldDelegate>();
|
|
||||||
|
|
||||||
List<string?> extensions = new();
|
List<string?> extensions = new();
|
||||||
|
|
||||||
var count = getArrayLength(jEnv, extensionsArray);
|
for (int i = 0; i < extensionsLength; i++)
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
{
|
||||||
var obj = getObjectArrayElement(jEnv, extensionsArray, i);
|
extensions.Add(Marshal.PtrToStringAnsi((IntPtr)extensionsArray[i]));
|
||||||
var ext = obj.Transform<JObjectLocalRef, JStringLocalRef>();
|
|
||||||
|
|
||||||
extensions.Add(GetString(jEnv, ext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((long)driverHandle != 0)
|
if (driverHandle != 0)
|
||||||
{
|
{
|
||||||
VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle);
|
VulkanLoader = new VulkanLoader((IntPtr)driverHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateSurface createSurfaceFunc = instance =>
|
CreateSurface createSurfaceFunc = instance =>
|
||||||
@ -446,36 +405,29 @@ namespace LibRyujinx
|
|||||||
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JArrayLocalRef CreateStringArray(JEnvRef jEnv, List<string> strings)
|
private unsafe static IntPtr CreateStringArray(List<string> strings)
|
||||||
{
|
{
|
||||||
JEnvValue value = jEnv.Environment;
|
uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
|
||||||
ref JNativeInterface jInterface = ref value.Functions;
|
var array = (char**)Marshal.AllocHGlobal((int)size);
|
||||||
IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer;
|
Unsafe.InitBlockUnaligned(array, 0, size);
|
||||||
IntPtr findClassPtr = jInterface.FindClassPointer;
|
|
||||||
IntPtr setObjectArrayElementPtr = jInterface.SetObjectArrayElementPointer;
|
|
||||||
|
|
||||||
var newObjectArray = newObjectArrayPtr.GetUnsafeDelegate<NewObjectArrayDelegate>();
|
|
||||||
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
|
|
||||||
var setObjectArrayElement = setObjectArrayElementPtr.GetUnsafeDelegate<SetObjectArrayElementDelegate>();
|
|
||||||
var array = newObjectArray(jEnv, strings.Count, findClass(jEnv, GetCCharSequence("java/lang/String")), CreateString(jEnv, "")._value);
|
|
||||||
|
|
||||||
for (int i = 0; i < strings.Count; i++)
|
for (int i = 0; i < strings.Count; i++)
|
||||||
{
|
{
|
||||||
setObjectArrayElement(jEnv, array, i, CreateString(jEnv, strings[i])._value);
|
array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return (nint)array;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetSize")]
|
||||||
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JnaSetRendererSizeNative(int width, int height)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
Renderer?.Window?.SetSize(width, height);
|
Renderer?.Window?.SetSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
|
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererRunLoop")]
|
||||||
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniRunLoopNative()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetSwapBuffersCallback(() =>
|
SetSwapBuffersCallback(() =>
|
||||||
@ -487,84 +439,32 @@ namespace LibRyujinx
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_loggingSetEnabled")]
|
[UnmanagedCallersOnly(EntryPoint = "loggingSetEnabled")]
|
||||||
public static void JniSetLoggingEnabledNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt logLevel, JBoolean enabled)
|
public static void JniSetLoggingEnabledNative(int logLevel, bool enabled)
|
||||||
{
|
{
|
||||||
Logger.SetEnable((LogLevel)(int)logLevel, enabled);
|
Logger.SetEnable((LogLevel)logLevel, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")]
|
[UnmanagedCallersOnly(EntryPoint = "deviceGetGameInfo")]
|
||||||
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
|
public unsafe static void JniGetGameInfo(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
|
||||||
{
|
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
|
||||||
var info = GetGameInfo(GetString(jEnv, path));
|
|
||||||
return GetInfo(jEnv, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
|
|
||||||
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JLong extension)
|
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
using var stream = OpenFile(fileDescriptor);
|
using var stream = OpenFile(fileDescriptor);
|
||||||
var ext = GetStoredString(extension);
|
var ext = Marshal.PtrToStringAnsi(extension);
|
||||||
var info = GetGameInfo(stream, ext.ToLower());
|
var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
|
||||||
return GetInfo(jEnv, info);
|
var i = (GameInfoNative*)infoPtr;
|
||||||
|
var n = new GameInfoNative(info);
|
||||||
|
i->TitleId = n.TitleId;
|
||||||
|
i->TitleName = n.TitleName;
|
||||||
|
i->Version = n.Version;
|
||||||
|
i->FileSize = n.FileSize;
|
||||||
|
i->Icon = n.Icon;
|
||||||
|
i->Version = n.Version;
|
||||||
|
i->Developer = n.Developer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info)
|
[UnmanagedCallersOnly(EntryPoint = "graphicsRendererSetVsync")]
|
||||||
{
|
public static void JnaSetVsyncStateNative(bool enabled)
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
|
||||||
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
|
|
||||||
|
|
||||||
JEnvValue value = jEnv.Environment;
|
|
||||||
ref JNativeInterface jInterface = ref value.Functions;
|
|
||||||
IntPtr findClassPtr = jInterface.FindClassPointer;
|
|
||||||
IntPtr newGlobalRefPtr = jInterface.NewGlobalRefPointer;
|
|
||||||
IntPtr getFieldIdPtr = jInterface.GetFieldIdPointer;
|
|
||||||
IntPtr getMethodPtr = jInterface.GetMethodIdPointer;
|
|
||||||
IntPtr newObjectPtr = jInterface.NewObjectPointer;
|
|
||||||
IntPtr setObjectFieldPtr = jInterface.SetObjectFieldPointer;
|
|
||||||
IntPtr setDoubleFieldPtr = jInterface.SetDoubleFieldPointer;
|
|
||||||
|
|
||||||
|
|
||||||
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
|
|
||||||
var newGlobalRef = newGlobalRefPtr.GetUnsafeDelegate<NewGlobalRefDelegate>();
|
|
||||||
var getFieldId = getFieldIdPtr.GetUnsafeDelegate<GetFieldIdDelegate>();
|
|
||||||
var getMethod = getMethodPtr.GetUnsafeDelegate<GetMethodIdDelegate>();
|
|
||||||
var newObject = newObjectPtr.GetUnsafeDelegate<NewObjectDelegate>();
|
|
||||||
var setObjectField = setObjectFieldPtr.GetUnsafeDelegate<SetObjectFieldDelegate>();
|
|
||||||
var setDoubleField = setDoubleFieldPtr.GetUnsafeDelegate<SetDoubleFieldDelegate>();
|
|
||||||
|
|
||||||
var javaClass = findClass(jEnv, javaClassName);
|
|
||||||
var newGlobal = newGlobalRef(jEnv, javaClass._value);
|
|
||||||
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
|
|
||||||
var newObj = newObject(jEnv, javaClass, constructor, 0);
|
|
||||||
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value);
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value);
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value);
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value);
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Icon"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, Convert.ToBase64String(info?.Icon ?? Array.Empty<byte>()))._value);
|
|
||||||
setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d);
|
|
||||||
|
|
||||||
return newObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JStringLocalRef CreateString(JEnvRef jEnv, string? s)
|
|
||||||
{
|
|
||||||
s ??= string.Empty;
|
|
||||||
|
|
||||||
var ptr = Marshal.StringToHGlobalAnsi(s);
|
|
||||||
|
|
||||||
var str = createString(jEnv, ptr);
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(ptr);
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")]
|
|
||||||
public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled)
|
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetVsyncState(enabled);
|
SetVsyncState(enabled);
|
||||||
@ -577,196 +477,197 @@ namespace LibRyujinx
|
|||||||
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
|
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")]
|
[UnmanagedCallersOnly(EntryPoint = "inputInitialize")]
|
||||||
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JnaInitializeInput(int width, int height)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
InitializeInput(width, height);
|
InitializeInput(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetClientSize")]
|
||||||
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JnaSetClientSize(int width, int height)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetClientSize(width, height);
|
SetClientSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetTouchPoint")]
|
||||||
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y)
|
public static void JnaSetTouchPoint(int x, int y)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetTouchPoint(x, y);
|
SetTouchPoint(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")]
|
[UnmanagedCallersOnly(EntryPoint = "inputReleaseTouchPoint")]
|
||||||
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JnaReleaseTouchPoint()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
ReleaseTouchPoint();
|
ReleaseTouchPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")]
|
[UnmanagedCallersOnly(EntryPoint = "inputUpdate")]
|
||||||
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniUpdateInput()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
UpdateInput();
|
UpdateInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetButtonPressed")]
|
||||||
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JnaSetButtonPressed(int button, int id)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetButtonPressed((GamepadButtonInputId)(int)button, id);
|
SetButtonPressed((GamepadButtonInputId)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetButtonReleased")]
|
||||||
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JnaSetButtonReleased(int button, int id)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetButtonReleased((GamepadButtonInputId)(int)button, id);
|
SetButtonReleased((GamepadButtonInputId)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetAccelerometerData")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetAccelerometerData")]
|
||||||
public static void JniSetAccelerometerData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id)
|
public static void JniSetAccelerometerData(float x, float y, float z, int id)
|
||||||
{
|
{
|
||||||
var accel = new Vector3(x, y, z);
|
var accel = new Vector3(x, y, z);
|
||||||
SetAccelerometerData(accel, id);
|
SetAccelerometerData(accel, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetGyroData")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetGyroData")]
|
||||||
public static void JniSetGyroData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id)
|
public static void JniSetGyroData(float x, float y, float z, int id)
|
||||||
{
|
{
|
||||||
var gryo = new Vector3(x, y, z);
|
var gryo = new Vector3(x, y, z);
|
||||||
SetGryoData(gryo, id);
|
SetGryoData(gryo, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
|
[UnmanagedCallersOnly(EntryPoint = "inputSetStickAxis")]
|
||||||
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
|
public static void JnaSetStickAxis(int stick, float x, float y, int id)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
|
SetStickAxis((StickInputId)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")]
|
[UnmanagedCallersOnly(EntryPoint = "inputConnectGamepad")]
|
||||||
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index)
|
public static int JnaConnectGamepad(int index)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
return ConnectGamepad(index);
|
return ConnectGamepad(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
|
[UnmanagedCallersOnly(EntryPoint = "userGetOpenedUser")]
|
||||||
public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static IntPtr JniGetOpenedUser()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetOpenedUser();
|
var userId = GetOpenedUser();
|
||||||
|
var ptr = Marshal.StringToHGlobalAnsi(userId);
|
||||||
|
|
||||||
return storeString(userId);
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
|
[UnmanagedCallersOnly(EntryPoint = "userGetUserPicture")]
|
||||||
public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
public static IntPtr JniGetUserPicture(IntPtr userIdPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetStoredString(userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
|
|
||||||
return storeString(GetUserPicture(userId));
|
return Marshal.StringToHGlobalAnsi(GetUserPicture(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")]
|
[UnmanagedCallersOnly(EntryPoint = "userSetUserPicture")]
|
||||||
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
|
public static void JniGetUserPicture(IntPtr userIdPtr, IntPtr picturePtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
|
||||||
|
|
||||||
SetUserPicture(userId, picture);
|
SetUserPicture(userId, picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
|
[UnmanagedCallersOnly(EntryPoint = "userGetUserName")]
|
||||||
public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
public static IntPtr JniGetUserName(IntPtr userIdPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetStoredString(userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
|
|
||||||
return storeString(GetUserName(userId));
|
return Marshal.StringToHGlobalAnsi(GetUserName(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")]
|
[UnmanagedCallersOnly(EntryPoint = "userSetUserName")]
|
||||||
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
|
public static void JniSetUserName(IntPtr userIdPtr, IntPtr userNamePtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
|
||||||
|
|
||||||
SetUserName(userId, userName);
|
SetUserName(userId, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
|
[UnmanagedCallersOnly(EntryPoint = "userGetAllUsers")]
|
||||||
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static IntPtr JniGetAllUsers()
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var users = GetAllUsers();
|
var users = GetAllUsers();
|
||||||
|
|
||||||
return CreateStringArray(jEnv, users.ToList());
|
return CreateStringArray(users.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
|
[UnmanagedCallersOnly(EntryPoint = "userAddUser")]
|
||||||
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
|
public static void JniAddUser(IntPtr userNamePtr, IntPtr picturePtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
|
||||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
|
||||||
|
|
||||||
AddUser(userName, picture);
|
AddUser(userName, picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
|
[UnmanagedCallersOnly(EntryPoint = "userDeleteUser")]
|
||||||
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static void JniDeleteUser(IntPtr userIdPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
|
|
||||||
DeleteUser(userId);
|
DeleteUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")]
|
[UnmanagedCallersOnly(EntryPoint = "uiHandlerSetup")]
|
||||||
public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniSetupUiHandler()
|
||||||
{
|
{
|
||||||
SetupUiHandler();
|
SetupUiHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")]
|
[UnmanagedCallersOnly(EntryPoint = "uiHandlerWait")]
|
||||||
public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniWaitUiHandler()
|
||||||
{
|
{
|
||||||
WaitUiHandler();
|
WaitUiHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")]
|
[UnmanagedCallersOnly(EntryPoint = "uiHandlerStopWait")]
|
||||||
public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniStopUiHandlerWait()
|
||||||
{
|
{
|
||||||
StopUiHandlerWait();
|
StopUiHandlerWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")]
|
[UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")]
|
||||||
public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input)
|
public static void JniSetUiHandlerResponse(bool isOkPressed, long input)
|
||||||
{
|
{
|
||||||
SetUiHandlerResponse(isOkPressed, input);
|
SetUiHandlerResponse(isOkPressed, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
[UnmanagedCallersOnly(EntryPoint = "userOpenUser")]
|
||||||
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
public static void JniOpenUser(IntPtr userIdPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetStoredString(userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
|
|
||||||
OpenUser(userId);
|
OpenUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
|
[UnmanagedCallersOnly(EntryPoint = "userCloseUser")]
|
||||||
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static void JniCloseUser(IntPtr userIdPtr)
|
||||||
{
|
{
|
||||||
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
|
||||||
|
|
||||||
CloseUser(userId);
|
CloseUser(userId);
|
||||||
}
|
}
|
||||||
|
@ -121,10 +121,13 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json");
|
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json");
|
||||||
if (File.Exists(addOnContentMetadataPath))
|
if (File.Exists(addOnContentMetadataPath))
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}");
|
||||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
|
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer);
|
||||||
|
Logger.Info?.Print(LogClass.Loader, $"DLC Found : {addOnContentMetadataPath}. Available: {dlcContainerList.Count}");
|
||||||
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Loader, $"DLC Contents Available: {downloadableContentContainer.DownloadableContentNcaList.Count}");
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
|
@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "org.ryujinx.android"
|
applicationId "org.ryujinx.android"
|
||||||
minSdk 30
|
minSdk 30
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 10030
|
versionCode 10031
|
||||||
versionName '1.0.30t'
|
versionName '1.0.31'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@ -76,6 +76,7 @@ tasks.named("preBuild") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.14.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation platform('androidx.compose:compose-bom:2024.05.00')
|
implementation platform('androidx.compose:compose-bom:2024.05.00')
|
||||||
|
@ -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
|
import androidx.activity.ComponentActivity
|
||||||
|
|
||||||
abstract class BaseActivity : ComponentActivity() {
|
abstract class BaseActivity : ComponentActivity() {
|
||||||
companion object{
|
companion object {
|
||||||
val crashHandler = CrashHandler()
|
val crashHandler = CrashHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ import java.io.File
|
|||||||
import java.lang.Thread.UncaughtExceptionHandler
|
import java.lang.Thread.UncaughtExceptionHandler
|
||||||
|
|
||||||
class CrashHandler : UncaughtExceptionHandler {
|
class CrashHandler : UncaughtExceptionHandler {
|
||||||
var crashLog : String = ""
|
var crashLog: String = ""
|
||||||
override fun uncaughtException(t: Thread, e: Throwable) {
|
override fun uncaughtException(t: Thread, e: Throwable) {
|
||||||
crashLog += e.toString() + "\n"
|
crashLog += e.toString() + "\n"
|
||||||
|
|
||||||
File(MainActivity.AppPath + "${File.separator}Logs${File.separator}crash.log").writeText(crashLog)
|
File(MainActivity.AppPath + "${File.separator}Logs${File.separator}crash.log").writeText(
|
||||||
|
crashLog
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,8 @@ typealias GamePadConfig = RadialGamePadConfig
|
|||||||
|
|
||||||
class GameController(var activity: Activity) {
|
class GameController(var activity: Activity) {
|
||||||
|
|
||||||
companion object{
|
companion object {
|
||||||
private fun Create(context: Context, activity: Activity, controller: GameController) : View
|
private fun Create(context: Context, controller: GameController): View {
|
||||||
{
|
|
||||||
val inflator = LayoutInflater.from(context)
|
val inflator = LayoutInflater.from(context)
|
||||||
val view = inflator.inflate(R.layout.game_layout, null)
|
val view = inflator.inflate(R.layout.game_layout, null)
|
||||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||||
@ -44,12 +43,13 @@ class GameController(var activity: Activity) {
|
|||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Compose(viewModel: MainViewModel) : Unit {
|
fun Compose(viewModel: MainViewModel): Unit {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxSize(), factory = { context ->
|
modifier = Modifier.fillMaxSize(), factory = { context ->
|
||||||
val controller = GameController(viewModel.activity)
|
val controller = GameController(viewModel.activity)
|
||||||
val c = Create(context, viewModel.activity, controller)
|
val c = Create(context, controller)
|
||||||
viewModel.activity.lifecycleScope.apply {
|
viewModel.activity.lifecycleScope.apply {
|
||||||
viewModel.activity.lifecycleScope.launch {
|
viewModel.activity.lifecycleScope.launch {
|
||||||
val events = merge(
|
val events = merge(
|
||||||
@ -70,12 +70,11 @@ class GameController(var activity: Activity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var ryujinxNative: RyujinxNative
|
|
||||||
private var controllerView: View? = null
|
private var controllerView: View? = null
|
||||||
var leftGamePad: GamePad
|
var leftGamePad: GamePad
|
||||||
var rightGamePad: GamePad
|
var rightGamePad: GamePad
|
||||||
var controllerId: Int = -1
|
var controllerId: Int = -1
|
||||||
val isVisible : Boolean
|
val isVisible: Boolean
|
||||||
get() {
|
get() {
|
||||||
controllerView?.apply {
|
controllerView?.apply {
|
||||||
return this.isVisible
|
return this.isVisible
|
||||||
@ -95,27 +94,25 @@ class GameController(var activity: Activity) {
|
|||||||
leftGamePad.gravityY = 1f
|
leftGamePad.gravityY = 1f
|
||||||
rightGamePad.gravityX = 1f
|
rightGamePad.gravityX = 1f
|
||||||
rightGamePad.gravityY = 1f
|
rightGamePad.gravityY = 1f
|
||||||
|
|
||||||
ryujinxNative = RyujinxNative.instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVisible(isVisible: Boolean){
|
fun setVisible(isVisible: Boolean) {
|
||||||
controllerView?.apply {
|
controllerView?.apply {
|
||||||
this.isVisible = isVisible
|
this.isVisible = isVisible
|
||||||
|
|
||||||
if(isVisible)
|
if (isVisible)
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect(){
|
fun connect() {
|
||||||
if(controllerId == -1)
|
if (controllerId == -1)
|
||||||
controllerId = RyujinxNative.instance.inputConnectGamepad(0)
|
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEvent(ev: Event) {
|
private fun handleEvent(ev: Event) {
|
||||||
if(controllerId == -1)
|
if (controllerId == -1)
|
||||||
controllerId = ryujinxNative.inputConnectGamepad(0)
|
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
|
||||||
|
|
||||||
controllerId.apply {
|
controllerId.apply {
|
||||||
when (ev) {
|
when (ev) {
|
||||||
@ -123,11 +120,11 @@ class GameController(var activity: Activity) {
|
|||||||
val action = ev.action
|
val action = ev.action
|
||||||
when (action) {
|
when (action) {
|
||||||
KeyEvent.ACTION_UP -> {
|
KeyEvent.ACTION_UP -> {
|
||||||
ryujinxNative.inputSetButtonReleased(ev.id, this)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(ev.id, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.ACTION_DOWN -> {
|
KeyEvent.ACTION_DOWN -> {
|
||||||
ryujinxNative.inputSetButtonPressed(ev.id, this)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(ev.id, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,36 +132,82 @@ class GameController(var activity: Activity) {
|
|||||||
is Event.Direction -> {
|
is Event.Direction -> {
|
||||||
val direction = ev.id
|
val direction = ev.id
|
||||||
|
|
||||||
when(direction) {
|
when (direction) {
|
||||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||||
if (ev.xAxis > 0) {
|
if (ev.xAxis > 0) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
} else if (ev.xAxis < 0) {
|
} else if (ev.xAxis < 0) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (ev.yAxis < 0) {
|
if (ev.yAxis < 0) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
} else if (ev.yAxis > 0) {
|
} else if (ev.yAxis > 0) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||||
ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this)
|
RyujinxNative.jnaInstance.inputSetStickAxis(
|
||||||
|
1,
|
||||||
|
ev.xAxis,
|
||||||
|
-ev.yAxis,
|
||||||
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GamePadButtonInputId.RightStick.ordinal -> {
|
GamePadButtonInputId.RightStick.ordinal -> {
|
||||||
ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this)
|
RyujinxNative.jnaInstance.inputSetStickAxis(
|
||||||
|
2,
|
||||||
|
ev.xAxis,
|
||||||
|
-ev.yAxis,
|
||||||
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
private var _isStarted: Boolean = false
|
private var _isStarted: Boolean = false
|
||||||
private val nativeWindow: NativeWindow
|
private val nativeWindow: NativeWindow
|
||||||
|
|
||||||
private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
holder.addCallback(this)
|
holder.addCallback(this)
|
||||||
|
|
||||||
@ -47,21 +45,21 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
if (_width != width || _height != height) {
|
if (_width != width || _height != height) {
|
||||||
val window = nativeWindow.requeryWindowHandle()
|
val window = nativeWindow.requeryWindowHandle()
|
||||||
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
|
RyujinxNative.jnaInstance.graphicsSetSurface(window, nativeWindow.nativePointer)
|
||||||
|
|
||||||
nativeWindow.swapInterval = 0;
|
nativeWindow.swapInterval = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
_width = width
|
_width = width
|
||||||
_height = height
|
_height = height
|
||||||
|
|
||||||
_nativeRyujinx.graphicsRendererSetSize(
|
RyujinxNative.jnaInstance.graphicsRendererSetSize(
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
)
|
)
|
||||||
|
|
||||||
if (_isStarted) {
|
if (_isStarted) {
|
||||||
_nativeRyujinx.inputSetClientSize(width, height)
|
RyujinxNative.jnaInstance.inputSetClientSize(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,14 +84,14 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
_isStarted = true
|
_isStarted = true
|
||||||
|
|
||||||
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel;
|
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
|
||||||
|
|
||||||
_nativeRyujinx.inputInitialize(width, height)
|
RyujinxNative.jnaInstance.inputInitialize(width, height)
|
||||||
|
|
||||||
val id = mainViewModel.physicalControllerManager?.connect()
|
val id = mainViewModel.physicalControllerManager?.connect()
|
||||||
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
|
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
|
||||||
|
|
||||||
_nativeRyujinx.graphicsRendererSetSize(
|
RyujinxNative.jnaInstance.graphicsRendererSetSize(
|
||||||
surfaceHolder.surfaceFrame.width(),
|
surfaceHolder.surfaceFrame.width(),
|
||||||
surfaceHolder.surfaceFrame.height()
|
surfaceHolder.surfaceFrame.height()
|
||||||
)
|
)
|
||||||
@ -108,12 +106,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
var c = 0
|
var c = 0
|
||||||
val helper = NativeHelpers.instance
|
val helper = NativeHelpers.instance
|
||||||
while (_isStarted) {
|
while (_isStarted) {
|
||||||
_nativeRyujinx.inputUpdate()
|
RyujinxNative.jnaInstance.inputUpdate()
|
||||||
Thread.sleep(1)
|
Thread.sleep(1)
|
||||||
|
|
||||||
showLoading?.apply {
|
showLoading?.apply {
|
||||||
if (value) {
|
if (value) {
|
||||||
var value = helper.getProgressValue()
|
val value = helper.getProgressValue()
|
||||||
|
|
||||||
if (value != -1f)
|
if (value != -1f)
|
||||||
progress?.apply {
|
progress?.apply {
|
||||||
@ -134,9 +132,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
c = 0
|
c = 0
|
||||||
mainViewModel.updateStats(
|
mainViewModel.updateStats(
|
||||||
_nativeRyujinx.deviceGetGameFifo(),
|
RyujinxNative.jnaInstance.deviceGetGameFifo(),
|
||||||
_nativeRyujinx.deviceGetGameFrameRate(),
|
RyujinxNative.jnaInstance.deviceGetGameFrameRate(),
|
||||||
_nativeRyujinx.deviceGetGameFrameTime()
|
RyujinxNative.jnaInstance.deviceGetGameFrameTime()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +146,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
thread {
|
thread {
|
||||||
mainViewModel.activity.uiHandler.listen()
|
mainViewModel.activity.uiHandler.listen()
|
||||||
}
|
}
|
||||||
_nativeRyujinx.graphicsRendererRunLoop()
|
RyujinxNative.jnaInstance.graphicsRendererRunLoop()
|
||||||
|
|
||||||
game?.close()
|
game?.close()
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.ryujinx.android
|
|||||||
|
|
||||||
enum class GamePadButtonInputId {
|
enum class GamePadButtonInputId {
|
||||||
None,
|
None,
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
A,
|
A,
|
||||||
B,
|
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>,
|
currentProgressName: MutableState<String>,
|
||||||
finish: () -> Unit
|
finish: () -> Unit
|
||||||
) {
|
) {
|
||||||
var fPath = path + "/${file.name}";
|
var fPath = path + "/${file.name}"
|
||||||
var callback: FileCallback? = object : FileCallback() {
|
var callback: FileCallback? = object : FileCallback() {
|
||||||
override fun onFailed(errorCode: FileCallback.ErrorCode) {
|
override fun onFailed(errorCode: ErrorCode) {
|
||||||
super.onFailed(errorCode)
|
super.onFailed(errorCode)
|
||||||
File(fPath).delete()
|
File(fPath).delete()
|
||||||
finish()
|
finish()
|
||||||
@ -189,7 +189,6 @@ class Helpers {
|
|||||||
dir.mkdirs()
|
dir.mkdirs()
|
||||||
}
|
}
|
||||||
ZipInputStream(stream).use { zip ->
|
ZipInputStream(stream).use { zip ->
|
||||||
var count = 0
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val header = zip.nextEntry ?: break
|
val header = zip.nextEntry ?: break
|
||||||
if (!folders.any { header.fileName.startsWith(it) }) {
|
if (!folders.any { header.fileName.startsWith(it) }) {
|
||||||
@ -217,7 +216,7 @@ class Helpers {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isImporting.value = false
|
isImporting.value = false
|
||||||
RyujinxNative.instance.deviceReloadFilesystem()
|
RyujinxNative.jnaInstance.deviceReloadFilesystem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import compose.icons.CssGgIcons
|
|||||||
import compose.icons.cssggicons.Games
|
import compose.icons.cssggicons.Games
|
||||||
|
|
||||||
class Icons {
|
class Icons {
|
||||||
companion object{
|
companion object {
|
||||||
/// Icons exported from https://www.composables.com/icons
|
/// Icons exported from https://www.composables.com/icons
|
||||||
@Composable
|
@Composable
|
||||||
fun listView(color: Color): ImageVector {
|
fun listView(color: Color): ImageVector {
|
||||||
@ -216,6 +216,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun applets(color: Color): ImageVector {
|
fun applets(color: Color): ImageVector {
|
||||||
return remember {
|
return remember {
|
||||||
@ -331,6 +332,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun playArrow(color: Color): ImageVector {
|
fun playArrow(color: Color): ImageVector {
|
||||||
return remember {
|
return remember {
|
||||||
@ -372,6 +374,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun folderOpen(color: Color): ImageVector {
|
fun folderOpen(color: Color): ImageVector {
|
||||||
return remember {
|
return remember {
|
||||||
@ -437,6 +440,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun gameUpdate(): ImageVector {
|
fun gameUpdate(): ImageVector {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
@ -514,6 +518,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun download(): ImageVector {
|
fun download(): ImageVector {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
@ -583,6 +588,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun vSync(): ImageVector {
|
fun vSync(): ImageVector {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
@ -653,6 +659,7 @@ class Icons {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun videoGame(): ImageVector {
|
fun videoGame(): ImageVector {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
@ -753,7 +760,7 @@ class Icons {
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun Preview(){
|
fun Preview() {
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -9,11 +9,12 @@ import java.net.URLConnection
|
|||||||
|
|
||||||
class Logging(private var viewModel: MainViewModel) {
|
class Logging(private var viewModel: MainViewModel) {
|
||||||
val logPath = MainActivity.AppPath + "/Logs"
|
val logPath = MainActivity.AppPath + "/Logs"
|
||||||
init{
|
|
||||||
|
init {
|
||||||
File(logPath).mkdirs()
|
File(logPath).mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestExport(){
|
fun requestExport() {
|
||||||
val files = File(logPath).listFiles()
|
val files = File(logPath).listFiles()
|
||||||
files?.apply {
|
files?.apply {
|
||||||
val zipExportPath = MainActivity.AppPath + "/log.zip"
|
val zipExportPath = MainActivity.AppPath + "/log.zip"
|
||||||
@ -22,7 +23,7 @@ class Logging(private var viewModel: MainViewModel) {
|
|||||||
if (files.isNotEmpty()) {
|
if (files.isNotEmpty()) {
|
||||||
val zipFile = ZipFile(zipExportPath)
|
val zipFile = ZipFile(zipExportPath)
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
if(file.isFile) {
|
if (file.isFile) {
|
||||||
zipFile.addFile(file)
|
zipFile.addFile(file)
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@ -30,13 +31,17 @@ class Logging(private var viewModel: MainViewModel) {
|
|||||||
zipFile.close()
|
zipFile.close()
|
||||||
}
|
}
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
val zip =File (zipExportPath)
|
val zip = File(zipExportPath)
|
||||||
val uri = FileProvider.getUriForFile(viewModel.activity, viewModel.activity.packageName + ".fileprovider", zip)
|
val uri = FileProvider.getUriForFile(
|
||||||
|
viewModel.activity,
|
||||||
|
viewModel.activity.packageName + ".fileprovider",
|
||||||
|
zip
|
||||||
|
)
|
||||||
val intent = Intent(Intent.ACTION_SEND)
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name))
|
intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name))
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
val chooser = Intent.createChooser(intent, "Share logs");
|
val chooser = Intent.createChooser(intent, "Share logs")
|
||||||
viewModel.activity.startActivity(chooser)
|
viewModel.activity.startActivity(chooser)
|
||||||
} else {
|
} else {
|
||||||
File(zipExportPath).delete()
|
File(zipExportPath).delete()
|
||||||
@ -45,7 +50,7 @@ class Logging(private var viewModel: MainViewModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
if(File(logPath).exists()){
|
if (File(logPath).exists()) {
|
||||||
File(logPath).deleteRecursively()
|
File(logPath).deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class MainActivity : BaseActivity() {
|
|||||||
fun frameEnded(gameTime: Long) {
|
fun frameEnded(gameTime: Long) {
|
||||||
mainViewModel?.activity?.apply {
|
mainViewModel?.activity?.apply {
|
||||||
if (isActive && QuickSettings(this).enablePerformanceMode) {
|
if (isActive && QuickSettings(this).enablePerformanceMode) {
|
||||||
mainViewModel?.performanceManager?.setTurboMode(true);
|
mainViewModel?.performanceManager?.setTurboMode(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mainViewModel?.gameHost?.hideProgressIndicator()
|
mainViewModel?.gameHost?.hideProgressIndicator()
|
||||||
@ -65,40 +65,40 @@ class MainActivity : BaseActivity() {
|
|||||||
val appPath: String = AppPath
|
val appPath: String = AppPath
|
||||||
|
|
||||||
var quickSettings = QuickSettings(this)
|
var quickSettings = QuickSettings(this)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Debug.ordinal,
|
LogLevel.Debug.ordinal,
|
||||||
quickSettings.enableDebugLogs
|
quickSettings.enableDebugLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Info.ordinal,
|
LogLevel.Info.ordinal,
|
||||||
quickSettings.enableInfoLogs
|
quickSettings.enableInfoLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Stub.ordinal,
|
LogLevel.Stub.ordinal,
|
||||||
quickSettings.enableStubLogs
|
quickSettings.enableStubLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Warning.ordinal,
|
LogLevel.Warning.ordinal,
|
||||||
quickSettings.enableWarningLogs
|
quickSettings.enableWarningLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Error.ordinal,
|
LogLevel.Error.ordinal,
|
||||||
quickSettings.enableErrorLogs
|
quickSettings.enableErrorLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.AccessLog.ordinal,
|
LogLevel.AccessLog.ordinal,
|
||||||
quickSettings.enableAccessLogs
|
quickSettings.enableAccessLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Guest.ordinal,
|
LogLevel.Guest.ordinal,
|
||||||
quickSettings.enableGuestLogs
|
quickSettings.enableGuestLogs
|
||||||
)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
LogLevel.Trace.ordinal,
|
LogLevel.Trace.ordinal,
|
||||||
quickSettings.enableTraceLogs
|
quickSettings.enableTraceLogs
|
||||||
)
|
)
|
||||||
val success =
|
val success =
|
||||||
RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
|
RyujinxNative.jnaInstance.javaInitialize(appPath)
|
||||||
|
|
||||||
uiHandler = UiHandler()
|
uiHandler = UiHandler()
|
||||||
_isInit = success
|
_isInit = success
|
||||||
|
@ -15,18 +15,20 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
|
|||||||
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
|
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
|
||||||
private var controllerId: Int = -1
|
private var controllerId: Int = -1
|
||||||
|
|
||||||
private val motionGyroOrientation : FloatArray = FloatArray(3)
|
private val motionGyroOrientation: FloatArray = FloatArray(3)
|
||||||
private val motionAcelOrientation : FloatArray = FloatArray(3)
|
private val motionAcelOrientation: FloatArray = FloatArray(3)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||||
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||||
setOrientation90()
|
setOrientation90()
|
||||||
var orientationListener = object : OrientationEventListener(activity){
|
var orientationListener = object : OrientationEventListener(activity) {
|
||||||
override fun onOrientationChanged(orientation: Int) {
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
when{
|
when {
|
||||||
isWithinOrientationRange(orientation, 270) -> {
|
isWithinOrientationRange(orientation, 270) -> {
|
||||||
setOrientation270()
|
setOrientation270()
|
||||||
}
|
}
|
||||||
|
|
||||||
isWithinOrientationRange(orientation, 90) -> {
|
isWithinOrientationRange(orientation, 90) -> {
|
||||||
setOrientation90()
|
setOrientation90()
|
||||||
}
|
}
|
||||||
@ -34,8 +36,8 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isWithinOrientationRange(
|
private fun isWithinOrientationRange(
|
||||||
currentOrientation : Int, targetOrientation : Int, epsilon : Int = 90
|
currentOrientation: Int, targetOrientation: Int, epsilon: Int = 90
|
||||||
) : Boolean {
|
): Boolean {
|
||||||
return currentOrientation > targetOrientation - epsilon
|
return currentOrientation > targetOrientation - epsilon
|
||||||
&& currentOrientation < targetOrientation + epsilon
|
&& currentOrientation < targetOrientation + epsilon
|
||||||
}
|
}
|
||||||
@ -50,6 +52,7 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
|
|||||||
motionAcelOrientation[1] = -1.0f
|
motionAcelOrientation[1] = -1.0f
|
||||||
motionAcelOrientation[2] = -1.0f
|
motionAcelOrientation[2] = -1.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOrientation90() {
|
fun setOrientation90() {
|
||||||
motionGyroOrientation[0] = 1.0f
|
motionGyroOrientation[0] = 1.0f
|
||||||
motionGyroOrientation[1] = -1.0f
|
motionGyroOrientation[1] = -1.0f
|
||||||
@ -59,30 +62,38 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
|
|||||||
motionAcelOrientation[2] = -1.0f
|
motionAcelOrientation[2] = -1.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setControllerId(id: Int){
|
fun setControllerId(id: Int) {
|
||||||
controllerId = id
|
controllerId = id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun register(){
|
fun register() {
|
||||||
if(isRegistered)
|
if (isRegistered)
|
||||||
return
|
return
|
||||||
gyro?.apply {
|
gyro?.apply {
|
||||||
sensorManager.registerListener(this@MotionSensorManager, gyro, SensorManager.SENSOR_DELAY_GAME)
|
sensorManager.registerListener(
|
||||||
|
this@MotionSensorManager,
|
||||||
|
gyro,
|
||||||
|
SensorManager.SENSOR_DELAY_GAME
|
||||||
|
)
|
||||||
}
|
}
|
||||||
accelerometer?.apply {
|
accelerometer?.apply {
|
||||||
sensorManager.registerListener(this@MotionSensorManager, accelerometer, SensorManager.SENSOR_DELAY_GAME)
|
sensorManager.registerListener(
|
||||||
|
this@MotionSensorManager,
|
||||||
|
accelerometer,
|
||||||
|
SensorManager.SENSOR_DELAY_GAME
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isRegistered = true;
|
isRegistered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregister(){
|
fun unregister() {
|
||||||
sensorManager.unregisterListener(this)
|
sensorManager.unregisterListener(this)
|
||||||
isRegistered = false
|
isRegistered = false
|
||||||
|
|
||||||
if (controllerId != -1){
|
if (controllerId != -1) {
|
||||||
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
|
RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
|
||||||
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
|
RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,20 +107,25 @@ class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
|
|||||||
val y = motionAcelOrientation[1] * event.values[0]
|
val y = motionAcelOrientation[1] * event.values[0]
|
||||||
val z = motionAcelOrientation[2] * event.values[2]
|
val z = motionAcelOrientation[2] * event.values[2]
|
||||||
|
|
||||||
RyujinxNative.instance.inputSetAccelerometerData(x, y, z, controllerId)
|
RyujinxNative.jnaInstance.inputSetAccelerometerData(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Sensor.TYPE_GYROSCOPE -> {
|
Sensor.TYPE_GYROSCOPE -> {
|
||||||
val x = motionGyroOrientation[0] * event.values[1]
|
val x = motionGyroOrientation[0] * event.values[1]
|
||||||
val y = motionGyroOrientation[1] * event.values[0]
|
val y = motionGyroOrientation[1] * event.values[0]
|
||||||
val z = motionGyroOrientation[2] * event.values[2]
|
val z = motionGyroOrientation[2] * event.values[2]
|
||||||
RyujinxNative.instance.inputSetGyroData(x, y, z, controllerId)
|
RyujinxNative.jnaInstance.inputSetGyroData(x, y, z, controllerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RyujinxNative.instance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
|
RyujinxNative.jnaInstance.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
|
||||||
RyujinxNative.instance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
|
RyujinxNative.jnaInstance.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ class NativeHelpers {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instance = NativeHelpers()
|
val instance = NativeHelpers()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("ryujinxjni")
|
System.loadLibrary("ryujinxjni")
|
||||||
}
|
}
|
||||||
@ -27,18 +28,18 @@ class NativeHelpers {
|
|||||||
external fun getMaxSwapInterval(nativeWindow: Long): Int
|
external fun getMaxSwapInterval(nativeWindow: Long): Int
|
||||||
external fun getMinSwapInterval(nativeWindow: Long): Int
|
external fun getMinSwapInterval(nativeWindow: Long): Int
|
||||||
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
|
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
|
||||||
external fun getProgressInfo() : String
|
external fun getProgressInfo(): String
|
||||||
external fun getProgressValue() : Float
|
external fun getProgressValue(): Float
|
||||||
external fun storeStringJava(string: String) : Long
|
external fun storeStringJava(string: String): Long
|
||||||
external fun getStringJava(id: Long) : String
|
external fun getStringJava(id: Long): String
|
||||||
external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
|
external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
|
||||||
external fun getUiHandlerRequestType() : Int
|
external fun getUiHandlerRequestType(): Int
|
||||||
external fun getUiHandlerRequestTitle() : Long
|
external fun getUiHandlerRequestTitle(): Long
|
||||||
external fun getUiHandlerRequestMessage() : Long
|
external fun getUiHandlerRequestMessage(): Long
|
||||||
external fun getUiHandlerMinLength() : Int
|
external fun getUiHandlerMinLength(): Int
|
||||||
external fun getUiHandlerMaxLength() : Int
|
external fun getUiHandlerMaxLength(): Int
|
||||||
external fun getUiHandlerKeyboardMode() : Int
|
external fun getUiHandlerKeyboardMode(): Int
|
||||||
external fun getUiHandlerRequestWatermark() : Long
|
external fun getUiHandlerRequestWatermark(): Long
|
||||||
external fun getUiHandlerRequestInitialText() : Long
|
external fun getUiHandlerRequestInitialText(): Long
|
||||||
external fun getUiHandlerRequestSubtitle() : Long
|
external fun getUiHandlerRequestSubtitle(): Long
|
||||||
}
|
}
|
||||||
|
@ -4,35 +4,35 @@ import android.view.SurfaceView
|
|||||||
|
|
||||||
class NativeWindow(val surface: SurfaceView) {
|
class NativeWindow(val surface: SurfaceView) {
|
||||||
var nativePointer: Long
|
var nativePointer: Long
|
||||||
var nativeHelpers: NativeHelpers = NativeHelpers.instance
|
private val nativeHelpers: NativeHelpers = NativeHelpers.instance
|
||||||
private var _swapInterval : Int = 0
|
private var _swapInterval: Int = 0
|
||||||
|
|
||||||
var maxSwapInterval : Int = 0
|
var maxSwapInterval: Int = 0
|
||||||
get() {
|
get() {
|
||||||
return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer)
|
return if (nativePointer == -1L) 0 else nativeHelpers.getMaxSwapInterval(nativePointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
var minSwapInterval : Int = 0
|
var minSwapInterval: Int = 0
|
||||||
get() {
|
get() {
|
||||||
return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer)
|
return if (nativePointer == -1L) 0 else nativeHelpers.getMinSwapInterval(nativePointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
var swapInterval : Int
|
var swapInterval: Int
|
||||||
get() {
|
get() {
|
||||||
return _swapInterval
|
return _swapInterval
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
if(nativePointer == -1L || nativeHelpers.setSwapInterval(nativePointer, value) == 0)
|
if (nativePointer == -1L || nativeHelpers.setSwapInterval(nativePointer, value) == 0)
|
||||||
_swapInterval = value
|
_swapInterval = value
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface);
|
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
|
||||||
|
|
||||||
swapInterval = maxOf(1, minSwapInterval)
|
swapInterval = maxOf(1, minSwapInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requeryWindowHandle() : Long {
|
fun requeryWindowHandle(): Long {
|
||||||
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
|
nativePointer = nativeHelpers.getNativeWindow(surface.holder.surface)
|
||||||
|
|
||||||
swapInterval = swapInterval
|
swapInterval = swapInterval
|
||||||
|
@ -15,10 +15,10 @@ import java.io.RandomAccessFile
|
|||||||
class PerformanceMonitor {
|
class PerformanceMonitor {
|
||||||
val numberOfCores = Runtime.getRuntime().availableProcessors()
|
val numberOfCores = Runtime.getRuntime().availableProcessors()
|
||||||
|
|
||||||
fun getFrequencies() : List<Double> {
|
fun getFrequencies(): List<Double> {
|
||||||
val frequencies = mutableListOf<Double>()
|
val frequencies = mutableListOf<Double>()
|
||||||
for (i in 0..<numberOfCores){
|
for (i in 0..<numberOfCores) {
|
||||||
var freq = 0.0;
|
var freq = 0.0
|
||||||
try {
|
try {
|
||||||
val reader = RandomAccessFile(
|
val reader = RandomAccessFile(
|
||||||
"/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_cur_freq",
|
"/sys/devices/system/cpu/cpu${i}/cpufreq/scaling_cur_freq",
|
||||||
@ -27,8 +27,7 @@ class PerformanceMonitor {
|
|||||||
val f = reader.readLine()
|
val f = reader.readLine()
|
||||||
reader.close()
|
reader.close()
|
||||||
freq = f.toDouble() / 1000.0
|
freq = f.toDouble() / 1000.0
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e:Exception){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,14 +37,14 @@ class PerformanceMonitor {
|
|||||||
return frequencies.toList()
|
return frequencies.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMemoryUsage() : List<Int> {
|
fun getMemoryUsage(): List<Int> {
|
||||||
val mem = mutableListOf<Int>()
|
val mem = mutableListOf<Int>()
|
||||||
MainActivity.mainViewModel?.activity?.apply {
|
MainActivity.mainViewModel?.activity?.apply {
|
||||||
val actManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
val actManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
||||||
val memInfo = ActivityManager.MemoryInfo()
|
val memInfo = ActivityManager.MemoryInfo()
|
||||||
actManager.getMemoryInfo(memInfo)
|
actManager.getMemoryInfo(memInfo)
|
||||||
val availMemory = memInfo.availMem.toDouble()/(1024*1024)
|
val availMemory = memInfo.availMem.toDouble() / (1024 * 1024)
|
||||||
val totalMemory= memInfo.totalMem.toDouble()/(1024*1024)
|
val totalMemory = memInfo.totalMem.toDouble() / (1024 * 1024)
|
||||||
|
|
||||||
mem.add((totalMemory - availMemory).toInt())
|
mem.add((totalMemory - availMemory).toInt())
|
||||||
mem.add(totalMemory.toInt())
|
mem.add(totalMemory.toInt())
|
||||||
@ -55,11 +54,11 @@ class PerformanceMonitor {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderUsage() {
|
fun RenderUsage() {
|
||||||
LazyColumn{
|
LazyColumn {
|
||||||
val frequencies = getFrequencies()
|
val frequencies = getFrequencies()
|
||||||
val mem = getMemoryUsage()
|
val mem = getMemoryUsage()
|
||||||
|
|
||||||
for (i in 0..<numberOfCores){
|
for (i in 0..<numberOfCores) {
|
||||||
item {
|
item {
|
||||||
Row {
|
Row {
|
||||||
Text(modifier = Modifier.padding(2.dp), text = "CPU ${i}")
|
Text(modifier = Modifier.padding(2.dp), text = "CPU ${i}")
|
||||||
@ -69,7 +68,7 @@ class PerformanceMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mem.isNotEmpty()) {
|
if (mem.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Row {
|
Row {
|
||||||
Text(modifier = Modifier.padding(2.dp), text = "Used")
|
Text(modifier = Modifier.padding(2.dp), text = "Used")
|
||||||
|
@ -7,25 +7,23 @@ import org.ryujinx.android.viewmodels.QuickSettings
|
|||||||
|
|
||||||
class PhysicalControllerManager(val activity: MainActivity) {
|
class PhysicalControllerManager(val activity: MainActivity) {
|
||||||
private var controllerId: Int = -1
|
private var controllerId: Int = -1
|
||||||
private var ryujinxNative: RyujinxNative = RyujinxNative.instance
|
|
||||||
|
|
||||||
fun onKeyEvent(event: KeyEvent) : Boolean{
|
fun onKeyEvent(event: KeyEvent): Boolean {
|
||||||
val id = getGamePadButtonInputId(event.keyCode)
|
val id = getGamePadButtonInputId(event.keyCode)
|
||||||
if(id != GamePadButtonInputId.None) {
|
if (id != GamePadButtonInputId.None) {
|
||||||
val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0
|
val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0
|
||||||
if (/*controllerId != -1 &&*/ isNotFallback) {
|
if (/*controllerId != -1 &&*/ isNotFallback) {
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
KeyEvent.ACTION_UP -> {
|
KeyEvent.ACTION_UP -> {
|
||||||
ryujinxNative.inputSetButtonReleased(id.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(id.ordinal, controllerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.ACTION_DOWN -> {
|
KeyEvent.ACTION_DOWN -> {
|
||||||
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(id.ordinal, controllerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
} else if (!isNotFallback) {
|
||||||
else if(!isNotFallback){
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,45 +32,91 @@ class PhysicalControllerManager(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onMotionEvent(ev: MotionEvent) {
|
fun onMotionEvent(ev: MotionEvent) {
|
||||||
if(true) {
|
if (true) {
|
||||||
if(ev.action == MotionEvent.ACTION_MOVE) {
|
if (ev.action == MotionEvent.ACTION_MOVE) {
|
||||||
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
|
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
|
||||||
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
|
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
|
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
|
||||||
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
|
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId)
|
RyujinxNative.jnaInstance.inputSetStickAxis(
|
||||||
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId)
|
1,
|
||||||
|
leftStickX,
|
||||||
|
-leftStickY,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetStickAxis(
|
||||||
|
2,
|
||||||
|
rightStickX,
|
||||||
|
-rightStickY,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
|
||||||
ev.device?.apply {
|
ev.device?.apply {
|
||||||
if(sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD){
|
if (sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD) {
|
||||||
// Controller uses HAT
|
// Controller uses HAT
|
||||||
val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X)
|
val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X)
|
||||||
val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||||
if(dPadVert == 0.0f){
|
if (dPadVert == 0.0f) {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, controllerId)
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if(dPadHor == 0.0f){
|
if (dPadHor == 0.0f) {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, controllerId)
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dPadVert < 0.0f){
|
if (dPadVert < 0.0f) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, controllerId)
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if(dPadHor < 0.0f){
|
if (dPadHor < 0.0f) {
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, controllerId)
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dPadVert > 0.0f){
|
if (dPadVert > 0.0f) {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, controllerId)
|
GamePadButtonInputId.DpadUp.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
|
GamePadButtonInputId.DpadDown.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if(dPadHor > 0.0f){
|
if (dPadHor > 0.0f) {
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, controllerId)
|
RyujinxNative.jnaInstance.inputSetButtonReleased(
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, controllerId)
|
GamePadButtonInputId.DpadLeft.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.inputSetButtonPressed(
|
||||||
|
GamePadButtonInputId.DpadRight.ordinal,
|
||||||
|
controllerId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,12 +124,12 @@ class PhysicalControllerManager(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect() : Int {
|
fun connect(): Int {
|
||||||
controllerId = ryujinxNative.inputConnectGamepad(0)
|
controllerId = RyujinxNative.jnaInstance.inputConnectGamepad(0)
|
||||||
return controllerId
|
return controllerId
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect(){
|
fun disconnect() {
|
||||||
controllerId = -1
|
controllerId = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,12 @@ class RyujinxApplication : Application() {
|
|||||||
instance = this
|
instance = this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
|
fun getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var instance : RyujinxApplication
|
lateinit var instance: RyujinxApplication
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val context : Context get() = instance.applicationContext
|
val context: Context get() = instance.applicationContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,11 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import com.sun.jna.Library
|
||||||
|
import com.sun.jna.Native
|
||||||
import org.ryujinx.android.viewmodels.GameInfo
|
import org.ryujinx.android.viewmodels.GameInfo
|
||||||
|
|
||||||
@Suppress("KotlinJniMissingFunction")
|
interface RyujinxNativeJna : Library {
|
||||||
class RyujinxNative {
|
fun deviceInitialize(
|
||||||
external fun initialize(appPath: Long): Boolean
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val instance: RyujinxNative = RyujinxNative()
|
|
||||||
|
|
||||||
init {
|
|
||||||
System.loadLibrary("ryujinx")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun deviceInitialize(
|
|
||||||
isHostMapped: Boolean, useNce: Boolean,
|
isHostMapped: Boolean, useNce: Boolean,
|
||||||
systemLanguage: Int,
|
systemLanguage: Int,
|
||||||
regionCode: Int,
|
regionCode: Int,
|
||||||
@ -22,60 +13,81 @@ class RyujinxNative {
|
|||||||
enableDockedMode: Boolean,
|
enableDockedMode: Boolean,
|
||||||
enablePtc: Boolean,
|
enablePtc: Boolean,
|
||||||
enableInternetAccess: Boolean,
|
enableInternetAccess: Boolean,
|
||||||
timeZone: Long,
|
timeZone: String,
|
||||||
ignoreMissingServices: Boolean
|
ignoreMissingServices: Boolean
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
|
fun graphicsInitialize(
|
||||||
external fun graphicsInitializeRenderer(
|
rescale: Float = 1f,
|
||||||
|
maxAnisotropy: Float = 1f,
|
||||||
|
fastGpuTime: Boolean = true,
|
||||||
|
fast2DCopy: Boolean = true,
|
||||||
|
enableMacroJit: Boolean = false,
|
||||||
|
enableMacroHLE: Boolean = true,
|
||||||
|
enableShaderCache: Boolean = true,
|
||||||
|
enableTextureRecompression: Boolean = false,
|
||||||
|
backendThreading: Int = BackendThreading.Auto.ordinal
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
fun graphicsInitializeRenderer(
|
||||||
extensions: Array<String>,
|
extensions: Array<String>,
|
||||||
|
extensionsLength: Int,
|
||||||
driver: Long
|
driver: Long
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
external fun deviceLoad(game: String): Boolean
|
fun javaInitialize(appPath: String): Boolean
|
||||||
external fun deviceLaunchMiiEditor(): Boolean
|
fun deviceLaunchMiiEditor(): Boolean
|
||||||
external fun deviceGetGameFrameRate(): Double
|
fun deviceGetGameFrameRate(): Double
|
||||||
external fun deviceGetGameFrameTime(): Double
|
fun deviceGetGameFrameTime(): Double
|
||||||
external fun deviceGetGameFifo(): Double
|
fun deviceGetGameFifo(): Double
|
||||||
external fun deviceGetGameInfo(fileDescriptor: Int, extension: Long): GameInfo
|
fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
|
||||||
external fun deviceGetGameInfoFromPath(path: String): GameInfo
|
fun graphicsRendererSetSize(width: Int, height: Int)
|
||||||
external fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
|
fun graphicsRendererSetVsync(enabled: Boolean)
|
||||||
external fun graphicsRendererSetSize(width: Int, height: Int)
|
fun graphicsRendererRunLoop()
|
||||||
external fun graphicsRendererSetVsync(enabled: Boolean)
|
fun deviceReloadFilesystem()
|
||||||
external fun graphicsRendererRunLoop()
|
fun inputInitialize(width: Int, height: Int)
|
||||||
external fun deviceReloadFilesystem()
|
fun inputSetClientSize(width: Int, height: Int)
|
||||||
external fun inputInitialize(width: Int, height: Int)
|
fun inputSetTouchPoint(x: Int, y: Int)
|
||||||
external fun inputSetClientSize(width: Int, height: Int)
|
fun inputReleaseTouchPoint()
|
||||||
external fun inputSetTouchPoint(x: Int, y: Int)
|
fun inputUpdate()
|
||||||
external fun inputReleaseTouchPoint()
|
fun inputSetButtonPressed(button: Int, id: Int)
|
||||||
external fun inputUpdate()
|
fun inputSetButtonReleased(button: Int, id: Int)
|
||||||
external fun inputSetButtonPressed(button: Int, id: Int)
|
fun inputConnectGamepad(index: Int): Int
|
||||||
external fun inputSetButtonReleased(button: Int, id: Int)
|
fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
|
||||||
external fun inputConnectGamepad(index: Int): Int
|
fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
|
||||||
external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
|
fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
|
||||||
external fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
|
fun graphicsSetSurface(surface: Long, window: Long)
|
||||||
external fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
|
fun deviceCloseEmulation()
|
||||||
external fun graphicsSetSurface(surface: Long, window: Long)
|
fun deviceSignalEmulationClose()
|
||||||
external fun deviceCloseEmulation()
|
fun userGetOpenedUser(): String
|
||||||
external fun deviceSignalEmulationClose()
|
fun userGetUserPicture(userId: String): String
|
||||||
external fun deviceGetDlcTitleId(path: Long, ncaPath: Long): Long
|
fun userSetUserPicture(userId: String, picture: String)
|
||||||
external fun deviceGetDlcContentList(path: Long, titleId: Long): Array<String>
|
fun userGetUserName(userId: String): String
|
||||||
external fun userGetOpenedUser(): Long
|
fun userSetUserName(userId: String, userName: String)
|
||||||
external fun userGetUserPicture(userId: Long): Long
|
fun userAddUser(username: String, picture: String)
|
||||||
external fun userSetUserPicture(userId: String, picture: String)
|
fun userDeleteUser(userId: String)
|
||||||
external fun userGetUserName(userId: Long): Long
|
fun userOpenUser(userId: String)
|
||||||
external fun userSetUserName(userId: String, userName: String)
|
fun userCloseUser(userId: String)
|
||||||
external fun userGetAllUsers(): Array<String>
|
fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
|
||||||
external fun userAddUser(username: String, picture: String)
|
fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): String
|
||||||
external fun userDeleteUser(userId: String)
|
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
||||||
external fun userOpenUser(userId: Long)
|
fun deviceGetInstalledFirmwareVersion(): String
|
||||||
external fun userCloseUser(userId: String)
|
fun uiHandlerSetup()
|
||||||
external fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
|
fun uiHandlerWait()
|
||||||
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
|
fun uiHandlerStopWait()
|
||||||
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
|
||||||
external fun deviceGetInstalledFirmwareVersion() : Long
|
fun deviceGetDlcTitleId(path: String, ncaPath: String): String
|
||||||
external fun uiHandlerSetup()
|
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
|
||||||
external fun uiHandlerWait()
|
fun userGetAllUsers(): Array<String>
|
||||||
external fun uiHandlerStopWait()
|
fun deviceGetDlcContentList(path: String, titleId: Long): Array<String>
|
||||||
external fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
|
}
|
||||||
|
|
||||||
|
class RyujinxNative {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val jnaInstance: RyujinxNativeJna = Native.load(
|
||||||
|
"ryujinx",
|
||||||
|
RyujinxNativeJna::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -50,13 +51,13 @@ class UiHandler {
|
|||||||
var shouldListen = true
|
var shouldListen = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
RyujinxNative.instance.uiHandlerSetup()
|
RyujinxNative.jnaInstance.uiHandlerSetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun listen() {
|
fun listen() {
|
||||||
showMessage.value = false
|
showMessage.value = false
|
||||||
while (shouldListen) {
|
while (shouldListen) {
|
||||||
RyujinxNative.instance.uiHandlerWait()
|
RyujinxNative.jnaInstance.uiHandlerWait()
|
||||||
|
|
||||||
title =
|
title =
|
||||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
|
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
|
||||||
@ -79,7 +80,7 @@ class UiHandler {
|
|||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
shouldListen = false
|
shouldListen = false
|
||||||
RyujinxNative.instance.uiHandlerStopWait()
|
RyujinxNative.jnaInstance.uiHandlerStopWait()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -96,23 +97,24 @@ class UiHandler {
|
|||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validate() : Boolean{
|
fun validate(): Boolean {
|
||||||
if(inputText.value.isEmpty()){
|
if (inputText.value.isEmpty()) {
|
||||||
validation.value = "Must be between ${minLength} and ${maxLength} characters"
|
validation.value = "Must be between ${minLength} and ${maxLength} characters"
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
return inputText.value.length < minLength || inputText.value.length > maxLength
|
return inputText.value.length < minLength || inputText.value.length > maxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInputType(): KeyboardType {
|
fun getInputType(): KeyboardType {
|
||||||
return when(mode){
|
return when (mode) {
|
||||||
KeyboardMode.Default -> KeyboardType.Text
|
KeyboardMode.Default -> KeyboardType.Text
|
||||||
KeyboardMode.Numeric -> KeyboardType.Decimal
|
KeyboardMode.Numeric -> KeyboardType.Decimal
|
||||||
KeyboardMode.ASCII -> KeyboardType.Ascii
|
KeyboardMode.ASCII -> KeyboardType.Ascii
|
||||||
else -> { KeyboardType.Text}
|
else -> {
|
||||||
|
KeyboardType.Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,15 +127,15 @@ class UiHandler {
|
|||||||
NativeHelpers.instance.storeStringJava(inputListener.value)
|
NativeHelpers.instance.storeStringJava(inputListener.value)
|
||||||
}
|
}
|
||||||
showMessageListener.value = false
|
showMessageListener.value = false
|
||||||
RyujinxNative.instance.uiHandlerSetResponse(true, input)
|
RyujinxNative.jnaInstance.uiHandlerSetResponse(true, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showMessageListener.value) {
|
if (showMessageListener.value) {
|
||||||
AlertDialog(
|
BasicAlertDialog(
|
||||||
|
onDismissRequest = { },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.wrapContentWidth()
|
.wrapContentWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
onDismissRequest = { },
|
|
||||||
properties = DialogProperties(dismissOnBackPress = false, false)
|
properties = DialogProperties(dismissOnBackPress = false, false)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
|
@ -21,7 +21,7 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
private val applicationName = "Ryujinx"
|
private val applicationName = "Ryujinx"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DEFAULT_ROOT_PROJECTION : Array<String> = arrayOf(
|
private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
|
||||||
DocumentsContract.Root.COLUMN_ROOT_ID,
|
DocumentsContract.Root.COLUMN_ROOT_ID,
|
||||||
DocumentsContract.Root.COLUMN_MIME_TYPES,
|
DocumentsContract.Root.COLUMN_MIME_TYPES,
|
||||||
DocumentsContract.Root.COLUMN_FLAGS,
|
DocumentsContract.Root.COLUMN_FLAGS,
|
||||||
@ -32,7 +32,7 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
|
DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
|
||||||
)
|
)
|
||||||
|
|
||||||
private val DEFAULT_DOCUMENT_PROJECTION : Array<String> = arrayOf(
|
private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
@ -41,19 +41,19 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
DocumentsContract.Document.COLUMN_SIZE
|
DocumentsContract.Document.COLUMN_SIZE
|
||||||
)
|
)
|
||||||
|
|
||||||
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".providers"
|
const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".providers"
|
||||||
|
|
||||||
const val ROOT_ID : String = "root"
|
const val ROOT_ID: String = "root"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() : Boolean {
|
override fun onCreate(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The [File] that corresponds to the document ID supplied by [getDocumentId]
|
* @return The [File] that corresponds to the document ID supplied by [getDocumentId]
|
||||||
*/
|
*/
|
||||||
private fun getFile(documentId : String) : File {
|
private fun getFile(documentId: String): File {
|
||||||
if (documentId.startsWith(ROOT_ID)) {
|
if (documentId.startsWith(ROOT_ID)) {
|
||||||
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
|
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
|
||||||
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
|
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
|
||||||
@ -66,17 +66,20 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
/**
|
/**
|
||||||
* @return A unique ID for the provided [File]
|
* @return A unique ID for the provided [File]
|
||||||
*/
|
*/
|
||||||
private fun getDocumentId(file : File) : String {
|
private fun getDocumentId(file: File): String {
|
||||||
return "$ROOT_ID/${file.toRelativeString(baseDirectory)}"
|
return "$ROOT_ID/${file.toRelativeString(baseDirectory)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryRoots(projection : Array<out String>?) : Cursor {
|
override fun queryRoots(projection: Array<out String>?): Cursor {
|
||||||
val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
|
val cursor = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
|
||||||
|
|
||||||
cursor.newRow().apply {
|
cursor.newRow().apply {
|
||||||
add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
|
add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT_ID)
|
||||||
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
|
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
|
||||||
add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD)
|
add(
|
||||||
|
DocumentsContract.Root.COLUMN_FLAGS,
|
||||||
|
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
|
||||||
|
)
|
||||||
add(DocumentsContract.Root.COLUMN_TITLE, applicationName)
|
add(DocumentsContract.Root.COLUMN_TITLE, applicationName)
|
||||||
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
|
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
|
||||||
add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
|
add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*")
|
||||||
@ -87,22 +90,23 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryDocument(documentId : String?, projection : Array<out String>?) : Cursor {
|
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
|
||||||
val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
|
val cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
|
||||||
return includeFile(cursor, documentId, null)
|
return includeFile(cursor, documentId, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isChildDocument(parentDocumentId : String?, documentId : String?) : Boolean {
|
override fun isChildDocument(parentDocumentId: String?, documentId: String?): Boolean {
|
||||||
return documentId?.startsWith(parentDocumentId!!) ?: false
|
return documentId?.startsWith(parentDocumentId!!) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file
|
* @return A new [File] with a unique name based off the supplied [name], not conflicting with any existing file
|
||||||
*/
|
*/
|
||||||
fun File.resolveWithoutConflict(name : String) : File {
|
fun File.resolveWithoutConflict(name: String): File {
|
||||||
var file = resolve(name)
|
var file = resolve(name)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
var noConflictId = 1 // Makes sure two files don't have the same name by adding a number to the end
|
var noConflictId =
|
||||||
|
1 // Makes sure two files don't have the same name by adding a number to the end
|
||||||
val extension = name.substringAfterLast('.')
|
val extension = name.substringAfterLast('.')
|
||||||
val baseName = name.substringBeforeLast('.')
|
val baseName = name.substringBeforeLast('.')
|
||||||
while (file.exists())
|
while (file.exists())
|
||||||
@ -111,7 +115,11 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createDocument(parentDocumentId : String?, mimeType : String?, displayName : String) : String? {
|
override fun createDocument(
|
||||||
|
parentDocumentId: String?,
|
||||||
|
mimeType: String?,
|
||||||
|
displayName: String
|
||||||
|
): String {
|
||||||
val parentFile = getFile(parentDocumentId!!)
|
val parentFile = getFile(parentDocumentId!!)
|
||||||
val newFile = parentFile.resolveWithoutConflict(displayName)
|
val newFile = parentFile.resolveWithoutConflict(displayName)
|
||||||
|
|
||||||
@ -123,20 +131,20 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
if (!newFile.createNewFile())
|
if (!newFile.createNewFile())
|
||||||
throw IOException("Failed to create file")
|
throw IOException("Failed to create file")
|
||||||
}
|
}
|
||||||
} catch (e : IOException) {
|
} catch (e: IOException) {
|
||||||
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
|
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDocumentId(newFile)
|
return getDocumentId(newFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDocument(documentId : String?) {
|
override fun deleteDocument(documentId: String?) {
|
||||||
val file = getFile(documentId!!)
|
val file = getFile(documentId!!)
|
||||||
if (!file.delete())
|
if (!file.delete())
|
||||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeDocument(documentId : String, parentDocumentId : String?) {
|
override fun removeDocument(documentId: String, parentDocumentId: String?) {
|
||||||
val parent = getFile(parentDocumentId!!)
|
val parent = getFile(parentDocumentId!!)
|
||||||
val file = getFile(documentId)
|
val file = getFile(documentId)
|
||||||
|
|
||||||
@ -148,18 +156,19 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun renameDocument(documentId : String?, displayName : String?) : String? {
|
override fun renameDocument(documentId: String?, displayName: String?): String {
|
||||||
if (displayName == null)
|
if (displayName == null)
|
||||||
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
|
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
|
||||||
|
|
||||||
val sourceFile = getFile(documentId!!)
|
val sourceFile = getFile(documentId!!)
|
||||||
val sourceParentFile = sourceFile.parentFile ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
|
val sourceParentFile = sourceFile.parentFile
|
||||||
|
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
|
||||||
val destFile = sourceParentFile.resolve(displayName)
|
val destFile = sourceParentFile.resolve(displayName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!sourceFile.renameTo(destFile))
|
if (!sourceFile.renameTo(destFile))
|
||||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
|
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
|
||||||
} catch (e : Exception) {
|
} catch (e: Exception) {
|
||||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
|
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,16 +176,16 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyDocument(
|
private fun copyDocument(
|
||||||
sourceDocumentId : String, sourceParentDocumentId : String,
|
sourceDocumentId: String, sourceParentDocumentId: String,
|
||||||
targetParentDocumentId : String?
|
targetParentDocumentId: String?
|
||||||
) : String? {
|
): String? {
|
||||||
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
|
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
|
||||||
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
|
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
|
||||||
|
|
||||||
return copyDocument(sourceDocumentId, targetParentDocumentId)
|
return copyDocument(sourceDocumentId, targetParentDocumentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun copyDocument(sourceDocumentId : String, targetParentDocumentId : String?) : String? {
|
override fun copyDocument(sourceDocumentId: String, targetParentDocumentId: String?): String {
|
||||||
val parent = getFile(targetParentDocumentId!!)
|
val parent = getFile(targetParentDocumentId!!)
|
||||||
val oldFile = getFile(sourceDocumentId)
|
val oldFile = getFile(sourceDocumentId)
|
||||||
val newFile = parent.resolveWithoutConflict(oldFile.name)
|
val newFile = parent.resolveWithoutConflict(oldFile.name)
|
||||||
@ -190,7 +199,7 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
inStream.copyTo(outStream)
|
inStream.copyTo(outStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e : IOException) {
|
} catch (e: IOException) {
|
||||||
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
|
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId': ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,9 +207,9 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun moveDocument(
|
override fun moveDocument(
|
||||||
sourceDocumentId : String, sourceParentDocumentId : String?,
|
sourceDocumentId: String, sourceParentDocumentId: String?,
|
||||||
targetParentDocumentId : String?
|
targetParentDocumentId: String?
|
||||||
) : String? {
|
): String? {
|
||||||
try {
|
try {
|
||||||
val newDocumentId = copyDocument(
|
val newDocumentId = copyDocument(
|
||||||
sourceDocumentId, sourceParentDocumentId!!,
|
sourceDocumentId, sourceParentDocumentId!!,
|
||||||
@ -208,12 +217,12 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
)
|
)
|
||||||
removeDocument(sourceDocumentId, sourceParentDocumentId)
|
removeDocument(sourceDocumentId, sourceParentDocumentId)
|
||||||
return newDocumentId
|
return newDocumentId
|
||||||
} catch (e : FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
throw FileNotFoundException("Couldn't move document '$sourceDocumentId'")
|
throw FileNotFoundException("Couldn't move document '$sourceDocumentId'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun includeFile(cursor : MatrixCursor, documentId : String?, file : File?) : MatrixCursor {
|
private fun includeFile(cursor: MatrixCursor, documentId: String?, file: File?): MatrixCursor {
|
||||||
val localDocumentId = documentId ?: file?.let { getDocumentId(it) }
|
val localDocumentId = documentId ?: file?.let { getDocumentId(it) }
|
||||||
val localFile = file ?: getFile(documentId!!)
|
val localFile = file ?: getFile(documentId!!)
|
||||||
|
|
||||||
@ -232,7 +241,10 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
|
|
||||||
cursor.newRow().apply {
|
cursor.newRow().apply {
|
||||||
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
|
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
|
||||||
add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, if (localFile == baseDirectory) applicationName else localFile.name)
|
add(
|
||||||
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
|
if (localFile == baseDirectory) applicationName else localFile.name
|
||||||
|
)
|
||||||
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
|
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
|
||||||
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
|
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
|
||||||
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
|
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
|
||||||
@ -244,14 +256,14 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTypeForFile(file : File) : Any? {
|
private fun getTypeForFile(file: File): Any? {
|
||||||
return if (file.isDirectory)
|
return if (file.isDirectory)
|
||||||
DocumentsContract.Document.MIME_TYPE_DIR
|
DocumentsContract.Document.MIME_TYPE_DIR
|
||||||
else
|
else
|
||||||
getTypeForName(file.name)
|
getTypeForName(file.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTypeForName(name : String) : Any? {
|
private fun getTypeForName(name: String): Any {
|
||||||
val lastDot = name.lastIndexOf('.')
|
val lastDot = name.lastIndexOf('.')
|
||||||
if (lastDot >= 0) {
|
if (lastDot >= 0) {
|
||||||
val extension = name.substring(lastDot + 1)
|
val extension = name.substring(lastDot + 1)
|
||||||
@ -262,7 +274,11 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
return "application/octect-stream"
|
return "application/octect-stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryChildDocuments(parentDocumentId : String?, projection : Array<out String>?, sortOrder : String?) : Cursor {
|
override fun queryChildDocuments(
|
||||||
|
parentDocumentId: String?,
|
||||||
|
projection: Array<out String>?,
|
||||||
|
sortOrder: String?
|
||||||
|
): Cursor {
|
||||||
var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
|
var cursor = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
|
||||||
|
|
||||||
val parent = getFile(parentDocumentId!!)
|
val parent = getFile(parentDocumentId!!)
|
||||||
@ -272,7 +288,11 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openDocument(documentId : String?, mode : String?, signal : CancellationSignal?) : ParcelFileDescriptor {
|
override fun openDocument(
|
||||||
|
documentId: String?,
|
||||||
|
mode: String?,
|
||||||
|
signal: CancellationSignal?
|
||||||
|
): ParcelFileDescriptor {
|
||||||
val file = documentId?.let { getFile(it) }
|
val file = documentId?.let { getFile(it) }
|
||||||
val accessMode = ParcelFileDescriptor.parseMode(mode)
|
val accessMode = ParcelFileDescriptor.parseMode(mode)
|
||||||
return ParcelFileDescriptor.open(file, accessMode)
|
return ParcelFileDescriptor.open(file, accessMode)
|
||||||
|
@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import com.anggrayudi.storage.file.getAbsolutePath
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -40,14 +39,14 @@ class DlcViewModel(val titleId: String) {
|
|||||||
val path = file.getAbsolutePath(storageHelper.storage.context)
|
val path = file.getAbsolutePath(storageHelper.storage.context)
|
||||||
if (path.isNotEmpty()) {
|
if (path.isNotEmpty()) {
|
||||||
data?.apply {
|
data?.apply {
|
||||||
val contents = RyujinxNative.instance.deviceGetDlcContentList(
|
val contents = RyujinxNative.jnaInstance.deviceGetDlcContentList(
|
||||||
NativeHelpers.instance.storeStringJava(path),
|
path,
|
||||||
titleId.toLong(16)
|
titleId.toLong(16)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (contents.isNotEmpty()) {
|
if (contents.isNotEmpty()) {
|
||||||
val contentPath = path
|
val contentPath = path
|
||||||
val container = DlcContainerList(contentPath);
|
val container = DlcContainerList(contentPath)
|
||||||
|
|
||||||
for (content in contents)
|
for (content in contents)
|
||||||
container.dlc_nca_list.add(
|
container.dlc_nca_list.add(
|
||||||
@ -90,7 +89,7 @@ class DlcViewModel(val titleId: String) {
|
|||||||
val containerPath = container.path
|
val containerPath = container.path
|
||||||
|
|
||||||
if (!File(containerPath).exists())
|
if (!File(containerPath).exists())
|
||||||
continue;
|
continue
|
||||||
|
|
||||||
for (dlc in container.dlc_nca_list) {
|
for (dlc in container.dlc_nca_list) {
|
||||||
val enabled = remember {
|
val enabled = remember {
|
||||||
@ -102,7 +101,10 @@ class DlcViewModel(val titleId: String) {
|
|||||||
enabled,
|
enabled,
|
||||||
containerPath,
|
containerPath,
|
||||||
dlc.fullPath,
|
dlc.fullPath,
|
||||||
NativeHelpers.instance.getStringJava(RyujinxNative.instance.deviceGetDlcTitleId(NativeHelpers.instance.storeStringJava(containerPath), NativeHelpers.instance.storeStringJava(dlc.fullPath)))
|
RyujinxNative.jnaInstance.deviceGetDlcTitleId(
|
||||||
|
containerPath,
|
||||||
|
dlc.fullPath
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -143,11 +145,13 @@ data class DlcContainerList(
|
|||||||
data class DlcContainer(
|
data class DlcContainer(
|
||||||
var enabled: Boolean = false,
|
var enabled: Boolean = false,
|
||||||
var titleId: String = "",
|
var titleId: String = "",
|
||||||
var fullPath: String = "")
|
var fullPath: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
data class DlcItem(
|
data class DlcItem(
|
||||||
var name:String = "",
|
var name: String = "",
|
||||||
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
|
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
|
||||||
var containerPath: String = "",
|
var containerPath: String = "",
|
||||||
var fullPath: String = "",
|
var fullPath: String = "",
|
||||||
var titleId: String = "")
|
var titleId: String = ""
|
||||||
|
)
|
||||||
|
@ -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 android.os.ParcelFileDescriptor
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.anggrayudi.storage.file.extension
|
import com.anggrayudi.storage.file.extension
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
|
||||||
|
|
||||||
@ -24,8 +23,8 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
init {
|
init {
|
||||||
fileName = file.name
|
fileName = file.name
|
||||||
val pid = open()
|
val pid = open()
|
||||||
val ext = NativeHelpers.instance.storeStringJava(file.extension)
|
val gameInfo = GameInfo()
|
||||||
val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, ext)
|
RyujinxNative.jnaInstance.deviceGetGameInfo(pid, file.extension, gameInfo)
|
||||||
close()
|
close()
|
||||||
|
|
||||||
fileSize = gameInfo.FileSize
|
fileSize = gameInfo.FileSize
|
||||||
@ -46,28 +45,28 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun open() : Int {
|
fun open(): Int {
|
||||||
descriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
|
descriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
|
||||||
|
|
||||||
return descriptor?.fd ?: 0
|
return descriptor?.fd ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openUpdate() : Int {
|
fun openUpdate(): Int {
|
||||||
if(titleId?.isNotEmpty() == true) {
|
if (titleId?.isNotEmpty() == true) {
|
||||||
val vm = TitleUpdateViewModel(titleId ?: "")
|
val vm = TitleUpdateViewModel(titleId ?: "")
|
||||||
|
|
||||||
if(vm.data?.selected?.isNotEmpty() == true){
|
if (vm.data?.selected?.isNotEmpty() == true) {
|
||||||
val uri = Uri.parse(vm.data?.selected)
|
val uri = Uri.parse(vm.data?.selected)
|
||||||
val file = DocumentFile.fromSingleUri(context, uri)
|
val file = DocumentFile.fromSingleUri(context, uri)
|
||||||
if(file?.exists() == true){
|
if (file?.exists() == true) {
|
||||||
updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
|
updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
|
||||||
|
|
||||||
return updateDescriptor ?.fd ?: -1;
|
return updateDescriptor?.fd ?: -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun close() {
|
fun close() {
|
||||||
@ -78,16 +77,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameInfo {
|
enum class FileType {
|
||||||
var FileSize = 0.0
|
|
||||||
var TitleName: String? = null
|
|
||||||
var TitleId: String? = null
|
|
||||||
var Developer: String? = null
|
|
||||||
var Version: String? = null
|
|
||||||
var Icon: String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class FileType{
|
|
||||||
None,
|
None,
|
||||||
Nsp,
|
Nsp,
|
||||||
Xci,
|
Xci,
|
||||||
|
@ -46,21 +46,23 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filter(query : String){
|
fun filter(query: String) {
|
||||||
gameList.clear()
|
gameList.clear()
|
||||||
gameList.addAll(loadedCache.filter { it.titleName != null && it.titleName!!.isNotEmpty() && (query.trim()
|
gameList.addAll(loadedCache.filter {
|
||||||
|
it.titleName != null && it.titleName!!.isNotEmpty() && (query.trim()
|
||||||
.isEmpty() || it.titleName!!.lowercase(Locale.getDefault())
|
.isEmpty() || it.titleName!!.lowercase(Locale.getDefault())
|
||||||
.contains(query)) })
|
.contains(query))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestReload(){
|
fun requestReload() {
|
||||||
shouldReload = true
|
shouldReload = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadGameList() {
|
private fun reloadGameList() {
|
||||||
var storage = activity?.storageHelper ?: return
|
activity?.storageHelper ?: return
|
||||||
|
|
||||||
if(isLoading)
|
if (isLoading)
|
||||||
return
|
return
|
||||||
val folder = gameFolderPath ?: return
|
val folder = gameFolderPath ?: return
|
||||||
|
|
||||||
@ -70,13 +72,12 @@ class HomeViewModel(
|
|||||||
thread {
|
thread {
|
||||||
try {
|
try {
|
||||||
loadedCache.clear()
|
loadedCache.clear()
|
||||||
val files = mutableListOf<GameModel>()
|
|
||||||
for (file in folder.search(false, DocumentFileType.FILE)) {
|
for (file in folder.search(false, DocumentFileType.FILE)) {
|
||||||
if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro")
|
if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro")
|
||||||
activity.let {
|
activity.let {
|
||||||
val item = GameModel(file, it)
|
val item = GameModel(file, it)
|
||||||
|
|
||||||
if(item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true) {
|
if (item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true && item.titleName != "Unknown") {
|
||||||
loadedCache.add(item)
|
loadedCache.add(item)
|
||||||
gameList.add(item)
|
gameList.add(item)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import org.ryujinx.android.GameController
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
import org.ryujinx.android.GraphicsConfiguration
|
|
||||||
import org.ryujinx.android.Logging
|
import org.ryujinx.android.Logging
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
import org.ryujinx.android.MotionSensorManager
|
import org.ryujinx.android.MotionSensorManager
|
||||||
@ -55,24 +54,19 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun closeGame() {
|
fun closeGame() {
|
||||||
RyujinxNative.instance.deviceSignalEmulationClose()
|
RyujinxNative.jnaInstance.deviceSignalEmulationClose()
|
||||||
gameHost?.close()
|
gameHost?.close()
|
||||||
RyujinxNative.instance.deviceCloseEmulation()
|
RyujinxNative.jnaInstance.deviceCloseEmulation()
|
||||||
motionSensorManager?.unregister()
|
motionSensorManager?.unregister()
|
||||||
physicalControllerManager?.disconnect()
|
physicalControllerManager?.disconnect()
|
||||||
motionSensorManager?.setControllerId(-1)
|
motionSensorManager?.setControllerId(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshFirmwareVersion() {
|
fun refreshFirmwareVersion() {
|
||||||
var handle = RyujinxNative.instance.deviceGetInstalledFirmwareVersion()
|
firmwareVersion = RyujinxNative.jnaInstance.deviceGetInstalledFirmwareVersion()
|
||||||
if (handle != -1L) {
|
|
||||||
firmwareVersion = NativeHelpers.instance.getStringJava(handle)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGame(game: GameModel): Boolean {
|
fun loadGame(game: GameModel): Boolean {
|
||||||
val nativeRyujinx = RyujinxNative.instance
|
|
||||||
|
|
||||||
val descriptor = game.open()
|
val descriptor = game.open()
|
||||||
|
|
||||||
if (descriptor == 0)
|
if (descriptor == 0)
|
||||||
@ -85,12 +79,12 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
|
|
||||||
val settings = QuickSettings(activity)
|
val settings = QuickSettings(activity)
|
||||||
|
|
||||||
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
var success = RyujinxNative.jnaInstance.graphicsInitialize(
|
||||||
EnableShaderCache = settings.enableShaderCache
|
enableShaderCache = settings.enableShaderCache,
|
||||||
EnableTextureRecompression = settings.enableTextureRecompression
|
enableTextureRecompression = settings.enableTextureRecompression,
|
||||||
ResScale = settings.resScale
|
rescale = settings.resScale,
|
||||||
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
|
backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
|
||||||
})
|
)
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return false
|
return false
|
||||||
@ -139,8 +133,11 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
success = nativeRyujinx.graphicsInitializeRenderer(
|
val extensions = nativeInterop.VkRequiredExtensions
|
||||||
nativeInterop.VkRequiredExtensions!!,
|
|
||||||
|
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
|
||||||
|
extensions!!,
|
||||||
|
extensions.size,
|
||||||
driverHandle
|
driverHandle
|
||||||
)
|
)
|
||||||
if (!success)
|
if (!success)
|
||||||
@ -151,7 +148,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
// We are only able to initialize the emulation context on the main thread
|
// We are only able to initialize the emulation context on the main thread
|
||||||
success = nativeRyujinx.deviceInitialize(
|
success = RyujinxNative.jnaInstance.deviceInitialize(
|
||||||
settings.isHostMapped,
|
settings.isHostMapped,
|
||||||
settings.useNce,
|
settings.useNce,
|
||||||
SystemLanguage.AmericanEnglish.ordinal,
|
SystemLanguage.AmericanEnglish.ordinal,
|
||||||
@ -160,7 +157,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
settings.enableDocked,
|
settings.enableDocked,
|
||||||
settings.enablePtc,
|
settings.enablePtc,
|
||||||
false,
|
false,
|
||||||
NativeHelpers.instance.storeStringJava("UTC"),
|
"UTC",
|
||||||
settings.ignoreMissingServices
|
settings.ignoreMissingServices
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -173,28 +170,24 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
if (!success)
|
if (!success)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
success = nativeRyujinx.deviceLoadDescriptor(descriptor, game.type.ordinal, update)
|
success =
|
||||||
|
RyujinxNative.jnaInstance.deviceLoadDescriptor(descriptor, game.type.ordinal, update)
|
||||||
|
|
||||||
if (!success)
|
return success
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadMiiEditor(): Boolean {
|
fun loadMiiEditor(): Boolean {
|
||||||
val nativeRyujinx = RyujinxNative.instance
|
|
||||||
|
|
||||||
gameModel = null
|
gameModel = null
|
||||||
isMiiEditorLaunched = true
|
isMiiEditorLaunched = true
|
||||||
|
|
||||||
val settings = QuickSettings(activity)
|
val settings = QuickSettings(activity)
|
||||||
|
|
||||||
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
var success = RyujinxNative.jnaInstance.graphicsInitialize(
|
||||||
EnableShaderCache = settings.enableShaderCache
|
enableShaderCache = settings.enableShaderCache,
|
||||||
EnableTextureRecompression = settings.enableTextureRecompression
|
enableTextureRecompression = settings.enableTextureRecompression,
|
||||||
ResScale = settings.resScale
|
rescale = settings.resScale,
|
||||||
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
|
backendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
|
||||||
})
|
)
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return false
|
return false
|
||||||
@ -243,8 +236,11 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
success = nativeRyujinx.graphicsInitializeRenderer(
|
val extensions = nativeInterop.VkRequiredExtensions
|
||||||
nativeInterop.VkRequiredExtensions!!,
|
|
||||||
|
success = RyujinxNative.jnaInstance.graphicsInitializeRenderer(
|
||||||
|
extensions!!,
|
||||||
|
extensions.size,
|
||||||
driverHandle
|
driverHandle
|
||||||
)
|
)
|
||||||
if (!success)
|
if (!success)
|
||||||
@ -255,7 +251,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
// We are only able to initialize the emulation context on the main thread
|
// We are only able to initialize the emulation context on the main thread
|
||||||
success = nativeRyujinx.deviceInitialize(
|
success = RyujinxNative.jnaInstance.deviceInitialize(
|
||||||
settings.isHostMapped,
|
settings.isHostMapped,
|
||||||
settings.useNce,
|
settings.useNce,
|
||||||
SystemLanguage.AmericanEnglish.ordinal,
|
SystemLanguage.AmericanEnglish.ordinal,
|
||||||
@ -264,7 +260,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
settings.enableDocked,
|
settings.enableDocked,
|
||||||
settings.enablePtc,
|
settings.enablePtc,
|
||||||
false,
|
false,
|
||||||
NativeHelpers.instance.storeStringJava("UTC"),
|
"UTC",
|
||||||
settings.ignoreMissingServices
|
settings.ignoreMissingServices
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -277,12 +273,9 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
if (!success)
|
if (!success)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
success = nativeRyujinx.deviceLaunchMiiEditor()
|
success = RyujinxNative.jnaInstance.deviceLaunchMiiEditor()
|
||||||
|
|
||||||
if (!success)
|
return success
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearPptcCache(titleId: String) {
|
fun clearPptcCache(titleId: String) {
|
||||||
|
@ -12,7 +12,6 @@ import com.anggrayudi.storage.file.extension
|
|||||||
import com.anggrayudi.storage.file.getAbsolutePath
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
import org.ryujinx.android.LogLevel
|
import org.ryujinx.android.LogLevel
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -28,7 +27,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
sharedPref = getPreferences()
|
sharedPref = getPreferences()
|
||||||
previousFolderCallback = activity.storageHelper!!.onFolderSelected
|
previousFolderCallback = activity.storageHelper!!.onFolderSelected
|
||||||
previousFileCallback = activity.storageHelper!!.onFileSelected
|
previousFileCallback = activity.storageHelper!!.onFileSelected
|
||||||
activity.storageHelper!!.onFolderSelected = { requestCode, folder ->
|
activity.storageHelper!!.onFolderSelected = { _, folder ->
|
||||||
run {
|
run {
|
||||||
val p = folder.getAbsolutePath(activity)
|
val p = folder.getAbsolutePath(activity)
|
||||||
val editor = sharedPref.edit()
|
val editor = sharedPref.edit()
|
||||||
@ -146,18 +145,24 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
editor.apply()
|
editor.apply()
|
||||||
activity.storageHelper!!.onFolderSelected = previousFolderCallback
|
activity.storageHelper!!.onFolderSelected = previousFolderCallback
|
||||||
|
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value)
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Debug.ordinal, enableDebugLogs.value)
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value)
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Info.ordinal, enableInfoLogs.value)
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Stub.ordinal, enableStubLogs.value)
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Stub.ordinal, enableStubLogs.value)
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Warning.ordinal, enableWarningLogs.value)
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Error.ordinal, enableErrorLogs.value)
|
LogLevel.Warning.ordinal,
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.AccessLog.ordinal, enableAccessLogs.value)
|
enableWarningLogs.value
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, enableGuestLogs.value)
|
)
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value)
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Error.ordinal, enableErrorLogs.value)
|
||||||
|
RyujinxNative.jnaInstance.loggingSetEnabled(
|
||||||
|
LogLevel.AccessLog.ordinal,
|
||||||
|
enableAccessLogs.value
|
||||||
|
)
|
||||||
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Guest.ordinal, enableGuestLogs.value)
|
||||||
|
RyujinxNative.jnaInstance.loggingSetEnabled(LogLevel.Trace.ordinal, enableTraceLogs.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openGameFolder() {
|
fun openGameFolder() {
|
||||||
val path = sharedPref?.getString("gameFolder", "") ?: ""
|
val path = sharedPref.getString("gameFolder", "") ?: ""
|
||||||
|
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
activity.storageHelper?.storage?.openFolderPicker()
|
activity.storageHelper?.storage?.openFolderPicker()
|
||||||
@ -169,13 +174,13 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun importProdKeys() {
|
fun importProdKeys() {
|
||||||
activity.storageHelper!!.onFileSelected = { requestCode, files ->
|
activity.storageHelper!!.onFileSelected = { _, files ->
|
||||||
run {
|
run {
|
||||||
activity.storageHelper!!.onFileSelected = previousFileCallback
|
activity.storageHelper!!.onFileSelected = previousFileCallback
|
||||||
val file = files.firstOrNull()
|
val file = files.firstOrNull()
|
||||||
file?.apply {
|
file?.apply {
|
||||||
if (name == "prod.keys") {
|
if (name == "prod.keys") {
|
||||||
val outputFile = File(MainActivity.AppPath + "/system");
|
val outputFile = File(MainActivity.AppPath + "/system")
|
||||||
outputFile.delete()
|
outputFile.delete()
|
||||||
|
|
||||||
thread {
|
thread {
|
||||||
@ -183,9 +188,6 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
activity,
|
activity,
|
||||||
outputFile,
|
outputFile,
|
||||||
callback = object : FileCallback() {
|
callback = object : FileCallback() {
|
||||||
override fun onCompleted(result: Any) {
|
|
||||||
super.onCompleted(result)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,14 +211,13 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
val descriptor =
|
val descriptor =
|
||||||
activity.contentResolver.openFileDescriptor(file.uri, "rw")
|
activity.contentResolver.openFileDescriptor(file.uri, "rw")
|
||||||
descriptor?.use { d ->
|
descriptor?.use { d ->
|
||||||
val version = RyujinxNative.instance.deviceVerifyFirmware(
|
selectedFirmwareVersion =
|
||||||
|
RyujinxNative.jnaInstance.deviceVerifyFirmware(
|
||||||
d.fd,
|
d.fd,
|
||||||
extension == "xci"
|
extension == "xci"
|
||||||
)
|
)
|
||||||
selectedFirmwareFile = file
|
selectedFirmwareFile = file
|
||||||
if (version != -1L) {
|
if (selectedFirmwareVersion.isEmpty()) {
|
||||||
selectedFirmwareVersion =
|
|
||||||
NativeHelpers.instance.getStringJava(version)
|
|
||||||
installState.value = FirmwareInstallState.Query
|
installState.value = FirmwareInstallState.Query
|
||||||
} else {
|
} else {
|
||||||
installState.value = FirmwareInstallState.Cancelled
|
installState.value = FirmwareInstallState.Cancelled
|
||||||
@ -246,7 +247,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
installState.value = FirmwareInstallState.Install
|
installState.value = FirmwareInstallState.Install
|
||||||
thread {
|
thread {
|
||||||
try {
|
try {
|
||||||
RyujinxNative.instance.deviceInstallFirmware(
|
RyujinxNative.jnaInstance.deviceInstallFirmware(
|
||||||
d.fd,
|
d.fd,
|
||||||
extension == "xci"
|
extension == "xci"
|
||||||
)
|
)
|
||||||
|
@ -53,8 +53,11 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
if (requestCode == UpdateRequestCode) {
|
if (requestCode == UpdateRequestCode) {
|
||||||
val file = files.firstOrNull()
|
val file = files.firstOrNull()
|
||||||
file?.apply {
|
file?.apply {
|
||||||
if(file.extension == "nsp"){
|
if (file.extension == "nsp") {
|
||||||
storageHelper.storage.context.contentResolver.takePersistableUriPermission(file.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
storageHelper.storage.context.contentResolver.takePersistableUriPermission(
|
||||||
|
file.uri,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
)
|
||||||
currentPaths.add(file.uri.toString())
|
currentPaths.add(file.uri.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +75,7 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
currentPaths.forEach {
|
currentPaths.forEach {
|
||||||
val uri = Uri.parse(it)
|
val uri = Uri.parse(it)
|
||||||
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
||||||
if(file?.exists() == true){
|
if (file?.exists() == true) {
|
||||||
existingPaths.add(it)
|
existingPaths.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,20 +111,19 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
currentPaths.forEach {
|
currentPaths.forEach {
|
||||||
val uri = Uri.parse(it)
|
val uri = Uri.parse(it)
|
||||||
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
||||||
if(file?.exists() == true){
|
if (file?.exists() == true) {
|
||||||
savedUpdates.add(it)
|
savedUpdates.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.paths = savedUpdates
|
metadata.paths = savedUpdates
|
||||||
|
|
||||||
if(selected.isNotEmpty()){
|
if (selected.isNotEmpty()) {
|
||||||
val uri = Uri.parse(selected)
|
val uri = Uri.parse(selected)
|
||||||
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
val file = DocumentFile.fromSingleUri(storageHelper.storage.context, uri)
|
||||||
if(file?.exists() == true){
|
if (file?.exists() == true) {
|
||||||
metadata.selected = selected
|
metadata.selected = selected
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
metadata.selected = selected
|
metadata.selected = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.ryujinx.android.viewmodels
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
|
|
||||||
@ -14,52 +13,45 @@ class UserViewModel {
|
|||||||
|
|
||||||
fun refreshUsers() {
|
fun refreshUsers() {
|
||||||
userList.clear()
|
userList.clear()
|
||||||
val native = RyujinxNative.instance
|
|
||||||
val helper = NativeHelpers.instance
|
|
||||||
val decoder = Base64.getDecoder()
|
val decoder = Base64.getDecoder()
|
||||||
openedUser = UserModel()
|
openedUser = UserModel()
|
||||||
openedUser.id = helper.getStringJava(native.userGetOpenedUser())
|
openedUser.id = RyujinxNative.jnaInstance.userGetOpenedUser()
|
||||||
if (openedUser.id.isNotEmpty()) {
|
if (openedUser.id.isNotEmpty()) {
|
||||||
openedUser.username =
|
openedUser.username = RyujinxNative.jnaInstance.userGetUserName(openedUser.id)
|
||||||
helper.getStringJava(native.userGetUserName(helper.storeStringJava(openedUser.id)))
|
|
||||||
openedUser.userPicture = decoder.decode(
|
openedUser.userPicture = decoder.decode(
|
||||||
helper.getStringJava(
|
RyujinxNative.jnaInstance.userGetUserPicture(
|
||||||
native.userGetUserPicture(
|
openedUser.id
|
||||||
helper.storeStringJava(openedUser.id)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val users = native.userGetAllUsers()
|
val users = RyujinxNative.jnaInstance.userGetAllUsers()
|
||||||
for (user in users) {
|
for (user in users) {
|
||||||
userList.add(
|
userList.add(
|
||||||
UserModel(
|
UserModel(
|
||||||
user,
|
user,
|
||||||
helper.getStringJava(native.userGetUserName(helper.storeStringJava(user))),
|
RyujinxNative.jnaInstance.userGetUserName(user),
|
||||||
decoder.decode(
|
decoder.decode(
|
||||||
helper.getStringJava(
|
RyujinxNative.jnaInstance.userGetUserPicture(user)
|
||||||
native.userGetUserPicture(
|
|
||||||
helper.storeStringJava(user)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openUser(userModel: UserModel){
|
fun openUser(userModel: UserModel) {
|
||||||
val native = RyujinxNative.instance
|
RyujinxNative.jnaInstance.userOpenUser(userModel.id)
|
||||||
val helper = NativeHelpers.instance
|
|
||||||
native.userOpenUser(helper.storeStringJava(userModel.id))
|
|
||||||
|
|
||||||
refreshUsers()
|
refreshUsers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class UserModel(var id : String = "", var username: String = "", var userPicture: ByteArray? = null) {
|
data class UserModel(
|
||||||
|
var id: String = "",
|
||||||
|
var username: String = "",
|
||||||
|
var userPicture: ByteArray? = null
|
||||||
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
@ -18,7 +18,7 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
const val DriverFolder: String = "drivers"
|
const val DriverFolder: String = "drivers"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppPath() : String {
|
private fun getAppPath(): String {
|
||||||
var appPath =
|
var appPath =
|
||||||
MainActivity.AppPath
|
MainActivity.AppPath
|
||||||
appPath += "/"
|
appPath += "/"
|
||||||
@ -26,18 +26,18 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
return appPath
|
return appPath
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureDriverPath() : File {
|
fun ensureDriverPath(): File {
|
||||||
val driverPath = getAppPath() + DriverFolder
|
val driverPath = getAppPath() + DriverFolder
|
||||||
|
|
||||||
val driverFolder = File(driverPath)
|
val driverFolder = File(driverPath)
|
||||||
|
|
||||||
if(!driverFolder.exists())
|
if (!driverFolder.exists())
|
||||||
driverFolder.mkdirs()
|
driverFolder.mkdirs()
|
||||||
|
|
||||||
return driverFolder
|
return driverFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAvailableDrivers() : MutableList<DriverMetadata> {
|
fun getAvailableDrivers(): MutableList<DriverMetadata> {
|
||||||
val driverFolder = ensureDriverPath()
|
val driverFolder = ensureDriverPath()
|
||||||
|
|
||||||
val folders = driverFolder.walkTopDown()
|
val folders = driverFolder.walkTopDown()
|
||||||
@ -45,10 +45,10 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
val drivers = mutableListOf<DriverMetadata>()
|
val drivers = mutableListOf<DriverMetadata>()
|
||||||
|
|
||||||
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
|
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
|
||||||
if(selectedDriverFile.exists()){
|
if (selectedDriverFile.exists()) {
|
||||||
selected = selectedDriverFile.readText()
|
selected = selectedDriverFile.readText()
|
||||||
|
|
||||||
if(!File(selected).exists()) {
|
if (!File(selected).exists()) {
|
||||||
selected = ""
|
selected = ""
|
||||||
saveSelected()
|
saveSelected()
|
||||||
}
|
}
|
||||||
@ -56,13 +56,13 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
|
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
|
|
||||||
for (folder in folders){
|
for (folder in folders) {
|
||||||
if(folder.isDirectory && folder.parent == driverFolder.absolutePath){
|
if (folder.isDirectory && folder.parent == driverFolder.absolutePath) {
|
||||||
val meta = File(folder.absolutePath + "/meta.json")
|
val meta = File(folder.absolutePath + "/meta.json")
|
||||||
|
|
||||||
if(meta.exists()){
|
if (meta.exists()) {
|
||||||
val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java)
|
val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java)
|
||||||
if(metadata.name.isNotEmpty()) {
|
if (metadata.name.isNotEmpty()) {
|
||||||
val driver = folder.absolutePath + "/${metadata.libraryName}"
|
val driver = folder.absolutePath + "/${metadata.libraryName}"
|
||||||
metadata.driverPath = driver
|
metadata.driverPath = driver
|
||||||
if (File(driver).exists())
|
if (File(driver).exists())
|
||||||
@ -82,10 +82,10 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
selectedDriverFile.writeText(selected)
|
selectedDriverFile.writeText(selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeSelected(){
|
fun removeSelected() {
|
||||||
if(selected.isNotEmpty()){
|
if (selected.isNotEmpty()) {
|
||||||
val sel = File(selected)
|
val sel = File(selected)
|
||||||
if(sel.exists()) {
|
if (sel.exists()) {
|
||||||
sel.parentFile?.deleteRecursively()
|
sel.parentFile?.deleteRecursively()
|
||||||
}
|
}
|
||||||
selected = ""
|
selected = ""
|
||||||
@ -102,12 +102,11 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
onFileSelected = { requestCode, files ->
|
onFileSelected = { requestCode, files ->
|
||||||
run {
|
run {
|
||||||
onFileSelected = callBack
|
onFileSelected = callBack
|
||||||
if(requestCode == DriverRequestCode)
|
if (requestCode == DriverRequestCode) {
|
||||||
{
|
|
||||||
val file = files.firstOrNull()
|
val file = files.firstOrNull()
|
||||||
file?.apply {
|
file?.apply {
|
||||||
val path = file.getAbsolutePath(storage.context)
|
val path = file.getAbsolutePath(storage.context)
|
||||||
if (path.isNotEmpty()){
|
if (path.isNotEmpty()) {
|
||||||
val name = file.name?.removeSuffix("." + file.extension) ?: ""
|
val name = file.name?.removeSuffix("." + file.extension) ?: ""
|
||||||
val driverFolder = ensureDriverPath()
|
val driverFolder = ensureDriverPath()
|
||||||
val extractionFolder = File(driverFolder.absolutePath + "/${name}")
|
val extractionFolder = File(driverFolder.absolutePath + "/${name}")
|
||||||
@ -117,14 +116,18 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
zip.entries().asSequence().forEach { entry ->
|
zip.entries().asSequence().forEach { entry ->
|
||||||
|
|
||||||
zip.getInputStream(entry).use { input ->
|
zip.getInputStream(entry).use { input ->
|
||||||
val filePath = extractionFolder.absolutePath + File.separator + entry.name
|
val filePath =
|
||||||
|
extractionFolder.absolutePath + File.separator + entry.name
|
||||||
|
|
||||||
if (!entry.isDirectory) {
|
if (!entry.isDirectory) {
|
||||||
File(filePath).delete()
|
File(filePath).delete()
|
||||||
val bos = BufferedOutputStream(FileOutputStream(filePath))
|
val bos =
|
||||||
|
BufferedOutputStream(FileOutputStream(filePath))
|
||||||
val bytesIn = ByteArray(4096)
|
val bytesIn = ByteArray(4096)
|
||||||
var read: Int
|
var read: Int
|
||||||
while (input.read(bytesIn).also { read = it } != -1) {
|
while (input.read(bytesIn)
|
||||||
|
.also { read = it } != -1
|
||||||
|
) {
|
||||||
bos.write(bytesIn, 0, read)
|
bos.write(bytesIn, 0, read)
|
||||||
}
|
}
|
||||||
bos.close()
|
bos.close()
|
||||||
@ -144,7 +147,8 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openFilePicker(DriverRequestCode,
|
openFilePicker(
|
||||||
|
DriverRequestCode,
|
||||||
filterMimeTypes = arrayOf("application/zip")
|
filterMimeTypes = arrayOf("application/zip")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -152,14 +156,14 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class DriverMetadata(
|
data class DriverMetadata(
|
||||||
var schemaVersion : Int = 0,
|
var schemaVersion: Int = 0,
|
||||||
var name : String = "",
|
var name: String = "",
|
||||||
var description : String = "",
|
var description: String = "",
|
||||||
var author : String = "",
|
var author: String = "",
|
||||||
var packageVersion : String = "",
|
var packageVersion: String = "",
|
||||||
var vendor : String = "",
|
var vendor: String = "",
|
||||||
var driverVersion : String = "",
|
var driverVersion: String = "",
|
||||||
var minApi : Int = 0,
|
var minApi: Int = 0,
|
||||||
var libraryName : String = "",
|
var libraryName: String = "",
|
||||||
var driverPath : String = ""
|
var driverPath: String = ""
|
||||||
)
|
)
|
@ -50,13 +50,19 @@ class DlcViews {
|
|||||||
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Column {
|
Column {
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween) {
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
Text(text = "DLC for ${name}", textAlign = TextAlign.Center, modifier = Modifier.align(
|
) {
|
||||||
|
Text(
|
||||||
|
text = "DLC for ${name}",
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.align(
|
||||||
Alignment.CenterVertically
|
Alignment.CenterVertically
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -65,17 +71,20 @@ class DlcViews {
|
|||||||
shape = MaterialTheme.shapes.medium
|
shape = MaterialTheme.shapes.medium
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if(refresh.value) {
|
if (refresh.value) {
|
||||||
dlcList.clear()
|
dlcList.clear()
|
||||||
dlcList.addAll(viewModel.getDlc())
|
dlcList.addAll(viewModel.getDlc())
|
||||||
refresh.value = false
|
refresh.value = false
|
||||||
}
|
}
|
||||||
LazyColumn(modifier = Modifier
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(400.dp)){
|
.height(400.dp)
|
||||||
|
) {
|
||||||
items(dlcList) { dlcItem ->
|
items(dlcList) { dlcItem ->
|
||||||
dlcItem.apply {
|
dlcItem.apply {
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
@ -94,7 +103,8 @@ class DlcViews {
|
|||||||
viewModel.remove(dlcItem)
|
viewModel.remove(dlcItem)
|
||||||
refresh.value = true
|
refresh.value = true
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.Delete,
|
Icon(
|
||||||
|
Icons.Filled.Delete,
|
||||||
contentDescription = "remove"
|
contentDescription = "remove"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
|||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -80,8 +81,6 @@ class GameViews {
|
|||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
GameStats(mainViewModel)
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
val ryujinxNative = RyujinxNative.instance
|
|
||||||
|
|
||||||
val showController = remember {
|
val showController = remember {
|
||||||
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
|
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
|
||||||
}
|
}
|
||||||
@ -128,19 +127,19 @@ class GameViews {
|
|||||||
|
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
PointerEventType.Press -> {
|
PointerEventType.Press -> {
|
||||||
ryujinxNative.inputSetTouchPoint(
|
RyujinxNative.jnaInstance.inputSetTouchPoint(
|
||||||
position.x.roundToInt(),
|
position.x.roundToInt(),
|
||||||
position.y.roundToInt()
|
position.y.roundToInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerEventType.Release -> {
|
PointerEventType.Release -> {
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
RyujinxNative.jnaInstance.inputReleaseTouchPoint()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerEventType.Move -> {
|
PointerEventType.Move -> {
|
||||||
ryujinxNative.inputSetTouchPoint(
|
RyujinxNative.jnaInstance.inputSetTouchPoint(
|
||||||
position.x.roundToInt(),
|
position.x.roundToInt(),
|
||||||
position.y.roundToInt()
|
position.y.roundToInt()
|
||||||
)
|
)
|
||||||
@ -209,7 +208,7 @@ class GameViews {
|
|||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
showMore.value = false
|
showMore.value = false
|
||||||
showController.value = !showController.value
|
showController.value = !showController.value
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
RyujinxNative.jnaInstance.inputReleaseTouchPoint()
|
||||||
mainViewModel.controller?.setVisible(showController.value)
|
mainViewModel.controller?.setVisible(showController.value)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -220,7 +219,7 @@ class GameViews {
|
|||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
showMore.value = false
|
showMore.value = false
|
||||||
enableVsync.value = !enableVsync.value
|
enableVsync.value = !enableVsync.value
|
||||||
RyujinxNative.instance.graphicsRendererSetVsync(
|
RyujinxNative.jnaInstance.graphicsRendererSetVsync(
|
||||||
enableVsync.value
|
enableVsync.value
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
@ -262,10 +261,12 @@ class GameViews {
|
|||||||
|
|
||||||
if (progressValue.value > -1)
|
if (progressValue.value > -1)
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
|
progress = {
|
||||||
|
progressValue.value
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 16.dp),
|
.padding(top = 16.dp),
|
||||||
progress = progressValue.value
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
@ -279,7 +280,7 @@ class GameViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showBackNotice.value) {
|
if (showBackNotice.value) {
|
||||||
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
BasicAlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
||||||
Column {
|
Column {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -3,8 +3,6 @@ package org.ryujinx.android.views
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.basicMarquee
|
import androidx.compose.foundation.basicMarquee
|
||||||
@ -38,25 +36,22 @@ import androidx.compose.material.icons.filled.Search
|
|||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardElevation
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ElevatedCard
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.OutlinedCard
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SearchBar
|
import androidx.compose.material3.SearchBar
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -69,14 +64,10 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.HorizontalAlignmentLine
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Popup
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
import org.ryujinx.android.R
|
import org.ryujinx.android.R
|
||||||
@ -377,7 +368,7 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showLoading.value) {
|
if (showLoading.value) {
|
||||||
AlertDialog(onDismissRequest = { }) {
|
BasicAlertDialog(onDismissRequest = { }) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
@ -401,7 +392,7 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (openTitleUpdateDialog.value) {
|
if (openTitleUpdateDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
openTitleUpdateDialog.value = false
|
openTitleUpdateDialog.value = false
|
||||||
}) {
|
}) {
|
||||||
Surface(
|
Surface(
|
||||||
@ -419,7 +410,7 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (openDlcDialog.value) {
|
if (openDlcDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
openDlcDialog.value = false
|
openDlcDialog.value = false
|
||||||
}) {
|
}) {
|
||||||
Surface(
|
Surface(
|
||||||
@ -451,14 +442,13 @@ class HomeViews {
|
|||||||
thread {
|
thread {
|
||||||
showLoading.value = true
|
showLoading.value = true
|
||||||
val success =
|
val success =
|
||||||
viewModel.mainViewModel?.loadGame(viewModel.mainViewModel.selected!!)
|
viewModel.mainViewModel.loadGame(viewModel.mainViewModel.selected!!)
|
||||||
?: false
|
|
||||||
if (success) {
|
if (success) {
|
||||||
launchOnUiThread {
|
launchOnUiThread {
|
||||||
viewModel.mainViewModel?.navigateToGame()
|
viewModel.mainViewModel.navigateToGame()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModel.mainViewModel?.selected!!.close()
|
viewModel.mainViewModel.selected!!.close()
|
||||||
}
|
}
|
||||||
showLoading.value = false
|
showLoading.value = false
|
||||||
}
|
}
|
||||||
@ -487,7 +477,7 @@ class HomeViews {
|
|||||||
}, onClick = {
|
}, onClick = {
|
||||||
showAppMenu.value = false
|
showAppMenu.value = false
|
||||||
viewModel.mainViewModel?.clearPptcCache(
|
viewModel.mainViewModel?.clearPptcCache(
|
||||||
viewModel.mainViewModel?.selected?.titleId ?: ""
|
viewModel.mainViewModel.selected?.titleId ?: ""
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
@ -495,7 +485,7 @@ class HomeViews {
|
|||||||
}, onClick = {
|
}, onClick = {
|
||||||
showAppMenu.value = false
|
showAppMenu.value = false
|
||||||
viewModel.mainViewModel?.purgeShaderCache(
|
viewModel.mainViewModel?.purgeShaderCache(
|
||||||
viewModel.mainViewModel?.selected?.titleId ?: ""
|
viewModel.mainViewModel.selected?.titleId ?: ""
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
@ -548,7 +538,7 @@ class HomeViews {
|
|||||||
onClick = {
|
onClick = {
|
||||||
if (viewModel.mainViewModel?.selected != null) {
|
if (viewModel.mainViewModel?.selected != null) {
|
||||||
showAppActions.value = false
|
showAppActions.value = false
|
||||||
viewModel.mainViewModel?.apply {
|
viewModel.mainViewModel.apply {
|
||||||
selected = null
|
selected = null
|
||||||
}
|
}
|
||||||
selectedModel.value = null
|
selectedModel.value = null
|
||||||
@ -639,7 +629,7 @@ class HomeViews {
|
|||||||
onClick = {
|
onClick = {
|
||||||
if (viewModel.mainViewModel?.selected != null) {
|
if (viewModel.mainViewModel?.selected != null) {
|
||||||
showAppActions.value = false
|
showAppActions.value = false
|
||||||
viewModel.mainViewModel?.apply {
|
viewModel.mainViewModel.apply {
|
||||||
selected = null
|
selected = null
|
||||||
}
|
}
|
||||||
selectedModel.value = null
|
selectedModel.value = null
|
||||||
|
@ -16,7 +16,7 @@ class MainView {
|
|||||||
|
|
||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
||||||
composable("user") { UserViews.Main(mainViewModel, navController) }
|
composable("user") { UserViews.Main(mainViewModel) }
|
||||||
composable("game") { GameViews.Main() }
|
composable("game") { GameViews.Main() }
|
||||||
composable("settings") {
|
composable("settings") {
|
||||||
SettingViews.Main(
|
SettingViews.Main(
|
||||||
|
@ -29,10 +29,11 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -192,7 +193,7 @@ class SettingViews {
|
|||||||
)
|
)
|
||||||
settingsViewModel.navController.popBackStack()
|
settingsViewModel.navController.popBackStack()
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) { contentPadding ->
|
}) { contentPadding ->
|
||||||
@ -310,7 +311,7 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showFirwmareDialog.value) {
|
if (showFirwmareDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
if (firmwareInstallState.value != FirmwareInstallState.Install) {
|
if (firmwareInstallState.value != FirmwareInstallState.Install) {
|
||||||
showFirwmareDialog.value = false
|
showFirwmareDialog.value = false
|
||||||
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
|
settingsViewModel.clearFirmwareSelection(firmwareInstallState)
|
||||||
@ -552,7 +553,6 @@ class SettingViews {
|
|||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
val storage = MainActivity.StorageHelper
|
val storage = MainActivity.StorageHelper
|
||||||
storage?.apply {
|
storage?.apply {
|
||||||
val s = this.storage
|
|
||||||
val callBack = this.onFileSelected
|
val callBack = this.onFileSelected
|
||||||
onFileSelected = { requestCode, files ->
|
onFileSelected = { requestCode, files ->
|
||||||
run {
|
run {
|
||||||
@ -578,7 +578,7 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showImportWarning.value) {
|
if (showImportWarning.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
showImportWarning.value = false
|
showImportWarning.value = false
|
||||||
importFile.value = null
|
importFile.value = null
|
||||||
}) {
|
}) {
|
||||||
@ -626,7 +626,7 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showImportCompletion.value) {
|
if (showImportCompletion.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
showImportCompletion.value = false
|
showImportCompletion.value = false
|
||||||
importFile.value = null
|
importFile.value = null
|
||||||
mainViewModel.userViewModel.refreshUsers()
|
mainViewModel.userViewModel.refreshUsers()
|
||||||
@ -739,7 +739,7 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDriverSelectorOpen.value) {
|
if (isDriverSelectorOpen.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
BasicAlertDialog(onDismissRequest = {
|
||||||
isDriverSelectorOpen.value = false
|
isDriverSelectorOpen.value = false
|
||||||
|
|
||||||
if (isChanged.value) {
|
if (isChanged.value) {
|
||||||
@ -1064,7 +1064,7 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler() {
|
BackHandler {
|
||||||
settingsViewModel.save(
|
settingsViewModel.save(
|
||||||
isHostMapped,
|
isHostMapped,
|
||||||
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
||||||
|
@ -17,7 +17,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -43,10 +43,11 @@ class UserViews {
|
|||||||
companion object {
|
companion object {
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
|
fun Main(viewModel: MainViewModel? = null) {
|
||||||
val reload = remember {
|
val reload = remember {
|
||||||
mutableStateOf(true)
|
mutableStateOf(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModel?.userViewModel?.refreshUsers()
|
viewModel?.userViewModel?.refreshUsers()
|
||||||
reload.value = true
|
reload.value = true
|
||||||
@ -64,7 +65,7 @@ class UserViews {
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
viewModel?.navController?.popBackStack()
|
viewModel?.navController?.popBackStack()
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}) { contentPadding ->
|
}) { contentPadding ->
|
||||||
@ -133,10 +134,14 @@ class UserViews {
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
) {
|
) {
|
||||||
if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
|
if (viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
|
||||||
items(viewModel.userViewModel.userList) { user ->
|
items(viewModel.userViewModel.userList) { user ->
|
||||||
Image(
|
Image(
|
||||||
bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0)
|
bitmap = BitmapFactory.decodeByteArray(
|
||||||
|
user.userPicture,
|
||||||
|
0,
|
||||||
|
user.userPicture?.size ?: 0
|
||||||
|
)
|
||||||
.asImageBitmap(),
|
.asImageBitmap(),
|
||||||
contentDescription = "selected image",
|
contentDescription = "selected image",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
@ -165,6 +170,6 @@ class UserViews {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun Preview() {
|
fun Preview() {
|
||||||
UserViews.Main()
|
Main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user