From 897b411ae76a817421453120ca0d0812e3de233f Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Tue, 17 Oct 2023 23:05:44 -0600
Subject: [PATCH] service: caps: Implement SaveScreenShotEx0 and variants

---
 src/core/hle/service/caps/caps.cpp         |  7 +-
 src/core/hle/service/caps/caps_manager.cpp | 83 ++++++++++++++++++++++
 src/core/hle/service/caps/caps_manager.h   | 11 +++
 src/core/hle/service/caps/caps_ss.cpp      | 75 +++++++++++++++++--
 src/core/hle/service/caps/caps_ss.h        |  8 ++-
 src/core/hle/service/caps/caps_su.cpp      | 69 ++++++++++++++++--
 src/core/hle/service/caps/caps_su.h        |  8 ++-
 src/core/hle/service/caps/caps_types.h     |  2 +
 8 files changed, 250 insertions(+), 13 deletions(-)

diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 31dd98140e..cd1dfe9939 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -25,11 +25,12 @@ void LoopProcess(Core::System& system) {
     server_manager->RegisterNamedService(
         "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
 
-    server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
+    server_manager->RegisterNamedService(
+        "caps:ss", std::make_shared<IScreenShotService>(system, album_manager));
     server_manager->RegisterNamedService("caps:sc",
                                          std::make_shared<IScreenShotControlService>(system));
-    server_manager->RegisterNamedService("caps:su",
-                                         std::make_shared<IScreenShotApplicationService>(system));
+    server_manager->RegisterNamedService(
+        "caps:su", std::make_shared<IScreenShotApplicationService>(system, album_manager));
 
     ServerManager::RunServer(std::move(server_manager));
 }
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
index 2b4e3f0761..9c9454b993 100644
--- a/src/core/hle/service/caps/caps_manager.cpp
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -4,6 +4,7 @@
 #include <sstream>
 #include <stb_image.h>
 #include <stb_image_resize.h>
+#include <stb_image_write.h>
 
 #include "common/fs/file.h"
 #include "common/fs/path_util.h"
@@ -227,6 +228,49 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
                      +static_cast<int>(out_image_output.height), decoder_options.flags);
 }
 
+Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
+                                    const ScreenShotAttribute& attribute,
+                                    std::span<const u8> image_data, u64 aruid) {
+    return SaveScreenShot(out_entry, attribute, {}, image_data, aruid);
+}
+
+Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
+                                    const ScreenShotAttribute& attribute,
+                                    const ApplicationData& app_data, std::span<const u8> image_data,
+                                    u64 aruid) {
+    const u64 title_id = system.GetApplicationProcessProgramID();
+    const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
+
+    s64 posix_time{};
+    Result result = user_clock.GetCurrentTime(system, posix_time);
+
+    if (result.IsError()) {
+        return result;
+    }
+
+    const auto date = ConvertToAlbumDateTime(posix_time);
+
+    return SaveImage(out_entry, image_data, title_id, date);
+}
+
+Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
+                                          const ScreenShotAttribute& attribute,
+                                          const AlbumFileId& file_id,
+                                          std::span<const u8> image_data) {
+    const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
+
+    s64 posix_time{};
+    Result result = user_clock.GetCurrentTime(system, posix_time);
+
+    if (result.IsError()) {
+        return result;
+    }
+
+    const auto date = ConvertToAlbumDateTime(posix_time);
+
+    return SaveImage(out_entry, image_data, file_id.application_id, date);
+}
+
 Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
     const auto file = album_files.find(file_id);
 
@@ -365,6 +409,45 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
     return ResultSuccess;
 }
 
+Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image,
+                               u64 title_id, const AlbumFileDateTime& date) const {
+    const auto screenshot_path =
+        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir);
+    const std::string formatted_date =
+        fmt::format("{:04}-{:02}-{:02}_{:02}-{:02}-{:02}-{:03}", date.year, date.month, date.day,
+                    date.hour, date.minute, date.second, 0);
+    const std::string file_path =
+        fmt::format("{}/{:016x}_{}.png", screenshot_path, title_id, formatted_date);
+
+    const Common::FS::IOFile db_file{file_path, Common::FS::FileAccessMode::Write,
+                                     Common::FS::FileType::BinaryFile};
+
+    s32 len;
+    const u8* png = stbi_write_png_to_mem(image.data(), 0, 1280, 720, STBI_rgb_alpha, &len);
+
+    if (!png) {
+        return ResultFileCountLimit;
+    }
+
+    std::vector<u8> png_image(len);
+    std::memcpy(png_image.data(), png, len);
+
+    if (db_file.Write(png_image) != png_image.size()) {
+        return ResultFileCountLimit;
+    }
+
+    out_entry = {
+        .size = png_image.size(),
+        .hash = {},
+        .datetime = date,
+        .storage = AlbumStorage::Sd,
+        .content = ContentType::Screenshot,
+        .unknown = 1,
+    };
+
+    return ResultSuccess;
+}
+
 AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
     Time::TimeZone::CalendarInfo calendar_date{};
     const auto& time_zone_manager =
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
index f65eb12c18..44d85117fa 100644
--- a/src/core/hle/service/caps/caps_manager.h
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -58,6 +58,15 @@ public:
                                         std::vector<u8>& out_image, const AlbumFileId& file_id,
                                         const ScreenShotDecodeOption& decoder_options) const;
 
+    Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
+                          std::span<const u8> image_data, u64 aruid);
+    Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
+                          const ApplicationData& app_data, std::span<const u8> image_data,
+                          u64 aruid);
+    Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
+                                const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
+                                std::span<const u8> image_data);
+
 private:
     static constexpr std::size_t NandAlbumFileLimit = 1000;
     static constexpr std::size_t SdAlbumFileLimit = 10000;
@@ -67,6 +76,8 @@ private:
     Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
     Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
                      int height, ScreenShotDecoderFlag flag) const;
+    Result SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, u64 title_id,
+                     const AlbumFileDateTime& date) const;
 
     AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
 
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index d0d1b5425b..1ba2b7972b 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -1,19 +1,25 @@
 // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "common/logging/log.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_types.h"
+#include "core/hle/service/ipc_helpers.h"
+
 #include "core/hle/service/caps/caps_ss.h"
 
 namespace Service::Capture {
 
-IScreenShotService::IScreenShotService(Core::System& system_)
-    : ServiceFramework{system_, "caps:ss"} {
+IScreenShotService::IScreenShotService(Core::System& system_,
+                                       std::shared_ptr<AlbumManager> album_manager)
+    : ServiceFramework{system_, "caps:ss"}, manager{album_manager} {
     // clang-format off
     static const FunctionInfo functions[] = {
         {201, nullptr, "SaveScreenShot"},
         {202, nullptr, "SaveEditedScreenShot"},
-        {203, nullptr, "SaveScreenShotEx0"},
+        {203, &IScreenShotService::SaveScreenShotEx0, "SaveScreenShotEx0"},
         {204, nullptr, "SaveEditedScreenShotEx0"},
-        {206, nullptr, "Unknown206"},
+        {206, &IScreenShotService::SaveEditedScreenShotEx1, "SaveEditedScreenShotEx1"},
         {208, nullptr, "SaveScreenShotOfMovieEx1"},
         {1000, nullptr, "Unknown1000"},
     };
@@ -24,4 +30,65 @@ IScreenShotService::IScreenShotService(Core::System& system_)
 
 IScreenShotService::~IScreenShotService() = default;
 
+void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    struct Parameters {
+        ScreenShotAttribute attribute{};
+        u32 report_option{};
+        INSERT_PADDING_BYTES(0x4);
+        u64 applet_resource_user_id{};
+    };
+    static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
+
+    const auto parameters{rp.PopRaw<Parameters>()};
+    const auto image_data_buffer = ctx.ReadBuffer();
+
+    LOG_INFO(Service_Capture,
+             "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
+             parameters.report_option, image_data_buffer.size(),
+             parameters.applet_resource_user_id);
+
+    ApplicationAlbumEntry entry{};
+    const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
+                                                parameters.applet_resource_user_id);
+
+    IPC::ResponseBuilder rb{ctx, 10};
+    rb.Push(result);
+    rb.PushRaw(entry);
+}
+void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    struct Parameters {
+        ScreenShotAttribute attribute;
+        u64 width;
+        u64 height;
+        u64 thumbnail_width;
+        u64 thumbnail_height;
+        AlbumFileId file_id;
+    };
+    static_assert(sizeof(Parameters) == 0x78, "Parameters has incorrect size.");
+
+    const auto parameters{rp.PopRaw<Parameters>()};
+    const auto application_data_buffer = ctx.ReadBuffer(0);
+    const auto image_data_buffer = ctx.ReadBuffer(1);
+    const auto thumbnail_image_data_buffer = ctx.ReadBuffer(2);
+
+    LOG_INFO(Service_Capture,
+             "called, width={}, height={}, thumbnail_width={}, thumbnail_height={}, "
+             "application_id={:016x},  storage={},  type={}, app_data_buffer_size={}, "
+             "image_data_buffer_size={}, thumbnail_image_buffer_size={}",
+             parameters.width, parameters.height, parameters.thumbnail_width,
+             parameters.thumbnail_height, parameters.file_id.application_id,
+             parameters.file_id.storage, parameters.file_id.type, application_data_buffer.size(),
+             image_data_buffer.size(), thumbnail_image_data_buffer.size());
+
+    ApplicationAlbumEntry entry{};
+    const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
+                                                      parameters.file_id, image_data_buffer);
+
+    IPC::ResponseBuilder rb{ctx, 10};
+    rb.Push(result);
+    rb.PushRaw(entry);
+}
+
 } // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 381e44fd4b..a7e9972ab2 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -13,8 +13,14 @@ namespace Service::Capture {
 
 class IScreenShotService final : public ServiceFramework<IScreenShotService> {
 public:
-    explicit IScreenShotService(Core::System& system_);
+    explicit IScreenShotService(Core::System& system_, std::shared_ptr<AlbumManager> album_manager);
     ~IScreenShotService() override;
+
+private:
+    void SaveScreenShotEx0(HLERequestContext& ctx);
+    void SaveEditedScreenShotEx1(HLERequestContext& ctx);
+
+    std::shared_ptr<AlbumManager> manager;
 };
 
 } // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index cad173dc75..e85625ee49 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -2,19 +2,22 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "common/logging/log.h"
+#include "core/hle/service/caps/caps_manager.h"
 #include "core/hle/service/caps/caps_su.h"
+#include "core/hle/service/caps/caps_types.h"
 #include "core/hle/service/ipc_helpers.h"
 
 namespace Service::Capture {
 
-IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
-    : ServiceFramework{system_, "caps:su"} {
+IScreenShotApplicationService::IScreenShotApplicationService(
+    Core::System& system_, std::shared_ptr<AlbumManager> album_manager)
+    : ServiceFramework{system_, "caps:su"}, manager{album_manager} {
     // clang-format off
     static const FunctionInfo functions[] = {
         {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
         {201, nullptr, "SaveScreenShot"},
-        {203, nullptr, "SaveScreenShotEx0"},
-        {205, nullptr, "SaveScreenShotEx1"},
+        {203, &IScreenShotApplicationService::SaveScreenShotEx0, "SaveScreenShotEx0"},
+        {205, &IScreenShotApplicationService::SaveScreenShotEx1, "SaveScreenShotEx1"},
         {210, nullptr, "SaveScreenShotEx2"},
     };
     // clang-format on
@@ -36,4 +39,62 @@ void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx
     rb.Push(ResultSuccess);
 }
 
+void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    struct Parameters {
+        ScreenShotAttribute attribute{};
+        AlbumReportOption report_option{};
+        INSERT_PADDING_BYTES(0x4);
+        u64 applet_resource_user_id{};
+    };
+    static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
+
+    const auto parameters{rp.PopRaw<Parameters>()};
+    const auto image_data_buffer = ctx.ReadBuffer();
+
+    LOG_INFO(Service_Capture,
+             "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
+             parameters.report_option, image_data_buffer.size(),
+             parameters.applet_resource_user_id);
+
+    ApplicationAlbumEntry entry{};
+    const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
+                                                parameters.applet_resource_user_id);
+
+    IPC::ResponseBuilder rb{ctx, 10};
+    rb.Push(result);
+    rb.PushRaw(entry);
+}
+
+void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    struct Parameters {
+        ScreenShotAttribute attribute{};
+        AlbumReportOption report_option{};
+        INSERT_PADDING_BYTES(0x4);
+        u64 applet_resource_user_id{};
+    };
+    static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
+
+    const auto parameters{rp.PopRaw<Parameters>()};
+    const auto app_data_buffer = ctx.ReadBuffer(0);
+    const auto image_data_buffer = ctx.ReadBuffer(1);
+
+    LOG_INFO(Service_Capture,
+             "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
+             parameters.report_option, image_data_buffer.size(),
+             parameters.applet_resource_user_id);
+
+    ApplicationAlbumEntry entry{};
+    ApplicationData app_data{};
+    std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
+    const auto result =
+        manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer,
+                                parameters.applet_resource_user_id);
+
+    IPC::ResponseBuilder rb{ctx, 10};
+    rb.Push(result);
+    rb.PushRaw(entry);
+}
+
 } // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index 647e3059de..89e71f5068 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -10,14 +10,20 @@ class System;
 }
 
 namespace Service::Capture {
+class AlbumManager;
 
 class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
 public:
-    explicit IScreenShotApplicationService(Core::System& system_);
+    explicit IScreenShotApplicationService(Core::System& system_,
+                                           std::shared_ptr<AlbumManager> album_manager);
     ~IScreenShotApplicationService() override;
 
 private:
     void SetShimLibraryVersion(HLERequestContext& ctx);
+    void SaveScreenShotEx0(HLERequestContext& ctx);
+    void SaveScreenShotEx1(HLERequestContext& ctx);
+
+    std::shared_ptr<AlbumManager> manager;
 };
 
 } // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
index 7fd3579547..589ac28d31 100644
--- a/src/core/hle/service/caps/caps_types.h
+++ b/src/core/hle/service/caps/caps_types.h
@@ -20,6 +20,8 @@ enum class AlbumImageOrientation {
 enum class AlbumReportOption : s32 {
     Disable,
     Enable,
+    Unknown2,
+    Unknown3,
 };
 
 enum class ContentType : u8 {