diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index 20858eddd..1f0a46fa4 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -21,7 +21,6 @@ using System.IO; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; -using System.Security.Cryptography; using System.Text; using System.Threading; @@ -84,8 +83,9 @@ namespace LibRyujinx } [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; Logger.AddTarget( @@ -95,7 +95,7 @@ namespace LibRyujinx AsyncLogTargetOverflowAction.Block )); - var path = GetString(jEnv, jpath); + var path = GetStoredString(jpathId); var init = Initialize(path, enableDebugLogs); @@ -118,6 +118,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")] public static void JniReloadFileSystem() { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SwitchDevice?.ReloadFileSystem(); } @@ -132,9 +133,10 @@ namespace LibRyujinx JBoolean enableDockedMode, JBoolean enablePtc, JBoolean enableInternetAccess, - JStringLocalRef timeZone, + JLong timeZoneId, JBoolean ignoreMissingServices) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); AudioDriver = new OboeHardwareDeviceDriver(); return InitializeDevice(isHostMapped, useNce, @@ -144,13 +146,14 @@ namespace LibRyujinx enableDockedMode, enablePtc, enableInternetAccess, - GetString(jEnv, timeZone), + GetStoredString(timeZoneId), ignoreMissingServices); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")] public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0; return stats; @@ -159,6 +162,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")] public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0; return stats; @@ -167,6 +171,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")] public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0; return stats; @@ -175,6 +180,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")] public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); if (SwitchDevice?.EmulationContext == null) { return false; @@ -185,10 +191,23 @@ namespace LibRyujinx return LoadApplication(path); } - [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")] - public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JLong titleId) + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")] + 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); @@ -196,26 +215,30 @@ namespace LibRyujinx } [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")] public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SignalEmulationClose(); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")] public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); CloseEmulation(); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")] public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); if (SwitchDevice?.EmulationContext == null) { return false; @@ -229,6 +252,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")] public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); JEnvValue value = jEnv.Environment; ref JNativeInterface jInterface = ref value.Functions; IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer; @@ -271,6 +295,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")] public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); _surfacePtr = surfacePtr; _window = window; @@ -283,6 +308,7 @@ namespace LibRyujinx JArrayLocalRef extensionsArray, JLong driverHandle) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); if (Renderer != null) { return false; @@ -370,12 +396,14 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")] 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); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")] public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SetSwapBuffersCallback(() => { var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime(); @@ -387,21 +415,24 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")] public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var info = GetGameInfo(GetString(jEnv, path)); - return GetInfo(jEnv, info, out SHA256 _); + return GetInfo(jEnv, info); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")] 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); 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"); JEnvValue value = jEnv.Environment; @@ -427,24 +458,12 @@ namespace LibRyujinx var newGlobal = newGlobalRef(jEnv, javaClass._value); var constructor = getMethod(jEnv, javaClass, GetCCharSequence(""), GetCCharSequence("()V")); var newObj = newObject(jEnv, javaClass, constructor, 0); - sha = SHA256.Create(); - var iconCacheByte = sha.ComputeHash(info?.Icon ?? Array.Empty()); - 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()); - } setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value); setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value); setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value); setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value); - setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("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()))._value); setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d); return newObj; @@ -466,88 +485,102 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")] public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SetVsyncState(enabled); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")] public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); _swapBuffersCallback = Marshal.GetDelegateForFunctionPointer(swapBuffersCallback); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")] public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); InitializeInput(width, height); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")] public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SetClientSize(width, height); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")] public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SetTouchPoint(x, y); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")] public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); ReleaseTouchPoint(); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")] public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); UpdateInput(); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")] public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); SetButtonPressed((GamepadButtonInputId)(int)button, id); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")] 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); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")] 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); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")] public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); return ConnectGamepad(index); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")] public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var userId = GetOpenedUser(); return storeString(userId); } [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")] 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 picture = GetString(jEnv, picturePtr) ?? ""; @@ -555,16 +588,18 @@ namespace LibRyujinx } [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")] 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 userName = GetString(jEnv, userNamePtr) ?? ""; @@ -574,6 +609,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")] public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var users = GetAllUsers(); return CreateStringArray(jEnv, users.ToList()); @@ -582,6 +618,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")] 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 picture = GetString(jEnv, picturePtr) ?? ""; @@ -591,15 +628,17 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")] public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var userId = GetString(jEnv, userIdPtr) ?? ""; DeleteUser(userId); } [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); } @@ -607,6 +646,7 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")] public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr) { + Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); var userId = GetString(jEnv, userIdPtr) ?? ""; CloseUser(userId); diff --git a/src/LibRyujinx/LibRyujinx.Device.cs b/src/LibRyujinx/LibRyujinx.Device.cs index 724630bbb..f8dde580c 100644 --- a/src/LibRyujinx/LibRyujinx.Device.cs +++ b/src/LibRyujinx/LibRyujinx.Device.cs @@ -1,4 +1,6 @@ using ARMeilleure.Translation; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.Input.HLE; @@ -70,6 +72,13 @@ namespace LibRyujinx 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) { var emulationContext = SwitchDevice.EmulationContext; diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt index f8781021e..2e77ad21b 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameController.kt @@ -96,7 +96,7 @@ class GameController(var activity: Activity) { rightGamePad.gravityX = 1f rightGamePad.gravityY = 1f - ryujinxNative = RyujinxNative() + ryujinxNative = RyujinxNative.instance } fun setVisible(isVisible: Boolean){ @@ -110,7 +110,7 @@ class GameController(var activity: Activity) { fun connect(){ if(controllerId == -1) - controllerId = RyujinxNative().inputConnectGamepad(0) + controllerId = RyujinxNative.instance.inputConnectGamepad(0) } private fun handleEvent(ev: Event) { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index d483c11ee..48cfb4ff6 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -29,7 +29,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su private var _isStarted: Boolean = false private val nativeWindow: NativeWindow - private var _nativeRyujinx: RyujinxNative = RyujinxNative() + private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance init { holder.addCallback(this) @@ -84,7 +84,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su if (_isStarted) return - game = mainViewModel.gameModel + game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel; _nativeRyujinx.inputInitialize(width, height) @@ -110,7 +110,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su _updateThread = thread(start = true) { var c = 0 - val helper = NativeHelpers() + val helper = NativeHelpers.instance while (_isStarted) { _nativeRyujinx.inputUpdate() Thread.sleep(1) @@ -133,7 +133,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su if (c >= 1000) { if (helper.getProgressValue() == -1f) progress?.apply { - this.value = "Loading ${game!!.titleName}" + this.value = "Loading ${if(mainViewModel.isMiiEditorLaunched) "Mii Editor" else game!!.titleName}" } c = 0 mainViewModel.updateStats( diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt index aef544c7a..7cda8a3ac 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt @@ -216,7 +216,7 @@ class Helpers { } } finally { isImporting.value = false - RyujinxNative().deviceReloadFilesystem() + RyujinxNative.instance.deviceReloadFilesystem() } } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Icons.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Icons.kt index 54a508298..b6e2cea6b 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Icons.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Icons.kt @@ -23,6 +23,121 @@ class Icons { companion object{ /// Icons exported from https://www.composables.com/icons @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 { return remember { ImageVector.Builder( diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt index 499ac48c6..1c851484d 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt @@ -64,7 +64,7 @@ class MainActivity : BaseActivity() { return val appPath: String = AppPath - val success = RyujinxNative().initialize(appPath, false) + val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath), false) _isInit = success } override fun onCreate(savedInstanceState: Bundle?) { @@ -171,7 +171,7 @@ class MainActivity : BaseActivity() { super.onStop() if(isGameRunning) { - NativeHelpers().setTurboMode(false) + NativeHelpers.instance.setTurboMode(false) force60HzRefreshRate(false) } } @@ -181,7 +181,7 @@ class MainActivity : BaseActivity() { if(isGameRunning) { setFullScreen(true) - NativeHelpers().setTurboMode(true) + NativeHelpers.instance.setTurboMode(true) force60HzRefreshRate(true) } } @@ -190,7 +190,7 @@ class MainActivity : BaseActivity() { super.onPause() if(isGameRunning) { - NativeHelpers().setTurboMode(false) + NativeHelpers.instance.setTurboMode(false) force60HzRefreshRate(false) } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt index 9bfba4c19..dceab1e1e 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt @@ -5,6 +5,7 @@ import android.view.Surface class NativeHelpers { companion object { + val instance = NativeHelpers() init { System.loadLibrary("ryujinxjni") } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt index 74f96c058..dee3b4d61 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeWindow.kt @@ -4,7 +4,7 @@ import android.view.SurfaceView class NativeWindow(val surface: SurfaceView) { var nativePointer: Long - var nativeHelpers: NativeHelpers = NativeHelpers() + var nativeHelpers: NativeHelpers = NativeHelpers.instance private var _swapInterval : Int = 0 var maxSwapInterval : Int = 0 diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt index 0b7b16266..c61ac456e 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt @@ -5,7 +5,7 @@ import android.view.MotionEvent class PhysicalControllerManager(val activity: MainActivity) { private var controllerId: Int = -1 - private var ryujinxNative: RyujinxNative = RyujinxNative() + private var ryujinxNative: RyujinxNative = RyujinxNative.instance fun onKeyEvent(event: KeyEvent) : Boolean{ if(controllerId != -1) { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt index 2ef30c9c7..989226de6 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt @@ -4,10 +4,10 @@ import org.ryujinx.android.viewmodels.GameInfo @Suppress("KotlinJniMissingFunction") class RyujinxNative { - - external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean + external fun initialize(appPath: Long, enableDebugLogs : Boolean): Boolean companion object { + val instance: RyujinxNative = RyujinxNative() init { System.loadLibrary("ryujinx") } @@ -20,7 +20,7 @@ class RyujinxNative { enableDockedMode : Boolean, enablePtc : Boolean, enableInternetAccess : Boolean, - timeZone : String, + timeZone : Long, ignoreMissingServices : Boolean): Boolean external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean external fun graphicsInitializeRenderer( @@ -29,6 +29,7 @@ class RyujinxNative { ): Boolean external fun deviceLoad(game: String): Boolean + external fun deviceLaunchMiiEditor(): Boolean external fun deviceGetGameFrameRate(): Double external fun deviceGetGameFrameTime(): Double external fun deviceGetGameFifo(): Double @@ -51,16 +52,16 @@ class RyujinxNative { external fun graphicsSetSurface(surface: Long, window: Long) external fun deviceCloseEmulation() external fun deviceSignalEmulationClose() - external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String - external fun deviceGetDlcContentList(path: String, titleId: Long) : Array + external fun deviceGetDlcTitleId(path: Long, ncaPath: Long) : Long + external fun deviceGetDlcContentList(path: Long, titleId: Long) : Array 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 userGetUserName(userId: String) : String + external fun userGetUserName(userId: Long) : Long external fun userSetUserName(userId: String, userName: String) external fun userGetAllUsers() : Array external fun userAddUser(username: String, picture: String) external fun userDeleteUser(userId: String) - external fun userOpenUser(userId: String) + external fun userOpenUser(userId: Long) external fun userCloseUser(userId: String) } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/DlcViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/DlcViewModel.kt index 1d440c11c..a61eadf49 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/DlcViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/DlcViewModel.kt @@ -11,6 +11,7 @@ import com.anggrayudi.storage.file.getAbsolutePath import com.google.gson.Gson import com.google.gson.reflect.TypeToken import org.ryujinx.android.MainActivity +import org.ryujinx.android.NativeHelpers import org.ryujinx.android.RyujinxNative import java.io.File @@ -39,8 +40,8 @@ class DlcViewModel(val titleId: String) { val path = file.getAbsolutePath(storageHelper.storage.context) if (path.isNotEmpty()) { data?.apply { - var contents = RyujinxNative().deviceGetDlcContentList( - path, + var contents = RyujinxNative.instance.deviceGetDlcContentList( + NativeHelpers.instance.storeStringJava(path), titleId.toLong(16) ) @@ -101,7 +102,7 @@ class DlcViewModel(val titleId: String) { enabled, containerPath, 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 = mutableStateOf(false), var containerPath: String = "", var fullPath: String = "", - var titleId: String = "") \ No newline at end of file + var titleId: String = "") diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt index bf166b1c9..376ae33a8 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/GameModel.kt @@ -15,12 +15,12 @@ class GameModel(var file: DocumentFile, val context: Context) { var titleId: String? = null var developer: String? = null var version: String? = null - var iconCache: String? = null + var icon: String? = null init { fileName = file.name var pid = open() - val gameInfo = RyujinxNative().deviceGetGameInfo(pid, file.extension.contains("xci")) + val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, file.extension.contains("xci")) close() fileSize = gameInfo.FileSize @@ -28,7 +28,7 @@ class GameModel(var file: DocumentFile, val context: Context) { titleName = gameInfo.TitleName developer = gameInfo.Developer version = gameInfo.Version - iconCache = gameInfo.IconCache + icon = gameInfo.Icon } fun open() : Int { @@ -53,5 +53,5 @@ class GameInfo { var TitleId: String? = null var Developer: String? = null var Version: String? = null - var IconCache: String? = null + var Icon: String? = null } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt index bbfeb9e8a..2419dbb37 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt @@ -18,10 +18,10 @@ class HomeViewModel( val mainViewModel: MainViewModel? = null ) { private var isLoading: Boolean = false - private var gameList: SnapshotStateList? = null private var loadedCache: List = listOf() private var gameFolderPath: DocumentFile? = null private var sharedPref: SharedPreferences? = null + val gameList: SnapshotStateList = SnapshotStateList() init { if (activity != null) { @@ -68,53 +68,35 @@ class HomeViewModel( ) } - fun reloadGameList(ignoreCache: Boolean = false) { + fun reloadGameList() { var storage = activity?.storageHelper ?: return if(isLoading) return val folder = gameFolderPath ?: return + + gameList.clear() isLoading = true - - if(!ignoreCache) { - val files = mutableListOf() - - thread { - try { - for (file in folder.search(false, DocumentFileType.FILE)) { - if (file.extension == "xci" || file.extension == "nsp") - activity.let { - files.add(GameModel(file, it)) - } - } - - loadedCache = files.toList() - - isLoading = false - - applyFilter() - } finally { - isLoading = false + thread { + try { + val files = mutableListOf() + for (file in folder.search(false, DocumentFileType.FILE)) { + if (file.extension == "xci" || file.extension == "nsp") + activity.let { + val item = GameModel(file, it) + files.add(item) + gameList.add(item) + } } + + 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) { - gameList = list - reloadGameList(loadedCache.isNotEmpty()) } fun clearLoadedCache(){ diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt index ea859bb0f..7f451449d 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt @@ -29,6 +29,8 @@ class MainViewModel(val activity: MainActivity) { var controller: GameController? = null var performanceManager: PerformanceManager? = null var selected: GameModel? = null + var isMiiEditorLaunched = false + val userViewModel = UserViewModel() private var gameTimeState: MutableState? = null private var gameFpsState: MutableState? = null private var fifoState: MutableState? = null @@ -54,13 +56,13 @@ class MainViewModel(val activity: MainActivity) { } fun closeGame() { - RyujinxNative().deviceSignalEmulationClose() + RyujinxNative.instance.deviceSignalEmulationClose() gameHost?.close() - RyujinxNative().deviceCloseEmulation() + RyujinxNative.instance.deviceCloseEmulation() } fun loadGame(game:GameModel) : Boolean { - val nativeRyujinx = RyujinxNative() + val nativeRyujinx = RyujinxNative.instance val descriptor = game.open() @@ -68,6 +70,7 @@ class MainViewModel(val activity: MainActivity) { return false gameModel = game + isMiiEditorLaunched = false val settings = QuickSettings(activity) @@ -81,7 +84,7 @@ class MainViewModel(val activity: MainActivity) { if (!success) return false - val nativeHelpers = NativeHelpers() + val nativeHelpers = NativeHelpers.instance val nativeInterop = NativeGraphicsInterop() nativeInterop.VkRequiredExtensions = arrayOf( "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!! + "/", privateDriverPath, this.libraryName @@ -146,7 +149,7 @@ class MainViewModel(val activity: MainActivity) { settings.enableDocked, settings.enablePtc, false, - "UTC", + NativeHelpers.instance.storeStringJava("UTC"), settings.ignoreMissingServices ) @@ -167,6 +170,112 @@ class MainViewModel(val activity: MainActivity) { 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){ if(titleId.isNotEmpty()){ val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu" @@ -254,15 +363,4 @@ class MainViewModel(val activity: MainActivity) { this.progress = progress gameHost?.setProgressStates(showLoading, progressValue, progress) } - - fun setRefreshUserState(refreshUser: MutableState) - { - this.refreshUser = refreshUser - } - - fun requestUserRefresh(){ - refreshUser?.apply { - value = true - } - } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/UserViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/UserViewModel.kt new file mode 100644 index 000000000..6cb5cdcb7 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/UserViewModel.kt @@ -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() + + 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 + } +} diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/GameViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/GameViews.kt index d9402467b..63bd49209 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/GameViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/GameViews.kt @@ -74,7 +74,7 @@ class GameViews { Box(modifier = Modifier.fillMaxSize()) { GameStats(mainViewModel) - val ryujinxNative = RyujinxNative() + val ryujinxNative = RyujinxNative.instance val showController = remember { mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController) @@ -183,7 +183,7 @@ class GameViews { IconButton(modifier = Modifier.padding(4.dp), onClick = { showMore.value = false enableVsync.value = !enableVsync.value - RyujinxNative().graphicsRendererSetVsync(enableVsync.value) + RyujinxNative.instance.graphicsRendererSetVsync(enableVsync.value) }) { Icon( imageVector = Icons.vSync(), diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt index ea9aa289a..3914b47aa 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt @@ -48,7 +48,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.unit.dp import androidx.navigation.NavHostController -import coil.compose.AsyncImage 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.HomeViewModel -import java.io.File import java.util.Base64 import java.util.Locale import kotlin.concurrent.thread @@ -81,7 +75,6 @@ class HomeViews { viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null ) { - val native = RyujinxNative() val showAppActions = remember { mutableStateOf(false) } val showLoading = remember { mutableStateOf(false) } val openTitleUpdateDialog = remember { mutableStateOf(false) } @@ -90,31 +83,7 @@ class HomeViews { val query = remember { 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( modifier = Modifier.fillMaxSize(), topBar = { @@ -148,12 +117,14 @@ class HomeViews { IconButton(onClick = { navController?.navigate("user") }) { - if (pic.value.isNotEmpty()) { + if (viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) { + val pic = + viewModel.mainViewModel.userViewModel.openedUser.userPicture Image( bitmap = BitmapFactory.decodeByteArray( - pic.value, + pic, 0, - pic.value.size + pic?.size ?: 0 ) .asImageBitmap(), contentDescription = "user image", @@ -184,9 +155,26 @@ class HomeViews { ) }, bottomBar = { - BottomAppBar(actions = { + BottomAppBar( + actions = { if (showAppActions.value) { 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( org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface), @@ -210,13 +198,17 @@ class HomeViews { Text(text = "Clear PPTC Cache") }, onClick = { showAppMenu.value = false - viewModel.mainViewModel?.clearPptcCache(viewModel.mainViewModel?.selected?.titleId ?: "") + viewModel.mainViewModel?.clearPptcCache( + viewModel.mainViewModel?.selected?.titleId ?: "" + ) }) DropdownMenuItem(text = { Text(text = "Purge Shader Cache") }, onClick = { showAppMenu.value = false - viewModel.mainViewModel?.purgeShaderCache(viewModel.mainViewModel?.selected?.titleId ?: "") + viewModel.mainViewModel?.purgeShaderCache( + viewModel.mainViewModel?.selected?.titleId ?: "" + ) }) DropdownMenuItem(text = { 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( @@ -255,12 +280,7 @@ class HomeViews { ) { contentPadding -> Box(modifier = Modifier.padding(contentPadding)) { val list = remember { - mutableStateListOf() - } - if (refresh.value) { - viewModel.setViewList(list) - refresh.value = false - showAppActions.value = false + viewModel.gameList } val selectedModel = remember { mutableStateOf(viewModel.mainViewModel?.selected) @@ -365,6 +385,7 @@ class HomeViews { val color = if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface + val decoder = Base64.getDecoder() Surface( shape = MaterialTheme.shapes.medium, color = color, @@ -409,13 +430,12 @@ class HomeViews { ) { Row { if (!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") { - val iconSource = - MainActivity.AppPath + "/iconCache/" + gameModel.iconCache - val imageFile = File(iconSource) - if (imageFile.exists()) { + if (gameModel.icon?.isNotEmpty() == true) { + val pic = decoder.decode(gameModel.icon) val size = ImageSize / Resources.getSystem().displayMetrics.density - AsyncImage( - model = imageFile, + Image( + bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size) + .asImageBitmap(), contentDescription = gameModel.titleName + " icon", modifier = Modifier .padding(end = 8.dp) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt index c5898634c..d1c17d4b6 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt @@ -306,7 +306,7 @@ class SettingViews { thread { Helpers.importAppData(this, isImporting) showImportCompletion.value = true - mainViewModel.requestUserRefresh() + mainViewModel.userViewModel.refreshUsers() } } }, modifier = Modifier.padding(horizontal = 8.dp)) { @@ -329,7 +329,7 @@ class SettingViews { AlertDialog(onDismissRequest = { showImportCompletion.value = false importFile.value = null - mainViewModel.requestUserRefresh() + mainViewModel.userViewModel.refreshUsers() mainViewModel.homeViewModel.clearLoadedCache() }) { Card( diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt index f7c79a156..3cd8591d9 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.unit.dp import androidx.navigation.NavHostController -import org.ryujinx.android.NativeHelpers -import org.ryujinx.android.RyujinxNative import org.ryujinx.android.viewmodels.MainViewModel -import java.util.Base64 class UserViews { companion object { @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) { - val ryujinxNative = RyujinxNative() - val decoder = Base64.getDecoder() - val id = ryujinxNative.userGetOpenedUser() - val openedUser = remember { - mutableStateOf(NativeHelpers().getStringJava(id)) + val reload = remember { + mutableStateOf(true) } - - val openedUserPic = remember { - mutableStateOf(decoder.decode(ryujinxNative.userGetUserPicture(openedUser.value))) - } - val openedUserName = remember { - mutableStateOf(ryujinxNative.userGetUserName(openedUser.value)) - } - - val userList = remember { - mutableListOf("") - } - fun refresh() { - userList.clear() - userList.addAll(ryujinxNative.userGetAllUsers()) + viewModel?.userViewModel?.refreshUsers() + reload.value = true + } + LaunchedEffect(reload.value) { + reload.value = false } - - refresh() Scaffold(modifier = Modifier.fillMaxSize(), topBar = { @@ -102,25 +86,28 @@ class UserViews { .padding(4.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Image( - bitmap = BitmapFactory.decodeByteArray( - openedUserPic.value, - 0, - openedUserPic.value.size - ).asImageBitmap(), - contentDescription = "selected image", - contentScale = ContentScale.Crop, - modifier = Modifier - .padding(4.dp) - .size(96.dp) - .clip(CircleShape) - ) - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text(text = openedUserName.value) - Text(text = openedUser.value) + if (viewModel?.userViewModel?.openedUser?.id?.isNotEmpty() == true) { + val openUser = viewModel.userViewModel.openedUser + Image( + bitmap = BitmapFactory.decodeByteArray( + openUser.userPicture, + 0, + openUser.userPicture?.size ?: 0 + ).asImageBitmap(), + contentDescription = "selected image", + contentScale = ContentScale.Crop, + modifier = Modifier + .padding(4.dp) + .size(96.dp) + .clip(CircleShape) + ) + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text(text = openUser.username) + Text(text = openUser.id) + } } } @@ -139,34 +126,32 @@ class UserViews { ) } } + LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 96.dp), modifier = Modifier .fillMaxSize() .padding(4.dp) ) { - items(userList) { user -> - val pic = decoder.decode(ryujinxNative.userGetUserPicture(user)) - val name = ryujinxNative.userGetUserName(user) - Image( - bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size) - .asImageBitmap(), - contentDescription = "selected image", - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .padding(4.dp) - .clip(CircleShape) - .align(Alignment.CenterHorizontally) - .combinedClickable( - onClick = { - ryujinxNative.userOpenUser(user) - openedUser.value = user - openedUserPic.value = pic - openedUserName.value = name - viewModel?.requestUserRefresh() - }) - ) + if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) { + items(viewModel.userViewModel.userList) { user -> + Image( + bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0) + .asImageBitmap(), + contentDescription = "selected image", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .padding(4.dp) + .clip(CircleShape) + .align(Alignment.CenterHorizontally) + .combinedClickable( + onClick = { + viewModel.userViewModel.openUser(user) + reload.value = true + }) + ) + } } } }