forked from MeloNX/MeloNX
add dlc manager
This commit is contained in:
parent
fb562c8077
commit
7a85dc2e76
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
}
|
@ -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 = "")
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,7 +251,25 @@ 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)
|
||||
}
|
||||
|
||||
}
|
||||
@ -261,14 +279,15 @@ class HomeViews {
|
||||
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
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -107,7 +107,7 @@ class TitleUpdateViews {
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Add,
|
||||
contentDescription = "Remove"
|
||||
contentDescription = "Add"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user