android - fixes a few crashes in the user and home views

This commit is contained in:
Emmanuel Hansen 2023-10-31 19:28:36 +00:00
parent 71f3d22db8
commit 4fc253384b
20 changed files with 573 additions and 236 deletions

View File

@ -21,7 +21,6 @@ using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
@ -84,8 +83,9 @@ namespace LibRyujinx
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_initialize")]
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef jpath, JBoolean enableDebugLogs)
public static JBoolean JniInitialize(JEnvRef jEnv, JObjectLocalRef jObj, JLong jpathId, JBoolean enableDebugLogs)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SystemInfo.IsBionic = true;
Logger.AddTarget(
@ -95,7 +95,7 @@ namespace LibRyujinx
AsyncLogTargetOverflowAction.Block
));
var path = GetString(jEnv, jpath);
var path = GetStoredString(jpathId);
var init = Initialize(path, enableDebugLogs);
@ -118,6 +118,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceReloadFilesystem")]
public static void JniReloadFileSystem()
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SwitchDevice?.ReloadFileSystem();
}
@ -132,9 +133,10 @@ namespace LibRyujinx
JBoolean enableDockedMode,
JBoolean enablePtc,
JBoolean enableInternetAccess,
JStringLocalRef timeZone,
JLong timeZoneId,
JBoolean ignoreMissingServices)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
AudioDriver = new OboeHardwareDeviceDriver();
return InitializeDevice(isHostMapped,
useNce,
@ -144,13 +146,14 @@ namespace LibRyujinx
enableDockedMode,
enablePtc,
enableInternetAccess,
GetString(jEnv, timeZone),
GetStoredString(timeZoneId),
ignoreMissingServices);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFifo")]
public static JDouble JniGetGameFifo(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
return stats;
@ -159,6 +162,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameTime")]
public static JDouble JniGetGameFrameTime(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
return stats;
@ -167,6 +171,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameFrameRate")]
public static JDouble JniGetGameFrameRate(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var stats = SwitchDevice.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
return stats;
@ -175,6 +180,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")]
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
{
return false;
@ -185,10 +191,23 @@ 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)
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLaunchMiiEditor")]
public static JBoolean JniLaunchMiiEditApplet(JEnvRef jEnv, JObjectLocalRef jObj)
{
var list = GetDlcContentList(GetString(jEnv, pathPtr), (ulong)(long)titleId);
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
{
return false;
}
return LaunchMiiEditApplet();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcContentList")]
public static JArrayLocalRef JniGetDlcContentListNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong titleId)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var list = GetDlcContentList(GetStoredString(pathPtr), (ulong)(long)titleId);
debug_break(4);
@ -196,26 +215,30 @@ namespace LibRyujinx
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetDlcTitleId")]
public static JStringLocalRef JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr, JStringLocalRef ncaPath)
public static JLong JniGetDlcTitleIdNative(JEnvRef jEnv, JObjectLocalRef jObj, JLong pathPtr, JLong ncaPath)
{
return CreateString(jEnv, GetDlcTitleId(GetString(jEnv, pathPtr), GetString(jEnv, ncaPath)));
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
return storeString(GetDlcTitleId(GetStoredString(pathPtr), GetStoredString(ncaPath)));
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceSignalEmulationClose")]
public static void JniSignalEmulationCloseNative(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SignalEmulationClose();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceCloseEmulation")]
public static void JniCloseEmulationNative(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
CloseEmulation();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoadDescriptor")]
public static JBoolean JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt descriptor, JBoolean isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
{
return false;
@ -229,6 +252,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsInitialize")]
public static JBoolean JniInitializeGraphicsNative(JEnvRef jEnv, JObjectLocalRef jObj, JObjectLocalRef graphicObject)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
JEnvValue value = jEnv.Environment;
ref JNativeInterface jInterface = ref value.Functions;
IntPtr getObjectClassPtr = jInterface.GetObjectClassPointer;
@ -271,6 +295,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsSetSurface")]
public static void JniSetSurface(JEnvRef jEnv, JObjectLocalRef jObj, JLong surfacePtr, JLong window)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
_surfacePtr = surfacePtr;
_window = window;
@ -283,6 +308,7 @@ namespace LibRyujinx
JArrayLocalRef extensionsArray,
JLong driverHandle)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (Renderer != null)
{
return false;
@ -370,12 +396,14 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSize")]
public static void JniSetRendererSizeNative(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
Renderer?.Window?.SetSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetSwapBuffersCallback(() =>
{
var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime();
@ -387,21 +415,24 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfoFromPath")]
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef path)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var info = GetGameInfo(GetString(jEnv, path));
return GetInfo(jEnv, info, out SHA256 _);
return GetInfo(jEnv, info);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceGetGameInfo")]
public static JObjectLocalRef JniGetGameInfo(JEnvRef jEnv, JObjectLocalRef jObj, JInt fileDescriptor, JBoolean isXci)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
using var stream = OpenFile(fileDescriptor);
var info = GetGameInfo(stream, isXci);
return GetInfo(jEnv, info, out SHA256 _);
return GetInfo(jEnv, info);
}
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info, out SHA256 sha)
private static JObjectLocalRef GetInfo(JEnvRef jEnv, GameInfo? info)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var javaClassName = GetCCharSequence("org/ryujinx/android/viewmodels/GameInfo");
JEnvValue value = jEnv.Environment;
@ -427,24 +458,12 @@ namespace LibRyujinx
var newGlobal = newGlobalRef(jEnv, javaClass._value);
var constructor = getMethod(jEnv, javaClass, GetCCharSequence("<init>"), GetCCharSequence("()V"));
var newObj = newObject(jEnv, javaClass, constructor, 0);
sha = SHA256.Create();
var iconCacheByte = sha.ComputeHash(info?.Icon ?? Array.Empty<byte>());
var iconCache = BitConverter.ToString(iconCacheByte).Replace("-", "");
var cacheDirectory = Path.Combine(AppDataManager.BaseDirPath, "iconCache");
Directory.CreateDirectory(cacheDirectory);
var cachePath = Path.Combine(cacheDirectory, iconCache);
if (!File.Exists(cachePath))
{
File.WriteAllBytes(cachePath, info?.Icon ?? Array.Empty<byte>());
}
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleName"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleName)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("TitleId"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.TitleId)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Developer"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Developer)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Version"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, info?.Version)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("IconCache"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, iconCache)._value);
setObjectField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("Icon"), GetCCharSequence("Ljava/lang/String;")), CreateString(jEnv, Convert.ToBase64String(info?.Icon ?? Array.Empty<byte>()))._value);
setDoubleField(jEnv, newObj, getFieldId(jEnv, javaClass, GetCCharSequence("FileSize"), GetCCharSequence("D")), info?.FileSize ?? 0d);
return newObj;
@ -466,88 +485,102 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetVsync")]
public static void JniSetVsyncStateNative(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean enabled)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetVsyncState(enabled);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")]
public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputInitialize")]
public static void JniInitializeInput(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
InitializeInput(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetClientSize")]
public static void JniSetClientSize(JEnvRef jEnv, JObjectLocalRef jObj, JInt width, JInt height)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetClientSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetTouchPoint")]
public static void JniSetTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj, JInt x, JInt y)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetTouchPoint(x, y);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputReleaseTouchPoint")]
public static void JniReleaseTouchPoint(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
ReleaseTouchPoint();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputUpdate")]
public static void JniUpdateInput(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
UpdateInput();
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonPressed")]
public static void JniSetButtonPressed(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetButtonPressed((GamepadButtonInputId)(int)button, id);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetButtonReleased")]
public static void JniSetButtonReleased(JEnvRef jEnv, JObjectLocalRef jObj, JInt button, JInt id)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetButtonReleased((GamepadButtonInputId)(int)button, id);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")]
public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
SetStickAxis((StickInputId)(int)stick, new Vector2(float.IsNaN(x) ? 0 : x, float.IsNaN(y) ? 0 : y), id);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputConnectGamepad")]
public static JInt JniConnectGamepad(JEnvRef jEnv, JObjectLocalRef jObj, JInt index)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
return ConnectGamepad(index);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
public static JLong JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetOpenedUser();
return storeString(userId);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
public static JStringLocalRef JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
public static JLong JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
{
var userId = GetString(jEnv, userIdPtr) ?? "";
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? "";
return CreateString(jEnv, GetUserPicture(userId));
return storeString(GetUserPicture(userId));
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserPicture")]
public static void JniGetUserPicture(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef picturePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? "";
@ -555,16 +588,18 @@ namespace LibRyujinx
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserName")]
public static JStringLocalRef JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
public static JLong JniGetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
{
var userId = GetString(jEnv, userIdPtr) ?? "";
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? "";
return CreateString(jEnv, GetUserName(userId));
return storeString(GetUserName(userId));
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userSetUserName")]
public static void JniSetUserName(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr, JStringLocalRef userNamePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
var userName = GetString(jEnv, userNamePtr) ?? "";
@ -574,6 +609,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetAllUsers")]
public static JArrayLocalRef JniGetAllUsers(JEnvRef jEnv, JObjectLocalRef jObj)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var users = GetAllUsers();
return CreateStringArray(jEnv, users.ToList());
@ -582,6 +618,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userAddUser")]
public static void JniAddUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userNamePtr, JStringLocalRef picturePtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userName = GetString(jEnv, userNamePtr) ?? "";
var picture = GetString(jEnv, picturePtr) ?? "";
@ -591,15 +628,17 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userDeleteUser")]
public static void JniDeleteUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
DeleteUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
{
var userId = GetString(jEnv, userIdPtr) ?? "";
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetStoredString(userIdPtr) ?? "";
OpenUser(userId);
}
@ -607,6 +646,7 @@ namespace LibRyujinx
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userCloseUser")]
public static void JniCloseUser(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef userIdPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
var userId = GetString(jEnv, userIdPtr) ?? "";
CloseUser(userId);

View File

@ -1,4 +1,6 @@
using ARMeilleure.Translation;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE;
@ -70,6 +72,13 @@ namespace LibRyujinx
return (isXci ? emulationContext?.LoadXci(stream) : emulationContext.LoadNsp(stream)) ?? false;
}
public static bool LaunchMiiEditApplet()
{
string contentPath = SwitchDevice.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
return LoadApplication(contentPath);
}
public static bool LoadApplication(string? path)
{
var emulationContext = SwitchDevice.EmulationContext;

View File

@ -96,7 +96,7 @@ class GameController(var activity: Activity) {
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
ryujinxNative = RyujinxNative()
ryujinxNative = RyujinxNative.instance
}
fun setVisible(isVisible: Boolean){
@ -110,7 +110,7 @@ class GameController(var activity: Activity) {
fun connect(){
if(controllerId == -1)
controllerId = RyujinxNative().inputConnectGamepad(0)
controllerId = RyujinxNative.instance.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {

View File

@ -29,7 +29,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
private var _isStarted: Boolean = false
private val nativeWindow: NativeWindow
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
private var _nativeRyujinx: RyujinxNative = RyujinxNative.instance
init {
holder.addCallback(this)
@ -84,7 +84,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
if (_isStarted)
return
game = mainViewModel.gameModel
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel;
_nativeRyujinx.inputInitialize(width, height)
@ -110,7 +110,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
_updateThread = thread(start = true) {
var c = 0
val helper = NativeHelpers()
val helper = NativeHelpers.instance
while (_isStarted) {
_nativeRyujinx.inputUpdate()
Thread.sleep(1)
@ -133,7 +133,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
if (c >= 1000) {
if (helper.getProgressValue() == -1f)
progress?.apply {
this.value = "Loading ${game!!.titleName}"
this.value = "Loading ${if(mainViewModel.isMiiEditorLaunched) "Mii Editor" else game!!.titleName}"
}
c = 0
mainViewModel.updateStats(

View File

@ -216,7 +216,7 @@ class Helpers {
}
} finally {
isImporting.value = false
RyujinxNative().deviceReloadFilesystem()
RyujinxNative.instance.deviceReloadFilesystem()
}
}
}

View File

@ -23,6 +23,121 @@ class Icons {
companion object{
/// Icons exported from https://www.composables.com/icons
@Composable
fun applets(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "apps",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(9.708f, 33.125f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 2.02f, -0.834f)
quadToRelative(1.167f, 0f, 2f, 0.813f)
quadToRelative(0.834f, 0.812f, 0.834f, 2.021f)
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 1.979f, -0.834f)
reflectiveQuadToRelative(2f, 0.813f)
quadToRelative(0.833f, 0.812f, 0.833f, 2.021f)
quadToRelative(0f, 1.208f, -0.812f, 2.02f)
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -2.02f)
quadToRelative(0f, -1.167f, 0.813f, -2f)
quadToRelative(0.812f, -0.834f, 2.021f, -0.834f)
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
quadToRelative(0.813f, 0.812f, 0.813f, 2.021f)
quadToRelative(0f, 1.208f, -0.813f, 2.02f)
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
close()
moveTo(9.708f, 22.792f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 2.02f, -0.833f)
quadToRelative(1.167f, 0f, 2f, 0.812f)
quadToRelative(0.834f, 0.813f, 0.834f, 2.021f)
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
quadToRelative(-0.812f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 1.979f, -0.833f)
reflectiveQuadToRelative(2f, 0.812f)
quadToRelative(0.833f, 0.813f, 0.833f, 2.021f)
quadToRelative(0f, 1.167f, -0.812f, 1.979f)
quadToRelative(-0.813f, 0.813f, -2.021f, 0.813f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -1.979f)
reflectiveQuadToRelative(0.813f, -2f)
quadToRelative(0.812f, -0.833f, 2.021f, -0.833f)
quadToRelative(1.208f, 0f, 2.02f, 0.812f)
quadToRelative(0.813f, 0.813f, 0.813f, 2.021f)
quadToRelative(0f, 1.167f, -0.813f, 1.979f)
quadToRelative(-0.812f, 0.813f, -2.02f, 0.813f)
close()
moveTo(9.708f, 12.542f)
quadToRelative(-1.208f, 0f, -2.02f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 2.02f, -0.813f)
quadToRelative(1.167f, 0f, 2f, 0.813f)
quadToRelative(0.834f, 0.812f, 0.834f, 2.02f)
quadToRelative(0f, 1.167f, -0.813f, 2f)
quadToRelative(-0.812f, 0.834f, -2.021f, 0.834f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -1.979f, -0.813f)
quadToRelative(-0.813f, -0.812f, -0.813f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 1.979f, -0.813f)
reflectiveQuadToRelative(2f, 0.813f)
quadToRelative(0.833f, 0.812f, 0.833f, 2.02f)
quadToRelative(0f, 1.167f, -0.812f, 2f)
quadToRelative(-0.813f, 0.834f, -2.021f, 0.834f)
close()
moveToRelative(10.292f, 0f)
quadToRelative(-1.167f, 0f, -2f, -0.813f)
quadToRelative(-0.834f, -0.812f, -0.834f, -2.021f)
quadToRelative(0f, -1.208f, 0.813f, -2.02f)
quadToRelative(0.812f, -0.813f, 2.021f, -0.813f)
quadToRelative(1.208f, 0f, 2.02f, 0.813f)
quadToRelative(0.813f, 0.812f, 0.813f, 2.02f)
quadToRelative(0f, 1.167f, -0.813f, 2f)
quadToRelative(-0.812f, 0.834f, -2.02f, 0.834f)
close()
}
}.build()
}
}
@Composable
fun playArrow(color: Color): ImageVector {
return remember {
ImageVector.Builder(

View File

@ -64,7 +64,7 @@ class MainActivity : BaseActivity() {
return
val appPath: String = AppPath
val success = RyujinxNative().initialize(appPath, false)
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath), false)
_isInit = success
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -171,7 +171,7 @@ class MainActivity : BaseActivity() {
super.onStop()
if(isGameRunning) {
NativeHelpers().setTurboMode(false)
NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false)
}
}
@ -181,7 +181,7 @@ class MainActivity : BaseActivity() {
if(isGameRunning) {
setFullScreen(true)
NativeHelpers().setTurboMode(true)
NativeHelpers.instance.setTurboMode(true)
force60HzRefreshRate(true)
}
}
@ -190,7 +190,7 @@ class MainActivity : BaseActivity() {
super.onPause()
if(isGameRunning) {
NativeHelpers().setTurboMode(false)
NativeHelpers.instance.setTurboMode(false)
force60HzRefreshRate(false)
}
}

View File

@ -5,6 +5,7 @@ import android.view.Surface
class NativeHelpers {
companion object {
val instance = NativeHelpers()
init {
System.loadLibrary("ryujinxjni")
}

View File

@ -4,7 +4,7 @@ import android.view.SurfaceView
class NativeWindow(val surface: SurfaceView) {
var nativePointer: Long
var nativeHelpers: NativeHelpers = NativeHelpers()
var nativeHelpers: NativeHelpers = NativeHelpers.instance
private var _swapInterval : Int = 0
var maxSwapInterval : Int = 0

View File

@ -5,7 +5,7 @@ import android.view.MotionEvent
class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1
private var ryujinxNative: RyujinxNative = RyujinxNative()
private var ryujinxNative: RyujinxNative = RyujinxNative.instance
fun onKeyEvent(event: KeyEvent) : Boolean{
if(controllerId != -1) {

View File

@ -4,10 +4,10 @@ import org.ryujinx.android.viewmodels.GameInfo
@Suppress("KotlinJniMissingFunction")
class RyujinxNative {
external fun initialize(appPath: String, enableDebugLogs : Boolean): Boolean
external fun initialize(appPath: Long, enableDebugLogs : Boolean): Boolean
companion object {
val instance: RyujinxNative = RyujinxNative()
init {
System.loadLibrary("ryujinx")
}
@ -20,7 +20,7 @@ class RyujinxNative {
enableDockedMode : Boolean,
enablePtc : Boolean,
enableInternetAccess : Boolean,
timeZone : String,
timeZone : Long,
ignoreMissingServices : Boolean): Boolean
external fun graphicsInitialize(configuration: GraphicsConfiguration): Boolean
external fun graphicsInitializeRenderer(
@ -29,6 +29,7 @@ class RyujinxNative {
): Boolean
external fun deviceLoad(game: String): Boolean
external fun deviceLaunchMiiEditor(): Boolean
external fun deviceGetGameFrameRate(): Double
external fun deviceGetGameFrameTime(): Double
external fun deviceGetGameFifo(): Double
@ -51,16 +52,16 @@ class RyujinxNative {
external fun graphicsSetSurface(surface: Long, window: Long)
external fun deviceCloseEmulation()
external fun deviceSignalEmulationClose()
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
external fun deviceGetDlcTitleId(path: Long, ncaPath: Long) : Long
external fun deviceGetDlcContentList(path: Long, titleId: Long) : Array<String>
external fun userGetOpenedUser() : Long
external fun userGetUserPicture(userId: String) : String
external fun userGetUserPicture(userId: Long) : Long
external fun userSetUserPicture(userId: String, picture: String)
external fun userGetUserName(userId: String) : String
external fun userGetUserName(userId: Long) : Long
external fun userSetUserName(userId: String, userName: String)
external fun userGetAllUsers() : Array<String>
external fun userAddUser(username: String, picture: String)
external fun userDeleteUser(userId: String)
external fun userOpenUser(userId: String)
external fun userOpenUser(userId: Long)
external fun userCloseUser(userId: String)
}

View File

@ -11,6 +11,7 @@ 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.NativeHelpers
import org.ryujinx.android.RyujinxNative
import java.io.File
@ -39,8 +40,8 @@ class DlcViewModel(val titleId: String) {
val path = file.getAbsolutePath(storageHelper.storage.context)
if (path.isNotEmpty()) {
data?.apply {
var contents = RyujinxNative().deviceGetDlcContentList(
path,
var contents = RyujinxNative.instance.deviceGetDlcContentList(
NativeHelpers.instance.storeStringJava(path),
titleId.toLong(16)
)
@ -101,7 +102,7 @@ class DlcViewModel(val titleId: String) {
enabled,
containerPath,
dlc.fullPath,
RyujinxNative().deviceGetDlcTitleId(containerPath, dlc.fullPath)
NativeHelpers.instance.getStringJava(RyujinxNative.instance.deviceGetDlcTitleId(NativeHelpers.instance.storeStringJava(containerPath), NativeHelpers.instance.storeStringJava(dlc.fullPath)))
)
)
}
@ -149,4 +150,4 @@ data class DlcItem(
var isEnabled: MutableState<Boolean> = mutableStateOf(false),
var containerPath: String = "",
var fullPath: String = "",
var titleId: String = "")
var titleId: String = "")

View File

@ -15,12 +15,12 @@ class GameModel(var file: DocumentFile, val context: Context) {
var titleId: String? = null
var developer: String? = null
var version: String? = null
var iconCache: String? = null
var icon: String? = null
init {
fileName = file.name
var pid = open()
val gameInfo = RyujinxNative().deviceGetGameInfo(pid, file.extension.contains("xci"))
val gameInfo = RyujinxNative.instance.deviceGetGameInfo(pid, file.extension.contains("xci"))
close()
fileSize = gameInfo.FileSize
@ -28,7 +28,7 @@ class GameModel(var file: DocumentFile, val context: Context) {
titleName = gameInfo.TitleName
developer = gameInfo.Developer
version = gameInfo.Version
iconCache = gameInfo.IconCache
icon = gameInfo.Icon
}
fun open() : Int {
@ -53,5 +53,5 @@ class GameInfo {
var TitleId: String? = null
var Developer: String? = null
var Version: String? = null
var IconCache: String? = null
var Icon: String? = null
}

View File

@ -18,10 +18,10 @@ class HomeViewModel(
val mainViewModel: MainViewModel? = null
) {
private var isLoading: Boolean = false
private var gameList: SnapshotStateList<GameModel>? = null
private var loadedCache: List<GameModel> = listOf()
private var gameFolderPath: DocumentFile? = null
private var sharedPref: SharedPreferences? = null
val gameList: SnapshotStateList<GameModel> = SnapshotStateList()
init {
if (activity != null) {
@ -68,53 +68,35 @@ class HomeViewModel(
)
}
fun reloadGameList(ignoreCache: Boolean = false) {
fun reloadGameList() {
var storage = activity?.storageHelper ?: return
if(isLoading)
return
val folder = gameFolderPath ?: return
gameList.clear()
isLoading = true
if(!ignoreCache) {
val files = mutableListOf<GameModel>()
thread {
try {
for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp")
activity.let {
files.add(GameModel(file, it))
}
}
loadedCache = files.toList()
isLoading = false
applyFilter()
} finally {
isLoading = false
thread {
try {
val files = mutableListOf<GameModel>()
for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp")
activity.let {
val item = GameModel(file, it)
files.add(item)
gameList.add(item)
}
}
loadedCache = files.toList()
isLoading = false
} finally {
isLoading = false
}
}
else{
isLoading = false
applyFilter()
}
}
private fun applyFilter() {
if(isLoading)
return
gameList?.clear()
gameList?.addAll(loadedCache)
}
fun setViewList(list: SnapshotStateList<GameModel>) {
gameList = list
reloadGameList(loadedCache.isNotEmpty())
}
fun clearLoadedCache(){

View File

@ -29,6 +29,8 @@ class MainViewModel(val activity: MainActivity) {
var controller: GameController? = null
var performanceManager: PerformanceManager? = null
var selected: GameModel? = null
var isMiiEditorLaunched = false
val userViewModel = UserViewModel()
private var gameTimeState: MutableState<Double>? = null
private var gameFpsState: MutableState<Double>? = null
private var fifoState: MutableState<Double>? = null
@ -54,13 +56,13 @@ class MainViewModel(val activity: MainActivity) {
}
fun closeGame() {
RyujinxNative().deviceSignalEmulationClose()
RyujinxNative.instance.deviceSignalEmulationClose()
gameHost?.close()
RyujinxNative().deviceCloseEmulation()
RyujinxNative.instance.deviceCloseEmulation()
}
fun loadGame(game:GameModel) : Boolean {
val nativeRyujinx = RyujinxNative()
val nativeRyujinx = RyujinxNative.instance
val descriptor = game.open()
@ -68,6 +70,7 @@ class MainViewModel(val activity: MainActivity) {
return false
gameModel = game
isMiiEditorLaunched = false
val settings = QuickSettings(activity)
@ -81,7 +84,7 @@ class MainViewModel(val activity: MainActivity) {
if (!success)
return false
val nativeHelpers = NativeHelpers()
val nativeHelpers = NativeHelpers.instance
val nativeInterop = NativeGraphicsInterop()
nativeInterop.VkRequiredExtensions = arrayOf(
"VK_KHR_surface", "VK_KHR_android_surface"
@ -116,7 +119,7 @@ class MainViewModel(val activity: MainActivity) {
}
}
driverHandle = NativeHelpers().loadDriver(
driverHandle = NativeHelpers.instance.loadDriver(
activity.applicationInfo.nativeLibraryDir!! + "/",
privateDriverPath,
this.libraryName
@ -146,7 +149,7 @@ class MainViewModel(val activity: MainActivity) {
settings.enableDocked,
settings.enablePtc,
false,
"UTC",
NativeHelpers.instance.storeStringJava("UTC"),
settings.ignoreMissingServices
)
@ -167,6 +170,112 @@ class MainViewModel(val activity: MainActivity) {
return true
}
fun loadMiiEditor() : Boolean {
val nativeRyujinx = RyujinxNative.instance
gameModel = null
isMiiEditorLaunched = true
val settings = QuickSettings(activity)
var success = nativeRyujinx.graphicsInitialize(GraphicsConfiguration().apply {
EnableShaderCache = settings.enableShaderCache
EnableTextureRecompression = settings.enableTextureRecompression
ResScale = settings.resScale
BackendThreading = org.ryujinx.android.BackendThreading.Auto.ordinal
})
if (!success)
return false
val nativeHelpers = NativeHelpers.instance
val nativeInterop = NativeGraphicsInterop()
nativeInterop.VkRequiredExtensions = arrayOf(
"VK_KHR_surface", "VK_KHR_android_surface"
)
nativeInterop.VkCreateSurface = nativeHelpers.getCreateSurfacePtr()
nativeInterop.SurfaceHandle = 0
val driverViewModel = VulkanDriverViewModel(activity)
val drivers = driverViewModel.getAvailableDrivers()
var driverHandle = 0L
if (driverViewModel.selected.isNotEmpty()) {
val metaData = drivers.find { it.driverPath == driverViewModel.selected }
metaData?.apply {
val privatePath = activity.filesDir
val privateDriverPath = privatePath.canonicalPath + "/driver/"
val pD = File(privateDriverPath)
if (pD.exists())
pD.deleteRecursively()
pD.mkdirs()
val driver = File(driverViewModel.selected)
val parent = driver.parentFile
if (parent != null) {
for (file in parent.walkTopDown()) {
if (file.absolutePath == parent.absolutePath)
continue
file.copyTo(File(privateDriverPath + file.name), true)
}
}
driverHandle = NativeHelpers.instance.loadDriver(
activity.applicationInfo.nativeLibraryDir!! + "/",
privateDriverPath,
this.libraryName
)
}
}
success = nativeRyujinx.graphicsInitializeRenderer(
nativeInterop.VkRequiredExtensions!!,
driverHandle
)
if (!success)
return false
val semaphore = Semaphore(1, 0)
runBlocking {
semaphore.acquire()
launchOnUiThread {
// We are only able to initialize the emulation context on the main thread
success = nativeRyujinx.deviceInitialize(
settings.isHostMapped,
settings.useNce,
SystemLanguage.AmericanEnglish.ordinal,
RegionCode.USA.ordinal,
settings.enableVsync,
settings.enableDocked,
settings.enablePtc,
false,
NativeHelpers.instance.storeStringJava("UTC"),
settings.ignoreMissingServices
)
semaphore.release()
}
semaphore.acquire()
semaphore.release()
}
if (!success)
return false
success = nativeRyujinx.deviceLaunchMiiEditor()
if (!success)
return false
return true
}
fun clearPptcCache(titleId :String){
if(titleId.isNotEmpty()){
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
@ -254,15 +363,4 @@ class MainViewModel(val activity: MainActivity) {
this.progress = progress
gameHost?.setProgressStates(showLoading, progressValue, progress)
}
fun setRefreshUserState(refreshUser: MutableState<Boolean>)
{
this.refreshUser = refreshUser
}
fun requestUserRefresh(){
refreshUser?.apply {
value = true
}
}
}

View File

@ -0,0 +1,85 @@
package org.ryujinx.android.viewmodels
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative
import java.util.Base64
class UserViewModel {
var openedUser = UserModel()
val userList = mutableListOf<UserModel>()
init {
refreshUsers()
}
fun refreshUsers() {
userList.clear()
val native = RyujinxNative.instance
val helper = NativeHelpers.instance
val decoder = Base64.getDecoder()
openedUser = UserModel()
openedUser.id = helper.getStringJava(native.userGetOpenedUser())
if (openedUser.id.isNotEmpty()) {
openedUser.username =
helper.getStringJava(native.userGetUserName(helper.storeStringJava(openedUser.id)))
openedUser.userPicture = decoder.decode(
helper.getStringJava(
native.userGetUserPicture(
helper.storeStringJava(openedUser.id)
)
)
)
}
val users = native.userGetAllUsers()
for (user in users) {
userList.add(
UserModel(
user,
helper.getStringJava(native.userGetUserName(helper.storeStringJava(user))),
decoder.decode(
helper.getStringJava(
native.userGetUserPicture(
helper.storeStringJava(user)
)
)
)
)
)
}
}
fun openUser(userModel: UserModel){
val native = RyujinxNative.instance
val helper = NativeHelpers.instance
native.userOpenUser(helper.storeStringJava(userModel.id))
refreshUsers()
}
}
data class UserModel(var id : String = "", var username: String = "", var userPicture: ByteArray? = null) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserModel
if (id != other.id) return false
if (username != other.username) return false
if (userPicture != null) {
if (other.userPicture == null) return false
if (!userPicture.contentEquals(other.userPicture)) return false
} else if (other.userPicture != null) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + username.hashCode()
result = 31 * result + (userPicture?.contentHashCode() ?: 0)
return result
}
}

View File

@ -74,7 +74,7 @@ class GameViews {
Box(modifier = Modifier.fillMaxSize()) {
GameStats(mainViewModel)
val ryujinxNative = RyujinxNative()
val ryujinxNative = RyujinxNative.instance
val showController = remember {
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
@ -183,7 +183,7 @@ class GameViews {
IconButton(modifier = Modifier.padding(4.dp), onClick = {
showMore.value = false
enableVsync.value = !enableVsync.value
RyujinxNative().graphicsRendererSetVsync(enableVsync.value)
RyujinxNative.instance.graphicsRendererSetVsync(enableVsync.value)
}) {
Icon(
imageVector = Icons.vSync(),

View File

@ -48,7 +48,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@ -58,14 +57,9 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import coil.compose.AsyncImage
import com.anggrayudi.storage.extension.launchOnUiThread
import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.HomeViewModel
import java.io.File
import java.util.Base64
import java.util.Locale
import kotlin.concurrent.thread
@ -81,7 +75,6 @@ class HomeViews {
viewModel: HomeViewModel = HomeViewModel(),
navController: NavHostController? = null
) {
val native = RyujinxNative()
val showAppActions = remember { mutableStateOf(false) }
val showLoading = remember { mutableStateOf(false) }
val openTitleUpdateDialog = remember { mutableStateOf(false) }
@ -90,31 +83,7 @@ class HomeViews {
val query = remember {
mutableStateOf("")
}
val refresh = remember {
mutableStateOf(true)
}
val refreshUser = remember {
mutableStateOf(true)
}
viewModel.mainViewModel?.setRefreshUserState(refreshUser)
val user = remember {
mutableStateOf("")
}
val pic = remember {
mutableStateOf(ByteArray(0))
}
if (refreshUser.value) {
val id = native.userGetOpenedUser()
user.value = NativeHelpers().getStringJava(id)
if (user.value.isNotEmpty()) {
val decoder = Base64.getDecoder()
pic.value = decoder.decode(native.userGetUserPicture(user.value))
}
refreshUser.value = false;
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
@ -148,12 +117,14 @@ class HomeViews {
IconButton(onClick = {
navController?.navigate("user")
}) {
if (pic.value.isNotEmpty()) {
if (viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) {
val pic =
viewModel.mainViewModel.userViewModel.openedUser.userPicture
Image(
bitmap = BitmapFactory.decodeByteArray(
pic.value,
pic,
0,
pic.value.size
pic?.size ?: 0
)
.asImageBitmap(),
contentDescription = "user image",
@ -184,9 +155,26 @@ class HomeViews {
)
},
bottomBar = {
BottomAppBar(actions = {
BottomAppBar(
actions = {
if (showAppActions.value) {
IconButton(onClick = {
if(viewModel.mainViewModel?.selected != null) {
thread {
showLoading.value = true
val success =
viewModel.mainViewModel?.loadGame(viewModel.mainViewModel.selected!!)
?: false
if (success) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else {
viewModel.mainViewModel?.selected!!.close()
}
showLoading.value = false
}
}
}) {
Icon(
org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface),
@ -210,13 +198,17 @@ class HomeViews {
Text(text = "Clear PPTC Cache")
}, onClick = {
showAppMenu.value = false
viewModel.mainViewModel?.clearPptcCache(viewModel.mainViewModel?.selected?.titleId ?: "")
viewModel.mainViewModel?.clearPptcCache(
viewModel.mainViewModel?.selected?.titleId ?: ""
)
})
DropdownMenuItem(text = {
Text(text = "Purge Shader Cache")
}, onClick = {
showAppMenu.value = false
viewModel.mainViewModel?.purgeShaderCache(viewModel.mainViewModel?.selected?.titleId ?: "")
viewModel.mainViewModel?.purgeShaderCache(
viewModel.mainViewModel?.selected?.titleId ?: ""
)
})
DropdownMenuItem(text = {
Text(text = "Manage Updates")
@ -233,6 +225,39 @@ class HomeViews {
}
}
}
/*val showAppletMenu = remember { mutableStateOf(false) }
Box {
IconButton(onClick = {
showAppletMenu.value = true
}) {
Icon(
org.ryujinx.android.Icons.applets(MaterialTheme.colorScheme.onSurface),
contentDescription = "Applets"
)
}
DropdownMenu(
expanded = showAppletMenu.value,
onDismissRequest = { showAppletMenu.value = false }) {
DropdownMenuItem(text = {
Text(text = "Launch Mii Editor")
}, onClick = {
showAppletMenu.value = false
showLoading.value = true
thread {
val success =
viewModel.mainViewModel?.loadMiiEditor() ?: false
if (success) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else
viewModel.mainViewModel!!.isMiiEditorLaunched = false
showLoading.value = false
}
})
}
}*/
},
floatingActionButton = {
FloatingActionButton(
@ -255,12 +280,7 @@ class HomeViews {
) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding)) {
val list = remember {
mutableStateListOf<GameModel>()
}
if (refresh.value) {
viewModel.setViewList(list)
refresh.value = false
showAppActions.value = false
viewModel.gameList
}
val selectedModel = remember {
mutableStateOf(viewModel.mainViewModel?.selected)
@ -365,6 +385,7 @@ class HomeViews {
val color =
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
val decoder = Base64.getDecoder()
Surface(
shape = MaterialTheme.shapes.medium,
color = color,
@ -409,13 +430,12 @@ class HomeViews {
) {
Row {
if (!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") {
val iconSource =
MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
val imageFile = File(iconSource)
if (imageFile.exists()) {
if (gameModel.icon?.isNotEmpty() == true) {
val pic = decoder.decode(gameModel.icon)
val size = ImageSize / Resources.getSystem().displayMetrics.density
AsyncImage(
model = imageFile,
Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
.asImageBitmap(),
contentDescription = gameModel.titleName + " icon",
modifier = Modifier
.padding(end = 8.dp)

View File

@ -306,7 +306,7 @@ class SettingViews {
thread {
Helpers.importAppData(this, isImporting)
showImportCompletion.value = true
mainViewModel.requestUserRefresh()
mainViewModel.userViewModel.refreshUsers()
}
}
}, modifier = Modifier.padding(horizontal = 8.dp)) {
@ -329,7 +329,7 @@ class SettingViews {
AlertDialog(onDismissRequest = {
showImportCompletion.value = false
importFile.value = null
mainViewModel.requestUserRefresh()
mainViewModel.userViewModel.refreshUsers()
mainViewModel.homeViewModel.clearLoadedCache()
}) {
Card(

View File

@ -26,6 +26,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@ -36,40 +37,23 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.MainViewModel
import java.util.Base64
class UserViews {
companion object {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
val ryujinxNative = RyujinxNative()
val decoder = Base64.getDecoder()
val id = ryujinxNative.userGetOpenedUser()
val openedUser = remember {
mutableStateOf(NativeHelpers().getStringJava(id))
val reload = remember {
mutableStateOf(true)
}
val openedUserPic = remember {
mutableStateOf(decoder.decode(ryujinxNative.userGetUserPicture(openedUser.value)))
}
val openedUserName = remember {
mutableStateOf(ryujinxNative.userGetUserName(openedUser.value))
}
val userList = remember {
mutableListOf("")
}
fun refresh() {
userList.clear()
userList.addAll(ryujinxNative.userGetAllUsers())
viewModel?.userViewModel?.refreshUsers()
reload.value = true
}
LaunchedEffect(reload.value) {
reload.value = false
}
refresh()
Scaffold(modifier = Modifier.fillMaxSize(),
topBar = {
@ -102,25 +86,28 @@ class UserViews {
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Image(
bitmap = BitmapFactory.decodeByteArray(
openedUserPic.value,
0,
openedUserPic.value.size
).asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(96.dp)
.clip(CircleShape)
)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = openedUserName.value)
Text(text = openedUser.value)
if (viewModel?.userViewModel?.openedUser?.id?.isNotEmpty() == true) {
val openUser = viewModel.userViewModel.openedUser
Image(
bitmap = BitmapFactory.decodeByteArray(
openUser.userPicture,
0,
openUser.userPicture?.size ?: 0
).asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(4.dp)
.size(96.dp)
.clip(CircleShape)
)
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = openUser.username)
Text(text = openUser.id)
}
}
}
@ -139,34 +126,32 @@ class UserViews {
)
}
}
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 96.dp),
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
) {
items(userList) { user ->
val pic = decoder.decode(ryujinxNative.userGetUserPicture(user))
val name = ryujinxNative.userGetUserName(user)
Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size)
.asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
.clip(CircleShape)
.align(Alignment.CenterHorizontally)
.combinedClickable(
onClick = {
ryujinxNative.userOpenUser(user)
openedUser.value = user
openedUserPic.value = pic
openedUserName.value = name
viewModel?.requestUserRefresh()
})
)
if(viewModel?.userViewModel?.userList?.isNotEmpty() == true) {
items(viewModel.userViewModel.userList) { user ->
Image(
bitmap = BitmapFactory.decodeByteArray(user.userPicture, 0, user.userPicture?.size ?: 0)
.asImageBitmap(),
contentDescription = "selected image",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
.clip(CircleShape)
.align(Alignment.CenterHorizontally)
.combinedClickable(
onClick = {
viewModel.userViewModel.openUser(user)
reload.value = true
})
)
}
}
}
}