diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Caps | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Caps')
14 files changed, 434 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs new file mode 100644 index 00000000..6320fe28 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs @@ -0,0 +1,134 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Caps.Types; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + class CaptureManager + { + private string _sdCardPath; + + private uint _shimLibraryVersion; + + public CaptureManager(Switch device) + { + _sdCardPath = device.FileSystem.GetSdCardPath(); + } + + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + ulong shimLibraryVersion = context.RequestData.ReadUInt64(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + + ResultCode resultCode = ResultCode.OutOfRange; + + if (shimLibraryVersion != 0) + { + if (_shimLibraryVersion == shimLibraryVersion) + { + resultCode = ResultCode.Success; + } + else if (_shimLibraryVersion != 0) + { + resultCode = ResultCode.ShimLibraryVersionAlreadySet; + } + else if (shimLibraryVersion == 1) + { + resultCode = ResultCode.Success; + + _shimLibraryVersion = 1; + } + } + + return resultCode; + } + + public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry) + { + applicationAlbumEntry = default; + + if (screenshotData.Length == 0) + { + return ResultCode.NullInputBuffer; + } + + /* + // NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now. + if (appletResourceUserId == 0) + { + return ResultCode.InvalidArgument; + } + */ + + /* + // Doesn't occur in our case. + if (applicationAlbumEntry == null) + { + return ResultCode.NullOutputBuffer; + } + */ + + if (screenshotData.Length >= 0x384000) + { + DateTime currentDateTime = DateTime.Now; + + applicationAlbumEntry = new ApplicationAlbumEntry() + { + Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(), + TitleId = titleId, + AlbumFileDateTime = new AlbumFileDateTime() + { + Year = (ushort)currentDateTime.Year, + Month = (byte)currentDateTime.Month, + Day = (byte)currentDateTime.Day, + Hour = (byte)currentDateTime.Hour, + Minute = (byte)currentDateTime.Minute, + Second = (byte)currentDateTime.Second, + UniqueId = 0 + }, + AlbumStorage = AlbumStorage.Sd, + ContentType = ContentType.Screenshot, + Padding = new Array5<byte>(), + Unknown0x1f = 1 + }; + + // NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead. + string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId))).Remove(0x20); + string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00")); + string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + + // TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions. + Directory.CreateDirectory(folderPath); + + while (File.Exists(filePath)) + { + applicationAlbumEntry.AlbumFileDateTime.UniqueId++; + + filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + } + + // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. + Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath); + + return ResultCode.Success; + } + + return ResultCode.NullInputBuffer; + } + + private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash) + { + string fileName = $"{currentDateTime:yyyyMMddHHmmss}{applicationAlbumEntry.AlbumFileDateTime.UniqueId:00}-{hash}.jpg"; + + return Path.Combine(folderPath, fileName); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 00000000..4071b9cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:a")] + class IAlbumAccessorService : IpcService + { + public IAlbumAccessorService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs new file mode 100644 index 00000000..af99232e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs @@ -0,0 +1,69 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:u")] + class IAlbumApplicationService : IpcService + { + public IAlbumApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(102)] + // GetAlbumFileList0AafeAruidDeprecated(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer<ApplicationAlbumFileEntry, 0x6>) + public ResultCode GetAlbumFileList0AafeAruidDeprecated(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x30. + return GetAlbumFileList(context); + } + + [CommandCmif(142)] + // GetAlbumFileList3AaeAruid(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer<ApplicationAlbumFileEntry, 0x6>) + public ResultCode GetAlbumFileList3AaeAruid(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x20. + return GetAlbumFileList(context); + } + + private ResultCode GetAlbumFileList(ServiceCtx context) + { + ResultCode resultCode = ResultCode.Success; + ulong count = 0; + + ContentType contentType = (ContentType)context.RequestData.ReadUInt16(); + ulong startTime = context.RequestData.ReadUInt64(); + ulong endTime = context.RequestData.ReadUInt64(); + + context.RequestData.ReadUInt16(); // Alignment. + + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong applicationAlbumFileEntryPosition = context.Request.ReceiveBuff[0].Position; + ulong applicationAlbumFileEntrySize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, applicationAlbumFileEntryPosition, (int)applicationAlbumFileEntrySize); + + if (contentType > ContentType.Unknown || contentType == ContentType.ExtraMovie) + { + resultCode = ResultCode.InvalidContentType; + } + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + // Service populate the buffer with a ApplicationAlbumFileEntry related to the pid. + + Logger.Stub?.PrintStub(LogClass.ServiceCaps, new { contentType, startTime, endTime, appletResourceUserId }); + + context.ResponseData.Write(count); + + return resultCode; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs new file mode 100644 index 00000000..b16a4122 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:c")] + class IAlbumControlService : IpcService + { + public IAlbumControlService(ServiceCtx context) { } + + [CommandCmif(33)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs new file mode 100644 index 00000000..a3501286 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -0,0 +1,98 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:su")] // 6.0.0+ + class IScreenShotApplicationService : IpcService + { + public IScreenShotApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(203)] + // SaveScreenShotEx0(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx0(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + ulong screenshotDataPosition = context.Request.SendBuff[0].Position; + ulong screenshotDataSize = context.Request.SendBuff[0].Size; + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(205)] // 8.0.0+ + // SaveScreenShotEx1(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer<bytes, 0x15> ApplicationData, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx1(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + ulong applicationDataPosition = context.Request.SendBuff[0].Position; + ulong applicationDataSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + // TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now). + byte[] applicationData = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(210)] + // SaveScreenShotEx2(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, buffer<bytes, 0x15> UserIdList, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx2(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong userIdListPosition = context.Request.SendBuff[0].Position; + ulong userIdListSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + // TODO: Parse the UserIdList. + byte[] userIdList = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs new file mode 100644 index 00000000..337fa9ee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:sc")] + class IScreenShotControlService : IpcService + { + public IScreenShotControlService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs new file mode 100644 index 00000000..03703e05 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:ss")] // 2.0.0+ + class IScreenshotService : IpcService + { + public IScreenshotService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs new file mode 100644 index 00000000..2615eeda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + enum ResultCode + { + ModuleId = 206, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (2 << ErrorCodeShift) | ModuleId, + ShimLibraryVersionAlreadySet = (7 << ErrorCodeShift) | ModuleId, + OutOfRange = (8 << ErrorCodeShift) | ModuleId, + InvalidContentType = (14 << ErrorCodeShift) | ModuleId, + NullOutputBuffer = (141 << ErrorCodeShift) | ModuleId, + NullInputBuffer = (142 << ErrorCodeShift) | ModuleId, + BlacklistedPid = (822 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs new file mode 100644 index 00000000..b9bc799c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct AlbumFileDateTime + { + public ushort Year; + public byte Month; + public byte Day; + public byte Hour; + public byte Minute; + public byte Second; + public byte UniqueId; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs new file mode 100644 index 00000000..479675d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumImageOrientation : uint + { + Degrees0, + Degrees90, + Degrees180, + Degrees270 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs new file mode 100644 index 00000000..cfe6c1e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumStorage : byte + { + Nand, + Sd + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs new file mode 100644 index 00000000..699bb418 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct ApplicationAlbumEntry + { + public ulong Size; + public ulong TitleId; + public AlbumFileDateTime AlbumFileDateTime; + public AlbumStorage AlbumStorage; + public ContentType ContentType; + public Array5<byte> Padding; + public byte Unknown0x1f; // Always 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs new file mode 100644 index 00000000..5f8bb537 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum ContentType : byte + { + Screenshot, + Movie, + ExtraMovie, + Unknown + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs new file mode 100644 index 00000000..5528379a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct ScreenShotAttribute + { + public uint Unknown0x00; // Always 0 + public AlbumImageOrientation AlbumImageOrientation; + public uint Unknown0x08; // Always 0 + public uint Unknown0x0C; // Always 1 + public Array30<byte> Unknown0x10; // Always 0 + } +}
\ No newline at end of file |
