add dlc manager

This commit is contained in:
Emmanuel Hansen 2023-07-30 18:20:38 +00:00
parent 6ce263a15a
commit ae29ec8ba8
9 changed files with 642 additions and 113 deletions

View File

@ -152,6 +152,22 @@ namespace LibRyujinx
return LoadApplication(path);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JLong titleId)
{
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)
{
@ -296,6 +312,27 @@ namespace LibRyujinx
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")]
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
{

View File

@ -31,6 +31,7 @@ using Ryujinx.Common.Utilities;
using System.Globalization;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Common.Logging.Targets;
using System.Collections.Generic;
namespace LibRyujinx
{
@ -545,6 +546,83 @@ namespace LibRyujinx
return gameInfo;
}
public static string GetDlcTitleId(string path, string ncaPath)
{
if (File.Exists(path))
{
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
using UniqueRef<IFile> ncaFile = new();
partitionFileSystem.OpenFile(ref ncaFile.Ref, ncaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), ncaPath);
if (nca != null)
{
return nca.Header.TitleId.ToString("X16");
}
}
return string.Empty;
}
private static Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(SwitchDevice.VirtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
}
return null;
}
public static List<string> GetDlcContentList(string path, ulong titleId)
{
if(!File.Exists(path))
return new List<string>();
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
List<string> paths = new List<string>();
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != titleId)
{
break;
}
paths.Add(fileEntry.FullPath);
}
}
return paths;
}
}
public class SwitchDevice : IDisposable

View File

@ -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()
}
}
}
}

View File

@ -50,4 +50,6 @@ class RyujinxNative {
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>
}

View File

@ -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 = "")

View File

@ -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")
}
}
}
}
}

View File

@ -56,7 +56,6 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogWindowProvider
import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController
@ -236,11 +235,12 @@ class HomeViews {
showBottomSheet.value = false
},
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 = {
openDialog.value = false
openTitleUpdateDialog.value = false
}) {
Surface(
modifier = Modifier
@ -251,24 +251,43 @@ class HomeViews {
) {
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
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,
modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.fillMaxSize()) {
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card(
modifier = Modifier.padding(8.dp),
onClick = {
openDialog.value = true
openTitleUpdateDialog.value = true
}
) {
Column(modifier = Modifier.padding(16.dp)) {
Icon(
painter = painterResource(R.drawable.app_update),
contentDescription = "More",
contentDescription = "Game Updates",
tint = Color.Green,
modifier = Modifier
.width(48.dp)
@ -281,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)
}
}
}
}
}
@ -307,10 +348,13 @@ class HomeViews {
runBlocking {
launch {
showLoading.value = true
val success = viewModel.mainViewModel?.loadGame(gameModel) ?: false
if(success) {
val success =
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success) {
launchOnUiThread {
viewModel.mainViewModel?.activity?.setFullScreen(true)
viewModel.mainViewModel?.activity?.setFullScreen(
true
)
viewModel.mainViewModel?.navController?.navigate("game")
}
}

View File

@ -1,14 +1,12 @@
package org.ryujinx.android.views
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
@ -19,7 +17,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -38,12 +35,12 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.ryujinx.android.GameController
import org.ryujinx.android.GameHost
import org.ryujinx.android.Icons
import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.SettingsViewModel
@ -143,7 +140,7 @@ class MainView {
mainViewModel.controller?.setVisible(!mainViewModel.controller!!.isVisible)
}) {
Icon(
imageVector = rememberVideogameAsset(),
imageVector = Icons.VideoGame(),
contentDescription = "Toggle Virtual Pad"
)
}
@ -201,103 +198,6 @@ class MainView {
}
}
}
@Composable
fun rememberVideogameAsset(): 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()
}
}
@Composable
fun GameStats(mainViewModel: MainViewModel) {
val fifo = remember {

View File

@ -107,7 +107,7 @@ class TitleUpdateViews {
) {
Icon(
Icons.Filled.Add,
contentDescription = "Remove"
contentDescription = "Add"
)
}
}