aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Caps
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Caps
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Caps')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs134
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs69
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs98
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs15
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