forked from MeloNX/MeloNX
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
This commit is contained in:
parent
fcb511bbca
commit
8b7beb6f22
@ -1,10 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Common.Logging.Formatters;
|
using Ryujinx.Common.Logging.Formatters;
|
||||||
using Ryujinx.Common.Logging.Targets;
|
using Ryujinx.Common.Logging.Targets;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace LibRyujinx
|
namespace LibRyujinx
|
||||||
{
|
{
|
||||||
@ -26,7 +23,7 @@ namespace LibRyujinx
|
|||||||
Logcat.AndroidLogPrint(GetLogLevel(args.Level), _name, _formatter.Format(args));
|
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
|
return logLevel switch
|
||||||
{
|
{
|
||||||
@ -39,13 +36,14 @@ namespace LibRyujinx
|
|||||||
LogLevel.AccessLog => Logcat.LogLevel.Info,
|
LogLevel.AccessLog => Logcat.LogLevel.Info,
|
||||||
LogLevel.Notice => Logcat.LogLevel.Info,
|
LogLevel.Notice => Logcat.LogLevel.Info,
|
||||||
LogLevel.Trace => Logcat.LogLevel.Verbose,
|
LogLevel.Trace => Logcat.LogLevel.Verbose,
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException(),
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
using System;
|
using LibRyujinx.Jni;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using LibRyujinx.Jni.Pointers;
|
using LibRyujinx.Jni.Pointers;
|
||||||
|
using LibRyujinx.Jni.Primitives;
|
||||||
using LibRyujinx.Jni.References;
|
using LibRyujinx.Jni.References;
|
||||||
using LibRyujinx.Jni.Values;
|
using LibRyujinx.Jni.Values;
|
||||||
using LibRyujinx.Jni.Primitives;
|
using LibRyujinx.Shared.Audio.Oboe;
|
||||||
using LibRyujinx.Jni;
|
using Microsoft.Win32.SafeHandles;
|
||||||
using Rxmxnx.PInvoke;
|
using Rxmxnx.PInvoke;
|
||||||
using System.Text;
|
using Ryujinx.Common.Configuration;
|
||||||
using LibRyujinx.Jni.Internal.Pointers;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Logging.Targets;
|
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;
|
||||||
using Silk.NET.Vulkan.Extensions.KHR;
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
using LibRyujinx.Shared.Audio.Oboe;
|
using System;
|
||||||
using System.Threading;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using System.Numerics;
|
||||||
using Newtonsoft.Json.Linq;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using LibHac.Tools.Fs;
|
using System.Text;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using System.Threading;
|
||||||
|
|
||||||
namespace LibRyujinx
|
namespace LibRyujinx
|
||||||
{
|
{
|
||||||
@ -53,15 +54,7 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
|
||||||
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath, JBoolean enableDebugLogs)
|
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath, JBoolean enableDebugLogs)
|
||||||
{
|
{
|
||||||
var path = GetString(jEnv, jpath);
|
SystemInfo.IsBionic = true;
|
||||||
|
|
||||||
Ryujinx.Common.SystemInfo.SystemInfo.IsBionic = true;
|
|
||||||
|
|
||||||
var init = Initialize(path, enableDebugLogs);
|
|
||||||
|
|
||||||
AudioDriver = new OboeHardwareDeviceDriver();
|
|
||||||
|
|
||||||
_surfaceEvent = new ManualResetEvent(false);
|
|
||||||
|
|
||||||
Logger.AddTarget(
|
Logger.AddTarget(
|
||||||
new AsyncLogTargetWrapper(
|
new AsyncLogTargetWrapper(
|
||||||
@ -70,10 +63,18 @@ namespace LibRyujinx
|
|||||||
AsyncLogTargetOverflowAction.Block
|
AsyncLogTargetOverflowAction.Block
|
||||||
));
|
));
|
||||||
|
|
||||||
|
var path = GetString(jEnv, jpath);
|
||||||
|
|
||||||
|
var init = Initialize(path, enableDebugLogs);
|
||||||
|
|
||||||
|
AudioDriver = new OboeHardwareDeviceDriver();
|
||||||
|
|
||||||
|
_surfaceEvent = new ManualResetEvent(false);
|
||||||
|
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetString(JEnvRef jEnv, JStringLocalRef jString)
|
private static string? GetString(JEnvRef jEnv, JStringLocalRef jString)
|
||||||
{
|
{
|
||||||
var stringPtr = getStringPointer(jEnv, jString);
|
var stringPtr = getStringPointer(jEnv, jString);
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
var jobject = getObjectClass(jEnv, graphicObject);
|
var jobject = getObjectClass(jEnv, graphicObject);
|
||||||
|
|
||||||
GraphicsConfiguration graphicsConfiguration = new GraphicsConfiguration()
|
GraphicsConfiguration graphicsConfiguration = new()
|
||||||
{
|
{
|
||||||
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
|
EnableShaderCache = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableShaderCache"), GetCCharSequence("Z"))),
|
||||||
EnableMacroHLE = getBooleanField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("EnableMacroHLE"), 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"))),
|
MaxAnisotropy = getFloatField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("MaxAnisotropy"), GetCCharSequence("F"))),
|
||||||
BackendThreading = (BackendThreading)(int)getIntField(jEnv, graphicObject, getFieldId(jEnv, jobject, GetCCharSequence("BackendThreading"), GetCCharSequence("I")))
|
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);
|
return InitializeGraphics(graphicsConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CCharSequence GetCCharSequence(string s)
|
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")]
|
[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;
|
_surfacePtr = surfacePtr;
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ namespace LibRyujinx
|
|||||||
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
|
var getLongField = getLongFieldPtr.GetUnsafeDelegate<GetLongFieldDelegate>();
|
||||||
var getObjectField = getObjectFieldPtr.GetUnsafeDelegate<GetObjectFieldDelegate>();
|
var getObjectField = getObjectFieldPtr.GetUnsafeDelegate<GetObjectFieldDelegate>();
|
||||||
|
|
||||||
List<string> extensions = new List<string>();
|
List<string?> extensions = new();
|
||||||
|
|
||||||
var count = getArrayLength(jEnv, extensionsArray);
|
var count = getArrayLength(jEnv, extensionsArray);
|
||||||
|
|
||||||
@ -249,9 +250,9 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
_surfaceEvent.Set();
|
_surfaceEvent.Set();
|
||||||
|
|
||||||
_surfacePtr = (long)surfacePtr;
|
_surfacePtr = surfacePtr;
|
||||||
|
|
||||||
CreateSurface createSurfaceFunc = (IntPtr instance) =>
|
CreateSurface createSurfaceFunc = instance =>
|
||||||
{
|
{
|
||||||
_surfaceEvent.WaitOne();
|
_surfaceEvent.WaitOne();
|
||||||
_surfaceEvent.Reset();
|
_surfaceEvent.Reset();
|
||||||
@ -259,10 +260,10 @@ namespace LibRyujinx
|
|||||||
var api = Vk.GetApi();
|
var api = Vk.GetApi();
|
||||||
if (api.TryGetInstanceExtension(new Instance(instance), out KhrAndroidSurface surfaceExtension))
|
if (api.TryGetInstanceExtension(new Instance(instance), out KhrAndroidSurface surfaceExtension))
|
||||||
{
|
{
|
||||||
var createInfo = new AndroidSurfaceCreateInfoKHR()
|
var createInfo = new AndroidSurfaceCreateInfoKHR
|
||||||
{
|
{
|
||||||
SType = StructureType.AndroidSurfaceCreateInfoKhr,
|
SType = StructureType.AndroidSurfaceCreateInfoKhr,
|
||||||
Window = (nint*)_surfacePtr
|
Window = (nint*)_surfacePtr,
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = surfaceExtension.CreateAndroidSurface(new Instance(instance), createInfo, null, out var surface);
|
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")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")]
|
||||||
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
|
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
|
||||||
{
|
{
|
||||||
var info = GetGameInfo(GetString(jEnv, path)) ?? new GameInfo();
|
var info = GetGameInfo(GetString(jEnv, path));
|
||||||
SHA256 sha;
|
return GetInfo(jEnv, info, out SHA256 _);
|
||||||
return GetInfo(jEnv, info, out sha);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
|
||||||
@ -306,12 +306,11 @@ namespace LibRyujinx
|
|||||||
{
|
{
|
||||||
using var stream = OpenFile(fileDescriptor);
|
using var stream = OpenFile(fileDescriptor);
|
||||||
|
|
||||||
var info = GetGameInfo(stream, isXci) ?? new GameInfo();
|
var info = GetGameInfo(stream, isXci);
|
||||||
SHA256 sha;
|
return GetInfo(jEnv, info, out SHA256 _);
|
||||||
return GetInfo(jEnv, info, out sha);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
|
||||||
|
|
||||||
@ -339,7 +338,7 @@ namespace LibRyujinx
|
|||||||
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
|
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
|
||||||
var newObj = newObject(jEnv, javaClass, constructor, 0);
|
var newObj = newObject(jEnv, javaClass, constructor, 0);
|
||||||
sha = SHA256.Create();
|
sha = SHA256.Create();
|
||||||
var iconCacheByte = sha.ComputeHash(info.Icon ?? new byte[0]);
|
var iconCacheByte = sha.ComputeHash(info?.Icon ?? Array.Empty<byte>());
|
||||||
var iconCache = BitConverter.ToString(iconCacheByte).Replace("-", "");
|
var iconCache = BitConverter.ToString(iconCacheByte).Replace("-", "");
|
||||||
|
|
||||||
var cacheDirectory = Path.Combine(AppDataManager.BaseDirPath, "iconCache");
|
var cacheDirectory = Path.Combine(AppDataManager.BaseDirPath, "iconCache");
|
||||||
@ -348,21 +347,23 @@ namespace LibRyujinx
|
|||||||
var cachePath = Path.Combine(cacheDirectory, iconCache);
|
var cachePath = Path.Combine(cacheDirectory, iconCache);
|
||||||
if (!File.Exists(cachePath))
|
if (!File.Exists(cachePath))
|
||||||
{
|
{
|
||||||
File.WriteAllBytes(cachePath, info.Icon ?? new byte[0]);
|
File.WriteAllBytes(cachePath, info?.Icon ?? Array.Empty<byte>());
|
||||||
}
|
}
|
||||||
|
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info.TitleName)._value);
|
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value);
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info.TitleId)._value);
|
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value);
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info.Developer)._value);
|
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value);
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info.Version)._value);
|
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value);
|
||||||
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("IconCache"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, iconCache)._value);
|
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("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;
|
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 ptr = Marshal.StringToHGlobalAnsi(s);
|
||||||
|
|
||||||
var str = createString(jEnv, ptr);
|
var str = createString(jEnv, ptr);
|
||||||
@ -417,19 +418,19 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
|
||||||
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
||||||
{
|
{
|
||||||
SetButtonPressed((Ryujinx.Input.GamepadButtonInputId)(int)button, id);
|
SetButtonPressed((GamepadButtonInputId)(int)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
|
||||||
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
|
||||||
{
|
{
|
||||||
SetButtonReleased((Ryujinx.Input.GamepadButtonInputId)(int)button, id);
|
SetButtonReleased((GamepadButtonInputId)(int)button, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
|
||||||
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
|
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
|
||||||
{
|
{
|
||||||
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")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")]
|
||||||
@ -438,7 +439,7 @@ namespace LibRyujinx
|
|||||||
return ConnectGamepad(index);
|
return ConnectGamepad(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream OpenFile(int descriptor)
|
private static FileStream OpenFile(int descriptor)
|
||||||
{
|
{
|
||||||
var safeHandle = new SafeFileHandle(descriptor, false);
|
var safeHandle = new SafeFileHandle(descriptor, false);
|
||||||
|
|
||||||
@ -464,7 +465,7 @@ namespace LibRyujinx
|
|||||||
Warn = 0x05,
|
Warn = 0x05,
|
||||||
Error = 0x06,
|
Error = 0x06,
|
||||||
Fatal = 0x07,
|
Fatal = 0x07,
|
||||||
Silent = 0x08
|
Silent = 0x08,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,10 @@ namespace LibRyujinx
|
|||||||
1000,
|
1000,
|
||||||
AsyncLogTargetOverflowAction.Block
|
AsyncLogTargetOverflowAction.Block
|
||||||
));
|
));
|
||||||
|
|
||||||
|
Logger.Notice.Print(LogClass.Application, "Initializing...");
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"Using base path: {AppDataManager.BaseDirPath}");
|
||||||
|
|
||||||
SwitchDevice = new SwitchDevice();
|
SwitchDevice = new SwitchDevice();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -417,6 +421,8 @@ namespace LibRyujinx
|
|||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}");
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
21
src/RyujinxAndroid/.gitignore
vendored
21
src/RyujinxAndroid/.gitignore
vendored
@ -1,15 +1,12 @@
|
|||||||
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.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
|
local.properties
|
||||||
|
.DS_Store
|
||||||
|
build/
|
||||||
|
captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
app/src/main/jniLibs/arm64-v8a/**
|
||||||
|
!app/src/main/jniLibs/arm64-v8a/.gitkeep
|
||||||
|
1
src/RyujinxAndroid/app/.gitignore
vendored
1
src/RyujinxAndroid/app/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -9,7 +9,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.ryujinx.android"
|
applicationId "org.ryujinx.android"
|
||||||
minSdk 28
|
minSdk 30
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
@ -20,6 +20,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
|
//noinspection ChromeOsAbiSupport
|
||||||
abiFilters 'arm64-v8a'
|
abiFilters 'arm64-v8a'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,11 +39,11 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '17'
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose true
|
compose true
|
||||||
@ -52,6 +53,9 @@ android {
|
|||||||
kotlinCompilerExtensionVersion '1.3.2'
|
kotlinCompilerExtensionVersion '1.3.2'
|
||||||
}
|
}
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
|
jniLibs {
|
||||||
|
keepDebugSymbols += '**/libryujinx.so'
|
||||||
|
}
|
||||||
resources {
|
resources {
|
||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
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 'androidx.core:core-ktx:1.10.1'
|
||||||
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
|
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||||
@ -81,7 +89,7 @@ dependencies {
|
|||||||
implementation 'com.google.oboe:oboe:1.7.0'
|
implementation 'com.google.oboe:oboe:1.7.0'
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation "io.coil-kt:coil-compose:2.4.0"
|
implementation "io.coil-kt:coil-compose:2.4.0"
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
@ -89,7 +97,7 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
androidTestImplementation platform('androidx.compose:compose-bom:2023.06.00')
|
androidTestImplementation platform('androidx.compose:compose-bom:2023.06.00')
|
||||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
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-tooling'
|
||||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||||
}
|
}
|
@ -7,7 +7,9 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission
|
||||||
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<application
|
<application
|
||||||
@ -15,7 +17,6 @@
|
|||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:extractNativeLibs="true"
|
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
@ -27,7 +28,6 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
|
android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.RyujinxAndroid">
|
android:theme="@style/Theme.RyujinxAndroid">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -6,40 +6,26 @@ import android.view.KeyEvent
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
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.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
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.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.lifecycle.flowWithLifecycle
|
|
||||||
import com.swordfish.radialgamepad.library.RadialGamePad
|
import com.swordfish.radialgamepad.library.RadialGamePad
|
||||||
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
import com.swordfish.radialgamepad.library.config.ButtonConfig
|
||||||
import com.swordfish.radialgamepad.library.config.CrossConfig
|
import com.swordfish.radialgamepad.library.config.CrossConfig
|
||||||
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
import com.swordfish.radialgamepad.library.config.CrossContentDescription
|
||||||
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
|
||||||
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
|
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.config.SecondaryDialConfig
|
||||||
import com.swordfish.radialgamepad.library.event.Event
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.subscribe
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
typealias GamePad = RadialGamePad
|
typealias GamePad = RadialGamePad
|
||||||
@ -56,7 +42,7 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
return this.isVisible
|
return this.isVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -80,22 +66,22 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
|
|
||||||
lifecycleScope.apply {
|
lifecycleScope.apply {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var events = merge(leftGamePad.events(),rightGamePad.events())
|
val events = merge(leftGamePad.events(),rightGamePad.events())
|
||||||
.shareIn(lifecycleScope, SharingStarted.Lazily)
|
.shareIn(lifecycleScope, SharingStarted.Lazily)
|
||||||
|
|
||||||
events.safeCollect {
|
events.safeCollect {
|
||||||
handleEvent(it)
|
handleEvent(it)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Create(context: Context) : View
|
private fun Create(context: Context) : View
|
||||||
{
|
{
|
||||||
var inflator = LayoutInflater.from(context);
|
val inflator = LayoutInflater.from(context)
|
||||||
var view = inflator.inflate(R.layout.game_layout, null)
|
val view = inflator.inflate(R.layout.game_layout, null)
|
||||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad);
|
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad)
|
||||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad);
|
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad)
|
||||||
|
|
||||||
controllerView = view
|
controllerView = view
|
||||||
|
|
||||||
@ -120,51 +106,43 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
if(controllerId == -1)
|
if(controllerId == -1)
|
||||||
controllerId = ryujinxNative.inputConnectGamepad(0)
|
controllerId = ryujinxNative.inputConnectGamepad(0)
|
||||||
|
|
||||||
controllerId?.apply {
|
controllerId.apply {
|
||||||
when (ev) {
|
when (ev) {
|
||||||
is Event.Button -> {
|
is Event.Button -> {
|
||||||
var action = ev.action
|
val action = ev.action
|
||||||
when (action) {
|
when (action) {
|
||||||
KeyEvent.ACTION_UP -> {
|
KeyEvent.ACTION_UP -> {
|
||||||
ryujinxNative.inputSetButtonReleased(ev.id, this)
|
ryujinxNative.inputSetButtonReleased(ev.id, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.ACTION_DOWN -> {
|
KeyEvent.ACTION_DOWN -> {
|
||||||
ryujinxNative.inputSetButtonPressed(ev.id, this)
|
ryujinxNative.inputSetButtonPressed(ev.id, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Event.Direction -> {
|
is Event.Direction -> {
|
||||||
var direction = ev.id
|
val direction = ev.id
|
||||||
|
|
||||||
when(direction) {
|
when(direction) {
|
||||||
GamePadButtonInputId.DpadUp.ordinal -> {
|
GamePadButtonInputId.DpadUp.ordinal -> {
|
||||||
if (ev.xAxis > 0)
|
if (ev.xAxis > 0) {
|
||||||
{
|
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.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.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
|
||||||
}
|
}
|
||||||
if (ev.yAxis < 0)
|
if (ev.yAxis < 0) {
|
||||||
{
|
|
||||||
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
ryujinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.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.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
|
||||||
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
ryujinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
|
||||||
}
|
}
|
||||||
@ -173,6 +151,7 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
GamePadButtonInputId.LeftStick.ordinal -> {
|
GamePadButtonInputId.LeftStick.ordinal -> {
|
||||||
ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this)
|
ryujinxNative.inputSetStickAxis(1, ev.xAxis, -ev.yAxis ,this)
|
||||||
}
|
}
|
||||||
|
|
||||||
GamePadButtonInputId.RightStick.ordinal -> {
|
GamePadButtonInputId.RightStick.ordinal -> {
|
||||||
ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this)
|
ryujinxNative.inputSetStickAxis(2, ev.xAxis, -ev.yAxis ,this)
|
||||||
}
|
}
|
||||||
@ -193,7 +172,7 @@ suspend fun <T> Flow<T>.safeCollect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
private fun generateConfig(isLeft: Boolean): GamePadConfig {
|
||||||
var distance = 0.05f
|
val distance = 0.05f
|
||||||
|
|
||||||
if (isLeft) {
|
if (isLeft) {
|
||||||
return GamePadConfig(
|
return GamePadConfig(
|
||||||
|
@ -2,14 +2,12 @@ package org.ryujinx.android
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import org.ryujinx.android.viewmodels.GameModel
|
import org.ryujinx.android.viewmodels.GameModel
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.viewmodels.QuickSettings
|
import org.ryujinx.android.viewmodels.QuickSettings
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
||||||
private var _renderingThreadWatcher: Thread? = null
|
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) {
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
var isStarted = _isStarted;
|
val isStarted = _isStarted
|
||||||
|
|
||||||
start(holder)
|
start(holder)
|
||||||
|
|
||||||
if(isStarted && (_width != width || _height != height))
|
if(isStarted && (_width != width || _height != height))
|
||||||
{
|
{
|
||||||
var nativeHelpers = NativeHelpers()
|
val nativeHelpers = NativeHelpers()
|
||||||
var window = nativeHelpers.getNativeWindow(holder.surface);
|
val window = nativeHelpers.getNativeWindow(holder.surface)
|
||||||
_nativeRyujinx.graphicsSetSurface(window);
|
_nativeRyujinx.graphicsSetSurface(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
_width = width;
|
_width = width
|
||||||
_height = height;
|
_height = height
|
||||||
|
|
||||||
if(_isStarted)
|
if(_isStarted)
|
||||||
{
|
{
|
||||||
@ -61,15 +59,15 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(surfaceHolder: SurfaceHolder) : Unit {
|
private fun start(surfaceHolder: SurfaceHolder) {
|
||||||
var game = gameModel ?: return
|
val game = gameModel ?: return
|
||||||
var path = game.getPath() ?: return
|
val path = game.getPath() ?: return
|
||||||
if (_isStarted)
|
if (_isStarted)
|
||||||
return
|
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 {
|
var success = _nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
||||||
EnableShaderCache = settings.enableShaderCache
|
EnableShaderCache = settings.enableShaderCache
|
||||||
@ -78,14 +76,14 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
var nativeHelpers = NativeHelpers()
|
val nativeHelpers = NativeHelpers()
|
||||||
var window = nativeHelpers.getNativeWindow(surfaceHolder.surface);
|
val window = nativeHelpers.getNativeWindow(surfaceHolder.surface)
|
||||||
nativeInterop = NativeGraphicsInterop()
|
nativeInterop = NativeGraphicsInterop()
|
||||||
nativeInterop!!.VkRequiredExtensions = arrayOf(
|
nativeInterop!!.VkRequiredExtensions = arrayOf(
|
||||||
"VK_KHR_surface", "VK_KHR_android_surface"
|
"VK_KHR_surface", "VK_KHR_android_surface"
|
||||||
);
|
)
|
||||||
nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
|
nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
|
||||||
nativeInterop!!.SurfaceHandle = window;
|
nativeInterop!!.SurfaceHandle = window
|
||||||
|
|
||||||
success = _nativeRyujinx.graphicsInitializeRenderer(
|
success = _nativeRyujinx.graphicsInitializeRenderer(
|
||||||
nativeInterop!!.VkRequiredExtensions!!,
|
nativeInterop!!.VkRequiredExtensions!!,
|
||||||
@ -104,7 +102,7 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
false,
|
false,
|
||||||
"UTC",
|
"UTC",
|
||||||
settings.ignoreMissingServices
|
settings.ignoreMissingServices
|
||||||
);
|
)
|
||||||
|
|
||||||
success = _nativeRyujinx.deviceLoad(path)
|
success = _nativeRyujinx.deviceLoad(path)
|
||||||
|
|
||||||
@ -124,12 +122,12 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
_nativeRyujinx.graphicsRendererSetSize(
|
_nativeRyujinx.graphicsRendererSetSize(
|
||||||
surfaceHolder.surfaceFrame.width(),
|
surfaceHolder.surfaceFrame.width(),
|
||||||
surfaceHolder.surfaceFrame.height()
|
surfaceHolder.surfaceFrame.height()
|
||||||
);
|
)
|
||||||
|
|
||||||
_guestThread = thread(start = true) {
|
_guestThread = thread(start = true) {
|
||||||
runGame()
|
runGame()
|
||||||
}
|
}
|
||||||
_isStarted = success;
|
_isStarted = success
|
||||||
|
|
||||||
_updateThread = thread(start = true) {
|
_updateThread = thread(start = true) {
|
||||||
var c = 0
|
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
|
||||||
_renderingThreadWatcher = thread(start = true) {
|
_renderingThreadWatcher = thread(start = true) {
|
||||||
var threadId = 0L;
|
var threadId = 0L
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
mainViewModel.performanceManager?.enable()
|
mainViewModel.performanceManager?.enable()
|
||||||
while (_isStarted) {
|
while (_isStarted) {
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
var newthreadId = mainViewModel.activity.getRenderingThreadId()
|
val newthreadId = mainViewModel.activity.getRenderingThreadId()
|
||||||
|
|
||||||
if (threadId != newthreadId) {
|
if (threadId != newthreadId) {
|
||||||
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
||||||
}
|
}
|
||||||
threadId = newthreadId;
|
threadId = newthreadId
|
||||||
if (threadId != 0L) {
|
if (threadId != 0L) {
|
||||||
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
|
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,17 @@ class Helpers {
|
|||||||
val split = docId.split(":".toRegex()).toTypedArray()
|
val split = docId.split(":".toRegex()).toTypedArray()
|
||||||
val type = split[0]
|
val type = split[0]
|
||||||
var contentUri: Uri? = null
|
var contentUri: Uri? = null
|
||||||
if ("image" == type) {
|
when (type) {
|
||||||
|
"image" -> {
|
||||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
} else if ("video" == type) {
|
}
|
||||||
|
"video" -> {
|
||||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
} else if ("audio" == type) {
|
}
|
||||||
|
"audio" -> {
|
||||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val selection = "_id=?"
|
val selection = "_id=?"
|
||||||
val selectionArgs = arrayOf(split[1])
|
val selectionArgs = arrayOf(split[1])
|
||||||
return getDataColumn(context, contentUri, selection, selectionArgs)
|
return getDataColumn(context, contentUri, selection, selectionArgs)
|
||||||
@ -58,13 +62,13 @@ class Helpers {
|
|||||||
val column = "_data"
|
val column = "_data"
|
||||||
val projection = arrayOf(column)
|
val projection = arrayOf(column)
|
||||||
try {
|
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()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
val column_index: Int = cursor.getColumnIndexOrThrow(column)
|
val column_index: Int = cursor.getColumnIndexOrThrow(column)
|
||||||
return cursor.getString(column_index)
|
return cursor.getString(column_index)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) cursor.close()
|
cursor?.close()
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.ryujinx.android
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.media.AudioDeviceInfo
|
import android.media.AudioDeviceInfo
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
@ -25,23 +24,19 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import com.anggrayudi.storage.SimpleStorageHelper
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
||||||
import org.ryujinx.android.viewmodels.HomeViewModel
|
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.views.HomeViews
|
import org.ryujinx.android.views.HomeViews
|
||||||
import org.ryujinx.android.views.MainView
|
import org.ryujinx.android.views.MainView
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
var physicalControllerManager: PhysicalControllerManager
|
var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this)
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
companion object {
|
companion object {
|
||||||
var mainViewModel: MainViewModel? = null
|
var mainViewModel: MainViewModel? = null
|
||||||
var AppPath : String?
|
var AppPath : String = ""
|
||||||
var StorageHelper: SimpleStorageHelper? = null
|
var StorageHelper: SimpleStorageHelper? = null
|
||||||
init {
|
|
||||||
AppPath = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun updateRenderSessionPerformance(gameTime : Long)
|
fun updateRenderSessionPerformance(gameTime : Long)
|
||||||
@ -56,7 +51,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
physicalControllerManager = PhysicalControllerManager(this)
|
|
||||||
storageHelper = SimpleStorageHelper(this)
|
storageHelper = SimpleStorageHelper(this)
|
||||||
StorageHelper = storageHelper
|
StorageHelper = storageHelper
|
||||||
System.loadLibrary("ryujinxjni")
|
System.loadLibrary("ryujinxjni")
|
||||||
@ -66,29 +60,28 @@ class MainActivity : ComponentActivity() {
|
|||||||
external fun getRenderingThreadId() : Long
|
external fun getRenderingThreadId() : Long
|
||||||
external fun initVm()
|
external fun initVm()
|
||||||
|
|
||||||
fun setFullScreen() :Unit {
|
fun setFullScreen() {
|
||||||
requestedOrientation =
|
requestedOrientation =
|
||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
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.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
insets.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
insets.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAudioDevice () : Int {
|
private fun getAudioDevice () : Int {
|
||||||
var audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
var devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
|
|
||||||
|
|
||||||
|
val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
||||||
|
|
||||||
return if (devices.isEmpty())
|
return if (devices.isEmpty())
|
||||||
0
|
0
|
||||||
else {
|
else {
|
||||||
var speaker = devices.find { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
|
val 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 earPiece = devices.find { it.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET }
|
||||||
if(earPiece != null)
|
if(earPiece != null)
|
||||||
return earPiece.id
|
return earPiece.id
|
||||||
if(speaker != null)
|
if(speaker != null)
|
||||||
@ -112,17 +105,23 @@ class MainActivity : ComponentActivity() {
|
|||||||
return super.dispatchGenericMotionEvent(ev)
|
return super.dispatchGenericMotionEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initialize() : Unit
|
private fun initialize() {
|
||||||
{
|
|
||||||
if (_isInit)
|
if (_isInit)
|
||||||
return
|
return
|
||||||
|
|
||||||
var appPath: String = AppPath ?: return
|
val appPath: String = AppPath
|
||||||
var success = RyujinxNative().initialize(appPath, false)
|
val success = RyujinxNative().initialize(appPath, false)
|
||||||
_isInit = success
|
_isInit = success
|
||||||
}
|
}
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if(
|
||||||
|
!Environment.isExternalStorageManager()
|
||||||
|
) {
|
||||||
|
storageHelper?.storage?.requestFullStorageAccess()
|
||||||
|
}
|
||||||
|
|
||||||
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
@ -130,14 +129,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
WindowCompat.setDecorFitsSystemWindows(window,false)
|
WindowCompat.setDecorFitsSystemWindows(window,false)
|
||||||
|
|
||||||
if(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
!Environment.isExternalStorageManager()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
storageHelper?.storage?.requestFullStorageAccess()
|
|
||||||
}
|
|
||||||
mainViewModel = MainViewModel(this)
|
mainViewModel = MainViewModel(this)
|
||||||
|
|
||||||
mainViewModel?.apply {
|
mainViewModel?.apply {
|
||||||
|
@ -14,7 +14,7 @@ class PerformanceManager(val performanceHintManager: PerformanceHintManager) {
|
|||||||
if(!_isEnabled || renderingSession != null)
|
if(!_isEnabled || renderingSession != null)
|
||||||
return
|
return
|
||||||
|
|
||||||
var threads = IntArray(1)
|
val threads = IntArray(1)
|
||||||
threads[0] = threadId.toInt()
|
threads[0] = threadId.toInt()
|
||||||
renderingSession = performanceHintManager.createHintSession(threads, DEFAULT_TARGET_NS)
|
renderingSession = performanceHintManager.createHintSession(threads, DEFAULT_TARGET_NS)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ class PhysicalControllerManager(val activity: MainActivity) {
|
|||||||
|
|
||||||
fun onKeyEvent(event: KeyEvent) : Boolean{
|
fun onKeyEvent(event: KeyEvent) : Boolean{
|
||||||
if(controllerId != -1) {
|
if(controllerId != -1) {
|
||||||
var id = GetGamePadButtonInputId(event.keyCode)
|
val id = GetGamePadButtonInputId(event.keyCode)
|
||||||
|
|
||||||
if(id != GamePadButtonInputId.None) {
|
if(id != GamePadButtonInputId.None) {
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
@ -21,7 +21,7 @@ class PhysicalControllerManager(val activity: MainActivity) {
|
|||||||
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId)
|
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +31,10 @@ class PhysicalControllerManager(val activity: MainActivity) {
|
|||||||
fun onMotionEvent(ev: MotionEvent) {
|
fun onMotionEvent(ev: MotionEvent) {
|
||||||
if(controllerId != -1) {
|
if(controllerId != -1) {
|
||||||
if(ev.action == MotionEvent.ACTION_MOVE) {
|
if(ev.action == MotionEvent.ACTION_MOVE) {
|
||||||
var leftStickX = ev.getAxisValue(MotionEvent.AXIS_X);
|
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
|
||||||
var leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y);
|
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
var rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z);
|
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
|
||||||
var rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ);
|
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId)
|
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId)
|
||||||
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId)
|
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
import android.view.Surface
|
|
||||||
import org.ryujinx.android.viewmodels.GameInfo
|
import org.ryujinx.android.viewmodels.GameInfo
|
||||||
import java.io.FileDescriptor
|
|
||||||
|
|
||||||
|
@Suppress("KotlinJniMissingFunction")
|
||||||
class RyujinxNative {
|
class RyujinxNative {
|
||||||
|
|
||||||
external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean
|
external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package org.ryujinx.android.viewmodels
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
import android.R.string
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.anggrayudi.storage.file.extension
|
import com.anggrayudi.storage.file.extension
|
||||||
import org.ryujinx.android.Helpers
|
import org.ryujinx.android.Helpers
|
||||||
@ -22,8 +19,8 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
fileName = file.name
|
fileName = file.name
|
||||||
var absPath = getPath()
|
val absPath = getPath()
|
||||||
var gameInfo = RyujinxNative().deviceGetGameInfoFromPath(absPath ?: "")
|
val gameInfo = RyujinxNative().deviceGetGameInfoFromPath(absPath ?: "")
|
||||||
|
|
||||||
fileSize = gameInfo.FileSize
|
fileSize = gameInfo.FileSize
|
||||||
titleId = gameInfo.TitleId
|
titleId = gameInfo.TitleId
|
||||||
@ -37,7 +34,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
|
|||||||
var uri = file.uri
|
var uri = file.uri
|
||||||
if (uri.scheme != "file")
|
if (uri.scheme != "file")
|
||||||
uri = Uri.parse("file://" + Helpers.getPath(context, file.uri))
|
uri = Uri.parse("file://" + Helpers.getPath(context, file.uri))
|
||||||
return uri.path;
|
return uri.path
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIsXci() : Boolean {
|
fun getIsXci() : Boolean {
|
||||||
|
@ -8,7 +8,6 @@ import com.anggrayudi.storage.file.DocumentFileCompat
|
|||||||
import com.anggrayudi.storage.file.DocumentFileType
|
import com.anggrayudi.storage.file.DocumentFileType
|
||||||
import com.anggrayudi.storage.file.FileFullPath
|
import com.anggrayudi.storage.file.FileFullPath
|
||||||
import com.anggrayudi.storage.file.extension
|
import com.anggrayudi.storage.file.extension
|
||||||
import com.anggrayudi.storage.file.fullName
|
|
||||||
import com.anggrayudi.storage.file.getAbsolutePath
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
import com.anggrayudi.storage.file.search
|
import com.anggrayudi.storage.file.search
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
@ -20,7 +19,7 @@ class HomeViewModel(
|
|||||||
private var gameList: SnapshotStateList<GameModel>? = null
|
private var gameList: SnapshotStateList<GameModel>? = null
|
||||||
private var loadedCache: List<GameModel> = listOf()
|
private var loadedCache: List<GameModel> = listOf()
|
||||||
private var gameFolderPath: DocumentFile? = null
|
private var gameFolderPath: DocumentFile? = null
|
||||||
private var sharedPref: SharedPreferences? = null;
|
private var sharedPref: SharedPreferences? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
@ -28,15 +27,15 @@ class HomeViewModel(
|
|||||||
activity.storageHelper!!.onFolderSelected = { requestCode, folder ->
|
activity.storageHelper!!.onFolderSelected = { requestCode, folder ->
|
||||||
run {
|
run {
|
||||||
gameFolderPath = folder
|
gameFolderPath = folder
|
||||||
var p = folder.getAbsolutePath(activity!!)
|
val p = folder.getAbsolutePath(activity!!)
|
||||||
var editor = sharedPref?.edit()
|
val editor = sharedPref?.edit()
|
||||||
editor?.putString("gameFolder", p);
|
editor?.putString("gameFolder", p)
|
||||||
editor?.apply()
|
editor?.apply()
|
||||||
reloadGameList()
|
reloadGameList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var savedFolder = sharedPref?.getString("gameFolder", "") ?: ""
|
val savedFolder = sharedPref?.getString("gameFolder", "") ?: ""
|
||||||
|
|
||||||
if (savedFolder.isNotEmpty()) {
|
if (savedFolder.isNotEmpty()) {
|
||||||
try {
|
try {
|
||||||
@ -56,26 +55,26 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openGameFolder() {
|
fun openGameFolder() {
|
||||||
var path = sharedPref?.getString("gameFolder", "") ?: ""
|
val path = sharedPref?.getString("gameFolder", "") ?: ""
|
||||||
|
|
||||||
if (path.isNullOrEmpty())
|
if (path.isEmpty())
|
||||||
activity?.storageHelper?.storage?.openFolderPicker();
|
activity?.storageHelper?.storage?.openFolderPicker()
|
||||||
else
|
else
|
||||||
activity?.storageHelper?.storage?.openFolderPicker(
|
activity?.storageHelper?.storage?.openFolderPicker(
|
||||||
activity!!.storageHelper!!.storage.requestCodeFolderPicker,
|
activity.storageHelper!!.storage.requestCodeFolderPicker,
|
||||||
FileFullPath(activity, path)
|
FileFullPath(activity, path)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadGameList() {
|
fun reloadGameList() {
|
||||||
var storage = activity?.storageHelper ?: return
|
var storage = activity?.storageHelper ?: return
|
||||||
var folder = gameFolderPath ?: return
|
val folder = gameFolderPath ?: return
|
||||||
|
|
||||||
var files = mutableListOf<GameModel>()
|
val files = mutableListOf<GameModel>()
|
||||||
|
|
||||||
for (file in folder.search(false, DocumentFileType.FILE)) {
|
for (file in folder.search(false, DocumentFileType.FILE)) {
|
||||||
if (file.extension == "xci" || file.extension == "nsp")
|
if (file.extension == "xci" || file.extension == "nsp")
|
||||||
activity?.let {
|
activity.let {
|
||||||
files.add(GameModel(file, it))
|
files.add(GameModel(file, it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +90,7 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setViewList(list: SnapshotStateList<GameModel>) {
|
fun setViewList(list: SnapshotStateList<GameModel>) {
|
||||||
gameList = list;
|
gameList = list
|
||||||
applyFilter()
|
applyFilter()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,14 +23,14 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
var hintService =
|
val hintService =
|
||||||
activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager
|
activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager
|
||||||
performanceManager = PerformanceManager(hintService)
|
performanceManager = PerformanceManager(hintService)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGame(game:GameModel) {
|
fun loadGame(game:GameModel) {
|
||||||
var controller = navController?: return;
|
val controller = navController?: return
|
||||||
activity.setFullScreen()
|
activity.setFullScreen()
|
||||||
GameHost.gameModel = game
|
GameHost.gameModel = game
|
||||||
controller.navigate("game")
|
controller.navigate("game")
|
||||||
|
@ -55,18 +55,18 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
resScale: MutableState<Float>,
|
resScale: MutableState<Float>,
|
||||||
useVirtualController: MutableState<Boolean>
|
useVirtualController: MutableState<Boolean>
|
||||||
){
|
){
|
||||||
var editor = sharedPref.edit()
|
val editor = sharedPref.edit()
|
||||||
|
|
||||||
editor.putBoolean("isHostMapped", isHostMapped?.value ?: true)
|
editor.putBoolean("isHostMapped", isHostMapped.value)
|
||||||
editor.putBoolean("useNce", useNce?.value ?: true)
|
editor.putBoolean("useNce", useNce.value)
|
||||||
editor.putBoolean("enableVsync", enableVsync?.value ?: true)
|
editor.putBoolean("enableVsync", enableVsync.value)
|
||||||
editor.putBoolean("enableDocked", enableDocked?.value ?: true)
|
editor.putBoolean("enableDocked", enableDocked.value)
|
||||||
editor.putBoolean("enablePtc", enablePtc?.value ?: true)
|
editor.putBoolean("enablePtc", enablePtc.value)
|
||||||
editor.putBoolean("ignoreMissingServices", ignoreMissingServices?.value ?: false)
|
editor.putBoolean("ignoreMissingServices", ignoreMissingServices.value)
|
||||||
editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true)
|
editor.putBoolean("enableShaderCache", enableShaderCache.value)
|
||||||
editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false)
|
editor.putBoolean("enableTextureRecompression", enableTextureRecompression.value)
|
||||||
editor.putFloat("resScale", resScale?.value ?: 1f)
|
editor.putFloat("resScale", resScale.value)
|
||||||
editor.putBoolean("useVirtualController", useVirtualController?.value ?: true)
|
editor.putBoolean("useVirtualController", useVirtualController.value)
|
||||||
|
|
||||||
editor.apply()
|
editor.apply()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.ryujinx.android.viewmodels
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
import androidx.appcompat.widget.ThemedSpinnerAdapter.Helper
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.compose.ui.text.toLowerCase
|
import androidx.compose.ui.text.toLowerCase
|
||||||
@ -24,23 +23,23 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
data?.paths?.apply {
|
data?.paths?.apply {
|
||||||
removeAt(index - 1);
|
removeAt(index - 1)
|
||||||
pathsState?.clear()
|
pathsState?.clear()
|
||||||
pathsState?.addAll(this)
|
pathsState?.addAll(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Add() {
|
fun Add() {
|
||||||
var callBack = storageHelper.onFileSelected
|
val callBack = storageHelper.onFileSelected
|
||||||
|
|
||||||
storageHelper.onFileSelected = { requestCode, files ->
|
storageHelper.onFileSelected = { requestCode, files ->
|
||||||
run {
|
run {
|
||||||
storageHelper.onFileSelected = callBack
|
storageHelper.onFileSelected = callBack
|
||||||
if(requestCode == UpdateRequestCode)
|
if(requestCode == UpdateRequestCode)
|
||||||
{
|
{
|
||||||
var file = files.firstOrNull()
|
val file = files.firstOrNull()
|
||||||
file?.apply {
|
file?.apply {
|
||||||
var path = Helpers.getPath(storageHelper.storage.context, file.uri)
|
val path = Helpers.getPath(storageHelper.storage.context, file.uri)
|
||||||
if(!path.isNullOrEmpty()){
|
if(!path.isNullOrEmpty()){
|
||||||
data?.apply {
|
data?.apply {
|
||||||
if(!paths.contains(path)) {
|
if(!paths.contains(path)) {
|
||||||
@ -62,20 +61,19 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
this.selected = ""
|
this.selected = ""
|
||||||
if(paths.isNotEmpty() && index > 0)
|
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]
|
this.selected = paths[ind]
|
||||||
}
|
}
|
||||||
var gson = Gson()
|
val gson = Gson()
|
||||||
var json = gson.toJson(this)
|
val json = gson.toJson(this)
|
||||||
jsonPath = (MainActivity.AppPath
|
jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||||
?: "") + "/games/" + titleId.toLowerCase(Locale.current)
|
|
||||||
File(jsonPath).mkdirs()
|
File(jsonPath).mkdirs()
|
||||||
File(jsonPath + "/updates.json").writeText(json)
|
File("$jsonPath/updates.json").writeText(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPaths(paths: SnapshotStateList<String>) {
|
fun setPaths(paths: SnapshotStateList<String>) {
|
||||||
pathsState = paths;
|
pathsState = paths
|
||||||
data?.apply {
|
data?.apply {
|
||||||
pathsState?.clear()
|
pathsState?.clear()
|
||||||
pathsState?.addAll(this.paths)
|
pathsState?.addAll(this.paths)
|
||||||
@ -86,16 +84,15 @@ class TitleUpdateViewModel(val titleId: String) {
|
|||||||
private var jsonPath: String
|
private var jsonPath: String
|
||||||
|
|
||||||
init {
|
init {
|
||||||
jsonPath = (MainActivity.AppPath
|
jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json"
|
||||||
?: "") + "/games/" + titleId.toLowerCase(Locale.current) + "/updates.json"
|
|
||||||
|
|
||||||
data = TitleUpdateMetadata()
|
data = TitleUpdateMetadata()
|
||||||
if (File(jsonPath).exists()) {
|
if (File(jsonPath).exists()) {
|
||||||
var gson = Gson()
|
val gson = Gson()
|
||||||
data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
|
data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
|
||||||
|
|
||||||
data?.apply {
|
data?.apply {
|
||||||
var existingPaths = mutableListOf<String>()
|
val existingPaths = mutableListOf<String>()
|
||||||
for (path in paths) {
|
for (path in paths) {
|
||||||
if (File(path).exists()) {
|
if (File(path).exists()) {
|
||||||
existingPaths.add(path)
|
existingPaths.add(path)
|
||||||
|
@ -12,22 +12,22 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
var selected: String = ""
|
var selected: String = ""
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DriverRequestCode: Int = 1003
|
const val DriverRequestCode: Int = 1003
|
||||||
const val DriverFolder: String = "drivers"
|
const val DriverFolder: String = "drivers"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAppPath() : String {
|
private fun getAppPath() : String {
|
||||||
var appPath =
|
var appPath =
|
||||||
(MainActivity.AppPath ?: activity.getExternalFilesDir(null)?.absolutePath ?: "");
|
MainActivity.AppPath
|
||||||
appPath += "/"
|
appPath += "/"
|
||||||
|
|
||||||
return appPath
|
return appPath
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureDriverPath() : File {
|
fun ensureDriverPath() : File {
|
||||||
var driverPath = getAppPath() + DriverFolder
|
val driverPath = getAppPath() + DriverFolder
|
||||||
|
|
||||||
var driverFolder = File(driverPath)
|
val driverFolder = File(driverPath)
|
||||||
|
|
||||||
if(!driverFolder.exists())
|
if(!driverFolder.exists())
|
||||||
driverFolder.mkdirs()
|
driverFolder.mkdirs()
|
||||||
@ -36,13 +36,13 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getAvailableDrivers() : MutableList<DriverMetadata> {
|
fun getAvailableDrivers() : MutableList<DriverMetadata> {
|
||||||
var driverFolder = ensureDriverPath()
|
val driverFolder = ensureDriverPath()
|
||||||
|
|
||||||
var folders = driverFolder.walkTopDown()
|
val folders = driverFolder.walkTopDown()
|
||||||
|
|
||||||
var drivers = mutableListOf<DriverMetadata>()
|
val drivers = mutableListOf<DriverMetadata>()
|
||||||
|
|
||||||
var selectedDriverFile = File(driverFolder.absolutePath + "/selected");
|
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
|
||||||
if(selectedDriverFile.exists()){
|
if(selectedDriverFile.exists()){
|
||||||
selected = selectedDriverFile.readText()
|
selected = selectedDriverFile.readText()
|
||||||
|
|
||||||
@ -52,16 +52,16 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var gson = Gson()
|
val gson = Gson()
|
||||||
|
|
||||||
for (folder in folders){
|
for (folder in folders){
|
||||||
if(folder.isDirectory() && folder.parent == driverFolder.absolutePath){
|
if(folder.isDirectory() && folder.parent == driverFolder.absolutePath){
|
||||||
var meta = File(folder.absolutePath + "/meta.json")
|
val meta = File(folder.absolutePath + "/meta.json")
|
||||||
|
|
||||||
if(meta.exists()){
|
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()) {
|
if(metadata.name.isNotEmpty()) {
|
||||||
var driver = folder.absolutePath + "/${metadata.libraryName}"
|
val driver = folder.absolutePath + "/${metadata.libraryName}"
|
||||||
metadata.driverPath = driver
|
metadata.driverPath = driver
|
||||||
if (File(driver).exists())
|
if (File(driver).exists())
|
||||||
drivers.add(metadata)
|
drivers.add(metadata)
|
||||||
@ -74,18 +74,17 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveSelected() {
|
fun saveSelected() {
|
||||||
var driverFolder = ensureDriverPath()
|
val driverFolder = ensureDriverPath()
|
||||||
|
|
||||||
var selectedDriverFile = File(driverFolder.absolutePath + "/selected")
|
val selectedDriverFile = File(driverFolder.absolutePath + "/selected")
|
||||||
selectedDriverFile.writeText(selected)
|
selectedDriverFile.writeText(selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeSelected(){
|
fun removeSelected(){
|
||||||
if(selected.isNotEmpty()){
|
if(selected.isNotEmpty()){
|
||||||
var sel = File(selected)
|
val sel = File(selected)
|
||||||
if(sel.exists()) {
|
if(sel.exists()) {
|
||||||
var parent = sel.parentFile
|
sel.parentFile?.deleteRecursively()
|
||||||
parent.deleteRecursively()
|
|
||||||
}
|
}
|
||||||
selected = ""
|
selected = ""
|
||||||
|
|
||||||
@ -96,20 +95,20 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
fun add(refresh: MutableState<Boolean>) {
|
fun add(refresh: MutableState<Boolean>) {
|
||||||
activity.storageHelper?.apply {
|
activity.storageHelper?.apply {
|
||||||
|
|
||||||
var callBack = this.onFileSelected
|
val callBack = this.onFileSelected
|
||||||
|
|
||||||
onFileSelected = { requestCode, files ->
|
onFileSelected = { requestCode, files ->
|
||||||
run {
|
run {
|
||||||
onFileSelected = callBack
|
onFileSelected = callBack
|
||||||
if(requestCode == DriverRequestCode)
|
if(requestCode == DriverRequestCode)
|
||||||
{
|
{
|
||||||
var file = files.firstOrNull()
|
val file = files.firstOrNull()
|
||||||
file?.apply {
|
file?.apply {
|
||||||
var path = Helpers.getPath(storage.context, file.uri)
|
val path = Helpers.getPath(storage.context, file.uri)
|
||||||
if(!path.isNullOrEmpty()){
|
if(!path.isNullOrEmpty()){
|
||||||
var name = file.name?.removeSuffix("." + file.extension) ?: ""
|
val name = file.name?.removeSuffix("." + file.extension) ?: ""
|
||||||
var driverFolder = ensureDriverPath()
|
val driverFolder = ensureDriverPath()
|
||||||
var extractionFolder = File(driverFolder.absolutePath + "/${name}")
|
val extractionFolder = File(driverFolder.absolutePath + "/${name}")
|
||||||
extractionFolder.mkdirs()
|
extractionFolder.mkdirs()
|
||||||
ZipFile(path)?.use { zip ->
|
ZipFile(path)?.use { zip ->
|
||||||
zip.entries().asSequence().forEach { entry ->
|
zip.entries().asSequence().forEach { entry ->
|
||||||
@ -118,7 +117,7 @@ class VulkanDriverViewModel(val activity: MainActivity) {
|
|||||||
val filePath = extractionFolder.absolutePath + File.separator + entry.name
|
val filePath = extractionFolder.absolutePath + File.separator + entry.name
|
||||||
|
|
||||||
if (!entry.isDirectory) {
|
if (!entry.isDirectory) {
|
||||||
var length = input.available()
|
val length = input.available()
|
||||||
val bytesIn = ByteArray(length)
|
val bytesIn = ByteArray(length)
|
||||||
input.read(bytesIn)
|
input.read(bytesIn)
|
||||||
File(filePath).writeBytes(bytesIn)
|
File(filePath).writeBytes(bytesIn)
|
||||||
|
@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeContentPadding
|
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
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.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import androidx.compose.ui.window.Popup
|
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
@ -78,11 +73,11 @@ class HomeViews {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainTopBar(navController: NavHostController) {
|
fun MainTopBar(navController: NavHostController) {
|
||||||
var topBarSize = remember {
|
val topBarSize = remember {
|
||||||
mutableStateOf(0)
|
mutableStateOf(0)
|
||||||
}
|
}
|
||||||
Column {
|
Column {
|
||||||
var showOptionsPopup = remember {
|
val showOptionsPopup = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@ -116,7 +111,7 @@ class HomeViews {
|
|||||||
actions = {
|
actions = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
showOptionsPopup.value = true;
|
showOptionsPopup.value = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -171,7 +166,7 @@ class HomeViews {
|
|||||||
fun Home(viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null) {
|
fun Home(viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null) {
|
||||||
val sheetState = rememberModalBottomSheetState()
|
val sheetState = rememberModalBottomSheetState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var showBottomSheet = remember { mutableStateOf(false) }
|
val showBottomSheet = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@ -195,7 +190,7 @@ class HomeViews {
|
|||||||
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Box(modifier = Modifier.padding(contentPadding)) {
|
Box(modifier = Modifier.padding(contentPadding)) {
|
||||||
var list = remember {
|
val list = remember {
|
||||||
mutableStateListOf<GameModel>()
|
mutableStateListOf<GameModel>()
|
||||||
}
|
}
|
||||||
viewModel.setViewList(list)
|
viewModel.setViewList(list)
|
||||||
@ -227,8 +222,8 @@ class HomeViews {
|
|||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
) {
|
) {
|
||||||
var titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
var name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
TitleUpdateViews.Main(titleId, name, openDialog)
|
TitleUpdateViews.Main(titleId, name, openDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +262,7 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GameItem(gameModel: GameModel, viewModel: HomeViewModel, showSheet : MutableState<Boolean>) {
|
fun GameItem(gameModel: GameModel, viewModel: HomeViewModel, showSheet : MutableState<Boolean>) {
|
||||||
Card(shape = MaterialTheme.shapes.medium,
|
Card(shape = MaterialTheme.shapes.medium,
|
||||||
@ -291,8 +286,8 @@ class HomeViews {
|
|||||||
Row {
|
Row {
|
||||||
if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000")
|
if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000")
|
||||||
{
|
{
|
||||||
var iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
|
val iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
|
||||||
var imageFile = File(iconSource)
|
val imageFile = File(iconSource)
|
||||||
if(imageFile.exists()) {
|
if(imageFile.exists()) {
|
||||||
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
||||||
AsyncImage(model = imageFile,
|
AsyncImage(model = imageFile,
|
||||||
@ -320,7 +315,7 @@ class HomeViews {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NotAvailableIcon() {
|
fun NotAvailableIcon() {
|
||||||
var size = ImageSize / Resources.getSystem().displayMetrics.density
|
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Add,
|
Icons.Filled.Add,
|
||||||
contentDescription = "Options",
|
contentDescription = "Options",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.ryujinx.android.views
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -73,7 +72,7 @@ class MainView {
|
|||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
GameStats(mainViewModel)
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
var ryujinxNative = RyujinxNative()
|
val ryujinxNative = RyujinxNative()
|
||||||
|
|
||||||
// touch surface
|
// touch surface
|
||||||
Surface(color = Color.Transparent, modifier = Modifier
|
Surface(color = Color.Transparent, modifier = Modifier
|
||||||
@ -82,27 +81,30 @@ class MainView {
|
|||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
awaitPointerEventScope {
|
awaitPointerEventScope {
|
||||||
while (true) {
|
while (true) {
|
||||||
Thread.sleep(2);
|
Thread.sleep(2)
|
||||||
val event = awaitPointerEvent()
|
val event = awaitPointerEvent()
|
||||||
|
|
||||||
if(controller.isVisible)
|
if(controller.isVisible)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var change = event
|
val change = event
|
||||||
.component1()
|
.component1()
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
change?.apply {
|
change?.apply {
|
||||||
var position = this.position
|
val position = this.position
|
||||||
|
|
||||||
if (event.type == PointerEventType.Press) {
|
when (event.type) {
|
||||||
|
PointerEventType.Press -> {
|
||||||
ryujinxNative.inputSetTouchPoint(
|
ryujinxNative.inputSetTouchPoint(
|
||||||
position.x.roundToInt(),
|
position.x.roundToInt(),
|
||||||
position.y.roundToInt()
|
position.y.roundToInt()
|
||||||
)
|
)
|
||||||
} else if (event.type == PointerEventType.Release) {
|
}
|
||||||
|
PointerEventType.Release -> {
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
ryujinxNative.inputReleaseTouchPoint()
|
||||||
|
|
||||||
} else if (event.type == PointerEventType.Move) {
|
}
|
||||||
|
PointerEventType.Move -> {
|
||||||
ryujinxNative.inputSetTouchPoint(
|
ryujinxNative.inputSetTouchPoint(
|
||||||
position.x.roundToInt(),
|
position.x.roundToInt(),
|
||||||
position.y.roundToInt()
|
position.y.roundToInt()
|
||||||
@ -112,6 +114,7 @@ class MainView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
}
|
}
|
||||||
controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
|
controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
|
||||||
@ -128,7 +131,7 @@ class MainView {
|
|||||||
}
|
}
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberVideogameAsset(): ImageVector {
|
fun rememberVideogameAsset(): ImageVector {
|
||||||
var primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
return remember {
|
return remember {
|
||||||
ImageVector.Builder(
|
ImageVector.Builder(
|
||||||
name = "videogame_asset",
|
name = "videogame_asset",
|
||||||
@ -224,20 +227,20 @@ class MainView {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GameStats(mainViewModel: MainViewModel){
|
fun GameStats(mainViewModel: MainViewModel){
|
||||||
var fifo = remember {
|
val fifo = remember {
|
||||||
mutableStateOf(0.0)
|
mutableStateOf(0.0)
|
||||||
}
|
}
|
||||||
var gameFps = remember {
|
val gameFps = remember {
|
||||||
mutableStateOf(0.0)
|
mutableStateOf(0.0)
|
||||||
}
|
}
|
||||||
var gameTime = remember {
|
val gameTime = remember {
|
||||||
mutableStateOf(0.0)
|
mutableStateOf(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface(modifier = Modifier.padding(16.dp),
|
Surface(modifier = Modifier.padding(16.dp),
|
||||||
color = MaterialTheme.colorScheme.surface.copy(0.4f)) {
|
color = MaterialTheme.colorScheme.surface.copy(0.4f)) {
|
||||||
Column {
|
Column {
|
||||||
var gameTimeVal = 0.0;
|
var gameTimeVal = 0.0
|
||||||
if (!gameTime.value.isInfinite())
|
if (!gameTime.value.isInfinite())
|
||||||
gameTimeVal = gameTime.value
|
gameTimeVal = gameTime.value
|
||||||
Text(text = "${String.format("%.3f", fifo.value)} %")
|
Text(text = "${String.format("%.3f", fifo.value)} %")
|
||||||
|
@ -13,36 +13,24 @@ import androidx.compose.animation.expandVertically
|
|||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
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.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
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.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.ryujinx.android.viewmodels.SettingsViewModel
|
import org.ryujinx.android.viewmodels.SettingsViewModel
|
||||||
import org.ryujinx.android.viewmodels.VulkanDriverViewModel
|
|
||||||
|
|
||||||
class SettingViews {
|
class SettingViews {
|
||||||
companion object {
|
companion object {
|
||||||
@ -63,38 +50,38 @@ class SettingViews {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Main(settingsViewModel: SettingsViewModel) {
|
fun Main(settingsViewModel: SettingsViewModel) {
|
||||||
var loaded = remember {
|
val loaded = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHostMapped = remember {
|
val isHostMapped = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var useNce = remember {
|
val useNce = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var enableVsync = remember {
|
val enableVsync = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var enableDocked = remember {
|
val enableDocked = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var enablePtc = remember {
|
val enablePtc = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var ignoreMissingServices = remember {
|
val ignoreMissingServices = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var enableShaderCache = remember {
|
val enableShaderCache = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var enableTextureRecompression = remember {
|
val enableTextureRecompression = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var resScale = remember {
|
val resScale = remember {
|
||||||
mutableStateOf(1f)
|
mutableStateOf(1f)
|
||||||
}
|
}
|
||||||
var useVirtualController = remember {
|
val useVirtualController = remember {
|
||||||
mutableStateOf(true)
|
mutableStateOf(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,8 +457,8 @@ class SettingViews {
|
|||||||
title: String,
|
title: String,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
var expanded = false
|
val expanded = false
|
||||||
var mutableExpanded = remember {
|
val mutableExpanded = remember {
|
||||||
mutableStateOf(expanded)
|
mutableStateOf(expanded)
|
||||||
}
|
}
|
||||||
val transitionState = remember {
|
val transitionState = remember {
|
||||||
|
@ -3,11 +3,9 @@ package org.ryujinx.android.views
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
@ -35,7 +33,7 @@ class TitleUpdateViews {
|
|||||||
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
|
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
|
||||||
val viewModel = TitleUpdateViewModel(titleId)
|
val viewModel = TitleUpdateViewModel(titleId)
|
||||||
|
|
||||||
var selected = remember { mutableStateOf(0) }
|
val selected = remember { mutableStateOf(0) }
|
||||||
viewModel.data?.apply {
|
viewModel.data?.apply {
|
||||||
selected.value = paths.indexOf(this.selected) + 1
|
selected.value = paths.indexOf(this.selected) + 1
|
||||||
}
|
}
|
||||||
@ -67,14 +65,14 @@ class TitleUpdateViews {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var paths = remember {
|
val paths = remember {
|
||||||
mutableStateListOf<String>()
|
mutableStateListOf<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.setPaths(paths)
|
viewModel.setPaths(paths)
|
||||||
var index = 1
|
var index = 1
|
||||||
for (path in paths) {
|
for (path in paths) {
|
||||||
var i = index
|
val i = index
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
Row(modifier = Modifier.padding(8.dp)) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = (selected.value == i),
|
selected = (selected.value == i),
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
|
@ -21,3 +21,15 @@ kotlin.code.style=official
|
|||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
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
|
139
src/RyujinxAndroid/libryujinx/build.gradle
Normal file
139
src/RyujinxAndroid/libryujinx/build.gradle
Normal file
@ -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")
|
@ -16,3 +16,4 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
rootProject.name = "RyujinxAndroid"
|
rootProject.name = "RyujinxAndroid"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
include ':libryujinx'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user