From 8b7beb6f22b74044031795b23945e8f8b704083a Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 22 Jul 2023 07:37:11 +0200 Subject: [PATCH] Cleanup gitignore and project file structure Switch to Java 17 LTS Keep libryujinx symbols Add gradle module for libryujinx Update dependencies Raise minSdk to 30 to fix linter errors Make stripSymbols a gradle property Preserve other jni libraries Fix file trees Fix AndroidManifest.xml warnings Suppress Google Play permission warning Add preBuild dependency on libryujinx for app Add toolchain path to all operating systems correctly Make dotnet executable path configurable Fix OS detection Only build LibRyujinx if source or project files changed Add toolchain path to output Fix PATH variable on Windows I spent ~7 hours debugging this. I searched for a bug in the Exec task and found nothing. I tried different ways to invoke the dotnet command to make sure PATH is always set before. I also modified Microsoft.NETCore.Native.Unix.targets to echo PATH via Exec and via Text. But in the end I was confused about seeing two PATH variables when checking the dotnet.exe subprocess with ProcessHacker. This made me try setting the Path variable instead of PATH and to my surprise this just worked. Turns out Windows environment variables are case-sensitive and Windows uses Path instead of PATH like Unix. God, I love Microsoft Windows. :) Cleanup LibRyujinx and add more verbose logging Cleanup RyujinxAndroid --- src/LibRyujinx/Android/AndroidLogTarget.cs | 10 +- src/LibRyujinx/Android/JniExportedMethods.cs | 107 +++++++------- src/LibRyujinx/LibRyujinx.cs | 6 + src/LibRyujinx/LibRyujinx.csproj | 6 +- src/RyujinxAndroid/.gitignore | 21 ++- src/RyujinxAndroid/app/.gitignore | 1 - src/RyujinxAndroid/app/build.gradle | 22 ++- .../app/src/main/AndroidManifest.xml | 8 +- .../org/ryujinx/android/GameController.kt | 61 +++----- .../main/java/org/ryujinx/android/GameHost.kt | 46 +++--- .../main/java/org/ryujinx/android/Helpers.kt | 20 ++- .../java/org/ryujinx/android/MainActivity.kt | 49 +++--- .../org/ryujinx/android/PerformanceManager.kt | 2 +- .../android/PhysicalControllerManager.kt | 12 +- .../java/org/ryujinx/android/RyujinxNative.kt | 3 +- .../ryujinx/android/viewmodels/GameModel.kt | 9 +- .../android/viewmodels/HomeViewModel.kt | 27 ++-- .../android/viewmodels/MainViewModel.kt | 4 +- .../android/viewmodels/SettingsViewModel.kt | 22 +-- .../viewmodels/TitleUpdateViewModel.kt | 29 ++-- .../viewmodels/VulkanDriverViewModel.kt | 47 +++--- .../org/ryujinx/android/views/HomeViews.kt | 27 ++-- .../org/ryujinx/android/views/MainView.kt | 47 +++--- .../org/ryujinx/android/views/SettingViews.kt | 39 ++--- .../ryujinx/android/views/TitleUpdateViews.kt | 8 +- .../app/src/main/jniLibs/arm64-v8a/.gitkeep | 1 + src/RyujinxAndroid/gradle.properties | 14 +- src/RyujinxAndroid/libryujinx/build.gradle | 139 ++++++++++++++++++ src/RyujinxAndroid/settings.gradle | 1 + 29 files changed, 448 insertions(+), 340 deletions(-) delete mode 100644 src/RyujinxAndroid/app/.gitignore create mode 100644 src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/.gitkeep create mode 100644 src/RyujinxAndroid/libryujinx/build.gradle diff --git a/src/LibRyujinx/Android/AndroidLogTarget.cs b/src/LibRyujinx/Android/AndroidLogTarget.cs index 937b02bf2..109886f82 100644 --- a/src/LibRyujinx/Android/AndroidLogTarget.cs +++ b/src/LibRyujinx/Android/AndroidLogTarget.cs @@ -1,10 +1,7 @@ -using Ryujinx.Common; -using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Formatters; using Ryujinx.Common.Logging.Targets; using System; -using System.IO; -using System.Linq; namespace LibRyujinx { @@ -26,7 +23,7 @@ namespace LibRyujinx Logcat.AndroidLogPrint(GetLogLevel(args.Level), _name, _formatter.Format(args)); } - private Logcat.LogLevel GetLogLevel(LogLevel logLevel) + private static Logcat.LogLevel GetLogLevel(LogLevel logLevel) { return logLevel switch { @@ -39,13 +36,14 @@ namespace LibRyujinx LogLevel.AccessLog => Logcat.LogLevel.Info, LogLevel.Notice => Logcat.LogLevel.Info, LogLevel.Trace => Logcat.LogLevel.Verbose, - _ => throw new NotImplementedException() + _ => throw new NotImplementedException(), }; } public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index f9bf65434..77f5518f9 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -1,27 +1,28 @@ -using System; -using System.Runtime.InteropServices; -using Ryujinx.Common.Configuration; -using System.Collections.Generic; +using LibRyujinx.Jni; using LibRyujinx.Jni.Pointers; +using LibRyujinx.Jni.Primitives; using LibRyujinx.Jni.References; using LibRyujinx.Jni.Values; -using LibRyujinx.Jni.Primitives; -using LibRyujinx.Jni; +using LibRyujinx.Shared.Audio.Oboe; +using Microsoft.Win32.SafeHandles; using Rxmxnx.PInvoke; -using System.Text; -using LibRyujinx.Jni.Internal.Pointers; +using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Targets; +using Ryujinx.Common.SystemInfo; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Silk.NET.Core.Loader; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; -using LibRyujinx.Shared.Audio.Oboe; -using System.Threading; +using System; +using System.Collections.Generic; using System.IO; -using Microsoft.Win32.SafeHandles; -using Newtonsoft.Json.Linq; +using System.Numerics; +using System.Runtime.InteropServices; using System.Security.Cryptography; -using LibHac.Tools.Fs; -using Ryujinx.HLE.HOS.SystemState; +using System.Text; +using System.Threading; namespace LibRyujinx { @@ -53,9 +54,16 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")] public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath, JBoolean enableDebugLogs) { - var path = GetString(jEnv, jpath); + SystemInfo.IsBionic = true; - Ryujinx.Common.SystemInfo.SystemInfo.IsBionic = true; + Logger.AddTarget( + new AsyncLogTargetWrapper( + new AndroidLogTarget("Ryujinx"), + 1000, + AsyncLogTargetOverflowAction.Block + )); + + var path = GetString(jEnv, jpath); var init = Initialize(path, enableDebugLogs); @@ -63,17 +71,10 @@ namespace LibRyujinx _surfaceEvent = new ManualResetEvent(false); - Logger.AddTarget( - new AsyncLogTargetWrapper( - new AndroidLogTarget("Ryujinx"), - 1000, - AsyncLogTargetOverflowAction.Block - )); - return init; } - private static string GetString(JEnvRef jEnv, JStringLocalRef jString) + private static string? GetString(JEnvRef jEnv, JStringLocalRef jString) { var stringPtr = getStringPointer(jEnv, jString); @@ -179,7 +180,7 @@ namespace LibRyujinx var jobject = getObjectClass(jEnv, graphicObject); - GraphicsConfiguration graphicsConfiguration = new GraphicsConfiguration() + GraphicsConfiguration graphicsConfiguration = new() { EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))), EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), GetCCharSequence("Z"))), @@ -191,17 +192,17 @@ namespace LibRyujinx MaxAnisotropy = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("MaxAnisotropy"), GetCCharSequence("F"))), BackendThreading = (BackendThreading)(int)getIntField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("BackendThreading"), GetCCharSequence("I"))) }; - Silk.NET.Core.Loader.SearchPathContainer.Platform = Silk.NET.Core.Loader.UnderlyingPlatform.Android; + SearchPathContainer.Platform = UnderlyingPlatform.Android; return InitializeGraphics(graphicsConfiguration); } private static CCharSequence GetCCharSequence(string s) { - return (CCharSequence)Encoding.UTF8.GetBytes(s).AsSpan(); + return Encoding.UTF8.GetBytes(s).AsSpan(); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")] - public unsafe static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr) + public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr) { _surfacePtr = surfacePtr; @@ -235,7 +236,7 @@ namespace LibRyujinx var getLongField = getLongFieldPtr.GetUnsafeDelegate(); var getObjectField = getObjectFieldPtr.GetUnsafeDelegate(); - List extensions = new List(); + List extensions = new(); var count = getArrayLength(jEnv, extensionsArray); @@ -249,9 +250,9 @@ namespace LibRyujinx _surfaceEvent.Set(); - _surfacePtr = (long)surfacePtr; + _surfacePtr = surfacePtr; - CreateSurface createSurfaceFunc = (IntPtr instance) => + CreateSurface createSurfaceFunc = instance => { _surfaceEvent.WaitOne(); _surfaceEvent.Reset(); @@ -259,10 +260,10 @@ namespace LibRyujinx var api = Vk.GetApi(); if (api.TryGetInstanceExtension(new Instance(instance), out KhrAndroidSurface surfaceExtension)) { - var createInfo = new AndroidSurfaceCreateInfoKHR() + var createInfo = new AndroidSurfaceCreateInfoKHR { SType = StructureType.AndroidSurfaceCreateInfoKhr, - Window = (nint*)_surfacePtr + Window = (nint*)_surfacePtr, }; var result = surfaceExtension.CreateAndroidSurface(new Instance(instance), createInfo, null, out var surface); @@ -296,9 +297,8 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")] public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path) { - var info = GetGameInfo(GetString(jEnv, path)) ?? new GameInfo(); - SHA256 sha; - return GetInfo(jEnv, info, out sha); + var info = GetGameInfo(GetString(jEnv, path)); + return GetInfo(jEnv, info, out SHA256 _); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")] @@ -306,12 +306,11 @@ namespace LibRyujinx { using var stream = OpenFile(fileDescriptor); - var info = GetGameInfo(stream, isXci) ?? new GameInfo(); - SHA256 sha; - return GetInfo(jEnv, info, out sha); + var info = GetGameInfo(stream, isXci); + return GetInfo(jEnv, info, out SHA256 _); } - private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo info, out SHA256 sha) + private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info, out SHA256 sha) { var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo"); @@ -339,7 +338,7 @@ namespace LibRyujinx var constructor = getMethod(jEnv, javaClass, GetCCharSequence(""), GetCCharSequence("()V")); var newObj = newObject(jEnv, javaClass, constructor, 0); sha = SHA256.Create(); - var iconCacheByte = sha.ComputeHash(info.Icon ?? new byte[0]); + var iconCacheByte = sha.ComputeHash(info?.Icon ?? Array.Empty()); var iconCache = BitConverter.ToString(iconCacheByte).Replace("-", ""); var cacheDirectory = Path.Combine(AppDataManager.BaseDirPath, "iconCache"); @@ -348,21 +347,23 @@ namespace LibRyujinx var cachePath = Path.Combine(cacheDirectory, iconCache); if (!File.Exists(cachePath)) { - File.WriteAllBytes(cachePath, info.Icon ?? new byte[0]); + 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("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); - setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info.FileSize); + setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d); return newObj; } - private static JStringLocalRef CreateString(JEnvRef jEnv, string s) + private static JStringLocalRef CreateString(JEnvRef jEnv, string? s) { + s ??= string.Empty; + var ptr = Marshal.StringToHGlobalAnsi(s); var str = createString(jEnv, ptr); @@ -417,19 +418,19 @@ namespace LibRyujinx [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")] public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id) { - SetButtonPressed((Ryujinx.Input.GamepadButtonInputId)(int)button, id); + 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) { - SetButtonReleased((Ryujinx.Input.GamepadButtonInputId)(int)button, id); + 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) { - SetStickAxis((Ryujinx.Input.StickInputId)(int)stick, new System.Numerics.Vector2(x, y), id); + SetStickAxis((StickInputId)(int)stick, new Vector2(x, y), id); } [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")] @@ -438,7 +439,7 @@ namespace LibRyujinx return ConnectGamepad(index); } - private static Stream OpenFile(int descriptor) + private static FileStream OpenFile(int descriptor) { var safeHandle = new SafeFileHandle(descriptor, false); @@ -464,7 +465,7 @@ namespace LibRyujinx Warn = 0x05, Error = 0x06, Fatal = 0x07, - Silent = 0x08 + Silent = 0x08, } } } diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs index f747efc1c..be6967930 100644 --- a/src/LibRyujinx/LibRyujinx.cs +++ b/src/LibRyujinx/LibRyujinx.cs @@ -65,6 +65,10 @@ namespace LibRyujinx 1000, AsyncLogTargetOverflowAction.Block )); + + Logger.Notice.Print(LogClass.Application, "Initializing..."); + Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}"); + SwitchDevice = new SwitchDevice(); } catch (Exception ex) @@ -417,6 +421,8 @@ namespace LibRyujinx { using var ncaFile = new UniqueRef(); + Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}"); + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage()); diff --git a/src/LibRyujinx/LibRyujinx.csproj b/src/LibRyujinx/LibRyujinx.csproj index 0b82c85c5..7439d7ab4 100644 --- a/src/LibRyujinx/LibRyujinx.csproj +++ b/src/LibRyujinx/LibRyujinx.csproj @@ -2,8 +2,8 @@ net8.0 enable - lld - $(DefineConstants);FORCE_EXTERNAL_BASE_DIR + lld + $(DefineConstants);FORCE_EXTERNAL_BASE_DIR true @@ -39,4 +39,4 @@ - \ No newline at end of file + diff --git a/src/RyujinxAndroid/.gitignore b/src/RyujinxAndroid/.gitignore index aa724b770..314e02c26 100644 --- a/src/RyujinxAndroid/.gitignore +++ b/src/RyujinxAndroid/.gitignore @@ -1,15 +1,12 @@ +.idea/ *.iml .gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx local.properties +.DS_Store +build/ +captures +.externalNativeBuild +.cxx/ + +app/src/main/jniLibs/arm64-v8a/** +!app/src/main/jniLibs/arm64-v8a/.gitkeep diff --git a/src/RyujinxAndroid/app/.gitignore b/src/RyujinxAndroid/app/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/src/RyujinxAndroid/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/src/RyujinxAndroid/app/build.gradle b/src/RyujinxAndroid/app/build.gradle index b790abf66..002f49345 100644 --- a/src/RyujinxAndroid/app/build.gradle +++ b/src/RyujinxAndroid/app/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "org.ryujinx.android" - minSdk 28 + minSdk 30 targetSdk 33 versionCode 1 versionName "1.0" @@ -20,6 +20,7 @@ android { } ndk { + //noinspection ChromeOsAbiSupport abiFilters 'arm64-v8a' } @@ -38,11 +39,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { compose true @@ -52,6 +53,9 @@ android { kotlinCompilerExtensionVersion '1.3.2' } packagingOptions { + jniLibs { + keepDebugSymbols += '**/libryujinx.so' + } resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' } @@ -64,8 +68,12 @@ android { } } -dependencies { +tasks.named("preBuild") { + dependsOn ':libryujinx:assemble' +} +dependencies { + runtimeOnly project(":libryujinx") implementation 'androidx.core:core-ktx:1.10.1' implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' @@ -81,7 +89,7 @@ dependencies { implementation 'com.google.oboe:oboe:1.7.0' implementation "com.anggrayudi:storage:1.5.5" implementation "androidx.preference:preference-ktx:1.2.0" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2' implementation 'com.google.code.gson:gson:2.10.1' implementation "io.coil-kt:coil-compose:2.4.0" testImplementation 'junit:junit:4.13.2' @@ -89,7 +97,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation platform('androidx.compose:compose-bom:2023.06.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' } \ No newline at end of file diff --git a/src/RyujinxAndroid/app/src/main/AndroidManifest.xml b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml index d11615d69..b33362363 100644 --- a/src/RyujinxAndroid/app/src/main/AndroidManifest.xml +++ b/src/RyujinxAndroid/app/src/main/AndroidManifest.xml @@ -7,7 +7,9 @@ - + @@ -37,4 +37,4 @@ - \ No newline at end of file + 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 5cc1e20c3..2266a2eb7 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 @@ -6,40 +6,26 @@ import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.GraphicsLayerScope -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleCoroutineScope -import androidx.lifecycle.flowWithLifecycle import com.swordfish.radialgamepad.library.RadialGamePad import com.swordfish.radialgamepad.library.config.ButtonConfig import com.swordfish.radialgamepad.library.config.CrossConfig import com.swordfish.radialgamepad.library.config.CrossContentDescription import com.swordfish.radialgamepad.library.config.PrimaryDialConfig import com.swordfish.radialgamepad.library.config.RadialGamePadConfig -import com.swordfish.radialgamepad.library.config.RadialGamePadTheme import com.swordfish.radialgamepad.library.config.SecondaryDialConfig import com.swordfish.radialgamepad.library.event.Event -import com.swordfish.radialgamepad.library.event.GestureType -import com.swordfish.radialgamepad.library.haptics.HapticConfig import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.subscribe import kotlinx.coroutines.launch typealias GamePad = RadialGamePad @@ -56,7 +42,7 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = return this.isVisible } - return false; + return false } init { @@ -80,22 +66,22 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = lifecycleScope.apply { lifecycleScope.launch { - var events = merge(leftGamePad.events(),rightGamePad.events()) + val events = merge(leftGamePad.events(),rightGamePad.events()) .shareIn(lifecycleScope, SharingStarted.Lazily) events.safeCollect { handleEvent(it) - }; + } } } } private fun Create(context: Context) : View { - var inflator = LayoutInflater.from(context); - var view = inflator.inflate(R.layout.game_layout, null) - view.findViewById(R.id.leftcontainer)!!.addView(leftGamePad); - view.findViewById(R.id.rightcontainer)!!.addView(rightGamePad); + val inflator = LayoutInflater.from(context) + val view = inflator.inflate(R.layout.game_layout, null) + view.findViewById(R.id.leftcontainer)!!.addView(leftGamePad) + view.findViewById(R.id.rightcontainer)!!.addView(rightGamePad) controllerView = view @@ -120,51 +106,43 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = if(controllerId == -1) controllerId = ryujinxNative.inputConnectGamepad(0) - controllerId?.apply { + controllerId.apply { when (ev) { is Event.Button -> { - var action = ev.action + val action = ev.action when (action) { KeyEvent.ACTION_UP -> { ryujinxNative.inputSetButtonReleased(ev.id, this) } + KeyEvent.ACTION_DOWN -> { ryujinxNative.inputSetButtonPressed(ev.id, this) } } } + is Event.Direction -> { - var direction = ev.id + val direction = ev.id when(direction) { GamePadButtonInputId.DpadUp.ordinal -> { - if (ev.xAxis > 0) - { + if (ev.xAxis > 0) { ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this) - } - else if (ev.xAxis < 0) - { + } else if (ev.xAxis < 0) { ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this) - } - else - { + } else { ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this) } - if (ev.yAxis < 0) - { + if (ev.yAxis < 0) { ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this) - } - else if (ev.yAxis > 0) - { + } else if (ev.yAxis > 0) { ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this) - } - else - { + } else { ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this) ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this) } @@ -173,6 +151,7 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = GamePadButtonInputId.LeftStick.ordinal -> { ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this) } + GamePadButtonInputId.RightStick.ordinal -> { ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this) } @@ -193,7 +172,7 @@ suspend fun Flow.safeCollect( } private fun generateConfig(isLeft: Boolean): GamePadConfig { - var distance = 0.05f + val distance = 0.05f if (isLeft) { return GamePadConfig( 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 6cc28cc33..1ac670f0c 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 @@ -2,14 +2,12 @@ package org.ryujinx.android import android.content.Context import android.os.Build -import android.view.MotionEvent import android.view.SurfaceHolder import android.view.SurfaceView import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.QuickSettings import kotlin.concurrent.thread -import kotlin.math.roundToInt class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback { private var _renderingThreadWatcher: Thread? = null @@ -37,19 +35,19 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - var isStarted = _isStarted; + val isStarted = _isStarted start(holder) if(isStarted && (_width != width || _height != height)) { - var nativeHelpers = NativeHelpers() - var window = nativeHelpers.getNativeWindow(holder.surface); - _nativeRyujinx.graphicsSetSurface(window); + val nativeHelpers = NativeHelpers() + val window = nativeHelpers.getNativeWindow(holder.surface) + _nativeRyujinx.graphicsSetSurface(window) } - _width = width; - _height = height; + _width = width + _height = height if(_isStarted) { @@ -61,15 +59,15 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo } - private fun start(surfaceHolder: SurfaceHolder) : Unit { - var game = gameModel ?: return - var path = game.getPath() ?: return + private fun start(surfaceHolder: SurfaceHolder) { + val game = gameModel ?: return + val path = game.getPath() ?: return if (_isStarted) return - var surface = surfaceHolder.surface; + var surface = surfaceHolder.surface - var settings = QuickSettings(mainViewModel.activity) + val settings = QuickSettings(mainViewModel.activity) var success = _nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply { EnableShaderCache = settings.enableShaderCache @@ -78,14 +76,14 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo }) - var nativeHelpers = NativeHelpers() - var window = nativeHelpers.getNativeWindow(surfaceHolder.surface); + val nativeHelpers = NativeHelpers() + val window = nativeHelpers.getNativeWindow(surfaceHolder.surface) nativeInterop = NativeGraphicsInterop() nativeInterop!!.VkRequiredExtensions = arrayOf( "VK_KHR_surface", "VK_KHR_android_surface" - ); + ) nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr() - nativeInterop!!.SurfaceHandle = window; + nativeInterop!!.SurfaceHandle = window success = _nativeRyujinx.graphicsInitializeRenderer( nativeInterop!!.VkRequiredExtensions!!, @@ -104,7 +102,7 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo false, "UTC", settings.ignoreMissingServices - ); + ) success = _nativeRyujinx.deviceLoad(path) @@ -124,12 +122,12 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo _nativeRyujinx.graphicsRendererSetSize( surfaceHolder.surfaceFrame.width(), surfaceHolder.surfaceFrame.height() - ); + ) _guestThread = thread(start = true) { runGame() } - _isStarted = success; + _isStarted = success _updateThread = thread(start = true) { var c = 0 @@ -145,20 +143,20 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo } } - private fun runGame() : Unit{ + private fun runGame() { // RenderingThreadWatcher _renderingThreadWatcher = thread(start = true) { - var threadId = 0L; + var threadId = 0L if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { mainViewModel.performanceManager?.enable() while (_isStarted) { Thread.sleep(1000) - var newthreadId = mainViewModel.activity.getRenderingThreadId() + val newthreadId = mainViewModel.activity.getRenderingThreadId() if (threadId != newthreadId) { mainViewModel.performanceManager?.closeCurrentRenderingSession() } - threadId = newthreadId; + threadId = newthreadId if (threadId != 0L) { mainViewModel.performanceManager?.initializeRenderingSession(threadId) } 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 f08e44d8d..e833b82d4 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 @@ -34,12 +34,16 @@ class Helpers { val split = docId.split(":".toRegex()).toTypedArray() val type = split[0] var contentUri: Uri? = null - if ("image" == type) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } else if ("video" == type) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - } else if ("audio" == type) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + when (type) { + "image" -> { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + "video" -> { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } + "audio" -> { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } } val selection = "_id=?" val selectionArgs = arrayOf(split[1]) @@ -58,13 +62,13 @@ class Helpers { val column = "_data" val projection = arrayOf(column) try { - cursor = uri?.let { context.getContentResolver().query(it, projection, selection, selectionArgs,null) } + cursor = uri?.let { context.contentResolver.query(it, projection, selection, selectionArgs,null) } if (cursor != null && cursor.moveToFirst()) { val column_index: Int = cursor.getColumnIndexOrThrow(column) return cursor.getString(column_index) } } finally { - if (cursor != null) cursor.close() + cursor?.close() } return null } 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 00ab1b201..0a8ce1c68 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 @@ -2,7 +2,6 @@ package org.ryujinx.android import android.annotation.SuppressLint import android.content.Context -import android.content.Intent import android.content.pm.ActivityInfo import android.media.AudioDeviceInfo import android.media.AudioManager @@ -25,23 +24,19 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import com.anggrayudi.storage.SimpleStorageHelper import org.ryujinx.android.ui.theme.RyujinxAndroidTheme -import org.ryujinx.android.viewmodels.HomeViewModel import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.views.HomeViews import org.ryujinx.android.views.MainView class MainActivity : ComponentActivity() { - var physicalControllerManager: PhysicalControllerManager + var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this) private var _isInit: Boolean = false var storageHelper: SimpleStorageHelper? = null companion object { var mainViewModel: MainViewModel? = null - var AppPath : String? + var AppPath : String = "" var StorageHelper: SimpleStorageHelper? = null - init { - AppPath = "" - } @JvmStatic fun updateRenderSessionPerformance(gameTime : Long) @@ -56,7 +51,6 @@ class MainActivity : ComponentActivity() { } init { - physicalControllerManager = PhysicalControllerManager(this) storageHelper = SimpleStorageHelper(this) StorageHelper = storageHelper System.loadLibrary("ryujinxjni") @@ -66,29 +60,28 @@ class MainActivity : ComponentActivity() { external fun getRenderingThreadId() : Long external fun initVm() - fun setFullScreen() :Unit { + fun setFullScreen() { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - var insets = WindowCompat.getInsetsController(window, window.decorView) + val insets = WindowCompat.getInsetsController(window, window.decorView) - insets?.apply { + insets.apply { insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars()) insets.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } private fun getAudioDevice () : Int { - var audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager - - var devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager + val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) return if (devices.isEmpty()) 0 else { - var speaker = devices.find { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } - var earPiece = devices.find { it.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET } + val speaker = devices.find { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } + val earPiece = devices.find { it.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET } if(earPiece != null) return earPiece.id if(speaker != null) @@ -112,17 +105,23 @@ class MainActivity : ComponentActivity() { return super.dispatchGenericMotionEvent(ev) } - private fun initialize() : Unit - { - if(_isInit) + private fun initialize() { + if (_isInit) return - var appPath: String = AppPath ?: return - var success = RyujinxNative().initialize(appPath, false) + val appPath: String = AppPath + val success = RyujinxNative().initialize(appPath, false) _isInit = success } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + if( + !Environment.isExternalStorageManager() + ) { + storageHelper?.storage?.requestFullStorageAccess() + } + AppPath = this.getExternalFilesDir(null)!!.absolutePath initialize() @@ -130,14 +129,6 @@ class MainActivity : ComponentActivity() { window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES WindowCompat.setDecorFitsSystemWindows(window,false) - if(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - !Environment.isExternalStorageManager() - } else { - false - } - ) { - storageHelper?.storage?.requestFullStorageAccess() - } mainViewModel = MainViewModel(this) mainViewModel?.apply { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt index 4568158b5..b675854e5 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PerformanceManager.kt @@ -14,7 +14,7 @@ class PerformanceManager(val performanceHintManager: PerformanceHintManager) { if(!_isEnabled || renderingSession != null) return - var threads = IntArray(1) + val threads = IntArray(1) threads[0] = threadId.toInt() renderingSession = performanceHintManager.createHintSession(threads, DEFAULT_TARGET_NS) } 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 fe372bdcd..7191c883e 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 @@ -9,7 +9,7 @@ class PhysicalControllerManager(val activity: MainActivity) { fun onKeyEvent(event: KeyEvent) : Boolean{ if(controllerId != -1) { - var id = GetGamePadButtonInputId(event.keyCode) + val id = GetGamePadButtonInputId(event.keyCode) if(id != GamePadButtonInputId.None) { when (event.action) { @@ -21,7 +21,7 @@ class PhysicalControllerManager(val activity: MainActivity) { ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId) } } - return true; + return true } } @@ -31,10 +31,10 @@ class PhysicalControllerManager(val activity: MainActivity) { fun onMotionEvent(ev: MotionEvent) { if(controllerId != -1) { if(ev.action == MotionEvent.ACTION_MOVE) { - var leftStickX = ev.getAxisValue(MotionEvent.AXIS_X); - var leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y); - var rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z); - var rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ); + val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X) + val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y) + val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z) + val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ) ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId) ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId) } 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 7cc7bcda7..7400d309a 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 @@ -1,9 +1,8 @@ package org.ryujinx.android -import android.view.Surface import org.ryujinx.android.viewmodels.GameInfo -import java.io.FileDescriptor +@Suppress("KotlinJniMissingFunction") class RyujinxNative { external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean 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 2853a1c12..0a29d717e 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 @@ -1,10 +1,7 @@ package org.ryujinx.android.viewmodels -import android.R.string -import android.content.ContentResolver import android.content.Context import android.net.Uri -import android.os.ParcelFileDescriptor import androidx.documentfile.provider.DocumentFile import com.anggrayudi.storage.file.extension import org.ryujinx.android.Helpers @@ -22,8 +19,8 @@ class GameModel(var file: DocumentFile, val context: Context) { init { fileName = file.name - var absPath = getPath() - var gameInfo = RyujinxNative().deviceGetGameInfoFromPath(absPath ?: "") + val absPath = getPath() + val gameInfo = RyujinxNative().deviceGetGameInfoFromPath(absPath ?: "") fileSize = gameInfo.FileSize titleId = gameInfo.TitleId @@ -37,7 +34,7 @@ class GameModel(var file: DocumentFile, val context: Context) { var uri = file.uri if (uri.scheme != "file") uri = Uri.parse("file://" + Helpers.getPath(context, file.uri)) - return uri.path; + return uri.path } fun getIsXci() : Boolean { 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 0fffb7081..b17e45d1e 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 @@ -8,7 +8,6 @@ import com.anggrayudi.storage.file.DocumentFileCompat import com.anggrayudi.storage.file.DocumentFileType import com.anggrayudi.storage.file.FileFullPath import com.anggrayudi.storage.file.extension -import com.anggrayudi.storage.file.fullName import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.search import org.ryujinx.android.MainActivity @@ -20,7 +19,7 @@ class HomeViewModel( private var gameList: SnapshotStateList? = null private var loadedCache: List = listOf() private var gameFolderPath: DocumentFile? = null - private var sharedPref: SharedPreferences? = null; + private var sharedPref: SharedPreferences? = null init { if (activity != null) { @@ -28,15 +27,15 @@ class HomeViewModel( activity.storageHelper!!.onFolderSelected = { requestCode, folder -> run { gameFolderPath = folder - var p = folder.getAbsolutePath(activity!!) - var editor = sharedPref?.edit() - editor?.putString("gameFolder", p); + val p = folder.getAbsolutePath(activity!!) + val editor = sharedPref?.edit() + editor?.putString("gameFolder", p) editor?.apply() reloadGameList() } } - var savedFolder = sharedPref?.getString("gameFolder", "") ?: "" + val savedFolder = sharedPref?.getString("gameFolder", "") ?: "" if (savedFolder.isNotEmpty()) { try { @@ -56,26 +55,26 @@ class HomeViewModel( } fun openGameFolder() { - var path = sharedPref?.getString("gameFolder", "") ?: "" + val path = sharedPref?.getString("gameFolder", "") ?: "" - if (path.isNullOrEmpty()) - activity?.storageHelper?.storage?.openFolderPicker(); + if (path.isEmpty()) + activity?.storageHelper?.storage?.openFolderPicker() else activity?.storageHelper?.storage?.openFolderPicker( - activity!!.storageHelper!!.storage.requestCodeFolderPicker, + activity.storageHelper!!.storage.requestCodeFolderPicker, FileFullPath(activity, path) ) } fun reloadGameList() { var storage = activity?.storageHelper ?: return - var folder = gameFolderPath ?: return + val folder = gameFolderPath ?: return - var files = mutableListOf() + val files = mutableListOf() for (file in folder.search(false, DocumentFileType.FILE)) { if (file.extension == "xci" || file.extension == "nsp") - activity?.let { + activity.let { files.add(GameModel(file, it)) } } @@ -91,7 +90,7 @@ class HomeViewModel( } fun setViewList(list: SnapshotStateList) { - gameList = list; + gameList = list applyFilter() } } \ No newline at end of file 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 bd7ad9442..1c1e34399 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 @@ -23,14 +23,14 @@ class MainViewModel(val activity: MainActivity) { init { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - var hintService = + val hintService = activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager performanceManager = PerformanceManager(hintService) } } fun loadGame(game:GameModel) { - var controller = navController?: return; + val controller = navController?: return activity.setFullScreen() GameHost.gameModel = game controller.navigate("game") diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt index 73ed910ad..5db75c5f9 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/SettingsViewModel.kt @@ -55,18 +55,18 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main resScale: MutableState, useVirtualController: MutableState ){ - var editor = sharedPref.edit() + val editor = sharedPref.edit() - editor.putBoolean("isHostMapped", isHostMapped?.value ?: true) - editor.putBoolean("useNce", useNce?.value ?: true) - editor.putBoolean("enableVsync", enableVsync?.value ?: true) - editor.putBoolean("enableDocked", enableDocked?.value ?: true) - editor.putBoolean("enablePtc", enablePtc?.value ?: true) - editor.putBoolean("ignoreMissingServices", ignoreMissingServices?.value ?: false) - editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true) - editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false) - editor.putFloat("resScale", resScale?.value ?: 1f) - editor.putBoolean("useVirtualController", useVirtualController?.value ?: true) + editor.putBoolean("isHostMapped", isHostMapped.value) + editor.putBoolean("useNce", useNce.value) + editor.putBoolean("enableVsync", enableVsync.value) + editor.putBoolean("enableDocked", enableDocked.value) + editor.putBoolean("enablePtc", enablePtc.value) + editor.putBoolean("ignoreMissingServices", ignoreMissingServices.value) + editor.putBoolean("enableShaderCache", enableShaderCache.value) + editor.putBoolean("enableTextureRecompression", enableTextureRecompression.value) + editor.putFloat("resScale", resScale.value) + editor.putBoolean("useVirtualController", useVirtualController.value) editor.apply() } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt index 1a6e4db06..502d9990c 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt @@ -1,6 +1,5 @@ package org.ryujinx.android.viewmodels -import androidx.appcompat.widget.ThemedSpinnerAdapter.Helper import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toLowerCase @@ -24,23 +23,23 @@ class TitleUpdateViewModel(val titleId: String) { return data?.paths?.apply { - removeAt(index - 1); + removeAt(index - 1) pathsState?.clear() pathsState?.addAll(this) } } fun Add() { - var callBack = storageHelper.onFileSelected + val callBack = storageHelper.onFileSelected storageHelper.onFileSelected = { requestCode, files -> run { storageHelper.onFileSelected = callBack if(requestCode == UpdateRequestCode) { - var file = files.firstOrNull() + val file = files.firstOrNull() file?.apply { - var path = Helpers.getPath(storageHelper.storage.context, file.uri) + val path = Helpers.getPath(storageHelper.storage.context, file.uri) if(!path.isNullOrEmpty()){ data?.apply { if(!paths.contains(path)) { @@ -62,20 +61,19 @@ class TitleUpdateViewModel(val titleId: String) { this.selected = "" if(paths.isNotEmpty() && index > 0) { - var ind = max(index - 1, paths.count() - 1) + val ind = max(index - 1, paths.count() - 1) this.selected = paths[ind] } - var gson = Gson() - var json = gson.toJson(this) - jsonPath = (MainActivity.AppPath - ?: "") + "/games/" + titleId.toLowerCase(Locale.current) + val gson = Gson() + val json = gson.toJson(this) + jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) File(jsonPath).mkdirs() - File(jsonPath + "/updates.json").writeText(json) + File("$jsonPath/updates.json").writeText(json) } } fun setPaths(paths: SnapshotStateList) { - pathsState = paths; + pathsState = paths data?.apply { pathsState?.clear() pathsState?.addAll(this.paths) @@ -86,16 +84,15 @@ class TitleUpdateViewModel(val titleId: String) { private var jsonPath: String init { - jsonPath = (MainActivity.AppPath - ?: "") + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json" + jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json" data = TitleUpdateMetadata() if (File(jsonPath).exists()) { - var gson = Gson() + val gson = Gson() data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java) data?.apply { - var existingPaths = mutableListOf() + val existingPaths = mutableListOf() for (path in paths) { if (File(path).exists()) { existingPaths.add(path) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/VulkanDriverViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/VulkanDriverViewModel.kt index fc9fffaa9..bddba00ce 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/VulkanDriverViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/VulkanDriverViewModel.kt @@ -12,22 +12,22 @@ class VulkanDriverViewModel(val activity: MainActivity) { var selected: String = "" companion object { - val DriverRequestCode: Int = 1003 + const val DriverRequestCode: Int = 1003 const val DriverFolder: String = "drivers" } private fun getAppPath() : String { var appPath = - (MainActivity.AppPath ?: activity.getExternalFilesDir(null)?.absolutePath ?: ""); + MainActivity.AppPath appPath += "/" return appPath } fun ensureDriverPath() : File { - var driverPath = getAppPath() + DriverFolder + val driverPath = getAppPath() + DriverFolder - var driverFolder = File(driverPath) + val driverFolder = File(driverPath) if(!driverFolder.exists()) driverFolder.mkdirs() @@ -36,13 +36,13 @@ class VulkanDriverViewModel(val activity: MainActivity) { } fun getAvailableDrivers() : MutableList { - var driverFolder = ensureDriverPath() + val driverFolder = ensureDriverPath() - var folders = driverFolder.walkTopDown() + val folders = driverFolder.walkTopDown() - var drivers = mutableListOf() + val drivers = mutableListOf() - var selectedDriverFile = File(driverFolder.absolutePath + "/selected"); + val selectedDriverFile = File(driverFolder.absolutePath + "/selected") if(selectedDriverFile.exists()){ selected = selectedDriverFile.readText() @@ -52,16 +52,16 @@ class VulkanDriverViewModel(val activity: MainActivity) { } } - var gson = Gson() + val gson = Gson() for (folder in folders){ if(folder.isDirectory() && folder.parent == driverFolder.absolutePath){ - var meta = File(folder.absolutePath + "/meta.json") + val meta = File(folder.absolutePath + "/meta.json") if(meta.exists()){ - var metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java) + val metadata = gson.fromJson(meta.readText(), DriverMetadata::class.java) if(metadata.name.isNotEmpty()) { - var driver = folder.absolutePath + "/${metadata.libraryName}" + val driver = folder.absolutePath + "/${metadata.libraryName}" metadata.driverPath = driver if (File(driver).exists()) drivers.add(metadata) @@ -74,18 +74,17 @@ class VulkanDriverViewModel(val activity: MainActivity) { } fun saveSelected() { - var driverFolder = ensureDriverPath() + val driverFolder = ensureDriverPath() - var selectedDriverFile = File(driverFolder.absolutePath + "/selected") + val selectedDriverFile = File(driverFolder.absolutePath + "/selected") selectedDriverFile.writeText(selected) } fun removeSelected(){ if(selected.isNotEmpty()){ - var sel = File(selected) + val sel = File(selected) if(sel.exists()) { - var parent = sel.parentFile - parent.deleteRecursively() + sel.parentFile?.deleteRecursively() } selected = "" @@ -96,20 +95,20 @@ class VulkanDriverViewModel(val activity: MainActivity) { fun add(refresh: MutableState) { activity.storageHelper?.apply { - var callBack = this.onFileSelected + val callBack = this.onFileSelected onFileSelected = { requestCode, files -> run { onFileSelected = callBack if(requestCode == DriverRequestCode) { - var file = files.firstOrNull() + val file = files.firstOrNull() file?.apply { - var path = Helpers.getPath(storage.context, file.uri) + val path = Helpers.getPath(storage.context, file.uri) if(!path.isNullOrEmpty()){ - var name = file.name?.removeSuffix("." + file.extension) ?: "" - var driverFolder = ensureDriverPath() - var extractionFolder = File(driverFolder.absolutePath + "/${name}") + val name = file.name?.removeSuffix("." + file.extension) ?: "" + val driverFolder = ensureDriverPath() + val extractionFolder = File(driverFolder.absolutePath + "/${name}") extractionFolder.mkdirs() ZipFile(path)?.use { zip -> zip.entries().asSequence().forEach { entry -> @@ -118,7 +117,7 @@ class VulkanDriverViewModel(val activity: MainActivity) { val filePath = extractionFolder.absolutePath + File.separator + entry.name if (!entry.isDirectory) { - var length = input.available() + val length = input.available() val bytesIn = ByteArray(length) input.read(bytesIn) File(filePath).writeBytes(bytesIn) 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 811d16a07..8a2c1c3dc 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 @@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth @@ -56,12 +54,9 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogWindowProvider -import androidx.compose.ui.window.Popup import androidx.compose.ui.zIndex -import androidx.navigation.NavController import androidx.navigation.NavHostController import coil.compose.AsyncImage import org.ryujinx.android.MainActivity @@ -78,11 +73,11 @@ class HomeViews { @OptIn(ExperimentalMaterial3Api::class) @Composable fun MainTopBar(navController: NavHostController) { - var topBarSize = remember { + val topBarSize = remember { mutableStateOf(0) } Column { - var showOptionsPopup = remember { + val showOptionsPopup = remember { mutableStateOf(false) } TopAppBar( @@ -116,7 +111,7 @@ class HomeViews { actions = { IconButton( onClick = { - showOptionsPopup.value = true; + showOptionsPopup.value = true } ) { Icon( @@ -171,7 +166,7 @@ class HomeViews { fun Home(viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null) { val sheetState = rememberModalBottomSheetState() val scope = rememberCoroutineScope() - var showBottomSheet = remember { mutableStateOf(false) } + val showBottomSheet = remember { mutableStateOf(false) } Scaffold( modifier = Modifier.fillMaxSize(), @@ -195,7 +190,7 @@ class HomeViews { ) { contentPadding -> Box(modifier = Modifier.padding(contentPadding)) { - var list = remember { + val list = remember { mutableStateListOf() } viewModel.setViewList(list) @@ -227,8 +222,8 @@ class HomeViews { shape = MaterialTheme.shapes.large, tonalElevation = AlertDialogDefaults.TonalElevation ) { - var titleId = viewModel.mainViewModel?.selected?.titleId ?: "" - var name = viewModel.mainViewModel?.selected?.titleName ?: "" + val titleId = viewModel.mainViewModel?.selected?.titleId ?: "" + val name = viewModel.mainViewModel?.selected?.titleName ?: "" TitleUpdateViews.Main(titleId, name, openDialog) } @@ -267,7 +262,7 @@ class HomeViews { } } - @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) + @OptIn(ExperimentalFoundationApi::class) @Composable fun GameItem(gameModel: GameModel, viewModel: HomeViewModel, showSheet : MutableState) { Card(shape = MaterialTheme.shapes.medium, @@ -291,8 +286,8 @@ class HomeViews { Row { if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") { - var iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache - var imageFile = File(iconSource) + val iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache + val imageFile = File(iconSource) if(imageFile.exists()) { val size = ImageSize / Resources.getSystem().displayMetrics.density AsyncImage(model = imageFile, @@ -320,7 +315,7 @@ class HomeViews { @Composable fun NotAvailableIcon() { - var size = ImageSize / Resources.getSystem().displayMetrics.density + val size = ImageSize / Resources.getSystem().displayMetrics.density Icon( Icons.Filled.Add, contentDescription = "Options", diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt index ed3487433..8ec1d5650 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/MainView.kt @@ -1,6 +1,5 @@ package org.ryujinx.android.views -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -73,7 +72,7 @@ class MainView { Box(modifier = Modifier.fillMaxSize()) { GameStats(mainViewModel) - var ryujinxNative = RyujinxNative() + val ryujinxNative = RyujinxNative() // touch surface Surface(color = Color.Transparent, modifier = Modifier @@ -82,32 +81,36 @@ class MainView { .pointerInput(Unit) { awaitPointerEventScope { while (true) { - Thread.sleep(2); + Thread.sleep(2) val event = awaitPointerEvent() if(controller.isVisible) continue - var change = event + val change = event .component1() .firstOrNull() change?.apply { - var position = this.position + val position = this.position - if (event.type == PointerEventType.Press) { - ryujinxNative.inputSetTouchPoint( - position.x.roundToInt(), - position.y.roundToInt() - ) - } else if (event.type == PointerEventType.Release) { - ryujinxNative.inputReleaseTouchPoint() + when (event.type) { + PointerEventType.Press -> { + ryujinxNative.inputSetTouchPoint( + position.x.roundToInt(), + position.y.roundToInt() + ) + } + PointerEventType.Release -> { + ryujinxNative.inputReleaseTouchPoint() - } else if (event.type == PointerEventType.Move) { - ryujinxNative.inputSetTouchPoint( - position.x.roundToInt(), - position.y.roundToInt() - ) + } + PointerEventType.Move -> { + ryujinxNative.inputSetTouchPoint( + position.x.roundToInt(), + position.y.roundToInt() + ) + } } } } @@ -128,7 +131,7 @@ class MainView { } @Composable fun rememberVideogameAsset(): ImageVector { - var primaryColor = MaterialTheme.colorScheme.primary + val primaryColor = MaterialTheme.colorScheme.primary return remember { ImageVector.Builder( name = "videogame_asset", @@ -224,20 +227,20 @@ class MainView { @Composable fun GameStats(mainViewModel: MainViewModel){ - var fifo = remember { + val fifo = remember { mutableStateOf(0.0) } - var gameFps = remember { + val gameFps = remember { mutableStateOf(0.0) } - var gameTime = remember { + val gameTime = remember { mutableStateOf(0.0) } Surface(modifier = Modifier.padding(16.dp), color = MaterialTheme.colorScheme.surface.copy(0.4f)) { Column { - var gameTimeVal = 0.0; + var gameTimeVal = 0.0 if (!gameTime.value.isInfinite()) gameTimeVal = gameTime.value Text(text = "${String.format("%.3f", fifo.value)} %") 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 2a8601477..ae7823747 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 @@ -13,36 +13,24 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.KeyboardArrowUp -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.AlertDialogDefaults -import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider -import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -54,7 +42,6 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import org.ryujinx.android.viewmodels.SettingsViewModel -import org.ryujinx.android.viewmodels.VulkanDriverViewModel class SettingViews { companion object { @@ -63,38 +50,38 @@ class SettingViews { @OptIn(ExperimentalMaterial3Api::class) @Composable fun Main(settingsViewModel: SettingsViewModel) { - var loaded = remember { + val loaded = remember { mutableStateOf(false) } - var isHostMapped = remember { + val isHostMapped = remember { mutableStateOf(false) } - var useNce = remember { + val useNce = remember { mutableStateOf(false) } - var enableVsync = remember { + val enableVsync = remember { mutableStateOf(false) } - var enableDocked = remember { + val enableDocked = remember { mutableStateOf(false) } - var enablePtc = remember { + val enablePtc = remember { mutableStateOf(false) } - var ignoreMissingServices = remember { + val ignoreMissingServices = remember { mutableStateOf(false) } - var enableShaderCache = remember { + val enableShaderCache = remember { mutableStateOf(false) } - var enableTextureRecompression = remember { + val enableTextureRecompression = remember { mutableStateOf(false) } - var resScale = remember { + val resScale = remember { mutableStateOf(1f) } - var useVirtualController = remember { + val useVirtualController = remember { mutableStateOf(true) } @@ -470,8 +457,8 @@ class SettingViews { title: String, content: @Composable () -> Unit ) { - var expanded = false - var mutableExpanded = remember { + val expanded = false + val mutableExpanded = remember { mutableStateOf(expanded) } val transitionState = remember { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt index 4dff953a8..ce832199a 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt @@ -3,11 +3,9 @@ package org.ryujinx.android.views import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete @@ -35,7 +33,7 @@ class TitleUpdateViews { fun Main(titleId: String, name: String, openDialog: MutableState) { val viewModel = TitleUpdateViewModel(titleId) - var selected = remember { mutableStateOf(0) } + val selected = remember { mutableStateOf(0) } viewModel.data?.apply { selected.value = paths.indexOf(this.selected) + 1 } @@ -67,14 +65,14 @@ class TitleUpdateViews { ) } - var paths = remember { + val paths = remember { mutableStateListOf() } viewModel.setPaths(paths) var index = 1 for (path in paths) { - var i = index + val i = index Row(modifier = Modifier.padding(8.dp)) { RadioButton( selected = (selected.value == i), diff --git a/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/.gitkeep b/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/.gitkeep new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/jniLibs/arm64-v8a/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/RyujinxAndroid/gradle.properties b/src/RyujinxAndroid/gradle.properties index 3c5031eb7..30f543175 100644 --- a/src/RyujinxAndroid/gradle.properties +++ b/src/RyujinxAndroid/gradle.properties @@ -20,4 +20,16 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +# Build configuration +# It needs to be set to either "debug" or "release" and can also be overriden on a per build basis +# by adding -Dorg.ryujinx.config=NAME to the command line. +org.ryujinx.config=debug +# Controls stripping of symbols from libryujinx +# Setting this property to auto causes symbols to be stripped for release builds, +# but not for debug builds. +# Valid values are: ["auto", "-1", "true", "1", "false", "0"] +# Default: auto +org.ryujinx.symbols.strip=auto +# Output path of libryujinx.so +org.ryujinx.publish.path=app/src/main/jniLibs/arm64-v8a \ No newline at end of file diff --git a/src/RyujinxAndroid/libryujinx/build.gradle b/src/RyujinxAndroid/libryujinx/build.gradle new file mode 100644 index 000000000..ca22a0faa --- /dev/null +++ b/src/RyujinxAndroid/libryujinx/build.gradle @@ -0,0 +1,139 @@ +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform + +plugins { + id 'base' +} + +// Configurable properties + +// Path to the LLVM toolchain to use. This should be configured in your global gradle.properties +// See: https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home +def toolchainPath = providers.gradleProperty("org.ryujinx.llvm.toolchain.path").getOrNull() +// Path to the dotnet executable This should be configured in your global gradle.properties +// See: https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home +def dotnetExecutable = providers.gradleProperty("org.ryujinx.dotnet.bin").getOrElse("dotnet") +// Build configuration +def configuration = providers.gradleProperty("org.ryujinx.config").getOrElse("debug").toLowerCase() +// Publish directory +def publishDirectory = providers.gradleProperty("org.ryujinx.publish.path").getOrNull() +// Should the symbols be stripped from the published library? +// Per default the symbols will be stripped for release builds, but not for debug builds. +// This can be overridden using this property. +// Valid values are: ["auto", "-1", "true", "1", "false", "0"] +def stripSymbols = providers.gradleProperty("org.ryujinx.symbols.strip").getOrElse("") +//noinspection GroovyFallthrough +switch (stripSymbols) { + case "true": + case "1": + stripSymbols = true + break + case "false": + case "0": + stripSymbols = false + break + default: + stripSymbols = configuration == "release" + break +} +// Additional arguments for the dotnet publish command. +def additionalArgs = project.hasProperty("org.ryujinx.args") ? project.property("org.ryujinx.args") : "" + +configuration = configuration.substring(0, 1).toUpperCase() + configuration.substring(1) + +if (publishDirectory != null) { + publishDirectory = "${rootProject.projectDir}/${publishDirectory}" +} +else { + publishDirectory = libsDirectory.get().toString() +} + +// Trees + +ext.outputTree = fileTree("${buildDir}/publish") { + include '**/*' + builtBy 'compileLibRyujinx' +} + +ext.publishTree = fileTree(publishDirectory) { + include ext.outputTree.getFiles().collect { it.getName().toLowerCase() }.findAll { it.endsWith(".so") } + builtBy 'compileLibRyujinx' +} + +// Tasks + +tasks.register('compileLibRyujinx', Exec) { + def projectName = "LibRyujinx" + + workingDir "../../${projectName}" + + def solutionFiles = fileTree("../../") { + include '**/*.cs' + include '**/*.csproj' + exclude '**/bin/**' + exclude '**/obj/**' + exclude '**/RyujinxAndroid/**' + } + + inputs.files(solutionFiles) + .withPropertyName('sourceFiles') + .withPathSensitivity(PathSensitivity.RELATIVE) + .ignoreEmptyDirectories() + + outputs.file("${publishDirectory}/${projectName.toLowerCase()}.so") + + OperatingSystem os = DefaultNativePlatform.currentOperatingSystem + if (toolchainPath != null) { + if (os.isWindows()) { + // NOTE: This is not a typo. dotnet.exe actually uses Path instead of PATH. + environment "Path", "${toolchainPath};${providers.environmentVariable("PATH").get()}" + } + else { + environment "PATH", "${toolchainPath}:${providers.environmentVariable("PATH").get()}" + } + } + + doFirst { + println "Building ${projectName} in ${configuration} mode." + println "Configuration:" + println "\tusing: ${dotnetExecutable}" + println "\tStripSymbols: ${stripSymbols}" + println "\tadditional args: ${additionalArgs.split(" ")}" + println "\tcustom LLVM toolchain path: ${toolchainPath}" + } + + executable dotnetExecutable + args 'publish', + '-r', 'linux-bionic-arm64', + '-c', configuration, + "-p:DisableUnsupportedError=true", + "-p:PublishAotUsingRuntimePack=true", + "-p:StripSymbols=${stripSymbols}", + "--artifacts-path", buildDir + + args additionalArgs.split(" ") + + doLast { + project.sync { + from project.ext.outputTree.getFiles() + include '*.so' + into publishDirectory + rename (String originalName) -> originalName.toLowerCase() + duplicatesStrategy 'fail' + preserve { + include '.gitkeep' + include '*.so' + exclude { + project.ext.publishTree + } + } + } + } +} + +tasks.register("cleanLibRyujinx", Delete) { + delete project.ext.publishTree.getFiles() +} + +// Register tasks as standard lifecycle tasks +assemble.dependsOn("compileLibRyujinx") +clean.dependsOn("cleanLibRyujinx") diff --git a/src/RyujinxAndroid/settings.gradle b/src/RyujinxAndroid/settings.gradle index 50231595d..0c8087171 100644 --- a/src/RyujinxAndroid/settings.gradle +++ b/src/RyujinxAndroid/settings.gradle @@ -16,3 +16,4 @@ dependencyResolutionManagement { } rootProject.name = "RyujinxAndroid" include ':app' +include ':libryujinx'