Add the photoViewer applet
This commit is contained in:
parent
965fb9dd5f
commit
81c6aeec28
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumEntry.cs
Normal file
8
src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumEntry.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Caps.Types
|
||||
{
|
||||
struct AlbumEntry
|
||||
{
|
||||
public ulong EntrySize;
|
||||
public AlbumFileId FileId;
|
||||
}
|
||||
}
|
16
src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileId.cs
Normal file
16
src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileId.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
25
src/Ryujinx.HLE/HOS/Services/Ns/IDownloadTaskInterface.cs
Normal file
25
src/Ryujinx.HLE/HOS/Services/Ns/IDownloadTaskInterface.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user