forked from MeloNX/MeloNX
android - add adrenotools module
test restore driver selection fix adreno hooking fix adreno hooking fix unzip code refactor virtual pad composition separate game loading from surface creation add closing emulation(starting a new one is still broken), disabled audio safely close audio on game exit add dlc manager fix AsFlags rename conflict
This commit is contained in:
parent
f28f2dfeeb
commit
b28f9a6331
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libadrenotools"]
|
||||||
|
path = src/RyujinxAndroid/app/src/main/cpp/libraries/adrenotools
|
||||||
|
url = https://github.com/bylaws/libadrenotools.git
|
@ -62,11 +62,6 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool Unregister(OboeHardwareDeviceSession session)
|
|
||||||
{
|
|
||||||
return _sessions.TryRemove(session, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
@ -82,6 +77,8 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pauseEvent.Dispose();
|
_pauseEvent.Dispose();
|
||||||
|
|
||||||
|
_sessions.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
internal class OboeHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
internal class OboeHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||||
{
|
{
|
||||||
private OboeHardwareDeviceDriver _driver;
|
private OboeHardwareDeviceDriver _driver;
|
||||||
|
private bool _isClosed;
|
||||||
private bool _isWorkerActive;
|
private bool _isWorkerActive;
|
||||||
private Queue<OboeAudioBuffer> _queuedBuffers;
|
private Queue<OboeAudioBuffer> _queuedBuffers;
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
@ -59,6 +60,9 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
{
|
{
|
||||||
StartIfNotPlaying();
|
StartIfNotPlaying();
|
||||||
|
|
||||||
|
if (_isClosed)
|
||||||
|
break;
|
||||||
|
|
||||||
fixed(byte* ptr = buffer.Data)
|
fixed(byte* ptr = buffer.Data)
|
||||||
OboeInterop.WriteToSession(_session, (ulong)ptr, buffer.SampleCount);
|
OboeInterop.WriteToSession(_session, (ulong)ptr, buffer.SampleCount);
|
||||||
|
|
||||||
@ -90,18 +94,31 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_session == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PrepareToClose();
|
||||||
|
|
||||||
OboeInterop.CloseSession(_session);
|
OboeInterop.CloseSession(_session);
|
||||||
|
|
||||||
|
_session = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareToClose()
|
public override void PrepareToClose()
|
||||||
{
|
{
|
||||||
|
_isClosed = true;
|
||||||
_isWorkerActive = false;
|
_isWorkerActive = false;
|
||||||
_workerThread.Join();
|
_workerThread?.Join();
|
||||||
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartIfNotPlaying()
|
private void StartIfNotPlaying()
|
||||||
{
|
{
|
||||||
lock (_trackLock)
|
lock (_trackLock)
|
||||||
{
|
{
|
||||||
|
if (_isClosed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (OboeInterop.IsPlaying(_session) == 0)
|
if (OboeInterop.IsPlaying(_session) == 0)
|
||||||
{
|
{
|
||||||
Start();
|
Start();
|
||||||
@ -145,6 +162,9 @@ namespace LibRyujinx.Shared.Audio.Oboe
|
|||||||
|
|
||||||
public override void Start()
|
public override void Start()
|
||||||
{
|
{
|
||||||
|
if (_isClosed)
|
||||||
|
return;
|
||||||
|
|
||||||
OboeInterop.StartSession(_session);
|
OboeInterop.StartSession(_session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ namespace LibRyujinx
|
|||||||
private static ManualResetEvent _surfaceEvent;
|
private static ManualResetEvent _surfaceEvent;
|
||||||
private static long _surfacePtr;
|
private static long _surfacePtr;
|
||||||
|
|
||||||
|
public static VulkanLoader? VulkanLoader { get; private set; }
|
||||||
|
|
||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
private extern static IntPtr getStringPointer(JEnvRef jEnv, JStringLocalRef s);
|
private extern static IntPtr getStringPointer(JEnvRef jEnv, JStringLocalRef s);
|
||||||
|
|
||||||
@ -40,6 +42,9 @@ namespace LibRyujinx
|
|||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
internal extern static void setRenderingThread();
|
internal extern static void setRenderingThread();
|
||||||
|
|
||||||
|
[DllImport("libryujinxjni")]
|
||||||
|
internal extern static void debug_break(int code);
|
||||||
|
|
||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
internal extern static void onFrameEnd(double time);
|
internal extern static void onFrameEnd(double time);
|
||||||
|
|
||||||
@ -67,7 +72,7 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
var init = Initialize(path, enableDebugLogs);
|
var init = Initialize(path, enableDebugLogs);
|
||||||
|
|
||||||
AudioDriver = new OboeHardwareDeviceDriver();
|
_surfaceEvent?.Set();
|
||||||
|
|
||||||
_surfaceEvent = new ManualResetEvent(false);
|
_surfaceEvent = new ManualResetEvent(false);
|
||||||
|
|
||||||
@ -97,6 +102,7 @@ namespace LibRyujinx
|
|||||||
JStringLocalRef timeZone,
|
JStringLocalRef timeZone,
|
||||||
JBoolean ignoreMissingServices)
|
JBoolean ignoreMissingServices)
|
||||||
{
|
{
|
||||||
|
AudioDriver = new OboeHardwareDeviceDriver();
|
||||||
return InitializeDevice(isHostMapped,
|
return InitializeDevice(isHostMapped,
|
||||||
useNce,
|
useNce,
|
||||||
(SystemLanguage)(int)systemLanguage,
|
(SystemLanguage)(int)systemLanguage,
|
||||||
@ -146,6 +152,34 @@ namespace LibRyujinx
|
|||||||
return LoadApplication(path);
|
return LoadApplication(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
|
||||||
|
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JLong titleId)
|
||||||
|
{
|
||||||
|
var list = GetDlcContentList(GetString(jEnv, pathPtr), (ulong)(long)titleId);
|
||||||
|
|
||||||
|
debug_break(4);
|
||||||
|
|
||||||
|
return CreateStringArray(jEnv, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
|
||||||
|
public static JStringLocalRef JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JStringLocalRef ncaPath)
|
||||||
|
{
|
||||||
|
return CreateString(jEnv, GetDlcTitleId(GetString(jEnv, pathPtr), GetString(jEnv, ncaPath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
|
||||||
|
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
|
{
|
||||||
|
SignalEmulationClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
|
||||||
|
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
|
{
|
||||||
|
CloseEmulation();
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
|
||||||
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
|
||||||
{
|
{
|
||||||
@ -213,7 +247,7 @@ namespace LibRyujinx
|
|||||||
public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv,
|
public unsafe static JBoolean JniInitializeGraphicsRendererNative(JEnvRef jEnv,
|
||||||
JObjectLocalRef jObj,
|
JObjectLocalRef jObj,
|
||||||
JArrayLocalRef extensionsArray,
|
JArrayLocalRef extensionsArray,
|
||||||
JLong surfacePtr)
|
JLong driverHandle)
|
||||||
{
|
{
|
||||||
if (Renderer != null)
|
if (Renderer != null)
|
||||||
{
|
{
|
||||||
@ -248,16 +282,17 @@ namespace LibRyujinx
|
|||||||
extensions.Add(GetString(jEnv, ext));
|
extensions.Add(GetString(jEnv, ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
_surfaceEvent.Set();
|
if((long)driverHandle != 0)
|
||||||
|
{
|
||||||
_surfacePtr = surfacePtr;
|
VulkanLoader = new VulkanLoader((IntPtr)(long)driverHandle);
|
||||||
|
}
|
||||||
|
|
||||||
CreateSurface createSurfaceFunc = instance =>
|
CreateSurface createSurfaceFunc = instance =>
|
||||||
{
|
{
|
||||||
_surfaceEvent.WaitOne();
|
_surfaceEvent.WaitOne();
|
||||||
_surfaceEvent.Reset();
|
_surfaceEvent.Reset();
|
||||||
|
|
||||||
var api = Vk.GetApi();
|
var api = VulkanLoader?.GetApi() ?? 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
|
||||||
@ -277,6 +312,27 @@ namespace LibRyujinx
|
|||||||
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
return InitializeGraphicsRenderer(GraphicsBackend.Vulkan, createSurfaceFunc, extensions.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JArrayLocalRef CreateStringArray(JEnvRef jEnv, List<string> strings)
|
||||||
|
{
|
||||||
|
JEnvValue value = jEnv.Environment;
|
||||||
|
ref JNativeInterface jInterface = ref value.Functions;
|
||||||
|
IntPtr newObjectArrayPtr = jInterface.NewObjectArrayPointer;
|
||||||
|
IntPtr findClassPtr = jInterface.FindClassPointer;
|
||||||
|
IntPtr setObjectArrayElementPtr = jInterface.SetObjectArrayElementPointer;
|
||||||
|
|
||||||
|
var newObjectArray = newObjectArrayPtr.GetUnsafeDelegate<NewObjectArrayDelegate>();
|
||||||
|
var findClass = findClassPtr.GetUnsafeDelegate<FindClassDelegate>();
|
||||||
|
var setObjectArrayElement = setObjectArrayElementPtr.GetUnsafeDelegate<SetObjectArrayElementDelegate>();
|
||||||
|
var array = newObjectArray(jEnv, strings.Count, findClass(jEnv, GetCCharSequence("java/lang/String")), CreateString(jEnv, "")._value);
|
||||||
|
|
||||||
|
for (int i = 0; i < strings.Count; i++)
|
||||||
|
{
|
||||||
|
setObjectArrayElement(jEnv, array, i, CreateString(jEnv, strings[i])._value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
|
||||||
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
|
||||||
{
|
{
|
||||||
|
99
src/LibRyujinx/VulkanLoader.cs
Normal file
99
src/LibRyujinx/VulkanLoader.cs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Silk.NET.Core.Contexts;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LibRyujinx
|
||||||
|
{
|
||||||
|
public class VulkanLoader : IDisposable
|
||||||
|
{
|
||||||
|
private delegate IntPtr GetInstanceProcAddress(IntPtr instance, IntPtr name);
|
||||||
|
private delegate IntPtr GetDeviceProcAddress(IntPtr device, IntPtr name);
|
||||||
|
|
||||||
|
private IntPtr _loadedLibrary = IntPtr.Zero;
|
||||||
|
private GetInstanceProcAddress _getInstanceProcAddr;
|
||||||
|
private GetDeviceProcAddress _getDeviceProcAddr;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_loadedLibrary != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NativeLibrary.Free(_loadedLibrary);
|
||||||
|
_loadedLibrary = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanLoader(IntPtr driver)
|
||||||
|
{
|
||||||
|
_loadedLibrary = driver;
|
||||||
|
|
||||||
|
if (_loadedLibrary != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var instanceGetProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetInstanceProcAddr");
|
||||||
|
var deviceProc = NativeLibrary.GetExport(_loadedLibrary, "vkGetDeviceProcAddr");
|
||||||
|
|
||||||
|
_getInstanceProcAddr = Marshal.GetDelegateForFunctionPointer<GetInstanceProcAddress>(instanceGetProc);
|
||||||
|
_getDeviceProcAddr = Marshal.GetDelegateForFunctionPointer<GetDeviceProcAddress>(deviceProc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe Vk GetApi()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_loadedLibrary == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return Vk.GetApi();
|
||||||
|
}
|
||||||
|
var ctx = new MultiNativeContext(new INativeContext[1]);
|
||||||
|
var ret = new Vk(ctx);
|
||||||
|
ctx.Contexts[0] = new LamdaNativeContext
|
||||||
|
(
|
||||||
|
x =>
|
||||||
|
{
|
||||||
|
var xPtr = Marshal.StringToHGlobalAnsi(x);
|
||||||
|
byte* xp = (byte*)xPtr;
|
||||||
|
LibRyujinx.debug_break(0);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nint ptr = default;
|
||||||
|
ptr = _getInstanceProcAddr(ret.CurrentInstance.GetValueOrDefault().Handle, xPtr);
|
||||||
|
|
||||||
|
if (ptr == default)
|
||||||
|
{
|
||||||
|
ptr = _getInstanceProcAddr(IntPtr.Zero, xPtr);
|
||||||
|
|
||||||
|
if (ptr == default)
|
||||||
|
{
|
||||||
|
var currentDevice = ret.CurrentDevice.GetValueOrDefault().Handle;
|
||||||
|
if (currentDevice != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
ptr = _getDeviceProcAddr(currentDevice, xPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == default)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu, $"Failed to get function pointer: {x}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(xPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,12 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:appCategory="game"
|
android:appCategory="game"
|
||||||
android:theme="@style/Theme.RyujinxAndroid"
|
android:theme="@style/Theme.RyujinxAndroid"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:hardwareAccelerated="false"
|
||||||
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:theme="@style/Theme.RyujinxAndroid">
|
android:theme="@style/Theme.RyujinxAndroid">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -10,6 +10,11 @@ cmake_minimum_required(VERSION 3.22.1)
|
|||||||
|
|
||||||
project("ryujinxjni")
|
project("ryujinxjni")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||||
|
|
||||||
|
add_subdirectory("libraries/adrenotools")
|
||||||
|
|
||||||
# Creates and names a library, sets it as either STATIC
|
# Creates and names a library, sets it as either STATIC
|
||||||
# or SHARED, and provides the relative paths to its source code.
|
# or SHARED, and provides the relative paths to its source code.
|
||||||
# You can define multiple libraries, and CMake builds them for you.
|
# You can define multiple libraries, and CMake builds them for you.
|
||||||
@ -52,4 +57,6 @@ target_link_libraries( # Specifies the target library.
|
|||||||
oboe::oboe
|
oboe::oboe
|
||||||
${log-lib}
|
${log-lib}
|
||||||
-lvulkan
|
-lvulkan
|
||||||
-landroid)
|
-landroid
|
||||||
|
adrenotools
|
||||||
|
)
|
||||||
|
@ -10,8 +10,11 @@ void AudioSession::initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AudioSession::destroy() {
|
void AudioSession::destroy() {
|
||||||
|
if(stream == nullptr)
|
||||||
|
return;
|
||||||
stream->close();
|
stream->close();
|
||||||
delete stream;
|
|
||||||
|
stream = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSession::start() {
|
void AudioSession::start() {
|
||||||
@ -37,101 +40,108 @@ extern "C"
|
|||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_ryujinx_android_NativeHelpers_setDeviceId(
|
Java_org_ryujinx_android_NativeHelpers_setDeviceId(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jobject instance,
|
jobject instance,
|
||||||
jint device_id){
|
jint device_id) {
|
||||||
s_device_id = device_id;
|
s_device_id = device_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioSession* create_session(int sample_format,
|
AudioSession *create_session(int sample_format,
|
||||||
uint sample_rate,
|
uint sample_rate,
|
||||||
uint channel_count)
|
uint channel_count) {
|
||||||
{
|
using namespace oboe;
|
||||||
using namespace oboe;
|
|
||||||
|
|
||||||
AudioStreamBuilder builder;
|
AudioStreamBuilder builder;
|
||||||
|
|
||||||
AudioFormat format;
|
AudioFormat format;
|
||||||
|
|
||||||
switch (sample_format) {
|
switch (sample_format) {
|
||||||
case 0:
|
case 0:
|
||||||
format = AudioFormat::Invalid;
|
format = AudioFormat::Invalid;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
format = AudioFormat::I16;
|
format = AudioFormat::I16;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
format = AudioFormat::I24;
|
format = AudioFormat::I24;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
format = AudioFormat::I32;
|
format = AudioFormat::I32;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
format = AudioFormat::Float;
|
format = AudioFormat::Float;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
std::ostringstream string;
|
std::ostringstream string;
|
||||||
string << "Invalid Format" << sample_format;
|
string << "Invalid Format" << sample_format;
|
||||||
|
|
||||||
throw std::runtime_error(string.str());
|
throw std::runtime_error(string.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto session = new AudioSession();
|
auto session = new AudioSession();
|
||||||
session->initialize();
|
session->initialize();
|
||||||
|
|
||||||
session->format = format;
|
session->format = format;
|
||||||
session->channelCount = channel_count;
|
session->channelCount = channel_count;
|
||||||
|
|
||||||
builder.setDirection(Direction::Output)
|
builder.setDirection(Direction::Output)
|
||||||
->setPerformanceMode(PerformanceMode::LowLatency)
|
->setPerformanceMode(PerformanceMode::LowLatency)
|
||||||
->setSharingMode(SharingMode::Shared)
|
->setSharingMode(SharingMode::Shared)
|
||||||
->setFormat(format)
|
->setFormat(format)
|
||||||
->setChannelCount(channel_count)
|
->setChannelCount(channel_count)
|
||||||
->setSampleRate(sample_rate);
|
->setSampleRate(sample_rate);
|
||||||
AudioStream* stream;
|
AudioStream *stream;
|
||||||
if(builder.openStream(&stream) != oboe::Result::OK)
|
if (builder.openStream(&stream) != oboe::Result::OK) {
|
||||||
{
|
|
||||||
delete session;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
session->stream = stream;
|
|
||||||
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_session(AudioSession* session)
|
|
||||||
{
|
|
||||||
session->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop_session(AudioSession* session)
|
|
||||||
{
|
|
||||||
session->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_session_volume(AudioSession* session, float volume)
|
|
||||||
{
|
|
||||||
session->volume = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
float get_session_volume(AudioSession* session)
|
|
||||||
{
|
|
||||||
return session->volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
void close_session(AudioSession* session)
|
|
||||||
{
|
|
||||||
session->destroy();
|
|
||||||
|
|
||||||
delete session;
|
delete session;
|
||||||
|
session = nullptr;
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
session->stream = stream;
|
||||||
|
|
||||||
bool is_playing(AudioSession* session) {
|
return session;
|
||||||
return session->isStarted;
|
}
|
||||||
}
|
|
||||||
|
void start_session(AudioSession *session) {
|
||||||
void write_to_session(AudioSession* session, uint64_t data, uint64_t samples)
|
if (session == nullptr)
|
||||||
{
|
return;
|
||||||
session->read(data, samples);
|
session->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stop_session(AudioSession *session) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return;
|
||||||
|
session->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_session_volume(AudioSession *session, float volume) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return;
|
||||||
|
session->volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_session_volume(AudioSession *session) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return 0;
|
||||||
|
return session->volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_session(AudioSession *session) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return;
|
||||||
|
session->destroy();
|
||||||
|
|
||||||
|
delete session;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_playing(AudioSession *session) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return false;
|
||||||
|
return session->isStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_to_session(AudioSession *session, uint64_t data, uint64_t samples) {
|
||||||
|
if (session == nullptr)
|
||||||
|
return;
|
||||||
|
session->read(data, samples);
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
@ -15,6 +16,8 @@
|
|||||||
#include "vulkan_wrapper.h"
|
#include "vulkan_wrapper.h"
|
||||||
#include <vulkan/vulkan_android.h>
|
#include <vulkan/vulkan_android.h>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include "libraries/adrenotools/include/adrenotools/driver.h"
|
||||||
|
|
||||||
// A macro to pass call to Vulkan and check for return value for success
|
// A macro to pass call to Vulkan and check for return value for success
|
||||||
#define CALL_VK(func) \
|
#define CALL_VK(func) \
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "ryuijnx.h"
|
#include "ryuijnx.h"
|
||||||
#include "pthread.h"
|
#include "pthread.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
jmethodID _updateFrameTime;
|
jmethodID _updateFrameTime;
|
||||||
JNIEnv* _rendererEnv = nullptr;
|
JNIEnv* _rendererEnv = nullptr;
|
||||||
@ -179,13 +180,48 @@ Java_org_ryujinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
void onFrameEnd(double time){
|
void onFrameEnd(double time) {
|
||||||
auto env = getEnv(true);
|
auto env = getEnv(true);
|
||||||
auto cl = env->FindClass("org/ryujinx/android/MainActivity");
|
auto cl = env->FindClass("org/ryujinx/android/MainActivity");
|
||||||
_updateFrameTime = env->GetStaticMethodID( cl , "updateRenderSessionPerformance", "(J)V");
|
_updateFrameTime = env->GetStaticMethodID(cl, "updateRenderSessionPerformance", "(J)V");
|
||||||
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(now-_currentTimePoint).count();
|
auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
now - _currentTimePoint).count();
|
||||||
env->CallStaticVoidMethod(cl, _updateFrameTime,
|
env->CallStaticVoidMethod(cl, _updateFrameTime,
|
||||||
nano);
|
nano);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_org_ryujinx_android_NativeHelpers_loadDriver(JNIEnv *env, jobject thiz,
|
||||||
|
jstring native_lib_path,
|
||||||
|
jstring private_apps_path,
|
||||||
|
jstring driver_name) {
|
||||||
|
auto libPath = getStringPointer(env, native_lib_path);
|
||||||
|
auto privateAppsPath = getStringPointer(env, private_apps_path);
|
||||||
|
auto driverName = getStringPointer(env, driver_name);
|
||||||
|
|
||||||
|
auto handle = adrenotools_open_libvulkan(
|
||||||
|
RTLD_NOW,
|
||||||
|
ADRENOTOOLS_DRIVER_CUSTOM,
|
||||||
|
nullptr,
|
||||||
|
libPath,
|
||||||
|
privateAppsPath,
|
||||||
|
driverName,
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
delete libPath;
|
||||||
|
delete privateAppsPath;
|
||||||
|
delete driverName;
|
||||||
|
|
||||||
|
return (jlong)handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void debug_break(int code){
|
||||||
|
if(code >= 3)
|
||||||
|
int r = 0;
|
||||||
|
}
|
@ -13,6 +13,7 @@ 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.lifecycleScope
|
||||||
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
|
||||||
@ -27,11 +28,47 @@ import kotlinx.coroutines.flow.catch
|
|||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
|
|
||||||
typealias GamePad = RadialGamePad
|
typealias GamePad = RadialGamePad
|
||||||
typealias GamePadConfig = RadialGamePadConfig
|
typealias GamePadConfig = RadialGamePadConfig
|
||||||
|
|
||||||
class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) {
|
class GameController(var activity: Activity) {
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
private fun Create(context: Context, activity: Activity, controller: GameController) : View
|
||||||
|
{
|
||||||
|
val inflator = LayoutInflater.from(context)
|
||||||
|
val view = inflator.inflate(R.layout.game_layout, null)
|
||||||
|
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(controller.leftGamePad)
|
||||||
|
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(controller.rightGamePad)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun Compose(viewModel: MainViewModel) : Unit
|
||||||
|
{
|
||||||
|
AndroidView(
|
||||||
|
modifier = Modifier.fillMaxSize(), factory = { context ->
|
||||||
|
val controller = GameController(viewModel.activity)
|
||||||
|
val c = Create(context, viewModel.activity, controller)
|
||||||
|
viewModel.activity.lifecycleScope.apply {
|
||||||
|
viewModel.activity.lifecycleScope.launch {
|
||||||
|
val events = merge(controller.leftGamePad.events(),controller.rightGamePad.events())
|
||||||
|
.shareIn(viewModel.activity.lifecycleScope, SharingStarted.Lazily)
|
||||||
|
events.safeCollect {
|
||||||
|
controller.handleEvent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.controllerView = c
|
||||||
|
viewModel.setGameController(controller)
|
||||||
|
c
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var ryujinxNative: RyujinxNative
|
||||||
private var controllerView: View? = null
|
private var controllerView: View? = null
|
||||||
var leftGamePad: GamePad
|
var leftGamePad: GamePad
|
||||||
var rightGamePad: GamePad
|
var rightGamePad: GamePad
|
||||||
@ -56,36 +93,8 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
leftGamePad.gravityY = 1f
|
leftGamePad.gravityY = 1f
|
||||||
rightGamePad.gravityX = 1f
|
rightGamePad.gravityX = 1f
|
||||||
rightGamePad.gravityY = 1f
|
rightGamePad.gravityY = 1f
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
ryujinxNative = RyujinxNative()
|
||||||
fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit
|
|
||||||
{
|
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)})
|
|
||||||
|
|
||||||
lifecycleScope.apply {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val events = merge(leftGamePad.events(),rightGamePad.events())
|
|
||||||
.shareIn(lifecycleScope, SharingStarted.Lazily)
|
|
||||||
|
|
||||||
events.safeCollect {
|
|
||||||
handleEvent(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Create(context: Context) : View
|
|
||||||
{
|
|
||||||
val inflator = LayoutInflater.from(context)
|
|
||||||
val view = inflator.inflate(R.layout.game_layout, null)
|
|
||||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad)
|
|
||||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad)
|
|
||||||
|
|
||||||
controllerView = view
|
|
||||||
|
|
||||||
return controllerView as View
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVisible(isVisible: Boolean){
|
fun setVisible(isVisible: Boolean){
|
||||||
@ -99,7 +108,7 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
|
|
||||||
fun connect(){
|
fun connect(){
|
||||||
if(controllerId == -1)
|
if(controllerId == -1)
|
||||||
controllerId = ryujinxNative.inputConnectGamepad(0)
|
controllerId = RyujinxNative().inputConnectGamepad(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEvent(ev: Event) {
|
private fun handleEvent(ev: Event) {
|
||||||
|
@ -7,9 +7,12 @@ 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 org.ryujinx.android.viewmodels.VulkanDriverViewModel
|
||||||
|
import java.io.File
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
class GameHost(context: Context?, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
||||||
|
private var _isClosed: Boolean = false
|
||||||
private var _renderingThreadWatcher: Thread? = null
|
private var _renderingThreadWatcher: Thread? = null
|
||||||
private var _height: Int = 0
|
private var _height: Int = 0
|
||||||
private var _width: Int = 0
|
private var _width: Int = 0
|
||||||
@ -19,14 +22,9 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
private var _isStarted: Boolean = false
|
private var _isStarted: Boolean = false
|
||||||
private var _nativeWindow: Long = 0
|
private var _nativeWindow: Long = 0
|
||||||
private var _nativeHelper: NativeHelpers = NativeHelpers()
|
|
||||||
|
|
||||||
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
|
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
|
||||||
|
|
||||||
companion object {
|
|
||||||
var gameModel: GameModel? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
holder.addCallback(this)
|
holder.addCallback(this)
|
||||||
}
|
}
|
||||||
@ -35,11 +33,11 @@ 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) {
|
||||||
val isStarted = _isStarted
|
if(_isClosed)
|
||||||
|
return
|
||||||
start(holder)
|
start(holder)
|
||||||
|
|
||||||
if(isStarted && (_width != width || _height != height))
|
if(_width != width || _height != height)
|
||||||
{
|
{
|
||||||
val nativeHelpers = NativeHelpers()
|
val nativeHelpers = NativeHelpers()
|
||||||
val window = nativeHelpers.getNativeWindow(holder.surface)
|
val window = nativeHelpers.getNativeWindow(holder.surface)
|
||||||
@ -59,66 +57,33 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun close(){
|
||||||
|
_isClosed = true
|
||||||
|
_isInit = false
|
||||||
|
_isStarted = false
|
||||||
|
|
||||||
|
_updateThread?.join()
|
||||||
|
_renderingThreadWatcher?.join()
|
||||||
|
}
|
||||||
|
|
||||||
private fun start(surfaceHolder: SurfaceHolder) {
|
private fun start(surfaceHolder: SurfaceHolder) {
|
||||||
val game = gameModel ?: return
|
mainViewModel.gameHost = this
|
||||||
val path = game.getPath() ?: return
|
if(_isStarted)
|
||||||
if (_isStarted)
|
return;
|
||||||
return
|
|
||||||
|
|
||||||
var surface = surfaceHolder.surface
|
|
||||||
|
|
||||||
val settings = QuickSettings(mainViewModel.activity)
|
|
||||||
|
|
||||||
var success = _nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
|
||||||
EnableShaderCache = settings.enableShaderCache
|
|
||||||
EnableTextureRecompression = settings.enableTextureRecompression
|
|
||||||
ResScale = settings.resScale
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
val nativeHelpers = NativeHelpers()
|
|
||||||
val window = nativeHelpers.getNativeWindow(surfaceHolder.surface)
|
|
||||||
nativeInterop = NativeGraphicsInterop()
|
|
||||||
nativeInterop!!.VkRequiredExtensions = arrayOf(
|
|
||||||
"VK_KHR_surface", "VK_KHR_android_surface"
|
|
||||||
)
|
|
||||||
nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
|
|
||||||
nativeInterop!!.SurfaceHandle = window
|
|
||||||
|
|
||||||
success = _nativeRyujinx.graphicsInitializeRenderer(
|
|
||||||
nativeInterop!!.VkRequiredExtensions!!,
|
|
||||||
window
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
success = _nativeRyujinx.deviceInitialize(
|
|
||||||
settings.isHostMapped,
|
|
||||||
settings.useNce,
|
|
||||||
SystemLanguage.AmericanEnglish.ordinal,
|
|
||||||
RegionCode.USA.ordinal,
|
|
||||||
settings.enableVsync,
|
|
||||||
settings.enableDocked,
|
|
||||||
settings.enablePtc,
|
|
||||||
false,
|
|
||||||
"UTC",
|
|
||||||
settings.ignoreMissingServices
|
|
||||||
)
|
|
||||||
|
|
||||||
success = _nativeRyujinx.deviceLoad(path)
|
|
||||||
|
|
||||||
_nativeRyujinx.inputInitialize(width, height)
|
_nativeRyujinx.inputInitialize(width, height)
|
||||||
|
|
||||||
|
val settings = QuickSettings(mainViewModel.activity)
|
||||||
|
|
||||||
if(!settings.useVirtualController){
|
if(!settings.useVirtualController){
|
||||||
controller.setVisible(false)
|
mainViewModel.controller?.setVisible(false)
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
controller.connect()
|
mainViewModel.controller?.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
mainViewModel.activity.physicalControllerManager.connect()
|
mainViewModel.activity.physicalControllerManager.connect()
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
_nativeRyujinx.graphicsRendererSetSize(
|
_nativeRyujinx.graphicsRendererSetSize(
|
||||||
surfaceHolder.surfaceFrame.width(),
|
surfaceHolder.surfaceFrame.width(),
|
||||||
surfaceHolder.surfaceFrame.height()
|
surfaceHolder.surfaceFrame.height()
|
||||||
@ -127,7 +92,7 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
_guestThread = thread(start = true) {
|
_guestThread = thread(start = true) {
|
||||||
runGame()
|
runGame()
|
||||||
}
|
}
|
||||||
_isStarted = success
|
_isStarted = true
|
||||||
|
|
||||||
_updateThread = thread(start = true) {
|
_updateThread = thread(start = true) {
|
||||||
var c = 0
|
var c = 0
|
||||||
@ -161,6 +126,7 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
|
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_nativeRyujinx.graphicsRendererRunLoop()
|
_nativeRyujinx.graphicsRendererRunLoop()
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
class Icons {
|
||||||
|
companion object{
|
||||||
|
/// Icons exported from https://www.composables.com/icons
|
||||||
|
@Composable
|
||||||
|
fun Download(): ImageVector {
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "download",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = null,
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(20f, 26.25f)
|
||||||
|
quadToRelative(-0.25f, 0f, -0.479f, -0.083f)
|
||||||
|
quadToRelative(-0.229f, -0.084f, -0.438f, -0.292f)
|
||||||
|
lineToRelative(-6.041f, -6.083f)
|
||||||
|
quadToRelative(-0.417f, -0.375f, -0.396f, -0.917f)
|
||||||
|
quadToRelative(0.021f, -0.542f, 0.396f, -0.917f)
|
||||||
|
reflectiveQuadToRelative(0.916f, -0.396f)
|
||||||
|
quadToRelative(0.542f, -0.02f, 0.959f, 0.396f)
|
||||||
|
lineToRelative(3.791f, 3.792f)
|
||||||
|
verticalLineTo(8.292f)
|
||||||
|
quadToRelative(0f, -0.584f, 0.375f, -0.959f)
|
||||||
|
reflectiveQuadTo(20f, 6.958f)
|
||||||
|
quadToRelative(0.542f, 0f, 0.938f, 0.375f)
|
||||||
|
quadToRelative(0.395f, 0.375f, 0.395f, 0.959f)
|
||||||
|
verticalLineTo(21.75f)
|
||||||
|
lineToRelative(3.792f, -3.792f)
|
||||||
|
quadToRelative(0.375f, -0.416f, 0.917f, -0.396f)
|
||||||
|
quadToRelative(0.541f, 0.021f, 0.958f, 0.396f)
|
||||||
|
quadToRelative(0.375f, 0.375f, 0.375f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(-0.375f, 0.958f)
|
||||||
|
lineToRelative(-6.083f, 6.042f)
|
||||||
|
quadToRelative(-0.209f, 0.208f, -0.438f, 0.292f)
|
||||||
|
quadToRelative(-0.229f, 0.083f, -0.479f, 0.083f)
|
||||||
|
close()
|
||||||
|
moveTo(9.542f, 32.958f)
|
||||||
|
quadToRelative(-1.042f, 0f, -1.834f, -0.791f)
|
||||||
|
quadToRelative(-0.791f, -0.792f, -0.791f, -1.834f)
|
||||||
|
verticalLineToRelative(-4.291f)
|
||||||
|
quadToRelative(0f, -0.542f, 0.395f, -0.938f)
|
||||||
|
quadToRelative(0.396f, -0.396f, 0.938f, -0.396f)
|
||||||
|
quadToRelative(0.542f, 0f, 0.917f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(4.291f)
|
||||||
|
horizontalLineToRelative(20.916f)
|
||||||
|
verticalLineToRelative(-4.291f)
|
||||||
|
quadToRelative(0f, -0.542f, 0.375f, -0.938f)
|
||||||
|
quadToRelative(0.375f, -0.396f, 0.917f, -0.396f)
|
||||||
|
quadToRelative(0.583f, 0f, 0.958f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(4.291f)
|
||||||
|
quadToRelative(0f, 1.042f, -0.791f, 1.834f)
|
||||||
|
quadToRelative(-0.792f, 0.791f, -1.834f, 0.791f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun VideoGame(): ImageVector {
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "videogame_asset",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = SolidColor(primaryColor),
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(6.25f, 29.792f)
|
||||||
|
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
||||||
|
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
|
||||||
|
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
|
||||||
|
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
|
||||||
|
reflectiveQuadToRelative(-1.854f, 0.792f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0f, -2.625f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
moveToRelative(7.167f, -1.792f)
|
||||||
|
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
verticalLineToRelative(-2.791f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
verticalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
|
||||||
|
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(-0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(2.75f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
|
||||||
|
quadTo(8f, 19.458f, 8f, 20f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.937f, 0.375f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
verticalLineToRelative(2.791f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.938f, 0.375f)
|
||||||
|
close()
|
||||||
|
moveToRelative(11.125f, -0.5f)
|
||||||
|
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
|
||||||
|
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
|
||||||
|
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
|
||||||
|
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
|
||||||
|
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
|
||||||
|
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
|
||||||
|
close()
|
||||||
|
moveToRelative(3.916f, -5.833f)
|
||||||
|
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
|
||||||
|
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
|
||||||
|
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
|
||||||
|
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
|
||||||
|
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
|
||||||
|
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
|
||||||
|
close()
|
||||||
|
moveTo(6.25f, 27.167f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import android.view.KeyEvent
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -25,8 +26,10 @@ 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.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
|
import org.ryujinx.android.viewmodels.VulkanDriverViewModel
|
||||||
import org.ryujinx.android.views.HomeViews
|
import org.ryujinx.android.views.HomeViews
|
||||||
import org.ryujinx.android.views.MainView
|
import org.ryujinx.android.views.MainView
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@ -60,15 +63,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
external fun getRenderingThreadId() : Long
|
external fun getRenderingThreadId() : Long
|
||||||
external fun initVm()
|
external fun initVm()
|
||||||
|
|
||||||
fun setFullScreen() {
|
fun setFullScreen(fullscreen: Boolean) {
|
||||||
requestedOrientation =
|
requestedOrientation =
|
||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
||||||
|
|
||||||
val 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())
|
if (fullscreen) {
|
||||||
insets.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
} else {
|
||||||
|
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +103,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
event?.apply {
|
event?.apply {
|
||||||
return physicalControllerManager.onKeyEvent(this)
|
if(physicalControllerManager.onKeyEvent(this))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
@ -15,4 +15,6 @@ class NativeHelpers {
|
|||||||
external fun getNativeWindow(surface:Surface) : Long
|
external fun getNativeWindow(surface:Surface) : Long
|
||||||
external fun attachCurrentThread() : Unit
|
external fun attachCurrentThread() : Unit
|
||||||
external fun detachCurrentThread() : Unit
|
external fun detachCurrentThread() : Unit
|
||||||
|
|
||||||
|
external fun loadDriver(nativeLibPath:String, privateAppsPath:String, driverName:String) : Long
|
||||||
}
|
}
|
@ -25,7 +25,7 @@ class RyujinxNative {
|
|||||||
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
|
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
|
||||||
external fun graphicsInitializeRenderer(
|
external fun graphicsInitializeRenderer(
|
||||||
extensions: Array<String>,
|
extensions: Array<String>,
|
||||||
surface: Long
|
driver: Long
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
external fun deviceLoad(game: String): Boolean
|
external fun deviceLoad(game: String): Boolean
|
||||||
@ -47,5 +47,9 @@ class RyujinxNative {
|
|||||||
external fun inputSetButtonReleased(button: Int, id: Int): Unit
|
external fun inputSetButtonReleased(button: Int, id: Int): Unit
|
||||||
external fun inputConnectGamepad(index: Int): Int
|
external fun inputConnectGamepad(index: Int): Int
|
||||||
external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int): Unit
|
external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int): Unit
|
||||||
external fun graphicsSetSurface(surface: Long): String
|
external fun graphicsSetSurface(surface: Long)
|
||||||
|
external fun deviceCloseEmulation()
|
||||||
|
external fun deviceSignalEmulationClose()
|
||||||
|
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
|
||||||
|
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
|
||||||
}
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.text.intl.Locale
|
||||||
|
import androidx.compose.ui.text.toLowerCase
|
||||||
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DlcViewModel(val titleId: String) {
|
||||||
|
private var storageHelper: SimpleStorageHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val UpdateRequestCode = 1002
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(item: DlcItem) {
|
||||||
|
data?.apply {
|
||||||
|
this.removeAll { it.path == item.containerPath }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(refresh: MutableState<Boolean>) {
|
||||||
|
val callBack = storageHelper.onFileSelected
|
||||||
|
|
||||||
|
storageHelper.onFileSelected = { requestCode, files ->
|
||||||
|
run {
|
||||||
|
storageHelper.onFileSelected = callBack
|
||||||
|
if (requestCode == UpdateRequestCode) {
|
||||||
|
val file = files.firstOrNull()
|
||||||
|
file?.apply {
|
||||||
|
val path = file.getAbsolutePath(storageHelper.storage.context)
|
||||||
|
if (path.isNotEmpty()) {
|
||||||
|
data?.apply {
|
||||||
|
var contents = RyujinxNative().deviceGetDlcContentList(
|
||||||
|
path,
|
||||||
|
titleId.toLong(16)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (contents.isNotEmpty()) {
|
||||||
|
val contentPath = path
|
||||||
|
val container = DlcContainerList(contentPath);
|
||||||
|
|
||||||
|
for (content in contents)
|
||||||
|
container.dlc_nca_list.add(
|
||||||
|
DlcContainer(
|
||||||
|
true,
|
||||||
|
titleId,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.add(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storageHelper.openFilePicker(UpdateRequestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(items: List<DlcItem>) {
|
||||||
|
data?.apply {
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val json = gson.toJson(this)
|
||||||
|
jsonPath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||||
|
File(jsonPath).mkdirs()
|
||||||
|
File("$jsonPath/dlc.json").writeText(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getDlc(): List<DlcItem> {
|
||||||
|
var items = mutableListOf<DlcItem>()
|
||||||
|
|
||||||
|
data?.apply {
|
||||||
|
for (container in this) {
|
||||||
|
val containerPath = container.path
|
||||||
|
|
||||||
|
if (!File(containerPath).exists())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (dlc in container.dlc_nca_list) {
|
||||||
|
val enabled = remember {
|
||||||
|
mutableStateOf(dlc.enabled)
|
||||||
|
}
|
||||||
|
items.add(
|
||||||
|
DlcItem(
|
||||||
|
File(containerPath).name,
|
||||||
|
enabled,
|
||||||
|
containerPath,
|
||||||
|
dlc.fullPath,
|
||||||
|
RyujinxNative().deviceGetDlcTitleId(containerPath, dlc.fullPath)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: MutableList<DlcContainerList>? = null
|
||||||
|
private var jsonPath: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
jsonPath =
|
||||||
|
MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) + "/dlc.json"
|
||||||
|
storageHelper = MainActivity.StorageHelper!!
|
||||||
|
|
||||||
|
reloadFromDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reloadFromDisk() {
|
||||||
|
data = mutableListOf()
|
||||||
|
if (File(jsonPath).exists()) {
|
||||||
|
val gson = Gson()
|
||||||
|
val typeToken = object : TypeToken<MutableList<DlcContainerList>>() {}.type
|
||||||
|
data =
|
||||||
|
gson.fromJson<MutableList<DlcContainerList>>(File(jsonPath).readText(), typeToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DlcContainerList(
|
||||||
|
var path: String = "",
|
||||||
|
var dlc_nca_list: MutableList<DlcContainer> = mutableListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DlcContainer(
|
||||||
|
var enabled: Boolean = false,
|
||||||
|
var titleId: String = "",
|
||||||
|
var fullPath: String = "")
|
||||||
|
|
||||||
|
data class DlcItem(
|
||||||
|
var name:String = "",
|
||||||
|
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
|
||||||
|
var containerPath: String = "",
|
||||||
|
var fullPath: String = "",
|
||||||
|
var titleId: String = "")
|
@ -6,18 +6,28 @@ import android.os.Build
|
|||||||
import android.os.PerformanceHintManager
|
import android.os.PerformanceHintManager
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.GraphicsConfiguration
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.NativeGraphicsInterop
|
||||||
|
import org.ryujinx.android.NativeHelpers
|
||||||
import org.ryujinx.android.PerformanceManager
|
import org.ryujinx.android.PerformanceManager
|
||||||
|
import org.ryujinx.android.RegionCode
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import org.ryujinx.android.SystemLanguage
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
class MainViewModel(val activity: MainActivity) {
|
class MainViewModel(val activity: MainActivity) {
|
||||||
|
var gameHost: GameHost? = null
|
||||||
|
var controller: GameController? = null
|
||||||
var performanceManager: PerformanceManager? = null
|
var performanceManager: PerformanceManager? = null
|
||||||
var selected: GameModel? = null
|
var selected: GameModel? = null
|
||||||
private var gameTimeState: MutableState<Double>? = null
|
private var gameTimeState: MutableState<Double>? = null
|
||||||
private var gameFpsState: MutableState<Double>? = null
|
private var gameFpsState: MutableState<Double>? = null
|
||||||
private var fifoState: MutableState<Double>? = null
|
private var fifoState: MutableState<Double>? = null
|
||||||
private var navController : NavHostController? = null
|
var navController : NavHostController? = null
|
||||||
|
|
||||||
var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
|
var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
|
||||||
|
|
||||||
@ -29,15 +39,104 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGame(game:GameModel) {
|
fun closeGame() {
|
||||||
val controller = navController?: return
|
RyujinxNative().deviceSignalEmulationClose()
|
||||||
activity.setFullScreen()
|
gameHost?.close()
|
||||||
GameHost.gameModel = game
|
RyujinxNative().deviceCloseEmulation()
|
||||||
controller.navigate("game")
|
goBack()
|
||||||
|
activity.setFullScreen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNavController(controller: NavHostController) {
|
fun goBack(){
|
||||||
navController = controller
|
navController?.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadGame(game:GameModel) : Boolean {
|
||||||
|
var nativeRyujinx = RyujinxNative()
|
||||||
|
|
||||||
|
val path = game.getPath() ?: return false
|
||||||
|
|
||||||
|
val settings = QuickSettings(activity)
|
||||||
|
|
||||||
|
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
|
||||||
|
EnableShaderCache = settings.enableShaderCache
|
||||||
|
EnableTextureRecompression = settings.enableTextureRecompression
|
||||||
|
ResScale = settings.resScale
|
||||||
|
})
|
||||||
|
|
||||||
|
if(!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val nativeHelpers = NativeHelpers()
|
||||||
|
var nativeInterop = NativeGraphicsInterop()
|
||||||
|
nativeInterop!!.VkRequiredExtensions = arrayOf(
|
||||||
|
"VK_KHR_surface", "VK_KHR_android_surface"
|
||||||
|
)
|
||||||
|
nativeInterop!!.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
|
||||||
|
nativeInterop!!.SurfaceHandle = 0
|
||||||
|
|
||||||
|
var driverViewModel = VulkanDriverViewModel(activity);
|
||||||
|
var drivers = driverViewModel.getAvailableDrivers()
|
||||||
|
|
||||||
|
var driverHandle = 0L;
|
||||||
|
|
||||||
|
if (driverViewModel.selected.isNotEmpty()) {
|
||||||
|
var metaData = drivers.find { it.driverPath == driverViewModel.selected }
|
||||||
|
|
||||||
|
metaData?.apply {
|
||||||
|
var privatePath = activity.filesDir;
|
||||||
|
var privateDriverPath = privatePath.canonicalPath + "/driver/"
|
||||||
|
val pD = File(privateDriverPath)
|
||||||
|
if (pD.exists())
|
||||||
|
pD.deleteRecursively()
|
||||||
|
|
||||||
|
pD.mkdirs()
|
||||||
|
|
||||||
|
var driver = File(driverViewModel.selected)
|
||||||
|
var parent = driver.parentFile
|
||||||
|
for (file in parent.walkTopDown()) {
|
||||||
|
if (file.absolutePath == parent.absolutePath)
|
||||||
|
continue
|
||||||
|
file.copyTo(File(privateDriverPath + file.name), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
driverHandle = NativeHelpers().loadDriver(
|
||||||
|
activity.applicationInfo.nativeLibraryDir!! + "/",
|
||||||
|
privateDriverPath,
|
||||||
|
this.libraryName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
success = nativeRyujinx.graphicsInitializeRenderer(
|
||||||
|
nativeInterop!!.VkRequiredExtensions!!,
|
||||||
|
driverHandle
|
||||||
|
)
|
||||||
|
if(!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
success = nativeRyujinx.deviceInitialize(
|
||||||
|
settings.isHostMapped,
|
||||||
|
settings.useNce,
|
||||||
|
SystemLanguage.AmericanEnglish.ordinal,
|
||||||
|
RegionCode.USA.ordinal,
|
||||||
|
settings.enableVsync,
|
||||||
|
settings.enableDocked,
|
||||||
|
settings.enablePtc,
|
||||||
|
false,
|
||||||
|
"UTC",
|
||||||
|
settings.ignoreMissingServices
|
||||||
|
)
|
||||||
|
if(!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
success = nativeRyujinx.deviceLoad(path)
|
||||||
|
|
||||||
|
if(!success)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStatStates(
|
fun setStatStates(
|
||||||
@ -65,4 +164,11 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
this.value = gameTime
|
this.value = gameTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setGameController(controller: GameController) {
|
||||||
|
this.controller = controller
|
||||||
|
}
|
||||||
|
|
||||||
|
fun backCalled() {
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.ryujinx.android.viewmodels.DlcItem
|
||||||
|
import org.ryujinx.android.viewmodels.DlcViewModel
|
||||||
|
|
||||||
|
class DlcViews {
|
||||||
|
companion object {
|
||||||
|
@Composable
|
||||||
|
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
|
||||||
|
val viewModel = DlcViewModel(titleId)
|
||||||
|
|
||||||
|
var dlcList = remember {
|
||||||
|
mutableListOf<DlcItem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.data?.apply {
|
||||||
|
dlcList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
var refresh = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Column {
|
||||||
|
Row(modifier = Modifier.padding(8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Text(text = "DLC for ${name}", textAlign = TextAlign.Center, modifier = Modifier.align(
|
||||||
|
Alignment.CenterVertically
|
||||||
|
))
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.add(refresh)
|
||||||
|
},
|
||||||
|
modifier = Modifier.align(
|
||||||
|
Alignment.CenterVertically
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Add,
|
||||||
|
contentDescription = "Add"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
|
||||||
|
if(refresh.value) {
|
||||||
|
dlcList.clear()
|
||||||
|
dlcList.addAll(viewModel.getDlc())
|
||||||
|
refresh.value = false
|
||||||
|
}
|
||||||
|
LazyColumn(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(400.dp)){
|
||||||
|
items(dlcList) { dlcItem ->
|
||||||
|
dlcItem.apply {
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = (dlcItem.isEnabled.value),
|
||||||
|
onCheckedChange = { dlcItem.isEnabled.value = it })
|
||||||
|
Text(
|
||||||
|
text = dlcItem.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.wrapContentWidth(Alignment.Start)
|
||||||
|
.fillMaxWidth(0.9f)
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.remove(dlcItem)
|
||||||
|
refresh.value = true
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Filled.Delete,
|
||||||
|
contentDescription = "remove"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.align(Alignment.End),
|
||||||
|
onClick = {
|
||||||
|
openDialog.value = false
|
||||||
|
viewModel.save(dlcList)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text("Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ import androidx.compose.material3.FabPosition
|
|||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@ -59,6 +60,9 @@ import androidx.compose.ui.window.DialogWindowProvider
|
|||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
import org.ryujinx.android.R
|
import org.ryujinx.android.R
|
||||||
import org.ryujinx.android.viewmodels.GameModel
|
import org.ryujinx.android.viewmodels.GameModel
|
||||||
@ -143,14 +147,16 @@ class HomeViews {
|
|||||||
Column {
|
Column {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
navController.navigate("settings")
|
navController.navigate("settings")
|
||||||
}, modifier = Modifier.fillMaxWidth()
|
}, modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.align(Alignment.Start),
|
.align(Alignment.Start),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Settings,
|
Icons.Filled.Settings,
|
||||||
contentDescription = "Settings"
|
contentDescription = "Settings"
|
||||||
)
|
)
|
||||||
Text(text = "Settings", modifier = Modifier.padding(16.dp)
|
Text(text = "Settings", modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
.align(Alignment.CenterVertically))
|
.align(Alignment.CenterVertically))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,6 +173,7 @@ class HomeViews {
|
|||||||
val sheetState = rememberModalBottomSheetState()
|
val sheetState = rememberModalBottomSheetState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val showBottomSheet = remember { mutableStateOf(false) }
|
val showBottomSheet = remember { mutableStateOf(false) }
|
||||||
|
val showLoading = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
@ -198,22 +205,42 @@ class HomeViews {
|
|||||||
items(list) {
|
items(list) {
|
||||||
it.titleName?.apply {
|
it.titleName?.apply {
|
||||||
if (this.isNotEmpty())
|
if (this.isNotEmpty())
|
||||||
GameItem(it, viewModel, showBottomSheet)
|
GameItem(it, viewModel, showBottomSheet, showLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(showLoading.value){
|
||||||
|
AlertDialog(onDismissRequest = { }) {
|
||||||
|
Card(modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = MaterialTheme.shapes.medium) {
|
||||||
|
Column(modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()) {
|
||||||
|
Text(text = "Loading")
|
||||||
|
LinearProgressIndicator(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(showBottomSheet.value) {
|
if(showBottomSheet.value) {
|
||||||
ModalBottomSheet(onDismissRequest = {
|
ModalBottomSheet(onDismissRequest = {
|
||||||
showBottomSheet.value = false
|
showBottomSheet.value = false
|
||||||
},
|
},
|
||||||
sheetState = sheetState) {
|
sheetState = sheetState) {
|
||||||
val openDialog = remember { mutableStateOf(false) }
|
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
||||||
|
val openDlcDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if(openDialog.value) {
|
if(openTitleUpdateDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
AlertDialog(onDismissRequest = {
|
||||||
openDialog.value = false
|
openTitleUpdateDialog.value = false
|
||||||
}) {
|
}) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -224,24 +251,43 @@ class HomeViews {
|
|||||||
) {
|
) {
|
||||||
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
TitleUpdateViews.Main(titleId, name, openDialog)
|
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(openDlcDialog.value) {
|
||||||
|
AlertDialog(onDismissRequest = {
|
||||||
|
openDlcDialog.value = false
|
||||||
|
}) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
|
DlcViews.Main(titleId, name, openDlcDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Surface(color = MaterialTheme.colorScheme.surface,
|
Surface(color = MaterialTheme.colorScheme.surface,
|
||||||
modifier = Modifier.padding(16.dp)) {
|
modifier = Modifier.padding(16.dp)) {
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
Card(
|
Card(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
openDialog.value = true
|
openTitleUpdateDialog.value = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.app_update),
|
painter = painterResource(R.drawable.app_update),
|
||||||
contentDescription = "More",
|
contentDescription = "Game Updates",
|
||||||
tint = Color.Green,
|
tint = Color.Green,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(48.dp)
|
.width(48.dp)
|
||||||
@ -254,6 +300,28 @@ class HomeViews {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
onClick = {
|
||||||
|
openDlcDialog.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Icon(
|
||||||
|
imageVector = org.ryujinx.android.Icons.Download(),
|
||||||
|
contentDescription = "Game Dlc",
|
||||||
|
tint = Color.Green,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(48.dp)
|
||||||
|
.height(48.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(text = "Game DLC",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,15 +332,35 @@ class HomeViews {
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GameItem(gameModel: GameModel, viewModel: HomeViewModel, showSheet : MutableState<Boolean>) {
|
fun GameItem(
|
||||||
Card(shape = MaterialTheme.shapes.medium,
|
gameModel: GameModel,
|
||||||
|
viewModel: HomeViewModel,
|
||||||
|
showSheet: MutableState<Boolean>,
|
||||||
|
showLoading: MutableState<Boolean>
|
||||||
|
) {
|
||||||
|
Surface(shape = MaterialTheme.shapes.medium,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
|
if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
|
||||||
viewModel.mainViewModel?.loadGame(gameModel)
|
runBlocking {
|
||||||
|
launch {
|
||||||
|
showLoading.value = true
|
||||||
|
val success =
|
||||||
|
viewModel.mainViewModel?.loadGame(gameModel) ?: false
|
||||||
|
if (success) {
|
||||||
|
launchOnUiThread {
|
||||||
|
viewModel.mainViewModel?.activity?.setFullScreen(
|
||||||
|
true
|
||||||
|
)
|
||||||
|
viewModel.mainViewModel?.navController?.navigate("game")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
package org.ryujinx.android.views
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
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
|
||||||
@ -26,12 +35,12 @@ import androidx.compose.ui.input.pointer.PointerEventType
|
|||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import org.ryujinx.android.GameController
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.Icons
|
||||||
import org.ryujinx.android.RyujinxNative
|
import org.ryujinx.android.RyujinxNative
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.viewmodels.SettingsViewModel
|
import org.ryujinx.android.viewmodels.SettingsViewModel
|
||||||
@ -40,35 +49,40 @@ import kotlin.math.roundToInt
|
|||||||
class MainView {
|
class MainView {
|
||||||
companion object {
|
companion object {
|
||||||
@Composable
|
@Composable
|
||||||
fun Main(mainViewModel: MainViewModel){
|
fun Main(mainViewModel: MainViewModel) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
mainViewModel.setNavController(navController)
|
mainViewModel.navController = navController
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
||||||
composable("game") { GameView(mainViewModel) }
|
composable("game") { GameView(mainViewModel) }
|
||||||
composable("settings") { SettingViews.Main(SettingsViewModel(navController, mainViewModel.activity)) }
|
composable("settings") {
|
||||||
|
SettingViews.Main(
|
||||||
|
SettingsViewModel(
|
||||||
|
navController,
|
||||||
|
mainViewModel.activity
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GameView(mainViewModel: MainViewModel){
|
fun GameView(mainViewModel: MainViewModel) {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
val controller = remember {
|
|
||||||
GameController(mainViewModel.activity)
|
|
||||||
}
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
GameHost(context, controller, mainViewModel)
|
GameHost(context, mainViewModel)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
GameOverlay(mainViewModel, controller)
|
GameOverlay(mainViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GameOverlay(mainViewModel: MainViewModel, controller: GameController){
|
fun GameOverlay(mainViewModel: MainViewModel) {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
GameStats(mainViewModel)
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
@ -84,9 +98,6 @@ class MainView {
|
|||||||
Thread.sleep(2)
|
Thread.sleep(2)
|
||||||
val event = awaitPointerEvent()
|
val event = awaitPointerEvent()
|
||||||
|
|
||||||
if(controller.isVisible)
|
|
||||||
continue
|
|
||||||
|
|
||||||
val change = event
|
val change = event
|
||||||
.component1()
|
.component1()
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
@ -100,10 +111,12 @@ class MainView {
|
|||||||
position.y.roundToInt()
|
position.y.roundToInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerEventType.Release -> {
|
PointerEventType.Release -> {
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
ryujinxNative.inputReleaseTouchPoint()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerEventType.Move -> {
|
PointerEventType.Move -> {
|
||||||
ryujinxNative.inputSetTouchPoint(
|
ryujinxNative.inputSetTouchPoint(
|
||||||
position.x.roundToInt(),
|
position.x.roundToInt(),
|
||||||
@ -117,116 +130,76 @@ class MainView {
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
}
|
}
|
||||||
controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
|
GameController.Compose(mainViewModel)
|
||||||
Row(modifier = Modifier
|
Row(
|
||||||
.align(Alignment.BottomCenter)
|
modifier = Modifier
|
||||||
.padding(8.dp)) {
|
.align(Alignment.BottomCenter)
|
||||||
IconButton(modifier = Modifier.padding(4.dp),onClick = {
|
.padding(8.dp)
|
||||||
controller.setVisible(!controller.isVisible)
|
) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
mainViewModel.controller?.setVisible(!mainViewModel.controller!!.isVisible)
|
||||||
}) {
|
}) {
|
||||||
Icon(imageVector = rememberVideogameAsset(), contentDescription = "Toggle Virtual Pad")
|
Icon(
|
||||||
|
imageVector = Icons.VideoGame(),
|
||||||
|
contentDescription = "Toggle Virtual Pad"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var showBackNotice = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
showBackNotice.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBackNotice.value) {
|
||||||
|
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
||||||
|
Column {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = "Are you sure you want to exit the game?")
|
||||||
|
Text(text = "All unsaved data will be lost!")
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
mainViewModel.closeGame()
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Exit Game")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
showBackNotice.value = false
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberVideogameAsset(): ImageVector {
|
fun GameStats(mainViewModel: MainViewModel) {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
|
||||||
return remember {
|
|
||||||
ImageVector.Builder(
|
|
||||||
name = "videogame_asset",
|
|
||||||
defaultWidth = 40.0.dp,
|
|
||||||
defaultHeight = 40.0.dp,
|
|
||||||
viewportWidth = 40.0f,
|
|
||||||
viewportHeight = 40.0f
|
|
||||||
).apply {
|
|
||||||
path(
|
|
||||||
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
|
|
||||||
fillAlpha = 1f,
|
|
||||||
stroke = SolidColor(primaryColor),
|
|
||||||
strokeAlpha = 1f,
|
|
||||||
strokeLineWidth = 1.0f,
|
|
||||||
strokeLineCap = StrokeCap.Butt,
|
|
||||||
strokeLineJoin = StrokeJoin.Miter,
|
|
||||||
strokeLineMiter = 1f,
|
|
||||||
pathFillType = PathFillType.NonZero
|
|
||||||
) {
|
|
||||||
moveTo(6.25f, 29.792f)
|
|
||||||
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
|
||||||
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
|
|
||||||
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
|
||||||
horizontalLineToRelative(27.5f)
|
|
||||||
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
|
|
||||||
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
|
|
||||||
reflectiveQuadToRelative(-1.854f, 0.792f)
|
|
||||||
close()
|
|
||||||
moveToRelative(0f, -2.625f)
|
|
||||||
horizontalLineToRelative(27.5f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
horizontalLineTo(6.25f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
close()
|
|
||||||
moveToRelative(7.167f, -1.792f)
|
|
||||||
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
|
|
||||||
reflectiveQuadToRelative(0.375f, -0.917f)
|
|
||||||
verticalLineToRelative(-2.791f)
|
|
||||||
horizontalLineToRelative(2.75f)
|
|
||||||
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
|
|
||||||
reflectiveQuadToRelative(0.375f, -0.917f)
|
|
||||||
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
|
||||||
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
|
|
||||||
horizontalLineToRelative(-2.75f)
|
|
||||||
verticalLineToRelative(-2.75f)
|
|
||||||
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
|
||||||
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
|
|
||||||
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
|
|
||||||
reflectiveQuadToRelative(-0.375f, 0.938f)
|
|
||||||
verticalLineToRelative(2.75f)
|
|
||||||
horizontalLineToRelative(-2.75f)
|
|
||||||
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
|
|
||||||
quadTo(8f, 19.458f, 8f, 20f)
|
|
||||||
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
|
||||||
reflectiveQuadToRelative(0.937f, 0.375f)
|
|
||||||
horizontalLineToRelative(2.75f)
|
|
||||||
verticalLineToRelative(2.791f)
|
|
||||||
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
|
||||||
reflectiveQuadToRelative(0.938f, 0.375f)
|
|
||||||
close()
|
|
||||||
moveToRelative(11.125f, -0.5f)
|
|
||||||
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
|
|
||||||
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
|
|
||||||
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
|
|
||||||
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
|
|
||||||
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
|
|
||||||
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
|
||||||
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
|
|
||||||
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
|
|
||||||
close()
|
|
||||||
moveToRelative(3.916f, -5.833f)
|
|
||||||
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
|
|
||||||
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
|
|
||||||
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
|
|
||||||
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
|
|
||||||
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
|
|
||||||
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
|
||||||
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
|
|
||||||
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
|
|
||||||
close()
|
|
||||||
moveTo(6.25f, 27.167f)
|
|
||||||
verticalLineTo(12.833f)
|
|
||||||
verticalLineToRelative(14.334f)
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameStats(mainViewModel: MainViewModel){
|
|
||||||
val fifo = remember {
|
val fifo = remember {
|
||||||
mutableStateOf(0.0)
|
mutableStateOf(0.0)
|
||||||
}
|
}
|
||||||
@ -237,8 +210,10 @@ class MainView {
|
|||||||
mutableStateOf(0.0)
|
mutableStateOf(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface(modifier = Modifier.padding(16.dp),
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.surface.copy(0.4f)) {
|
modifier = Modifier.padding(16.dp),
|
||||||
|
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())
|
||||||
|
@ -13,24 +13,34 @@ 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.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
|
||||||
@ -42,6 +52,7 @@ 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 {
|
||||||
@ -124,16 +135,6 @@ class SettingViews {
|
|||||||
})
|
})
|
||||||
}) { contentPadding ->
|
}) { contentPadding ->
|
||||||
Column(modifier = Modifier.padding(contentPadding)) {
|
Column(modifier = Modifier.padding(contentPadding)) {
|
||||||
BackHandler {
|
|
||||||
settingsViewModel.save(
|
|
||||||
isHostMapped,
|
|
||||||
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
|
||||||
enableShaderCache,
|
|
||||||
enableTextureRecompression,
|
|
||||||
resScale,
|
|
||||||
useVirtualController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExpandableView(onCardArrowClick = { }, title = "System") {
|
ExpandableView(onCardArrowClick = { }, title = "System") {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
Row(
|
Row(
|
||||||
@ -279,7 +280,7 @@ class SettingViews {
|
|||||||
enableTextureRecompression.value = !enableTextureRecompression.value
|
enableTextureRecompression.value = !enableTextureRecompression.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/*Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
@ -332,7 +333,7 @@ class SettingViews {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(300.dp)) {
|
.height(300.dp)) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
@ -358,7 +359,7 @@ class SettingViews {
|
|||||||
for (driver in drivers) {
|
for (driver in drivers) {
|
||||||
var ind = driverIndex
|
var ind = driverIndex
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
@ -369,18 +370,23 @@ class SettingViews {
|
|||||||
driverViewModel.selected =
|
driverViewModel.selected =
|
||||||
driver.driverPath
|
driver.driverPath
|
||||||
})
|
})
|
||||||
Column {
|
Column(modifier = Modifier.clickable {
|
||||||
|
selectedDriver.value =
|
||||||
|
ind
|
||||||
|
isChanged.value =
|
||||||
|
true
|
||||||
|
driverViewModel.selected =
|
||||||
|
driver.driverPath
|
||||||
|
}) {
|
||||||
Text(text = driver.libraryName,
|
Text(text = driver.libraryName,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth())
|
||||||
.clickable {
|
Text(text = driver.driverVersion,
|
||||||
selectedDriver.value =
|
modifier = Modifier
|
||||||
ind
|
.fillMaxWidth())
|
||||||
isChanged.value =
|
Text(text = driver.description,
|
||||||
true
|
modifier = Modifier
|
||||||
driverViewModel.selected =
|
.fillMaxWidth())
|
||||||
driver.driverPath
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +429,7 @@ class SettingViews {
|
|||||||
Text(text = "Drivers")
|
Text(text = "Drivers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExpandableView(onCardArrowClick = { }, title = "Input") {
|
ExpandableView(onCardArrowClick = { }, title = "Input") {
|
||||||
@ -446,6 +452,18 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler() {
|
||||||
|
settingsViewModel.save(
|
||||||
|
isHostMapped,
|
||||||
|
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
||||||
|
enableShaderCache,
|
||||||
|
enableTextureRecompression,
|
||||||
|
resScale,
|
||||||
|
useVirtualController
|
||||||
|
)
|
||||||
|
settingsViewModel.navController.popBackStack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ class TitleUpdateViews {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Add,
|
Icons.Filled.Add,
|
||||||
contentDescription = "Remove"
|
contentDescription = "Add"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user