android - fixes a few crashes in the user and home views

This commit is contained in:
Emmanuel Hansen 2023-10-31 19:28:36 +00:00
parent 71f3d22db8
commit 4fc253384b
20 changed files with 573 additions and 236 deletions

View File

@ -21,7 +21,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;
@ -84,8 +83,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(
@ -95,7 +95,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);
@ -118,6 +118,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();
} }
@ -132,9 +133,10 @@ namespace LibRyujinx
JBoolean enableDockedMode, JBoolean enableDockedMode,
JBoolean enablePtc, JBoolean enablePtc,
JBoolean enableInternetAccess, JBoolean enableInternetAccess,
JStringLocalRef timeZone, JLong timeZoneId,
JBoolean ignoreMissingServices) JBoolean ignoreMissingServices)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
AudioDriver = new OboeHardwareDeviceDriver(); AudioDriver = new OboeHardwareDeviceDriver();
return InitializeDevice(isHostMapped, return InitializeDevice(isHostMapped,
useNce, useNce,
@ -144,13 +146,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;
@ -159,6 +162,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;
@ -167,6 +171,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;
@ -175,6 +180,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;
@ -185,10 +191,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);
@ -196,26 +215,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;
@ -229,6 +252,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;
@ -271,6 +295,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;
@ -283,6 +308,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;
@ -370,12 +396,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();
@ -387,21 +415,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;
@ -427,24 +458,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;
@ -466,88 +485,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 JLong 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();
return storeString(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) ?? "";
@ -555,16 +588,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) ?? "";
@ -574,6 +609,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());
@ -582,6 +618,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) ?? "";
@ -591,15 +628,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);
} }
@ -607,6 +646,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);

View File

@ -1,4 +1,6 @@
using ARMeilleure.Translation; using ARMeilleure.Translation;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
@ -70,6 +72,13 @@ namespace LibRyujinx
return (isXci ? emulationContext?.LoadXci(stream) : emulationContext.LoadNsp(stream)) ?? false; return (isXci ? emulationContext?.LoadXci(stream) : emulationContext.LoadNsp(stream)) ?? false;
} }
public static bool LaunchMiiEditApplet()
{
string contentPath = SwitchDevice.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
return LoadApplication(contentPath);
}
public static bool LoadApplication(string? path) public static bool LoadApplication(string? path)
{ {
var emulationContext = SwitchDevice.EmulationContext; var emulationContext = SwitchDevice.EmulationContext;

View File

@ -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) {

View File

@ -29,7 +29,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
private var _isStarted: Boolean = false private var _isStarted: Boolean = false
private val nativeWindow: NativeWindow private val nativeWindow: NativeWindow
private var _nativeRyujinx: RyujinxNative = RyujinxNative() private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
init { init {
holder.addCallback(this) holder.addCallback(this)
@ -84,7 +84,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
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)
@ -110,7 +110,7 @@ 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)
@ -133,7 +133,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
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( mainViewModel.updateStats(

View File

@ -216,7 +216,7 @@ class Helpers {
} }
} finally { } finally {
isImporting.value = false isImporting.value = false
RyujinxNative().deviceReloadFilesystem() RyujinxNative.instance.deviceReloadFilesystem()
} }
} }
} }

View File

@ -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(

View File

@ -64,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?) {
@ -171,7 +171,7 @@ class MainActivity : BaseActivity() {
super.onStop() super.onStop()
if(isGameRunning) { if(isGameRunning) {
NativeHelpers().setTurboMode(false) NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false) force60HzRefreshRate(false)
} }
} }
@ -181,7 +181,7 @@ class MainActivity : BaseActivity() {
if(isGameRunning) { if(isGameRunning) {
setFullScreen(true) setFullScreen(true)
NativeHelpers().setTurboMode(true) NativeHelpers.instance.setTurboMode(true)
force60HzRefreshRate(true) force60HzRefreshRate(true)
} }
} }
@ -190,7 +190,7 @@ class MainActivity : BaseActivity() {
super.onPause() super.onPause()
if(isGameRunning) { if(isGameRunning) {
NativeHelpers().setTurboMode(false) NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false) force60HzRefreshRate(false)
} }
} }

View File

@ -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")
} }

View File

@ -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

View File

@ -5,7 +5,7 @@ import android.view.MotionEvent
class PhysicalControllerManager(val activity: MainActivity) { class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1 private var controllerId: Int = -1
private var ryujinxNative: RyujinxNative = RyujinxNative() private var ryujinxNative: RyujinxNative = RyujinxNative.instance
fun onKeyEvent(event: KeyEvent) : Boolean{ fun onKeyEvent(event: KeyEvent) : Boolean{
if(controllerId != -1) { if(controllerId != -1) {

View File

@ -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() : Long 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)
} }

View File

@ -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 = "")

View File

@ -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
} }

View File

@ -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(){

View File

@ -29,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
@ -54,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()
@ -68,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)
@ -81,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"
@ -116,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
@ -146,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
) )
@ -167,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"
@ -254,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
}
}
} }

View File

@ -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
}
}

View File

@ -74,7 +74,7 @@ class GameViews {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
GameStats(mainViewModel) GameStats(mainViewModel)
val ryujinxNative = RyujinxNative() val ryujinxNative = RyujinxNative.instance
val showController = remember { val showController = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController) mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
@ -183,7 +183,7 @@ class GameViews {
IconButton(modifier = Modifier.padding(4.dp), onClick = { IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false showMore.value = false
enableVsync.value = !enableVsync.value enableVsync.value = !enableVsync.value
RyujinxNative().graphicsRendererSetVsync(enableVsync.value) RyujinxNative.instance.graphicsRendererSetVsync(enableVsync.value)
}) { }) {
Icon( Icon(
imageVector = Icons.vSync(), imageVector = Icons.vSync(),

View File

@ -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) {
val id = native.userGetOpenedUser()
user.value = NativeHelpers().getStringJava(id)
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)

View File

@ -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(

View File

@ -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)
val id = ryujinxNative.userGetOpenedUser()
val openedUser = remember {
mutableStateOf(NativeHelpers().getStringJava(id))
} }
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()
})
)
} }
} }
} }