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'