forked from MeloNX/MeloNX
android - drop game activity, replace with compose view
android - add string map android - fixes a few crashes in the user and home views android - bumb version android - bumb version, rebase over master android - remove oboe
This commit is contained in:
parent
a583d6bf46
commit
adda73f061
@ -6,6 +6,7 @@ using LibRyujinx.Jni.Values;
|
|||||||
using LibRyujinx.Shared.Audio.Oboe;
|
using LibRyujinx.Shared.Audio.Oboe;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using Microsoft.Win32.SafeHandles;
|
||||||
using Rxmxnx.PInvoke;
|
using Rxmxnx.PInvoke;
|
||||||
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Logging.Targets;
|
using Ryujinx.Common.Logging.Targets;
|
||||||
@ -21,7 +22,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -42,7 +42,23 @@ namespace LibRyujinx
|
|||||||
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
||||||
|
|
||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
private extern static void pushString(string ch);
|
private extern static long storeString(string ch);
|
||||||
|
[DllImport("libryujinxjni")]
|
||||||
|
private extern static IntPtr getString(long id);
|
||||||
|
|
||||||
|
private static string GetStoredString(long id)
|
||||||
|
{
|
||||||
|
var pointer = getString(id);
|
||||||
|
if (pointer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var str = Marshal.PtrToStringAnsi(pointer) ?? "";
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(pointer);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
internal extern static void setRenderingThread();
|
internal extern static void setRenderingThread();
|
||||||
@ -68,8 +84,9 @@ namespace LibRyujinx
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
|
||||||
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath, JBoolean enableDebugLogs)
|
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId, JBoolean enableDebugLogs)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SystemInfo.IsBionic = true;
|
SystemInfo.IsBionic = true;
|
||||||
|
|
||||||
Logger.AddTarget(
|
Logger.AddTarget(
|
||||||
@ -79,7 +96,7 @@ namespace LibRyujinx
|
|||||||
AsyncLogTargetOverflowAction.Block
|
AsyncLogTargetOverflowAction.Block
|
||||||
));
|
));
|
||||||
|
|
||||||
var path = GetString(jEnv, jpath);
|
var path = GetStoredString(jpathId);
|
||||||
|
|
||||||
var init = Initialize(path, enableDebugLogs);
|
var init = Initialize(path, enableDebugLogs);
|
||||||
|
|
||||||
@ -102,6 +119,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
|
||||||
public static void JniReloadFileSystem()
|
public static void JniReloadFileSystem()
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SwitchDevice?.ReloadFileSystem();
|
SwitchDevice?.ReloadFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,10 +134,11 @@ namespace LibRyujinx
|
|||||||
JBoolean enableDockedMode,
|
JBoolean enableDockedMode,
|
||||||
JBoolean enablePtc,
|
JBoolean enablePtc,
|
||||||
JBoolean enableInternetAccess,
|
JBoolean enableInternetAccess,
|
||||||
JStringLocalRef timeZone,
|
JLong timeZoneId,
|
||||||
JBoolean ignoreMissingServices)
|
JBoolean ignoreMissingServices)
|
||||||
{
|
{
|
||||||
AudioDriver = new OboeHardwareDeviceDriver();
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
AudioDriver = new OpenALHardwareDeviceDriver();//new OboeHardwareDeviceDriver();
|
||||||
return InitializeDevice(isHostMapped,
|
return InitializeDevice(isHostMapped,
|
||||||
useNce,
|
useNce,
|
||||||
(SystemLanguage)(int)systemLanguage,
|
(SystemLanguage)(int)systemLanguage,
|
||||||
@ -128,13 +147,14 @@ namespace LibRyujinx
|
|||||||
enableDockedMode,
|
enableDockedMode,
|
||||||
enablePtc,
|
enablePtc,
|
||||||
enableInternetAccess,
|
enableInternetAccess,
|
||||||
GetString(jEnv, timeZone),
|
GetStoredString(timeZoneId),
|
||||||
ignoreMissingServices);
|
ignoreMissingServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")]
|
||||||
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
@ -143,6 +163,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")]
|
||||||
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
@ -151,6 +172,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")]
|
||||||
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
@ -159,6 +181,7 @@ namespace LibRyujinx
|
|||||||
[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 JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (SwitchDevice?.EmulationContext == null)
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -169,10 +192,23 @@ namespace LibRyujinx
|
|||||||
return LoadApplication(path);
|
return LoadApplication(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")]
|
||||||
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JLong titleId)
|
public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
var list = GetDlcContentList(GetString(jEnv, pathPtr), (ulong)(long)titleId);
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LaunchMiiEditApplet();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
|
||||||
|
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId)
|
||||||
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId);
|
||||||
|
|
||||||
debug_break(4);
|
debug_break(4);
|
||||||
|
|
||||||
@ -180,26 +216,30 @@ namespace LibRyujinx
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
|
||||||
public static JStringLocalRef JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JStringLocalRef ncaPath)
|
public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath)
|
||||||
{
|
{
|
||||||
return CreateString(jEnv, GetDlcTitleId(GetString(jEnv, pathPtr), GetString(jEnv, ncaPath)));
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
return storeString(GetDlcTitleId(GetStoredString(pathPtr), GetStoredString(ncaPath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
||||||
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SignalEmulationClose();
|
SignalEmulationClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
|
||||||
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
CloseEmulation();
|
CloseEmulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
|
||||||
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (SwitchDevice?.EmulationContext == null)
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -213,6 +253,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
|
||||||
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
|
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
JEnvValue value = jEnv.Environment;
|
JEnvValue value = jEnv.Environment;
|
||||||
ref JNativeInterface jInterface = ref value.Functions;
|
ref JNativeInterface jInterface = ref value.Functions;
|
||||||
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
|
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
|
||||||
@ -255,6 +296,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
|
||||||
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
|
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
_surfacePtr = surfacePtr;
|
_surfacePtr = surfacePtr;
|
||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
@ -267,6 +309,7 @@ namespace LibRyujinx
|
|||||||
JArrayLocalRef extensionsArray,
|
JArrayLocalRef extensionsArray,
|
||||||
JLong driverHandle)
|
JLong driverHandle)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
if (Renderer != null)
|
if (Renderer != null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -354,12 +397,14 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
||||||
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
||||||
{
|
{
|
||||||
|
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 = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
|
||||||
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetSwapBuffersCallback(() =>
|
SetSwapBuffersCallback(() =>
|
||||||
{
|
{
|
||||||
var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime();
|
var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime();
|
||||||
@ -371,21 +416,24 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")]
|
||||||
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
|
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var info = GetGameInfo(GetString(jEnv, path));
|
var info = GetGameInfo(GetString(jEnv, path));
|
||||||
return GetInfo(jEnv, info, out SHA256 _);
|
return GetInfo(jEnv, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
|
||||||
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JBoolean isXci)
|
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JBoolean isXci)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
using var stream = OpenFile(fileDescriptor);
|
using var stream = OpenFile(fileDescriptor);
|
||||||
|
|
||||||
var info = GetGameInfo(stream, isXci);
|
var info = GetGameInfo(stream, isXci);
|
||||||
return GetInfo(jEnv, info, out SHA256 _);
|
return GetInfo(jEnv, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info, out SHA256 sha)
|
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
|
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
|
||||||
|
|
||||||
JEnvValue value = jEnv.Environment;
|
JEnvValue value = jEnv.Environment;
|
||||||
@ -411,24 +459,12 @@ namespace LibRyujinx
|
|||||||
var newGlobal = newGlobalRef(jEnv, javaClass._value);
|
var newGlobal = newGlobalRef(jEnv, javaClass._value);
|
||||||
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
|
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
|
||||||
var newObj = newObject(jEnv, javaClass, constructor, 0);
|
var newObj = newObject(jEnv, javaClass, constructor, 0);
|
||||||
sha = SHA256.Create();
|
|
||||||
var iconCacheByte = sha.ComputeHash(info?.Icon ?? Array.Empty<byte>());
|
|
||||||
var iconCache = BitConverter.ToString(iconCacheByte).Replace("-", "");
|
|
||||||
|
|
||||||
var cacheDirectory = Path.Combine(AppDataManager.BaseDirPath, "iconCache");
|
|
||||||
Directory.CreateDirectory(cacheDirectory);
|
|
||||||
|
|
||||||
var cachePath = Path.Combine(cacheDirectory, iconCache);
|
|
||||||
if (!File.Exists(cachePath))
|
|
||||||
{
|
|
||||||
File.WriteAllBytes(cachePath, info?.Icon ?? Array.Empty<byte>());
|
|
||||||
}
|
|
||||||
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value);
|
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("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("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("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value);
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("IconCache"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, iconCache)._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);
|
setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d);
|
||||||
|
|
||||||
return newObj;
|
return newObj;
|
||||||
@ -450,88 +486,102 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")]
|
||||||
public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled)
|
public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetVsyncState(enabled);
|
SetVsyncState(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")]
|
||||||
public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback)
|
public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
|
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")]
|
||||||
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
InitializeInput(width, height);
|
InitializeInput(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")]
|
||||||
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetClientSize(width, height);
|
SetClientSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")]
|
||||||
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y)
|
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetTouchPoint(x, y);
|
SetTouchPoint(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")]
|
||||||
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
ReleaseTouchPoint();
|
ReleaseTouchPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")]
|
||||||
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
UpdateInput();
|
UpdateInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
|
||||||
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetButtonPressed((GamepadButtonInputId)(int)button, id);
|
SetButtonPressed((GamepadButtonInputId)(int)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
|
||||||
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetButtonReleased((GamepadButtonInputId)(int)button, id);
|
SetButtonReleased((GamepadButtonInputId)(int)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
|
||||||
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
|
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
|
SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")]
|
||||||
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index)
|
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
return ConnectGamepad(index);
|
return ConnectGamepad(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
|
||||||
public static void JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetOpenedUser();
|
var userId = GetOpenedUser();
|
||||||
|
|
||||||
pushString(userId);
|
return storeString(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
|
||||||
public static JStringLocalRef JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
||||||
{
|
{
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
var userId = GetStoredString(userIdPtr) ?? "";
|
||||||
|
|
||||||
return CreateString(jEnv, GetUserPicture(userId));
|
return storeString(GetUserPicture(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")]
|
||||||
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
|
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
var picture = GetString(jEnv, picturePtr) ?? "";
|
||||||
|
|
||||||
@ -539,16 +589,18 @@ namespace LibRyujinx
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
|
||||||
public static JStringLocalRef JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
||||||
{
|
{
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
var userId = GetStoredString(userIdPtr) ?? "";
|
||||||
|
|
||||||
return CreateString(jEnv, GetUserName(userId));
|
return storeString(GetUserName(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")]
|
||||||
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
|
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
var userName = GetString(jEnv, userNamePtr) ?? "";
|
||||||
|
|
||||||
@ -558,6 +610,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
|
||||||
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var users = GetAllUsers();
|
var users = GetAllUsers();
|
||||||
|
|
||||||
return CreateStringArray(jEnv, users.ToList());
|
return CreateStringArray(jEnv, users.ToList());
|
||||||
@ -566,6 +619,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
|
||||||
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
|
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userName = GetString(jEnv, userNamePtr) ?? "";
|
var userName = GetString(jEnv, userNamePtr) ?? "";
|
||||||
var picture = GetString(jEnv, picturePtr) ?? "";
|
var picture = GetString(jEnv, picturePtr) ?? "";
|
||||||
|
|
||||||
@ -575,15 +629,17 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
|
||||||
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||||
|
|
||||||
DeleteUser(userId);
|
DeleteUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
||||||
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
||||||
{
|
{
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
|
var userId = GetStoredString(userIdPtr) ?? "";
|
||||||
|
|
||||||
OpenUser(userId);
|
OpenUser(userId);
|
||||||
}
|
}
|
||||||
@ -591,6 +647,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
|
||||||
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
|
||||||
{
|
{
|
||||||
|
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
|
||||||
var userId = GetString(jEnv, userIdPtr) ?? "";
|
var userId = GetString(jEnv, userIdPtr) ?? "";
|
||||||
|
|
||||||
CloseUser(userId);
|
CloseUser(userId);
|
||||||
|
@ -26,6 +26,7 @@ using LibHac.FsSystem;
|
|||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using OpenTK.Audio.OpenAL;
|
||||||
using Ryujinx.HLE.Loaders.Npdm;
|
using Ryujinx.HLE.Loaders.Npdm;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -76,6 +77,8 @@ namespace LibRyujinx
|
|||||||
Console.WriteLine(ex);
|
Console.WriteLine(ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpenALLibraryNameContainer.OverridePath = "libopenal.so";
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "org.ryujinx.android"
|
applicationId "org.ryujinx.android"
|
||||||
minSdk 30
|
minSdk 30
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 10001
|
versionCode 10004
|
||||||
versionName '1.0.1'
|
versionName '1.0.4'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@ -94,7 +94,6 @@ dependencies {
|
|||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3'
|
||||||
implementation 'com.github.swordfish90:radialgamepad:2.0.0'
|
implementation 'com.github.swordfish90:radialgamepad:2.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'com.google.oboe:oboe:1.7.0'
|
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
||||||
|
@ -27,14 +27,10 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RyujinxAndroid"
|
android:theme="@style/Theme.RyujinxAndroid"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
|
||||||
android:name=".GameActivity"
|
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/Theme.RyujinxAndroid" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:theme="@style/Theme.RyujinxAndroid">
|
android:theme="@style/Theme.RyujinxAndroid">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -28,7 +28,7 @@ add_library( # Sets the name of the library.
|
|||||||
|
|
||||||
# Provides a relative path to your source file(s).
|
# Provides a relative path to your source file(s).
|
||||||
vulkan_wrapper.cpp
|
vulkan_wrapper.cpp
|
||||||
oboe.cpp
|
string_helper.cpp
|
||||||
ryujinx.cpp)
|
ryujinx.cpp)
|
||||||
|
|
||||||
# Searches for a specified prebuilt library and stores the path as a
|
# Searches for a specified prebuilt library and stores the path as a
|
||||||
@ -44,8 +44,6 @@ find_library( # Sets the name of the path variable.
|
|||||||
# you want CMake to locate.
|
# you want CMake to locate.
|
||||||
log )
|
log )
|
||||||
|
|
||||||
find_package (oboe REQUIRED CONFIG)
|
|
||||||
|
|
||||||
# Specifies libraries CMake should link to your target library. You
|
# Specifies libraries CMake should link to your target library. You
|
||||||
# can link multiple libraries, such as libraries you define in this
|
# can link multiple libraries, such as libraries you define in this
|
||||||
# build script, prebuilt third-party libraries, or system libraries.
|
# build script, prebuilt third-party libraries, or system libraries.
|
||||||
@ -54,7 +52,6 @@ target_link_libraries( # Specifies the target library.
|
|||||||
ryujinxjni
|
ryujinxjni
|
||||||
# Links the target library to the log library
|
# Links the target library to the log library
|
||||||
# included in the NDK.
|
# included in the NDK.
|
||||||
oboe::oboe
|
|
||||||
${log-lib}
|
${log-lib}
|
||||||
-lvulkan
|
-lvulkan
|
||||||
-landroid
|
-landroid
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by Emmanuel Hansen on 6/27/2023.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "oboe.h"
|
|
||||||
|
|
||||||
static int s_device_id = 0;
|
|
||||||
|
|
||||||
void AudioSession::initialize() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioSession::destroy() {
|
|
||||||
if(stream == nullptr)
|
|
||||||
return;
|
|
||||||
stream->close();
|
|
||||||
|
|
||||||
stream = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioSession::start() {
|
|
||||||
isStarted = true;
|
|
||||||
|
|
||||||
stream->requestStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioSession::stop() {
|
|
||||||
isStarted = false;
|
|
||||||
stream->requestStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AudioSession::read(uint64_t data, uint64_t samples) {
|
|
||||||
int timeout = INT32_MAX;
|
|
||||||
|
|
||||||
stream->write((void*)data, samples, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_org_ryujinx_android_NativeHelpers_setDeviceId(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject instance,
|
|
||||||
jint device_id) {
|
|
||||||
s_device_id = device_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioSession *create_session(int sample_format,
|
|
||||||
uint sample_rate,
|
|
||||||
uint channel_count) {
|
|
||||||
using namespace oboe;
|
|
||||||
|
|
||||||
AudioStreamBuilder builder;
|
|
||||||
|
|
||||||
AudioFormat format;
|
|
||||||
|
|
||||||
switch (sample_format) {
|
|
||||||
case 0:
|
|
||||||
format = AudioFormat::Invalid;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
format = AudioFormat::I16;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
format = AudioFormat::I24;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
format = AudioFormat::I32;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
format = AudioFormat::Float;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::ostringstream string;
|
|
||||||
string << "Invalid Format" << sample_format;
|
|
||||||
|
|
||||||
throw std::runtime_error(string.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto session = new AudioSession();
|
|
||||||
session->initialize();
|
|
||||||
|
|
||||||
session->format = format;
|
|
||||||
session->channelCount = channel_count;
|
|
||||||
|
|
||||||
builder.setDirection(Direction::Output)
|
|
||||||
->setPerformanceMode(PerformanceMode::LowLatency)
|
|
||||||
->setSharingMode(SharingMode::Shared)
|
|
||||||
->setFormat(format)
|
|
||||||
->setChannelCount(channel_count)
|
|
||||||
->setSampleRate(sample_rate);
|
|
||||||
AudioStream *stream;
|
|
||||||
if (builder.openStream(&stream) != oboe::Result::OK) {
|
|
||||||
delete session;
|
|
||||||
session = nullptr;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
session->stream = stream;
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_session(AudioSession *session) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return;
|
|
||||||
session->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop_session(AudioSession *session) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return;
|
|
||||||
session->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_session_volume(AudioSession *session, float volume) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return;
|
|
||||||
session->volume = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
float get_session_volume(AudioSession *session) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return 0;
|
|
||||||
return session->volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
void close_session(AudioSession *session) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return;
|
|
||||||
session->destroy();
|
|
||||||
|
|
||||||
delete session;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_playing(AudioSession *session) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return false;
|
|
||||||
return session->isStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_to_session(AudioSession *session, uint64_t data, uint64_t samples) {
|
|
||||||
if (session == nullptr)
|
|
||||||
return;
|
|
||||||
session->read(data, samples);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by Emmanuel Hansen on 6/27/2023.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef RYUJINXNATIVE_OBOE_H
|
|
||||||
#define RYUJINXNATIVE_OBOE_H
|
|
||||||
|
|
||||||
#include <oboe/Oboe.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <jni.h>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
class AudioSession {
|
|
||||||
public:
|
|
||||||
oboe::AudioStream* stream;
|
|
||||||
float volume = 1.0f;
|
|
||||||
bool isStarted;
|
|
||||||
oboe::AudioFormat format;
|
|
||||||
uint channelCount;
|
|
||||||
|
|
||||||
void initialize();
|
|
||||||
void destroy();
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void read(uint64_t data, uint64_t samples);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //RYUJINXNATIVE_OBOE_H
|
|
@ -19,6 +19,7 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include "libraries/adrenotools/include/adrenotools/driver.h"
|
#include "libraries/adrenotools/include/adrenotools/driver.h"
|
||||||
#include "native_window.h"
|
#include "native_window.h"
|
||||||
|
#include "string_helper.h"
|
||||||
|
|
||||||
// A macro to pass call to Vulkan and check for return value for success
|
// A macro to pass call to Vulkan and check for return value for success
|
||||||
#define CALL_VK(func) \
|
#define CALL_VK(func) \
|
||||||
@ -46,5 +47,6 @@ JavaVM* _vm = nullptr;
|
|||||||
jobject _mainActivity = nullptr;
|
jobject _mainActivity = nullptr;
|
||||||
jclass _mainActivityClass = nullptr;
|
jclass _mainActivityClass = nullptr;
|
||||||
std::string _currentString = "";
|
std::string _currentString = "";
|
||||||
|
string_helper str_helper = string_helper();
|
||||||
|
|
||||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||||
|
@ -312,24 +312,28 @@ Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz
|
|||||||
return createStringFromStdString(env, progressInfo);
|
return createStringFromStdString(env, progressInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
long storeString(char* str){
|
||||||
|
return str_helper.store_cstring(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
const char* getString(long id){
|
||||||
|
auto str = str_helper.get_stored(id);
|
||||||
|
auto cstr = (char*)::malloc(str.length() + 1);
|
||||||
|
::strcpy(cstr, str.c_str());
|
||||||
|
return cstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz, jstring string) {
|
||||||
|
auto str = getStringPointer(env, string);
|
||||||
|
return str_helper.store_cstring(str);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_org_ryujinx_android_NativeHelpers_popStringJava(JNIEnv *env, jobject thiz) {
|
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong id) {
|
||||||
return createStringFromStdString(env, _currentString);
|
return createStringFromStdString(env, str_helper.get_stored(id));
|
||||||
}
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_org_ryujinx_android_NativeHelpers_pushStringJava(JNIEnv *env, jobject thiz, jstring string) {
|
|
||||||
_currentString = getStringPointer(env, string);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
void pushString(char* str){
|
|
||||||
_currentString = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
const char* popString(){
|
|
||||||
return _currentString.c_str();
|
|
||||||
}
|
}
|
||||||
|
24
src/RyujinxAndroid/app/src/main/cpp/string_helper.cpp
Normal file
24
src/RyujinxAndroid/app/src/main/cpp/string_helper.cpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Created by Emmanuel Hansen on 10/30/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "string_helper.h"
|
||||||
|
|
||||||
|
long string_helper::store_cstring(const char *cstr) {
|
||||||
|
auto id = ++current_id;
|
||||||
|
_map.insert({id, cstr});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
long string_helper::store_string(const string& str) {
|
||||||
|
auto id = ++current_id;
|
||||||
|
_map.insert({id, str});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
string string_helper::get_stored(long id) {
|
||||||
|
auto str = _map[id];
|
||||||
|
_map.erase(id);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
29
src/RyujinxAndroid/app/src/main/cpp/string_helper.h
Normal file
29
src/RyujinxAndroid/app/src/main/cpp/string_helper.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by Emmanuel Hansen on 10/30/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RYUJINXANDROID_STRING_HELPER_H
|
||||||
|
#define RYUJINXANDROID_STRING_HELPER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
using namespace std;
|
||||||
|
class string_helper {
|
||||||
|
public:
|
||||||
|
long store_cstring(const char * cstr);
|
||||||
|
long store_string(const string& str);
|
||||||
|
|
||||||
|
string get_stored(long id);
|
||||||
|
|
||||||
|
string_helper(){
|
||||||
|
_map = unordered_map<long,string>();
|
||||||
|
current_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
unordered_map<long, string> _map;
|
||||||
|
long current_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //RYUJINXANDROID_STRING_HELPER_H
|
@ -1,407 +0,0 @@
|
|||||||
package org.ryujinx.android
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.pointer.PointerEventType
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.compose.ui.window.Popup
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import compose.icons.CssGgIcons
|
|
||||||
import compose.icons.cssggicons.ToolbarBottom
|
|
||||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
|
||||||
import org.ryujinx.android.viewmodels.QuickSettings
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class GameActivity : BaseActivity() {
|
|
||||||
private var physicalControllerManager: PhysicalControllerManager =
|
|
||||||
PhysicalControllerManager(this)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
MainActivity.mainViewModel!!.physicalControllerManager = physicalControllerManager
|
|
||||||
setContent {
|
|
||||||
RyujinxAndroidTheme {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
GameView(mainViewModel = MainActivity.mainViewModel!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
|
||||||
event?.apply {
|
|
||||||
if (physicalControllerManager.onKeyEvent(this))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return super.dispatchKeyEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
|
||||||
ev?.apply {
|
|
||||||
physicalControllerManager.onMotionEvent(this)
|
|
||||||
}
|
|
||||||
return super.dispatchGenericMotionEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
|
|
||||||
NativeHelpers().setTurboMode(false)
|
|
||||||
force60HzRefreshRate(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
setFullScreen(true)
|
|
||||||
NativeHelpers().setTurboMode(true)
|
|
||||||
force60HzRefreshRate(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
|
|
||||||
NativeHelpers().setTurboMode(false)
|
|
||||||
force60HzRefreshRate(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun force60HzRefreshRate(enable: Boolean) {
|
|
||||||
// Hack for MIUI devices since they don't support the standard Android APIs
|
|
||||||
try {
|
|
||||||
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
|
|
||||||
setFpsIntent.putExtra("package_name", "org.ryujinx.android")
|
|
||||||
setFpsIntent.putExtra("isEnter", enable)
|
|
||||||
sendBroadcast(setFpsIntent)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enable)
|
|
||||||
display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
|
|
||||||
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
|
||||||
else
|
|
||||||
display?.supportedModes?.maxByOrNull { it.refreshRate }
|
|
||||||
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setFullScreen(fullscreen: Boolean) {
|
|
||||||
requestedOrientation =
|
|
||||||
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
|
||||||
|
|
||||||
val insets = WindowCompat.getInsetsController(window, window.decorView)
|
|
||||||
|
|
||||||
insets.apply {
|
|
||||||
if (fullscreen) {
|
|
||||||
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insets.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
} else {
|
|
||||||
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insets.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameView(mainViewModel: MainViewModel) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
factory = { context ->
|
|
||||||
GameHost(context, mainViewModel)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
GameOverlay(mainViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun GameOverlay(mainViewModel: MainViewModel) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
GameStats(mainViewModel)
|
|
||||||
|
|
||||||
val ryujinxNative = RyujinxNative()
|
|
||||||
|
|
||||||
val showController = remember {
|
|
||||||
mutableStateOf(QuickSettings(this@GameActivity).useVirtualController)
|
|
||||||
}
|
|
||||||
val enableVsync = remember {
|
|
||||||
mutableStateOf(QuickSettings(this@GameActivity).enableVsync)
|
|
||||||
}
|
|
||||||
val showMore = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val showLoading = remember {
|
|
||||||
mutableStateOf(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val progressValue = remember {
|
|
||||||
mutableStateOf(0.0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
val progress = remember {
|
|
||||||
mutableStateOf("Loading")
|
|
||||||
}
|
|
||||||
|
|
||||||
mainViewModel.setProgressStates(showLoading, progressValue, progress)
|
|
||||||
|
|
||||||
// touch surface
|
|
||||||
Surface(color = Color.Transparent, modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(0.dp)
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
awaitPointerEventScope {
|
|
||||||
while (true) {
|
|
||||||
val event = awaitPointerEvent()
|
|
||||||
if (showController.value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
val change = event
|
|
||||||
.component1()
|
|
||||||
.firstOrNull()
|
|
||||||
change?.apply {
|
|
||||||
val position = this.position
|
|
||||||
|
|
||||||
when (event.type) {
|
|
||||||
PointerEventType.Press -> {
|
|
||||||
ryujinxNative.inputSetTouchPoint(
|
|
||||||
position.x.roundToInt(),
|
|
||||||
position.y.roundToInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerEventType.Release -> {
|
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerEventType.Move -> {
|
|
||||||
ryujinxNative.inputSetTouchPoint(
|
|
||||||
position.x.roundToInt(),
|
|
||||||
position.y.roundToInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
}
|
|
||||||
if (!showLoading.value) {
|
|
||||||
GameController.Compose(mainViewModel)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = CssGgIcons.ToolbarBottom,
|
|
||||||
contentDescription = "Open Panel"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showMore.value) {
|
|
||||||
Popup(
|
|
||||||
alignment = Alignment.BottomCenter,
|
|
||||||
onDismissRequest = { showMore.value = false }) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
shape = MaterialTheme.shapes.medium
|
|
||||||
) {
|
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = false
|
|
||||||
showController.value = !showController.value
|
|
||||||
mainViewModel.controller?.setVisible(showController.value)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.videoGame(),
|
|
||||||
contentDescription = "Toggle Virtual Pad"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = false
|
|
||||||
enableVsync.value = !enableVsync.value
|
|
||||||
RyujinxNative().graphicsRendererSetVsync(enableVsync.value)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.vSync(),
|
|
||||||
tint = if (enableVsync.value) Color.Green else Color.Red,
|
|
||||||
contentDescription = "Toggle VSync"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val showBackNotice = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler {
|
|
||||||
showBackNotice.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLoading.value) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth(0.5f)
|
|
||||||
.align(Alignment.Center),
|
|
||||||
shape = MaterialTheme.shapes.medium
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(text = progress.value)
|
|
||||||
|
|
||||||
if (progressValue.value > -1)
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp),
|
|
||||||
progress = progressValue.value
|
|
||||||
)
|
|
||||||
else
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showBackNotice.value) {
|
|
||||||
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
|
||||||
Column {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.wrapContentWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.large,
|
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(text = "Are you sure you want to exit the game?")
|
|
||||||
Text(text = "All unsaved data will be lost!")
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Button(onClick = {
|
|
||||||
showBackNotice.value = false
|
|
||||||
mainViewModel.closeGame()
|
|
||||||
setFullScreen(false)
|
|
||||||
finishActivity(0)
|
|
||||||
}, modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(text = "Exit Game")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(onClick = {
|
|
||||||
showBackNotice.value = false
|
|
||||||
}, modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(text = "Dismiss")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameStats(mainViewModel: MainViewModel) {
|
|
||||||
val fifo = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
val gameFps = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
val gameTime = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surface.copy(0.4f)
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
var gameTimeVal = 0.0
|
|
||||||
if (!gameTime.value.isInfinite())
|
|
||||||
gameTimeVal = gameTime.value
|
|
||||||
Text(text = "${String.format("%.3f", fifo.value)} %")
|
|
||||||
Text(text = "${String.format("%.3f", gameFps.value)} FPS")
|
|
||||||
Text(text = "${String.format("%.3f", gameTimeVal)} ms")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainViewModel.setStatStates(fifo, gameFps, gameTime)
|
|
||||||
}
|
|
||||||
}
|
|
@ -96,7 +96,7 @@ class GameController(var activity: Activity) {
|
|||||||
rightGamePad.gravityX = 1f
|
rightGamePad.gravityX = 1f
|
||||||
rightGamePad.gravityY = 1f
|
rightGamePad.gravityY = 1f
|
||||||
|
|
||||||
ryujinxNative = RyujinxNative()
|
ryujinxNative = RyujinxNative.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVisible(isVisible: Boolean){
|
fun setVisible(isVisible: Boolean){
|
||||||
@ -110,7 +110,7 @@ class GameController(var activity: Activity) {
|
|||||||
|
|
||||||
fun connect(){
|
fun connect(){
|
||||||
if(controllerId == -1)
|
if(controllerId == -1)
|
||||||
controllerId = RyujinxNative().inputConnectGamepad(0)
|
controllerId = RyujinxNative.instance.inputConnectGamepad(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEvent(ev: Event) {
|
private fun handleEvent(ev: Event) {
|
||||||
|
@ -12,7 +12,8 @@ import org.ryujinx.android.viewmodels.QuickSettings
|
|||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context),
|
||||||
|
SurfaceHolder.Callback {
|
||||||
private var isProgressHidden: Boolean = false
|
private var isProgressHidden: Boolean = false
|
||||||
private var progress: MutableState<String>? = null
|
private var progress: MutableState<String>? = null
|
||||||
private var progressValue: MutableState<Float>? = null
|
private var progressValue: MutableState<Float>? = null
|
||||||
@ -26,9 +27,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
private var _guestThread: Thread? = null
|
private var _guestThread: Thread? = null
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
private var _isStarted: Boolean = false
|
private var _isStarted: Boolean = false
|
||||||
private val nativeWindow : NativeWindow
|
private val nativeWindow: NativeWindow
|
||||||
|
|
||||||
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
|
private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
|
||||||
|
|
||||||
init {
|
init {
|
||||||
holder.addCallback(this)
|
holder.addCallback(this)
|
||||||
@ -42,12 +43,11 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
if(_isClosed)
|
if (_isClosed)
|
||||||
return
|
return
|
||||||
start(holder)
|
start(holder)
|
||||||
|
|
||||||
if(_width != width || _height != height)
|
if (_width != width || _height != height) {
|
||||||
{
|
|
||||||
val window = nativeWindow.requeryWindowHandle()
|
val window = nativeWindow.requeryWindowHandle()
|
||||||
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
|
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
|
||||||
|
|
||||||
@ -62,8 +62,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
height
|
height
|
||||||
)
|
)
|
||||||
|
|
||||||
if(_isStarted)
|
if (_isStarted) {
|
||||||
{
|
|
||||||
_nativeRyujinx.inputSetClientSize(width, height)
|
_nativeRyujinx.inputSetClientSize(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +71,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun close(){
|
fun close() {
|
||||||
_isClosed = true
|
_isClosed = true
|
||||||
_isInit = false
|
_isInit = false
|
||||||
_isStarted = false
|
_isStarted = false
|
||||||
@ -82,19 +81,18 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun start(surfaceHolder: SurfaceHolder) {
|
private fun start(surfaceHolder: SurfaceHolder) {
|
||||||
if(_isStarted)
|
if (_isStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
game = mainViewModel.gameModel
|
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel;
|
||||||
|
|
||||||
_nativeRyujinx.inputInitialize(width, height)
|
_nativeRyujinx.inputInitialize(width, height)
|
||||||
|
|
||||||
val settings = QuickSettings(mainViewModel.activity)
|
val settings = QuickSettings(mainViewModel.activity)
|
||||||
|
|
||||||
if(!settings.useVirtualController){
|
if (!settings.useVirtualController) {
|
||||||
mainViewModel.controller?.setVisible(false)
|
mainViewModel.controller?.setVisible(false)
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
mainViewModel.controller?.connect()
|
mainViewModel.controller?.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,19 +110,19 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
_updateThread = thread(start = true) {
|
_updateThread = thread(start = true) {
|
||||||
var c = 0
|
var c = 0
|
||||||
val helper = NativeHelpers()
|
val helper = NativeHelpers.instance
|
||||||
while (_isStarted) {
|
while (_isStarted) {
|
||||||
_nativeRyujinx.inputUpdate()
|
_nativeRyujinx.inputUpdate()
|
||||||
Thread.sleep(1)
|
Thread.sleep(1)
|
||||||
|
|
||||||
showLoading?.apply {
|
showLoading?.apply {
|
||||||
if(value){
|
if (value) {
|
||||||
var value = helper.getProgressValue()
|
var value = helper.getProgressValue()
|
||||||
|
|
||||||
if(value != -1f)
|
if (value != -1f)
|
||||||
progress?.apply {
|
progress?.apply {
|
||||||
this.value = helper.getProgressInfo()
|
this.value = helper.getProgressInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
progressValue?.apply {
|
progressValue?.apply {
|
||||||
this.value = value
|
this.value = value
|
||||||
@ -133,12 +131,16 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
c++
|
c++
|
||||||
if (c >= 1000) {
|
if (c >= 1000) {
|
||||||
if(helper.getProgressValue() == -1f)
|
if (helper.getProgressValue() == -1f)
|
||||||
progress?.apply {
|
progress?.apply {
|
||||||
this.value = "Loading ${game!!.titleName}"
|
this.value = "Loading ${if(mainViewModel.isMiiEditorLaunched) "Mii Editor" else game!!.titleName}"
|
||||||
}
|
}
|
||||||
c = 0
|
c = 0
|
||||||
mainViewModel.updateStats(_nativeRyujinx.deviceGetGameFifo(), _nativeRyujinx.deviceGetGameFrameRate(), _nativeRyujinx.deviceGetGameFrameTime())
|
mainViewModel.updateStats(
|
||||||
|
_nativeRyujinx.deviceGetGameFifo(),
|
||||||
|
_nativeRyujinx.deviceGetGameFrameRate(),
|
||||||
|
_nativeRyujinx.deviceGetGameFrameTime()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ class Helpers {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isImporting.value = false
|
isImporting.value = false
|
||||||
RyujinxNative().deviceReloadFilesystem()
|
RyujinxNative.instance.deviceReloadFilesystem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,121 @@ 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 applets(color: Color): ImageVector {
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "apps",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(color),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = null,
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(9.708f, 33.125f)
|
||||||
|
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
|
||||||
|
quadToRelative(0f, -1.167f, 0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.834f, 2.02f, -0.834f)
|
||||||
|
quadToRelative(1.167f, 0f, 2f, 0.813f)
|
||||||
|
quadToRelative(0.834f, 0.812f, 0.834f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
|
||||||
|
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
|
||||||
|
quadToRelative(0f, -1.167f, 0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.834f, 1.979f, -0.834f)
|
||||||
|
reflectiveQuadToRelative(2f, 0.813f)
|
||||||
|
quadToRelative(0.833f, 0.812f, 0.833f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.208f, -0.812f, 2.02f)
|
||||||
|
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -2f, -0.813f)
|
||||||
|
quadToRelative(-0.834f, -0.812f, -0.834f, -2.02f)
|
||||||
|
quadToRelative(0f, -1.167f, 0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.834f, 2.021f, -0.834f)
|
||||||
|
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
|
||||||
|
quadToRelative(0.813f, 0.812f, 0.813f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
|
||||||
|
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveTo(9.708f, 22.792f)
|
||||||
|
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
|
||||||
|
reflectiveQuadToRelative(0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.833f, 2.02f, -0.833f)
|
||||||
|
quadToRelative(1.167f, 0f, 2f, 0.812f)
|
||||||
|
quadToRelative(0.834f, 0.813f, 0.834f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
|
||||||
|
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
|
||||||
|
reflectiveQuadToRelative(0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.833f, 1.979f, -0.833f)
|
||||||
|
reflectiveQuadToRelative(2f, 0.812f)
|
||||||
|
quadToRelative(0.833f, 0.813f, 0.833f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.812f, 1.979f)
|
||||||
|
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -2f, -0.813f)
|
||||||
|
quadToRelative(-0.834f, -0.812f, -0.834f, -1.979f)
|
||||||
|
reflectiveQuadToRelative(0.813f, -2f)
|
||||||
|
quadToRelative(0.812f, -0.833f, 2.021f, -0.833f)
|
||||||
|
quadToRelative(1.208f, 0f, 2.02f, 0.812f)
|
||||||
|
quadToRelative(0.813f, 0.813f, 0.813f, 2.021f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
|
||||||
|
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
|
||||||
|
close()
|
||||||
|
moveTo(9.708f, 12.542f)
|
||||||
|
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
|
||||||
|
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
|
||||||
|
quadToRelative(0.812f, -0.813f, 2.02f, -0.813f)
|
||||||
|
quadToRelative(1.167f, 0f, 2f, 0.813f)
|
||||||
|
quadToRelative(0.834f, 0.812f, 0.834f, 2.02f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.813f, 2f)
|
||||||
|
quadToRelative(-0.812f, 0.834f, -2.021f, 0.834f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
|
||||||
|
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
|
||||||
|
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
|
||||||
|
quadToRelative(0.812f, -0.813f, 1.979f, -0.813f)
|
||||||
|
reflectiveQuadToRelative(2f, 0.813f)
|
||||||
|
quadToRelative(0.833f, 0.812f, 0.833f, 2.02f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.812f, 2f)
|
||||||
|
quadToRelative(-0.813f, 0.834f, -2.021f, 0.834f)
|
||||||
|
close()
|
||||||
|
moveToRelative(10.292f, 0f)
|
||||||
|
quadToRelative(-1.167f, 0f, -2f, -0.813f)
|
||||||
|
quadToRelative(-0.834f, -0.812f, -0.834f, -2.021f)
|
||||||
|
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
|
||||||
|
quadToRelative(0.812f, -0.813f, 2.021f, -0.813f)
|
||||||
|
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
|
||||||
|
quadToRelative(0.813f, 0.812f, 0.813f, 2.02f)
|
||||||
|
quadToRelative(0f, 1.167f, -0.813f, 2f)
|
||||||
|
quadToRelative(-0.812f, 0.834f, -2.02f, 0.834f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
fun playArrow(color: Color): ImageVector {
|
fun playArrow(color: Color): ImageVector {
|
||||||
return remember {
|
return remember {
|
||||||
ImageVector.Builder(
|
ImageVector.Builder(
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -10,14 +15,20 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import com.anggrayudi.storage.SimpleStorageHelper
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.views.MainView
|
import org.ryujinx.android.views.MainView
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
|
private var physicalControllerManager: PhysicalControllerManager =
|
||||||
|
PhysicalControllerManager(this)
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
|
var isGameRunning = false
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
companion object {
|
companion object {
|
||||||
var mainViewModel: MainViewModel? = null
|
var mainViewModel: MainViewModel? = null
|
||||||
@ -53,7 +64,7 @@ class MainActivity : BaseActivity() {
|
|||||||
return
|
return
|
||||||
|
|
||||||
val appPath: String = AppPath
|
val appPath: String = AppPath
|
||||||
val success = RyujinxNative().initialize(appPath, false)
|
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath), false)
|
||||||
_isInit = success
|
_isInit = success
|
||||||
}
|
}
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -67,12 +78,14 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
||||||
|
|
||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
|
|
||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
WindowCompat.setDecorFitsSystemWindows(window,false)
|
WindowCompat.setDecorFitsSystemWindows(window,false)
|
||||||
|
|
||||||
mainViewModel = MainViewModel(this)
|
mainViewModel = MainViewModel(this)
|
||||||
|
mainViewModel!!.physicalControllerManager = physicalControllerManager
|
||||||
|
|
||||||
mainViewModel?.apply {
|
mainViewModel?.apply {
|
||||||
setContent {
|
setContent {
|
||||||
@ -98,4 +111,87 @@ class MainActivity : BaseActivity() {
|
|||||||
super.onRestoreInstanceState(savedInstanceState)
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
storageHelper?.onRestoreInstanceState(savedInstanceState)
|
storageHelper?.onRestoreInstanceState(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Game Stuff
|
||||||
|
private fun force60HzRefreshRate(enable: Boolean) {
|
||||||
|
// Hack for MIUI devices since they don't support the standard Android APIs
|
||||||
|
try {
|
||||||
|
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
|
||||||
|
setFpsIntent.putExtra("package_name", "org.ryujinx.android")
|
||||||
|
setFpsIntent.putExtra("isEnter", enable)
|
||||||
|
sendBroadcast(setFpsIntent)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
|
||||||
|
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||||
|
else
|
||||||
|
display?.supportedModes?.maxByOrNull { it.refreshRate }
|
||||||
|
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFullScreen(fullscreen: Boolean) {
|
||||||
|
requestedOrientation =
|
||||||
|
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
||||||
|
|
||||||
|
val insets = WindowCompat.getInsetsController(window, window.decorView)
|
||||||
|
|
||||||
|
insets.apply {
|
||||||
|
if (fullscreen) {
|
||||||
|
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
} else {
|
||||||
|
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
|
event?.apply {
|
||||||
|
if (physicalControllerManager.onKeyEvent(this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
||||||
|
ev?.apply {
|
||||||
|
physicalControllerManager.onMotionEvent(this)
|
||||||
|
}
|
||||||
|
return super.dispatchGenericMotionEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
NativeHelpers.instance.setTurboMode(false)
|
||||||
|
force60HzRefreshRate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
setFullScreen(true)
|
||||||
|
NativeHelpers.instance.setTurboMode(true)
|
||||||
|
force60HzRefreshRate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
NativeHelpers.instance.setTurboMode(false)
|
||||||
|
force60HzRefreshRate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.view.Surface
|
|||||||
class NativeHelpers {
|
class NativeHelpers {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val instance = NativeHelpers()
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("ryujinxjni")
|
System.loadLibrary("ryujinxjni")
|
||||||
}
|
}
|
||||||
@ -28,6 +29,6 @@ class NativeHelpers {
|
|||||||
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 pushStringJava(string: String)
|
external fun storeStringJava(string: String) : Long
|
||||||
external fun popStringJava() : String
|
external fun getStringJava(id: Long) : String
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import android.view.SurfaceView
|
|||||||
|
|
||||||
class NativeWindow(val surface: SurfaceView) {
|
class NativeWindow(val surface: SurfaceView) {
|
||||||
var nativePointer: Long
|
var nativePointer: Long
|
||||||
var nativeHelpers: NativeHelpers = NativeHelpers()
|
var nativeHelpers: NativeHelpers = NativeHelpers.instance
|
||||||
private var _swapInterval : Int = 0
|
private var _swapInterval : Int = 0
|
||||||
|
|
||||||
var maxSwapInterval : Int = 0
|
var maxSwapInterval : Int = 0
|
||||||
|
@ -3,9 +3,9 @@ package org.ryujinx.android
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
|
||||||
class PhysicalControllerManager(val activity: GameActivity) {
|
class PhysicalControllerManager(val activity: MainActivity) {
|
||||||
private var controllerId: Int = -1
|
private var controllerId: Int = -1
|
||||||
private var ryujinxNative: RyujinxNative = RyujinxNative()
|
private var ryujinxNative: RyujinxNative = RyujinxNative.instance
|
||||||
|
|
||||||
fun onKeyEvent(event: KeyEvent) : Boolean{
|
fun onKeyEvent(event: KeyEvent) : Boolean{
|
||||||
if(controllerId != -1) {
|
if(controllerId != -1) {
|
||||||
|
@ -4,10 +4,10 @@ import org.ryujinx.android.viewmodels.GameInfo
|
|||||||
|
|
||||||
@Suppress("KotlinJniMissingFunction")
|
@Suppress("KotlinJniMissingFunction")
|
||||||
class RyujinxNative {
|
class RyujinxNative {
|
||||||
|
external fun initialize(appPath: Long, enableDebugLogs : Boolean): Boolean
|
||||||
external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val instance: RyujinxNative = RyujinxNative()
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("ryujinx")
|
System.loadLibrary("ryujinx")
|
||||||
}
|
}
|
||||||
@ -20,7 +20,7 @@ class RyujinxNative {
|
|||||||
enableDockedMode : Boolean,
|
enableDockedMode : Boolean,
|
||||||
enablePtc : Boolean,
|
enablePtc : Boolean,
|
||||||
enableInternetAccess : Boolean,
|
enableInternetAccess : Boolean,
|
||||||
timeZone : String,
|
timeZone : Long,
|
||||||
ignoreMissingServices : Boolean): Boolean
|
ignoreMissingServices : Boolean): Boolean
|
||||||
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
|
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
|
||||||
external fun graphicsInitializeRenderer(
|
external fun graphicsInitializeRenderer(
|
||||||
@ -29,6 +29,7 @@ class RyujinxNative {
|
|||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
external fun deviceLoad(game: String): Boolean
|
external fun deviceLoad(game: String): Boolean
|
||||||
|
external fun deviceLaunchMiiEditor(): Boolean
|
||||||
external fun deviceGetGameFrameRate(): Double
|
external fun deviceGetGameFrameRate(): Double
|
||||||
external fun deviceGetGameFrameTime(): Double
|
external fun deviceGetGameFrameTime(): Double
|
||||||
external fun deviceGetGameFifo(): Double
|
external fun deviceGetGameFifo(): Double
|
||||||
@ -51,16 +52,16 @@ class RyujinxNative {
|
|||||||
external fun graphicsSetSurface(surface: Long, window: Long)
|
external fun graphicsSetSurface(surface: Long, window: Long)
|
||||||
external fun deviceCloseEmulation()
|
external fun deviceCloseEmulation()
|
||||||
external fun deviceSignalEmulationClose()
|
external fun deviceSignalEmulationClose()
|
||||||
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
|
external fun deviceGetDlcTitleId(path: Long, ncaPath: Long) : Long
|
||||||
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
|
external fun deviceGetDlcContentList(path: Long, titleId: Long) : Array<String>
|
||||||
external fun userGetOpenedUser()
|
external fun userGetOpenedUser() : Long
|
||||||
external fun userGetUserPicture(userId: String) : String
|
external fun userGetUserPicture(userId: Long) : Long
|
||||||
external fun userSetUserPicture(userId: String, picture: String)
|
external fun userSetUserPicture(userId: String, picture: String)
|
||||||
external fun userGetUserName(userId: String) : String
|
external fun userGetUserName(userId: Long) : Long
|
||||||
external fun userSetUserName(userId: String, userName: String)
|
external fun userSetUserName(userId: String, userName: String)
|
||||||
external fun userGetAllUsers() : Array<String>
|
external fun userGetAllUsers() : Array<String>
|
||||||
external fun userAddUser(username: String, picture: String)
|
external fun userAddUser(username: String, picture: String)
|
||||||
external fun userDeleteUser(userId: String)
|
external fun userDeleteUser(userId: String)
|
||||||
external fun userOpenUser(userId: String)
|
external fun userOpenUser(userId: Long)
|
||||||
external fun userCloseUser(userId: String)
|
external fun userCloseUser(userId: String)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ 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
|
||||||
|
|
||||||
@ -39,8 +40,8 @@ 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 {
|
||||||
var contents = RyujinxNative().deviceGetDlcContentList(
|
var contents = RyujinxNative.instance.deviceGetDlcContentList(
|
||||||
path,
|
NativeHelpers.instance.storeStringJava(path),
|
||||||
titleId.toLong(16)
|
titleId.toLong(16)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ class DlcViewModel(val titleId: String) {
|
|||||||
enabled,
|
enabled,
|
||||||
containerPath,
|
containerPath,
|
||||||
dlc.fullPath,
|
dlc.fullPath,
|
||||||
RyujinxNative().deviceGetDlcTitleId(containerPath, dlc.fullPath)
|
NativeHelpers.instance.getStringJava(RyujinxNative.instance.deviceGetDlcTitleId(NativeHelpers.instance.storeStringJava(containerPath), NativeHelpers.instance.storeStringJava(dlc.fullPath)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -149,4 +150,4 @@ data class DlcItem(
|
|||||||
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 = "")
|
||||||
|
@ -15,12 +15,12 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
var titleId: String? = null
|
var titleId: String? = null
|
||||||
var developer: String? = null
|
var developer: String? = null
|
||||||
var version: String? = null
|
var version: String? = null
|
||||||
var iconCache: String? = null
|
var icon: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fileName = file.name
|
fileName = file.name
|
||||||
var pid = open()
|
var pid = open()
|
||||||
val gameInfo = RyujinxNative().deviceGetGameInfo(pid, file.extension.contains("xci"))
|
val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, file.extension.contains("xci"))
|
||||||
close()
|
close()
|
||||||
|
|
||||||
fileSize = gameInfo.FileSize
|
fileSize = gameInfo.FileSize
|
||||||
@ -28,7 +28,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
titleName = gameInfo.TitleName
|
titleName = gameInfo.TitleName
|
||||||
developer = gameInfo.Developer
|
developer = gameInfo.Developer
|
||||||
version = gameInfo.Version
|
version = gameInfo.Version
|
||||||
iconCache = gameInfo.IconCache
|
icon = gameInfo.Icon
|
||||||
}
|
}
|
||||||
|
|
||||||
fun open() : Int {
|
fun open() : Int {
|
||||||
@ -53,5 +53,5 @@ class GameInfo {
|
|||||||
var TitleId: String? = null
|
var TitleId: String? = null
|
||||||
var Developer: String? = null
|
var Developer: String? = null
|
||||||
var Version: String? = null
|
var Version: String? = null
|
||||||
var IconCache: String? = null
|
var Icon: String? = null
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@ class HomeViewModel(
|
|||||||
val mainViewModel: MainViewModel? = null
|
val mainViewModel: MainViewModel? = null
|
||||||
) {
|
) {
|
||||||
private var isLoading: Boolean = false
|
private var isLoading: Boolean = false
|
||||||
private var gameList: SnapshotStateList<GameModel>? = null
|
|
||||||
private var loadedCache: List<GameModel> = listOf()
|
private var loadedCache: List<GameModel> = listOf()
|
||||||
private var gameFolderPath: DocumentFile? = null
|
private var gameFolderPath: DocumentFile? = null
|
||||||
private var sharedPref: SharedPreferences? = null
|
private var sharedPref: SharedPreferences? = null
|
||||||
|
val gameList: SnapshotStateList<GameModel> = SnapshotStateList()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
@ -68,53 +68,35 @@ class HomeViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadGameList(ignoreCache: Boolean = false) {
|
fun reloadGameList() {
|
||||||
var storage = activity?.storageHelper ?: return
|
var storage = activity?.storageHelper ?: return
|
||||||
|
|
||||||
if(isLoading)
|
if(isLoading)
|
||||||
return
|
return
|
||||||
val folder = gameFolderPath ?: return
|
val folder = gameFolderPath ?: return
|
||||||
|
|
||||||
|
gameList.clear()
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
thread {
|
||||||
if(!ignoreCache) {
|
try {
|
||||||
val files = mutableListOf<GameModel>()
|
val files = mutableListOf<GameModel>()
|
||||||
|
for (file in folder.search(false, DocumentFileType.FILE)) {
|
||||||
thread {
|
if (file.extension == "xci" || file.extension == "nsp")
|
||||||
try {
|
activity.let {
|
||||||
for (file in folder.search(false, DocumentFileType.FILE)) {
|
val item = GameModel(file, it)
|
||||||
if (file.extension == "xci" || file.extension == "nsp")
|
files.add(item)
|
||||||
activity.let {
|
gameList.add(item)
|
||||||
files.add(GameModel(file, it))
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedCache = files.toList()
|
|
||||||
|
|
||||||
isLoading = false
|
|
||||||
|
|
||||||
applyFilter()
|
|
||||||
} finally {
|
|
||||||
isLoading = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadedCache = files.toList()
|
||||||
|
|
||||||
|
isLoading = false
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
isLoading = false
|
|
||||||
applyFilter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyFilter() {
|
|
||||||
if(isLoading)
|
|
||||||
return
|
|
||||||
gameList?.clear()
|
|
||||||
gameList?.addAll(loadedCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setViewList(list: SnapshotStateList<GameModel>) {
|
|
||||||
gameList = list
|
|
||||||
reloadGameList(loadedCache.isNotEmpty())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearLoadedCache(){
|
fun clearLoadedCache(){
|
||||||
|
@ -2,7 +2,6 @@ package org.ryujinx.android.viewmodels
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PerformanceHintManager
|
import android.os.PerformanceHintManager
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@ -10,7 +9,6 @@ import androidx.navigation.NavHostController
|
|||||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import org.ryujinx.android.GameActivity
|
|
||||||
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.GraphicsConfiguration
|
||||||
@ -31,6 +29,8 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
var controller: GameController? = null
|
var controller: GameController? = null
|
||||||
var performanceManager: PerformanceManager? = null
|
var performanceManager: PerformanceManager? = null
|
||||||
var selected: GameModel? = null
|
var selected: GameModel? = null
|
||||||
|
var isMiiEditorLaunched = false
|
||||||
|
val userViewModel = UserViewModel()
|
||||||
private var gameTimeState: MutableState<Double>? = null
|
private var gameTimeState: MutableState<Double>? = null
|
||||||
private var gameFpsState: MutableState<Double>? = null
|
private var gameFpsState: MutableState<Double>? = null
|
||||||
private var fifoState: MutableState<Double>? = null
|
private var fifoState: MutableState<Double>? = null
|
||||||
@ -56,13 +56,13 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun closeGame() {
|
fun closeGame() {
|
||||||
RyujinxNative().deviceSignalEmulationClose()
|
RyujinxNative.instance.deviceSignalEmulationClose()
|
||||||
gameHost?.close()
|
gameHost?.close()
|
||||||
RyujinxNative().deviceCloseEmulation()
|
RyujinxNative.instance.deviceCloseEmulation()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGame(game:GameModel) : Boolean {
|
fun loadGame(game:GameModel) : Boolean {
|
||||||
val nativeRyujinx = RyujinxNative()
|
val nativeRyujinx = RyujinxNative.instance
|
||||||
|
|
||||||
val descriptor = game.open()
|
val descriptor = game.open()
|
||||||
|
|
||||||
@ -70,6 +70,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
gameModel = game
|
gameModel = game
|
||||||
|
isMiiEditorLaunched = false
|
||||||
|
|
||||||
val settings = QuickSettings(activity)
|
val settings = QuickSettings(activity)
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
if (!success)
|
if (!success)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
val nativeHelpers = NativeHelpers()
|
val nativeHelpers = NativeHelpers.instance
|
||||||
val nativeInterop = NativeGraphicsInterop()
|
val nativeInterop = NativeGraphicsInterop()
|
||||||
nativeInterop.VkRequiredExtensions = arrayOf(
|
nativeInterop.VkRequiredExtensions = arrayOf(
|
||||||
"VK_KHR_surface", "VK_KHR_android_surface"
|
"VK_KHR_surface", "VK_KHR_android_surface"
|
||||||
@ -118,7 +119,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driverHandle = NativeHelpers().loadDriver(
|
driverHandle = NativeHelpers.instance.loadDriver(
|
||||||
activity.applicationInfo.nativeLibraryDir!! + "/",
|
activity.applicationInfo.nativeLibraryDir!! + "/",
|
||||||
privateDriverPath,
|
privateDriverPath,
|
||||||
this.libraryName
|
this.libraryName
|
||||||
@ -148,7 +149,7 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
settings.enableDocked,
|
settings.enableDocked,
|
||||||
settings.enablePtc,
|
settings.enablePtc,
|
||||||
false,
|
false,
|
||||||
"UTC",
|
NativeHelpers.instance.storeStringJava("UTC"),
|
||||||
settings.ignoreMissingServices
|
settings.ignoreMissingServices
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -169,6 +170,112 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun loadMiiEditor() : Boolean {
|
||||||
|
val nativeRyujinx = RyujinxNative.instance
|
||||||
|
|
||||||
|
gameModel = null
|
||||||
|
isMiiEditorLaunched = true
|
||||||
|
|
||||||
|
val settings = QuickSettings(activity)
|
||||||
|
|
||||||
|
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
||||||
|
EnableShaderCache = settings.enableShaderCache
|
||||||
|
EnableTextureRecompression = settings.enableTextureRecompression
|
||||||
|
ResScale = settings.resScale
|
||||||
|
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val nativeHelpers = NativeHelpers.instance
|
||||||
|
val nativeInterop = NativeGraphicsInterop()
|
||||||
|
nativeInterop.VkRequiredExtensions = arrayOf(
|
||||||
|
"VK_KHR_surface", "VK_KHR_android_surface"
|
||||||
|
)
|
||||||
|
nativeInterop.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
|
||||||
|
nativeInterop.SurfaceHandle = 0
|
||||||
|
|
||||||
|
val driverViewModel = VulkanDriverViewModel(activity)
|
||||||
|
val drivers = driverViewModel.getAvailableDrivers()
|
||||||
|
|
||||||
|
var driverHandle = 0L
|
||||||
|
|
||||||
|
if (driverViewModel.selected.isNotEmpty()) {
|
||||||
|
val metaData = drivers.find { it.driverPath == driverViewModel.selected }
|
||||||
|
|
||||||
|
metaData?.apply {
|
||||||
|
val privatePath = activity.filesDir
|
||||||
|
val privateDriverPath = privatePath.canonicalPath + "/driver/"
|
||||||
|
val pD = File(privateDriverPath)
|
||||||
|
if (pD.exists())
|
||||||
|
pD.deleteRecursively()
|
||||||
|
|
||||||
|
pD.mkdirs()
|
||||||
|
|
||||||
|
val driver = File(driverViewModel.selected)
|
||||||
|
val parent = driver.parentFile
|
||||||
|
if (parent != null) {
|
||||||
|
for (file in parent.walkTopDown()) {
|
||||||
|
if (file.absolutePath == parent.absolutePath)
|
||||||
|
continue
|
||||||
|
file.copyTo(File(privateDriverPath + file.name), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driverHandle = NativeHelpers.instance.loadDriver(
|
||||||
|
activity.applicationInfo.nativeLibraryDir!! + "/",
|
||||||
|
privateDriverPath,
|
||||||
|
this.libraryName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
success = nativeRyujinx.graphicsInitializeRenderer(
|
||||||
|
nativeInterop.VkRequiredExtensions!!,
|
||||||
|
driverHandle
|
||||||
|
)
|
||||||
|
if (!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val semaphore = Semaphore(1, 0)
|
||||||
|
runBlocking {
|
||||||
|
semaphore.acquire()
|
||||||
|
launchOnUiThread {
|
||||||
|
// We are only able to initialize the emulation context on the main thread
|
||||||
|
success = nativeRyujinx.deviceInitialize(
|
||||||
|
settings.isHostMapped,
|
||||||
|
settings.useNce,
|
||||||
|
SystemLanguage.AmericanEnglish.ordinal,
|
||||||
|
RegionCode.USA.ordinal,
|
||||||
|
settings.enableVsync,
|
||||||
|
settings.enableDocked,
|
||||||
|
settings.enablePtc,
|
||||||
|
false,
|
||||||
|
NativeHelpers.instance.storeStringJava("UTC"),
|
||||||
|
settings.ignoreMissingServices
|
||||||
|
)
|
||||||
|
|
||||||
|
semaphore.release()
|
||||||
|
}
|
||||||
|
semaphore.acquire()
|
||||||
|
semaphore.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
success = nativeRyujinx.deviceLaunchMiiEditor()
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun clearPptcCache(titleId :String){
|
fun clearPptcCache(titleId :String){
|
||||||
if(titleId.isNotEmpty()){
|
if(titleId.isNotEmpty()){
|
||||||
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
|
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
|
||||||
@ -241,8 +348,9 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToGame() {
|
fun navigateToGame() {
|
||||||
val intent = Intent(activity, GameActivity::class.java)
|
activity.setFullScreen(true)
|
||||||
activity.startActivity(intent)
|
navController?.navigate("game")
|
||||||
|
activity.isGameRunning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProgressStates(
|
fun setProgressStates(
|
||||||
@ -255,15 +363,4 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
this.progress = progress
|
this.progress = progress
|
||||||
gameHost?.setProgressStates(showLoading, progressValue, progress)
|
gameHost?.setProgressStates(showLoading, progressValue, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRefreshUserState(refreshUser: MutableState<Boolean>)
|
|
||||||
{
|
|
||||||
this.refreshUser = refreshUser
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestUserRefresh(){
|
|
||||||
refreshUser?.apply {
|
|
||||||
value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
|
import org.ryujinx.android.NativeHelpers
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
class UserViewModel {
|
||||||
|
var openedUser = UserModel()
|
||||||
|
val userList = mutableListOf<UserModel>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
refreshUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshUsers() {
|
||||||
|
userList.clear()
|
||||||
|
val native = RyujinxNative.instance
|
||||||
|
val helper = NativeHelpers.instance
|
||||||
|
val decoder = Base64.getDecoder()
|
||||||
|
openedUser = UserModel()
|
||||||
|
openedUser.id = helper.getStringJava(native.userGetOpenedUser())
|
||||||
|
if (openedUser.id.isNotEmpty()) {
|
||||||
|
openedUser.username =
|
||||||
|
helper.getStringJava(native.userGetUserName(helper.storeStringJava(openedUser.id)))
|
||||||
|
openedUser.userPicture = decoder.decode(
|
||||||
|
helper.getStringJava(
|
||||||
|
native.userGetUserPicture(
|
||||||
|
helper.storeStringJava(openedUser.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val users = native.userGetAllUsers()
|
||||||
|
for (user in users) {
|
||||||
|
userList.add(
|
||||||
|
UserModel(
|
||||||
|
user,
|
||||||
|
helper.getStringJava(native.userGetUserName(helper.storeStringJava(user))),
|
||||||
|
decoder.decode(
|
||||||
|
helper.getStringJava(
|
||||||
|
native.userGetUserPicture(
|
||||||
|
helper.storeStringJava(user)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openUser(userModel: UserModel){
|
||||||
|
val native = RyujinxNative.instance
|
||||||
|
val helper = NativeHelpers.instance
|
||||||
|
native.userOpenUser(helper.storeStringJava(userModel.id))
|
||||||
|
|
||||||
|
refreshUsers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class UserModel(var id : String = "", var username: String = "", var userPicture: ByteArray? = null) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as UserModel
|
||||||
|
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (username != other.username) return false
|
||||||
|
if (userPicture != null) {
|
||||||
|
if (other.userPicture == null) return false
|
||||||
|
if (!userPicture.contentEquals(other.userPicture)) return false
|
||||||
|
} else if (other.userPicture != null) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = id.hashCode()
|
||||||
|
result = 31 * result + username.hashCode()
|
||||||
|
result = 31 * result + (userPicture?.contentHashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,319 @@
|
|||||||
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEventType
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.compose.ui.window.Popup
|
||||||
|
import compose.icons.CssGgIcons
|
||||||
|
import compose.icons.cssggicons.ToolbarBottom
|
||||||
|
import org.ryujinx.android.GameController
|
||||||
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.Icons
|
||||||
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
|
import org.ryujinx.android.viewmodels.QuickSettings
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class GameViews {
|
||||||
|
companion object {
|
||||||
|
@Composable
|
||||||
|
fun Main() {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
GameView(mainViewModel = MainActivity.mainViewModel!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameView(mainViewModel: MainViewModel) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
factory = { context ->
|
||||||
|
GameHost(context, mainViewModel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
GameOverlay(mainViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun GameOverlay(mainViewModel: MainViewModel) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
|
val ryujinxNative = RyujinxNative.instance
|
||||||
|
|
||||||
|
val showController = remember {
|
||||||
|
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
|
||||||
|
}
|
||||||
|
val enableVsync = remember {
|
||||||
|
mutableStateOf(QuickSettings(mainViewModel.activity).enableVsync)
|
||||||
|
}
|
||||||
|
val showMore = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val showLoading = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val progressValue = remember {
|
||||||
|
mutableStateOf(0.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
val progress = remember {
|
||||||
|
mutableStateOf("Loading")
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.setProgressStates(showLoading, progressValue, progress)
|
||||||
|
|
||||||
|
// touch surface
|
||||||
|
Surface(color = Color.Transparent, modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(0.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
awaitPointerEventScope {
|
||||||
|
while (true) {
|
||||||
|
val event = awaitPointerEvent()
|
||||||
|
if (showController.value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val change = event
|
||||||
|
.component1()
|
||||||
|
.firstOrNull()
|
||||||
|
change?.apply {
|
||||||
|
val position = this.position
|
||||||
|
|
||||||
|
when (event.type) {
|
||||||
|
PointerEventType.Press -> {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEventType.Release -> {
|
||||||
|
ryujinxNative.inputReleaseTouchPoint()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEventType.Move -> {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
}
|
||||||
|
if (!showLoading.value) {
|
||||||
|
GameController.Compose(mainViewModel)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CssGgIcons.ToolbarBottom,
|
||||||
|
contentDescription = "Open Panel"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showMore.value) {
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.BottomCenter,
|
||||||
|
onDismissRequest = { showMore.value = false }) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.padding(8.dp)) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = false
|
||||||
|
showController.value = !showController.value
|
||||||
|
mainViewModel.controller?.setVisible(showController.value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.videoGame(),
|
||||||
|
contentDescription = "Toggle Virtual Pad"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = false
|
||||||
|
enableVsync.value = !enableVsync.value
|
||||||
|
RyujinxNative.instance.graphicsRendererSetVsync(enableVsync.value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.vSync(),
|
||||||
|
tint = if (enableVsync.value) Color.Green else Color.Red,
|
||||||
|
contentDescription = "Toggle VSync"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showBackNotice = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
showBackNotice.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLoading.value) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(0.5f)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = progress.value)
|
||||||
|
|
||||||
|
if (progressValue.value > -1)
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
progress = progressValue.value
|
||||||
|
)
|
||||||
|
else
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBackNotice.value) {
|
||||||
|
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
||||||
|
Column {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = "Are you sure you want to exit the game?")
|
||||||
|
Text(text = "All unsaved data will be lost!")
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
showBackNotice.value = false
|
||||||
|
mainViewModel.closeGame()
|
||||||
|
mainViewModel.activity.setFullScreen(false)
|
||||||
|
mainViewModel.navController?.popBackStack()
|
||||||
|
mainViewModel.activity.isGameRunning = false
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Exit Game")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
showBackNotice.value = false
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameStats(mainViewModel: MainViewModel) {
|
||||||
|
val fifo = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
val gameFps = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
val gameTime = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surface.copy(0.4f)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
var gameTimeVal = 0.0
|
||||||
|
if (!gameTime.value.isInfinite())
|
||||||
|
gameTimeVal = gameTime.value
|
||||||
|
Text(text = "${String.format("%.3f", fifo.value)} %")
|
||||||
|
Text(text = "${String.format("%.3f", gameFps.value)} FPS")
|
||||||
|
Text(text = "${String.format("%.3f", gameTimeVal)} ms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.setStatStates(fifo, gameFps, gameTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
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.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -58,14 +57,9 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
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.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
import org.ryujinx.android.MainActivity
|
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
|
||||||
import org.ryujinx.android.viewmodels.GameModel
|
import org.ryujinx.android.viewmodels.GameModel
|
||||||
import org.ryujinx.android.viewmodels.HomeViewModel
|
import org.ryujinx.android.viewmodels.HomeViewModel
|
||||||
import java.io.File
|
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -81,7 +75,6 @@ class HomeViews {
|
|||||||
viewModel: HomeViewModel = HomeViewModel(),
|
viewModel: HomeViewModel = HomeViewModel(),
|
||||||
navController: NavHostController? = null
|
navController: NavHostController? = null
|
||||||
) {
|
) {
|
||||||
val native = RyujinxNative()
|
|
||||||
val showAppActions = remember { mutableStateOf(false) }
|
val showAppActions = remember { mutableStateOf(false) }
|
||||||
val showLoading = remember { mutableStateOf(false) }
|
val showLoading = remember { mutableStateOf(false) }
|
||||||
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
||||||
@ -90,31 +83,7 @@ class HomeViews {
|
|||||||
val query = remember {
|
val query = remember {
|
||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
val refresh = remember {
|
|
||||||
mutableStateOf(true)
|
|
||||||
}
|
|
||||||
val refreshUser = remember {
|
|
||||||
mutableStateOf(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.mainViewModel?.setRefreshUserState(refreshUser)
|
|
||||||
val user = remember {
|
|
||||||
mutableStateOf("")
|
|
||||||
}
|
|
||||||
val pic = remember {
|
|
||||||
mutableStateOf(ByteArray(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshUser.value) {
|
|
||||||
native.userGetOpenedUser()
|
|
||||||
user.value = NativeHelpers().popStringJava()
|
|
||||||
if (user.value.isNotEmpty()) {
|
|
||||||
val decoder = Base64.getDecoder()
|
|
||||||
pic.value = decoder.decode(native.userGetUserPicture(user.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshUser.value = false;
|
|
||||||
}
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -148,12 +117,14 @@ class HomeViews {
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
navController?.navigate("user")
|
navController?.navigate("user")
|
||||||
}) {
|
}) {
|
||||||
if (pic.value.isNotEmpty()) {
|
if (viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) {
|
||||||
|
val pic =
|
||||||
|
viewModel.mainViewModel.userViewModel.openedUser.userPicture
|
||||||
Image(
|
Image(
|
||||||
bitmap = BitmapFactory.decodeByteArray(
|
bitmap = BitmapFactory.decodeByteArray(
|
||||||
pic.value,
|
pic,
|
||||||
0,
|
0,
|
||||||
pic.value.size
|
pic?.size ?: 0
|
||||||
)
|
)
|
||||||
.asImageBitmap(),
|
.asImageBitmap(),
|
||||||
contentDescription = "user image",
|
contentDescription = "user image",
|
||||||
@ -184,9 +155,26 @@ class HomeViews {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
BottomAppBar(actions = {
|
BottomAppBar(
|
||||||
|
actions = {
|
||||||
if (showAppActions.value) {
|
if (showAppActions.value) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
|
if(viewModel.mainViewModel?.selected != null) {
|
||||||
|
thread {
|
||||||
|
showLoading.value = true
|
||||||
|
val success =
|
||||||
|
viewModel.mainViewModel?.loadGame(viewModel.mainViewModel.selected!!)
|
||||||
|
?: false
|
||||||
|
if (success) {
|
||||||
|
launchOnUiThread {
|
||||||
|
viewModel.mainViewModel?.navigateToGame()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.mainViewModel?.selected!!.close()
|
||||||
|
}
|
||||||
|
showLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface),
|
org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface),
|
||||||
@ -210,13 +198,17 @@ class HomeViews {
|
|||||||
Text(text = "Clear PPTC Cache")
|
Text(text = "Clear PPTC Cache")
|
||||||
}, onClick = {
|
}, onClick = {
|
||||||
showAppMenu.value = false
|
showAppMenu.value = false
|
||||||
viewModel.mainViewModel?.clearPptcCache(viewModel.mainViewModel?.selected?.titleId ?: "")
|
viewModel.mainViewModel?.clearPptcCache(
|
||||||
|
viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
|
)
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
Text(text = "Purge Shader Cache")
|
Text(text = "Purge Shader Cache")
|
||||||
}, onClick = {
|
}, onClick = {
|
||||||
showAppMenu.value = false
|
showAppMenu.value = false
|
||||||
viewModel.mainViewModel?.purgeShaderCache(viewModel.mainViewModel?.selected?.titleId ?: "")
|
viewModel.mainViewModel?.purgeShaderCache(
|
||||||
|
viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
|
)
|
||||||
})
|
})
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
Text(text = "Manage Updates")
|
Text(text = "Manage Updates")
|
||||||
@ -233,6 +225,39 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*\val showAppletMenu = remember { mutableStateOf(false) }
|
||||||
|
Box {
|
||||||
|
IconButton(onClick = {
|
||||||
|
showAppletMenu.value = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
org.ryujinx.android.Icons.applets(MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = "Applets"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showAppletMenu.value,
|
||||||
|
onDismissRequest = { showAppletMenu.value = false }) {
|
||||||
|
DropdownMenuItem(text = {
|
||||||
|
Text(text = "Launch Mii Editor")
|
||||||
|
}, onClick = {
|
||||||
|
showAppletMenu.value = false
|
||||||
|
showLoading.value = true
|
||||||
|
thread {
|
||||||
|
val success =
|
||||||
|
viewModel.mainViewModel?.loadMiiEditor() ?: false
|
||||||
|
if (success) {
|
||||||
|
launchOnUiThread {
|
||||||
|
viewModel.mainViewModel?.navigateToGame()
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
viewModel.mainViewModel!!.isMiiEditorLaunched = false
|
||||||
|
showLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}*/
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
@ -255,12 +280,7 @@ class HomeViews {
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Box(modifier = Modifier.padding(contentPadding)) {
|
Box(modifier = Modifier.padding(contentPadding)) {
|
||||||
val list = remember {
|
val list = remember {
|
||||||
mutableStateListOf<GameModel>()
|
viewModel.gameList
|
||||||
}
|
|
||||||
if (refresh.value) {
|
|
||||||
viewModel.setViewList(list)
|
|
||||||
refresh.value = false
|
|
||||||
showAppActions.value = false
|
|
||||||
}
|
}
|
||||||
val selectedModel = remember {
|
val selectedModel = remember {
|
||||||
mutableStateOf(viewModel.mainViewModel?.selected)
|
mutableStateOf(viewModel.mainViewModel?.selected)
|
||||||
@ -365,6 +385,7 @@ class HomeViews {
|
|||||||
val color =
|
val color =
|
||||||
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
|
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
|
||||||
|
|
||||||
|
val decoder = Base64.getDecoder()
|
||||||
Surface(
|
Surface(
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
color = color,
|
color = color,
|
||||||
@ -409,13 +430,12 @@ class HomeViews {
|
|||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
if (!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") {
|
if (!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") {
|
||||||
val iconSource =
|
if (gameModel.icon?.isNotEmpty() == true) {
|
||||||
MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
|
val pic = decoder.decode(gameModel.icon)
|
||||||
val imageFile = File(iconSource)
|
|
||||||
if (imageFile.exists()) {
|
|
||||||
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
||||||
AsyncImage(
|
Image(
|
||||||
model = imageFile,
|
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
|
||||||
|
.asImageBitmap(),
|
||||||
contentDescription = gameModel.titleName + " icon",
|
contentDescription = gameModel.titleName + " icon",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp)
|
.padding(end = 8.dp)
|
||||||
|
@ -17,6 +17,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, navController) }
|
||||||
|
composable("game") { GameViews.Main() }
|
||||||
composable("settings") {
|
composable("settings") {
|
||||||
SettingViews.Main(
|
SettingViews.Main(
|
||||||
SettingsViewModel(
|
SettingsViewModel(
|
||||||
|
@ -306,7 +306,7 @@ class SettingViews {
|
|||||||
thread {
|
thread {
|
||||||
Helpers.importAppData(this, isImporting)
|
Helpers.importAppData(this, isImporting)
|
||||||
showImportCompletion.value = true
|
showImportCompletion.value = true
|
||||||
mainViewModel.requestUserRefresh()
|
mainViewModel.userViewModel.refreshUsers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, modifier = Modifier.padding(horizontal = 8.dp)) {
|
}, modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||||
@ -329,7 +329,7 @@ class SettingViews {
|
|||||||
AlertDialog(onDismissRequest = {
|
AlertDialog(onDismissRequest = {
|
||||||
showImportCompletion.value = false
|
showImportCompletion.value = false
|
||||||
importFile.value = null
|
importFile.value = null
|
||||||
mainViewModel.requestUserRefresh()
|
mainViewModel.userViewModel.refreshUsers()
|
||||||
mainViewModel.homeViewModel.clearLoadedCache()
|
mainViewModel.homeViewModel.clearLoadedCache()
|
||||||
}) {
|
}) {
|
||||||
Card(
|
Card(
|
||||||
@ -461,7 +461,8 @@ class SettingViews {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(300.dp)
|
.height(350.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -494,7 +495,7 @@ class SettingViews {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
|
@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
@ -62,6 +64,7 @@ class TitleUpdateViews {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(250.dp)
|
.height(250.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
Row(modifier = Modifier.padding(8.dp)) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
|
@ -26,6 +26,7 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -36,40 +37,23 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
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.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import org.ryujinx.android.NativeHelpers
|
|
||||||
import org.ryujinx.android.RyujinxNative
|
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import java.util.Base64
|
|
||||||
|
|
||||||
class UserViews {
|
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, navController: NavHostController? = null) {
|
||||||
val ryujinxNative = RyujinxNative()
|
val reload = remember {
|
||||||
val decoder = Base64.getDecoder()
|
mutableStateOf(true)
|
||||||
ryujinxNative.userGetOpenedUser()
|
|
||||||
val openedUser = remember {
|
|
||||||
mutableStateOf(NativeHelpers().popStringJava())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val openedUserPic = remember {
|
|
||||||
mutableStateOf(decoder.decode(ryujinxNative.userGetUserPicture(openedUser.value)))
|
|
||||||
}
|
|
||||||
val openedUserName = remember {
|
|
||||||
mutableStateOf(ryujinxNative.userGetUserName(openedUser.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
val userList = remember {
|
|
||||||
mutableListOf("")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
userList.clear()
|
viewModel?.userViewModel?.refreshUsers()
|
||||||
userList.addAll(ryujinxNative.userGetAllUsers())
|
reload.value = true
|
||||||
|
}
|
||||||
|
LaunchedEffect(reload.value) {
|
||||||
|
reload.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh()
|
|
||||||
|
|
||||||
Scaffold(modifier = Modifier.fillMaxSize(),
|
Scaffold(modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -102,25 +86,28 @@ class UserViews {
|
|||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
if (viewModel?.userViewModel?.openedUser?.id?.isNotEmpty() == true) {
|
||||||
bitmap = BitmapFactory.decodeByteArray(
|
val openUser = viewModel.userViewModel.openedUser
|
||||||
openedUserPic.value,
|
Image(
|
||||||
0,
|
bitmap = BitmapFactory.decodeByteArray(
|
||||||
openedUserPic.value.size
|
openUser.userPicture,
|
||||||
).asImageBitmap(),
|
0,
|
||||||
contentDescription = "selected image",
|
openUser.userPicture?.size ?: 0
|
||||||
contentScale = ContentScale.Crop,
|
).asImageBitmap(),
|
||||||
modifier = Modifier
|
contentDescription = "selected image",
|
||||||
.padding(4.dp)
|
contentScale = ContentScale.Crop,
|
||||||
.size(96.dp)
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.padding(4.dp)
|
||||||
)
|
.size(96.dp)
|
||||||
Column(
|
.clip(CircleShape)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
)
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
Column(
|
||||||
) {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Text(text = openedUserName.value)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
Text(text = openedUser.value)
|
) {
|
||||||
|
Text(text = openUser.username)
|
||||||
|
Text(text = openUser.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,34 +126,32 @@ class UserViews {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Adaptive(minSize = 96.dp),
|
columns = GridCells.Adaptive(minSize = 96.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
) {
|
) {
|
||||||
items(userList) { user ->
|
if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
|
||||||
val pic = decoder.decode(ryujinxNative.userGetUserPicture(user))
|
items(viewModel.userViewModel.userList) { user ->
|
||||||
val name = ryujinxNative.userGetUserName(user)
|
Image(
|
||||||
Image(
|
bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0)
|
||||||
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
|
.asImageBitmap(),
|
||||||
.asImageBitmap(),
|
contentDescription = "selected image",
|
||||||
contentDescription = "selected image",
|
contentScale = ContentScale.Crop,
|
||||||
contentScale = ContentScale.Crop,
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxSize()
|
||||||
.fillMaxSize()
|
.padding(4.dp)
|
||||||
.padding(4.dp)
|
.clip(CircleShape)
|
||||||
.clip(CircleShape)
|
.align(Alignment.CenterHorizontally)
|
||||||
.align(Alignment.CenterHorizontally)
|
.combinedClickable(
|
||||||
.combinedClickable(
|
onClick = {
|
||||||
onClick = {
|
viewModel.userViewModel.openUser(user)
|
||||||
ryujinxNative.userOpenUser(user)
|
reload.value = true
|
||||||
openedUser.value = user
|
})
|
||||||
openedUserPic.value = pic
|
)
|
||||||
openedUserName.value = name
|
}
|
||||||
viewModel?.requestUserRefresh()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user