Add the photoViewer applet

This commit is contained in:
Jacobwasbeast 2025-02-07 06:20:12 -06:00
parent 965fb9dd5f
commit 81c6aeec28
19 changed files with 607 additions and 2 deletions

View File

@ -92,6 +92,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
return ResultCode.Success;
}
[CommandCmif(22)]
// GetHomeMenuFunctions() -> object<nn::am::service::IHomeMenuFunctions>
public ResultCode GetHomeMenuFunctions(ServiceCtx context)
{
MakeObject(context, new IHomeMenuFunctions(context.Device.System));
return ResultCode.Success;
}
[CommandCmif(23)]
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
public ResultCode GetGlobalStateController(ServiceCtx context)
{
MakeObject(context, new IGlobalStateController(context));
return ResultCode.Success;
}
[CommandCmif(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>

View File

@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
public ResultCode GetGlobalStateController(ServiceCtx context)
{
MakeObject(context, new IGlobalStateController());
MakeObject(context, new IGlobalStateController(context));
return ResultCode.Success;
}

View File

@ -1,5 +1,7 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Applets;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
@ -23,6 +25,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
_appletStandalone.InputData.Enqueue(miiEditInputData);
}
else if (context.Device.Processes.ActiveApplication.ProgramId == 0x010000000000100D)
{
_appletStandalone = new AppletStandalone()
{
AppletId = AppletId.PhotoViewer,
LibraryAppletMode = LibraryAppletMode.AllForeground,
};
CommonArguments arguments = new CommonArguments();
ReadOnlySpan<byte> data = MemoryMarshal.Cast<CommonArguments, byte>(MemoryMarshal.CreateReadOnlySpan(ref arguments, 1));
byte[] argumentsBytes = data.ToArray();
_appletStandalone.InputData.Enqueue(argumentsBytes);
byte[] optionBytes = BitConverter.GetBytes(1);
_appletStandalone.InputData.Enqueue(optionBytes);
}
else
{
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");

View File

@ -1,7 +1,17 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IAppletCommonFunctions : IpcService
{
public IAppletCommonFunctions() { }
[CommandCmif(70)]
// SetCpuBoostRequestPriority(s32) -> void
public ResultCode SetCpuBoostRequestPriority(ServiceCtx context)
{
Logger.Info?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
}
}

View File

@ -147,6 +147,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_acquiredSleepLockEventHandle);
// NOTE: This needs to be signaled when sleep lock is acquired so it does not just wait forever.
// However, since we don't support sleep lock yet, it's fine to signal immediately.
_acquiredSleepLockEvent.ReadableEvent.Signal();
Logger.Stub?.PrintStub(LogClass.ServiceAm);

View File

@ -1,7 +1,46 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IGlobalStateController : IpcService
{
public IGlobalStateController() { }
KEvent _hdcpAuthenticationFailedEvent;
int _hdcpAuthenticationFailedEventHandle;
public IGlobalStateController(ServiceCtx context)
{
_hdcpAuthenticationFailedEvent = new KEvent(context.Device.System.KernelContext);
_hdcpAuthenticationFailedEventHandle = -1;
}
[CommandCmif(14)]
// ShouldSleepOnBoot() -> u8
public ResultCode ShouldSleepOnBoot(ServiceCtx context)
{
context.ResponseData.Write(false);
return ResultCode.Success;
}
[CommandCmif(15)]
// GetHdcpAuthenticationFailedEvent() -> handle<copy>
public ResultCode GetHdcpAuthenticationFailedEvent(ServiceCtx context)
{
if (_hdcpAuthenticationFailedEventHandle == -1)
{
Result resultCode = context.Process.HandleTable.GenerateHandle(_hdcpAuthenticationFailedEvent.ReadableEvent, out _hdcpAuthenticationFailedEventHandle);
if (resultCode != Result.Success)
{
return (ResultCode)resultCode.ErrorCode;
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_hdcpAuthenticationFailedEventHandle);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
}
}

View File

@ -1,8 +1,317 @@
using LibHac.Common.FixedArrays;
using LibHac.FsSystem;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Caps.Types;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Caps
{
[Service("caps:a")]
class IAlbumAccessorService : IpcService
{
public Dictionary<AlbumFileDateTime,string> AlbumFiles { get; set; }
public IAlbumAccessorService(ServiceCtx context) { }
[CommandCmif(1)]
[CommandCmif(101)]
// GetAlbumFileList(unknown<u8>) -> (unknown<8>, buffer<unknown, 6>)
public ResultCode GetAlbumFileList(ServiceCtx context)
{
int storageId = context.RequestData.ReadInt32();
// 0 = Nand or 1 = Sd Card
if (storageId == 1)
{
return ResultCode.Success;
}
Logger.Info?.Print(LogClass.ServiceCaps, $"Initializing album files with storage ID {storageId}.");
Logger.Stub?.PrintStub(LogClass.ServiceCaps);
string path = Path.Combine(AppDataManager.BaseDirPath, "screenshots");
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
int count = 0;
var buffer = context.Request.ReceiveBuff[0];
ulong position = buffer.Position;
AlbumFiles = new Dictionary<AlbumFileDateTime, string>();
int limit = 10000;
Span<AlbumEntry> entries = stackalloc AlbumEntry[limit];
Logger.Info?.Print(LogClass.Application, $"Limitting to {entries.Length} photos.");
foreach (string file in System.IO.Directory.GetFiles(path))
{
if (count+1 >= entries.Length)
{
Logger.Warning?.Print(LogClass.ServiceCaps,$"Too many screenshots. Limiting to {entries.Length}.");
break;
}
if (System.IO.Path.GetFileName(file).EndsWith(".png") || System.IO.Path.GetFileName(file).EndsWith(".jpg"))
{
Logger.Stub?.Print(LogClass.Application, $"Adding screenshot {System.IO.Path.GetFileName(file)}");
AlbumEntry album_entry = new AlbumEntry();
album_entry.EntrySize = (ulong) System.IO.Path.GetFileName(file).Length;
album_entry.FileId = new AlbumFileId();
album_entry.FileId.ApplicationId = 0x0;
album_entry.FileId.Time = FromDateTime(System.IO.File.GetLastWriteTimeUtc(file), (byte)count);
if (AlbumFiles.ContainsKey(album_entry.FileId.Time))
{
Logger.Warning?.Print(LogClass.ServiceCaps,$"Duplicate photo found {System.IO.Path.GetFileName(file)}. Skipping.");
continue;
}
album_entry.FileId.Storage = (byte)AlbumStorage.Sd;
album_entry.FileId.Contents = 0;
album_entry.FileId.Field19_0 = 0;
album_entry.FileId.Field19_1 = 0;
album_entry.FileId.Reserved = 0;
entries[count] = album_entry;
count++;
AlbumFiles.Add(album_entry.FileId.Time, file);
}
}
byte[] entryArray = MemoryMarshal.Cast<AlbumEntry, byte>(MemoryMarshal.CreateReadOnlySpan(ref entries[0], count)).ToArray();
context.Memory.Write(buffer.Position, entryArray);
Logger.Info?.Print(LogClass.ServiceCaps, $"GetAlbumFileCount(): returning {count}");
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(2)]
// LoadAlbumScreenShotImage(unknown<0x18>) -> (unknown<8>, buffer<unknown, 6>)
public ResultCode LoadAlbumScreenShotImage(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImage(1280, 720, context, fileId);
}
[CommandCmif(5)]
// IsAlbumMounted() -> bool
public ResultCode IsAlbumMounted(ServiceCtx context)
{
// TODO: Implement this properly.
Logger.Stub?.PrintStub(LogClass.ServiceCaps);
context.ResponseData.Write(true);
return ResultCode.Success;
}
[CommandCmif(8)]
// LoadAlbumScreenShotImageEx0
public ResultCode LoadAlbumScreenShotImageEx0(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImage(1280, 720, context, fileId);
}
[CommandCmif(14)]
public ResultCode LoadAlbumScreenShotThumbnail(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImage(320, 180, context, fileId);
}
public ResultCode LoadImageEx(int width, int height, ServiceCtx context, AlbumFileId fileId)
{
var outputBuffer = context.Request.ReceiveBuff[0];
var inputBuffer = context.Request.ReceiveBuff[1];
var output = new LoadAlbumScreenShotImageOutput
{
width = width,
height = height,
attribute = new ScreenShotAttribute{
Unknown0x00 = {},
AlbumImageOrientation = AlbumImageOrientation.Degrees0,
Unknown0x08 = {},
Unknown0x10 = {}
}
};
string imagePath = AlbumFiles[fileId.Time];
ScaleBytes(width, height, imagePath, out Span<byte> scaledBytes);
ReadOnlySpan<byte> data = MemoryMarshal.Cast<LoadAlbumScreenShotImageOutput, byte>(MemoryMarshal.CreateReadOnlySpan(ref output, 1));
byte[] outputBytes = data.ToArray();
context.Memory.Write(outputBuffer.Position, outputBytes);
context.Memory.Write(inputBuffer.Position, scaledBytes);
return ResultCode.Success;
}
public ResultCode LoadImage(int width, int height, ServiceCtx context, AlbumFileId fileId)
{
var outputBuffer = context.Request.ReceiveBuff[0];
var output = new LoadAlbumScreenShotImageOutput
{
width = width,
height = height,
attribute = new ScreenShotAttribute{
Unknown0x00 = {},
AlbumImageOrientation = AlbumImageOrientation.Degrees0,
Unknown0x08 = {},
Unknown0x10 = {}
}
};
string imagePath = AlbumFiles[fileId.Time];
ScaleBytes(width, height, imagePath, out Span<byte> scaledBytes);
ReadOnlySpan<byte> data = MemoryMarshal.Cast<LoadAlbumScreenShotImageOutput, byte>(MemoryMarshal.CreateReadOnlySpan(ref output, 1));
byte[] outputBytes = data.ToArray();
context.ResponseData.Write(outputBytes);
context.Memory.Write(outputBuffer.Position, scaledBytes);
return ResultCode.Success;
}
public ResultCode LoadImageEx1(int width, int height, ServiceCtx context, AlbumFileId fileId)
{
var outputImageSettings = context.Request.ReceiveBuff[0];
var outputData = context.Request.ReceiveBuff[1];
var buff3 = context.Request.ReceiveBuff[2];
Logger.Info?.Print(LogClass.ServiceCaps, $"Loading thumbnail for {fileId.Time.UniqueId}");
var output = new LoadAlbumScreenShotImageOutput
{
width = width,
height = height,
attribute = new ScreenShotAttribute{
Unknown0x00 = {},
AlbumImageOrientation = AlbumImageOrientation.Degrees0,
Unknown0x08 = {},
Unknown0x10 = {}
}
};
string imagePath = AlbumFiles[fileId.Time];
ScaleBytes(width, height, imagePath, out Span<byte> scaledBytes);
ReadOnlySpan<byte> data = MemoryMarshal.Cast<LoadAlbumScreenShotImageOutput, byte>(MemoryMarshal.CreateReadOnlySpan(ref output, 1));
byte[] outputBytes = data.ToArray();
context.Memory.Write(outputImageSettings.Position, outputBytes);
context.Memory.Write(outputData.Position, scaledBytes);
return ResultCode.Success;
}
public void ScaleBytes(int width, int height, string imagePath, out Span<byte> output_bytes)
{
using (SKBitmap bitmap = SKBitmap.Decode(imagePath))
{
if (bitmap == null)
throw new ArgumentException("Unable to decode the input image.");
// STBI_rgb_alpha
SKImageInfo targetInfo = new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
using (SKBitmap scaledBitmap = new SKBitmap(targetInfo))
{
using (SKCanvas canvas = new SKCanvas(scaledBitmap))
{
canvas.Clear(SKColors.Transparent); // Clear canvas to avoid artifacts
var paint = new SKPaint
{
FilterQuality = SKFilterQuality.High, // High-quality scaling
IsAntialias = true, // Smooth edges
};
// Draw the scaled image
canvas.DrawBitmap(bitmap, new SKRect(0, 0, width, height), paint);
}
output_bytes = scaledBitmap.Bytes;
}
}
}
[CommandCmif(18)]
// GetAppletProgramIdTable(buffer<unknown, 70>) -> bool
public ResultCode GetAppletProgramIdTable(ServiceCtx context)
{
ulong tableBufPos = context.Request.ReceiveBuff[0].Position;
ulong tableBufSize = context.Request.ReceiveBuff[0].Size;
if (tableBufPos == 0)
{
return ResultCode.NullOutputBuffer;
}
context.Memory.Write(tableBufPos, 0x0100000000001000UL);
context.Memory.Write(tableBufPos + 8, 0x0100000000001fffUL);
context.ResponseData.Write(true);
return ResultCode.Success;
}
[CommandCmif(401)]
// GetAutoSavingStorage() -> bool
public ResultCode GetAutoSavingStorage(ServiceCtx context)
{
// TODO: Implement this properly.
Logger.Stub?.PrintStub(LogClass.ServiceCaps);
context.ResponseData.Write(false);
return ResultCode.Success;
}
[CommandCmif(1001)]
// LoadAlbumScreenShotThumbnailImageEx0(unknown<0x38>) -> (unknown<0x50>, buffer<unknown, 0x46>, buffer<unknown, 6>)
public ResultCode LoadAlbumScreenShotThumbnailImageEx0(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImageEx(320, 180, context, fileId);
}
[CommandCmif(1002)]
// LoadAlbumScreenShotImageEx1(unknown<0x38>) -> (buffer<unknown, 0x16>, buffer<unknown, 0x46>, buffer<unknown, 6>)
public ResultCode LoadAlbumScreenShotImageEx1(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImageEx1(1280, 720, context, fileId);
}
[CommandCmif(1003)]
public ResultCode LoadAlbumScreenShotThumbnailImageEx1(ServiceCtx context)
{
var fileId = context.RequestData.ReadStruct<AlbumFileId>();
return LoadImageEx1(320, 180, context, fileId);
}
public void GetWidthAndHeightFromInputBuffer(AlbumFileId id, out int width, out int height)
{
string path = AlbumFiles[id.Time];
width = 0;
height = 0;
using (SKBitmap bitmap = SKBitmap.Decode(path))
{
if (bitmap != null)
{
width = bitmap.Width;
height = bitmap.Height;
}
}
}
public static AlbumFileDateTime FromDateTime(DateTime dateTime, byte id)
{
return new AlbumFileDateTime
{
Year = (ushort)dateTime.Year,
Month = (byte)dateTime.Month,
Day = (byte)dateTime.Day,
Hour = (byte)dateTime.Hour,
Minute = (byte)dateTime.Minute,
Second = (byte)dateTime.Second,
UniqueId = id
};
}
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Caps.Types
{
struct AlbumEntry
{
public ulong EntrySize;
public AlbumFileId FileId;
}
}

View File

@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Caps.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct AlbumFileId
{
public ulong ApplicationId;
public AlbumFileDateTime Time;
public byte Storage;
public byte Contents;
public byte Field19_0;
public byte Field19_1;
public uint Reserved;
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Caps.Types
{
struct ApplicationAlbumFileEntry
{
public ApplicationAlbumEntry ApplicationAlbumEntry;
public AlbumFileDateTime Date;
public Common.Memory.Array8<byte> Unknown;
}
}

View File

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Caps.Types
{
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x500)]
struct LoadAlbumScreenShotImageOutput
{
public long width;
public long height;
public ScreenShotAttribute attribute;
}
}

View File

@ -0,0 +1,38 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Ns
{
class IContentManagementInterface : IpcService
{
public IContentManagementInterface(ServiceCtx context) { }
[CommandCmif(43)]
// CheckSdCardMountStatus()
public ResultCode CheckSdCardMountStatus(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceNs);
return ResultCode.Success;
}
// TODO: Implement proper space size calculation.
const long storageFreeAndTotalSpaceSize = 6999999999999L;
[CommandCmif(47)]
// GetTotalSpaceSize(u8 storage_id) -> u64
public ResultCode GetTotalSpaceSize(ServiceCtx context)
{
long storageId = context.RequestData.ReadByte();
context.ResponseData.Write(storageFreeAndTotalSpaceSize);
return ResultCode.Success;
}
[CommandCmif(48)]
// GetFreeSpaceSize(u8 storage_id) -> u64
public ResultCode GetFreeSpaceSize(ServiceCtx context)
{
long storageId = context.RequestData.ReadByte();
context.ResponseData.Write(storageFreeAndTotalSpaceSize);
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,25 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Ns
{
class IDownloadTaskInterface : IpcService
{
public IDownloadTaskInterface(ServiceCtx context) { }
[CommandCmif(707)]
// EnableAutoCommit()
public ResultCode EnableAutoCommit(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceNs);
return ResultCode.Success;
}
[CommandCmif(708)]
// DisableAutoCommit()
public ResultCode DisableAutoCommit(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceNs);
return ResultCode.Success;
}
}
}

View File

@ -24,5 +24,13 @@ namespace Ryujinx.HLE.HOS.Services.Ns
return ResultCode.Success;
}
[CommandCmif(1)]
// GetApplicationDesiredLanguage() -> u32
public ResultCode GetApplicationDesiredLanguage(ServiceCtx context)
{
context.ResponseData.Write((uint)context.Device.Configuration.Region);
return ResultCode.Success;
}
}
}

View File

@ -26,5 +26,23 @@ namespace Ryujinx.HLE.HOS.Services.Ns
return ResultCode.Success;
}
[CommandCmif(7997)]
// GetDownloadTaskInterface() -> object<nn::ns::detail::IDownloadTaskInterface>
public ResultCode GetDownloadTaskInterface(ServiceCtx context)
{
MakeObject(context, new IDownloadTaskInterface(context));
return ResultCode.Success;
}
[CommandCmif(7998)]
// GetContentManagementInterface() -> object<nn::ns::detail::IContentManagementInterface>
public ResultCode GetContentManagementInterface(ServiceCtx context)
{
MakeObject(context, new IContentManagementInterface(context));
return ResultCode.Success;
}
}
}

View File

@ -290,6 +290,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandCmif(79)]
// GetProductModel() -> s32
public ResultCode GetProductModel(ServiceCtx context)
{
context.ResponseData.Write(1);
Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
[CommandCmif(90)]
// GetMiiAuthorId() -> nn::util::Uuid

View File

@ -122,6 +122,56 @@
"zh_TW": "在獨立模式下開啟 Mii 編輯器小程式"
}
},
{
"ID": "MenuBarFileOpenAppletOpenPhotoViewerApplet",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "PhotoViewer Applet",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarFileOpenAppletOpenPhotoViewerAppletToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Open PhotoViewer Applet in Standalone mode",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabInputDirectMouseAccess",
"Translations": {

View File

@ -60,6 +60,11 @@
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
Icon="{ext:Icon fa-solid fa-person}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
<MenuItem
Name="PhotoViewerAppletMenuItem"
Header="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerApplet}"
Icon="{ext:Icon fa-solid fa-photo-film}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem

View File

@ -40,6 +40,7 @@ namespace Ryujinx.Ava.UI.Views.Main
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
PhotoViewerAppletMenuItem.Command = Commands.Create(OpenPhotoViewerApplet);
CloseRyujinxMenuItem.Command = Commands.Create(CloseWindow);
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
@ -154,6 +155,16 @@ namespace Ryujinx.Ava.UI.Views.Main
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
public AppletMetadata PhotoViewer => new(ViewModel.ContentManager,"photoViewer", 0x010000000000100D);
public async Task OpenPhotoViewerApplet()
{
if (PhotoViewer.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
{
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
}
public async Task OpenCheatManagerForCurrentApp()
{