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;
|
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)]
|
[CommandCmif(1000)]
|
||||||
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
|
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
|
||||||
|
@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
|||||||
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
|
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
|
||||||
public ResultCode GetGlobalStateController(ServiceCtx context)
|
public ResultCode GetGlobalStateController(ServiceCtx context)
|
||||||
{
|
{
|
||||||
MakeObject(context, new IGlobalStateController());
|
MakeObject(context, new IGlobalStateController(context));
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
|
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);
|
_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
|
else
|
||||||
{
|
{
|
||||||
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
|
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
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
|
||||||
{
|
{
|
||||||
class IAppletCommonFunctions : IpcService
|
class IAppletCommonFunctions : IpcService
|
||||||
{
|
{
|
||||||
public IAppletCommonFunctions() { }
|
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);
|
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);
|
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
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
|
||||||
{
|
{
|
||||||
class IGlobalStateController : IpcService
|
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
|
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||||
{
|
{
|
||||||
[Service("caps:a")]
|
[Service("caps:a")]
|
||||||
class IAlbumAccessorService : IpcService
|
class IAlbumAccessorService : IpcService
|
||||||
{
|
{
|
||||||
|
public Dictionary<AlbumFileDateTime,string> AlbumFiles { get; set; }
|
||||||
public IAlbumAccessorService(ServiceCtx context) { }
|
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;
|
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;
|
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;
|
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)]
|
[CommandCmif(90)]
|
||||||
// GetMiiAuthorId() -> nn::util::Uuid
|
// GetMiiAuthorId() -> nn::util::Uuid
|
||||||
|
@ -122,6 +122,56 @@
|
|||||||
"zh_TW": "在獨立模式下開啟 Mii 編輯器小程式"
|
"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",
|
"ID": "SettingsTabInputDirectMouseAccess",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
@ -60,6 +60,11 @@
|
|||||||
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
|
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
|
||||||
Icon="{ext:Icon fa-solid fa-person}"
|
Icon="{ext:Icon fa-solid fa-person}"
|
||||||
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
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>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -40,6 +40,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
||||||
|
|
||||||
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
|
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
|
||||||
|
PhotoViewerAppletMenuItem.Command = Commands.Create(OpenPhotoViewerApplet);
|
||||||
CloseRyujinxMenuItem.Command = Commands.Create(CloseWindow);
|
CloseRyujinxMenuItem.Command = Commands.Create(CloseWindow);
|
||||||
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
|
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
|
||||||
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
|
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);
|
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()
|
public async Task OpenCheatManagerForCurrentApp()
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user