From 32be8caa9d37471947e5d75f953f8fda7a3e1943 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Fri, 26 Mar 2021 01:16:08 +0100
Subject: [PATCH] caps: Implement SaveScreenShot calls and cleanup (#2140)

* caps: Implement SaveScreenShot calls and cleanup

This PR implement:
- caps:u IAlbumApplicationService (32) SetShimLibraryVersion
- caps:c IAlbumControlService (33) SetShimLibraryVersion
- caps:su IScreenShotApplicationService (32) SetShimLibraryVersion
- caps:su IScreenShotApplicationService (203/205/210) SaveScreenShotEx0/SaveScreenShotEx1/SaveScreenShotEx2

ImageSharp is used to save the raw screenshot data as a JPG file following what the service does.
All screenshots are save in: `%AppData%\Ryujinx\sdcard\Nintendo\Album` folder. (as example a screenshot file path will be `%AppData%\Ryujinx\sdcard\Nintendo\Album\2021\03\26\2021032601020300-0123456789ABCDEF0123456789ABCDEF.jpg`

This is needed by Animal Crossing: New Horizon where screenshots looks like this:

And this is needed in Monster Hunter Rise but screenshots are currently empty due to another issue.

* remove useless comment

* Addresses gdkchan feedback

* Addresses gdkchan feedback 2

* remove useless comment 2

* Fix nits
---
 Ryujinx.HLE/HOS/Horizon.cs                    |   3 +
 .../HOS/Services/Caps/CaptureManager.cs       | 143 ++++++++++++++++++
 .../Services/Caps/IAlbumApplicationService.cs |   7 +-
 .../HOS/Services/Caps/IAlbumControlService.cs |   7 +
 .../Caps/IScreenShotApplicationService.cs     |  84 +++++++++-
 Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs   |  17 +++
 .../Services/Caps/Types/AlbumFileDateTime.cs  |  16 ++
 .../Caps/Types/AlbumImageOrientation.cs       |  10 ++
 .../HOS/Services/Caps/Types/AlbumStorage.cs   |   8 +
 .../Caps/Types/ApplicationAlbumEntry.cs       |  17 +++
 .../HOS/Services/Caps/Types/ContentType.cs    |   9 ++
 .../Caps/Types/ScreenShotAttribute.cs         |  15 ++
 Ryujinx.HLE/Ryujinx.HLE.csproj                |   1 +
 13 files changed, 327 insertions(+), 10 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs

diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 39764c5b3e..daa8dcc3fb 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -22,6 +22,7 @@ using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemA
 using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Arp;
 using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
+using Ryujinx.HLE.HOS.Services.Caps;
 using Ryujinx.HLE.HOS.Services.Mii;
 using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
 using Ryujinx.HLE.HOS.Services.Nv;
@@ -86,6 +87,7 @@ namespace Ryujinx.HLE.HOS
         internal SharedFontManager Font { get; private set; }
 
         internal ContentManager ContentManager { get; private set; }
+        internal CaptureManager CaptureManager { get; private set; }
 
         internal KEvent VsyncEvent { get; private set; }
 
@@ -160,6 +162,7 @@ namespace Ryujinx.HLE.HOS
             DisplayResolutionChangeEvent = new KEvent(KernelContext);
 
             ContentManager = contentManager;
+            CaptureManager = new CaptureManager(device);
 
             // TODO: use set:sys (and get external clock source id from settings)
             // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
diff --git a/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
new file mode 100644
index 0000000000..37cc9bdab6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs
@@ -0,0 +1,143 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Caps.Types;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Jpeg;
+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();
+
+            SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
+            {
+                Quality = 100
+            });
+        }
+
+        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
+                };
+
+                using (SHA256 sha256Hash = SHA256.Create())
+                {
+                    // 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       = BitConverter.ToString(sha256Hash.ComputeHash(BitConverter.GetBytes(titleId))).Replace("-", "").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/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs
index 9b699f6028..88803ccc8a 100644
--- a/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs
+++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs
@@ -11,12 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
         // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
         public ResultCode SetShimLibraryVersion(ServiceCtx context)
         {
-            ulong shimLibraryVersion   = context.RequestData.ReadUInt64();
-            ulong appletResourceUserId = context.RequestData.ReadUInt64();
-
-            Logger.Stub?.PrintStub(LogClass.ServiceCaps, new { shimLibraryVersion, appletResourceUserId });
-
-            return ResultCode.Success;
+            return context.Device.System.CaptureManager.SetShimLibraryVersion(context);
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs
index de880153b3..48cb9cab4c 100644
--- a/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs
+++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs
@@ -4,5 +4,12 @@ namespace Ryujinx.HLE.HOS.Services.Caps
     class IAlbumControlService : IpcService
     {
         public IAlbumControlService(ServiceCtx context) { }
+
+        [Command(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/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
index 36bdb9f563..3824e7a34b 100644
--- a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
+++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs
@@ -1,4 +1,5 @@
-using Ryujinx.Common.Logging;
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Caps.Types;
 
 namespace Ryujinx.HLE.HOS.Services.Caps
 {
@@ -11,12 +12,87 @@ namespace Ryujinx.HLE.HOS.Services.Caps
         // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId)
         public ResultCode SetShimLibraryVersion(ServiceCtx context)
         {
-            ulong shimLibraryVersion   = context.RequestData.ReadUInt64();
+            return context.Device.System.CaptureManager.SetShimLibraryVersion(context);
+        }
+
+        [Command(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();
+
+            long screenshotDataPosition = context.Request.SendBuff[0].Position;
+            long screenshotDataSize     = context.Request.SendBuff[0].Size;
+
+            byte[] screenshotData = context.Memory.GetSpan((ulong)screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
+
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+
+            context.ResponseData.WriteStruct(applicationAlbumEntry);
+
+            return resultCode;
+        }
+
+        [Command(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();
+
+            long applicationDataPosition = context.Request.SendBuff[0].Position;
+            long applicationDataSize     = context.Request.SendBuff[0].Size;
+
+            long screenshotDataPosition = context.Request.SendBuff[1].Position;
+            long 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((ulong)applicationDataPosition, (int)applicationDataSize).ToArray();
+
+            byte[] screenshotData = context.Memory.GetSpan((ulong)screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
+
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+
+            context.ResponseData.WriteStruct(applicationAlbumEntry);
+
+            return resultCode;
+        }
+
+        [Command(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();
 
-            Logger.Stub?.PrintStub(LogClass.ServiceCaps, new { shimLibraryVersion, appletResourceUserId });
+            long userIdListPosition = context.Request.SendBuff[0].Position;
+            long userIdListSize     = context.Request.SendBuff[0].Size;
 
-            return ResultCode.Success;
+            long screenshotDataPosition = context.Request.SendBuff[1].Position;
+            long screenshotDataSize     = context.Request.SendBuff[1].Size;
+
+            // TODO: Parse the UserIdList.
+            byte[] userIdList = context.Memory.GetSpan((ulong)userIdListPosition, (int)userIdListSize).ToArray();
+
+            byte[] screenshotData = context.Memory.GetSpan((ulong)screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
+
+            ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
+
+            context.ResponseData.WriteStruct(applicationAlbumEntry);
+
+            return resultCode;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs
new file mode 100644
index 0000000000..c3e4c2cd10
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs
@@ -0,0 +1,17 @@
+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,
+        NullOutputBuffer             = (141 << ErrorCodeShift) | ModuleId,
+        NullInputBuffer              = (142 << ErrorCodeShift) | ModuleId,
+        BlacklistedPid               = (822 << ErrorCodeShift) | ModuleId
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs
new file mode 100644
index 0000000000..b9bc799c0c
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs
new file mode 100644
index 0000000000..479675d67a
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs
new file mode 100644
index 0000000000..cfe6c1e043
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs
new file mode 100644
index 0000000000..699bb41805
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs
new file mode 100644
index 0000000000..c1e7f0fcc8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Caps.Types
+{
+    enum ContentType : byte
+    {
+        Screenshot,
+        Movie,
+        ExtraMovie
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs b/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs
new file mode 100644
index 0000000000..5528379ada
--- /dev/null
+++ b/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
diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj
index 3d48d893fb..c9f30280e6 100644
--- a/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -22,6 +22,7 @@
     <PackageReference Include="Concentus" Version="1.1.7" />
     <PackageReference Include="LibHac" Version="0.12.0" />
     <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
+    <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
   </ItemGroup>
 
   <!-- Due to Concentus. -->