summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/hle/service/caps/caps.cpp2
-rw-r--r--src/core/hle/service/caps/caps_a.cpp314
-rw-r--r--src/core/hle/service/caps/caps_a.h118
-rw-r--r--src/core/hle/service/ns/ns.cpp27
-rw-r--r--src/core/hle/service/ns/ns.h4
5 files changed, 450 insertions, 15 deletions
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..0dbf04862 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -16,7 +16,7 @@ namespace Service::Capture {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
+ server_manager->RegisterNamedService("caps:a", std::make_shared<IAlbumAccessorService>(system));
server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 44267b284..e1f836e08 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,7 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <sstream>
+#include <stb_image.h>
+#include <stb_image_resize.h>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
#include "core/hle/service/caps/caps_a.h"
+#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
@@ -26,15 +33,16 @@ public:
}
};
-CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
+IAlbumAccessorService::IAlbumAccessorService(Core::System& system_)
+ : ServiceFramework{system_, "caps:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
{1, nullptr, "GetAlbumFileList"},
{2, nullptr, "LoadAlbumFile"},
- {3, nullptr, "DeleteAlbumFile"},
+ {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
{4, nullptr, "StorageCopyAlbumFile"},
- {5, nullptr, "IsAlbumMounted"},
+ {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
{6, nullptr, "GetAlbumUsage"},
{7, nullptr, "GetAlbumFileSize"},
{8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +55,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
{15, nullptr, "GetAlbumUsage3"},
{16, nullptr, "GetAlbumMountResult"},
{17, nullptr, "GetAlbumUsage16"},
- {18, nullptr, "Unknown18"},
+ {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
{19, nullptr, "Unknown19"},
{100, nullptr, "GetAlbumFileCountEx0"},
- {101, nullptr, "GetAlbumFileListEx0"},
+ {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
{202, nullptr, "SaveEditedScreenShot"},
{301, nullptr, "GetLastThumbnail"},
{302, nullptr, "GetLastOverlayMovieThumbnail"},
- {401, nullptr, "GetAutoSavingStorage"},
+ {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
{501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
{1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
- {1002, nullptr, "LoadAlbumScreenShotImageEx1"},
- {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"},
+ {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
+ {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
{8001, nullptr, "ForceAlbumUnmounted"},
{8002, nullptr, "ResetAlbumMountStatus"},
{8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +82,294 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
RegisterHandlers(functions);
}
-CAPS_A::~CAPS_A() = default;
+IAlbumAccessorService::~IAlbumAccessorService() = default;
+
+void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
+ file_id.application_id, file_id.storage, file_id.type);
+
+ if (file_id.storage == AlbumStorage::Sd) {
+ if (!Common::FS::RemoveFile(sd_image_paths[file_id.date.unique_id])) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}, is_mounted={}", storage, is_mounted);
+
+ if (storage == AlbumStorage::Sd) {
+ FindScreenshots();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_mounted);
+}
+
+void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
+ struct UnknownBuffer {
+ INSERT_PADDING_BYTES(0x10);
+ };
+ static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called");
+
+ std::vector<UnknownBuffer> buffer{};
+
+ if (!buffer.empty()) {
+ ctx.WriteBuffer(buffer);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(buffer.size()));
+}
+
+void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+ const auto flags{rp.Pop<u8>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
+
+ std::vector<AlbumEntry> entries{};
+
+ if (storage == AlbumStorage::Sd) {
+ AlbumEntry entry;
+ for (u8 i = 0; i < static_cast<u8>(sd_image_paths.size()); i++) {
+ if (GetAlbumEntry(entry, sd_image_paths[i]).IsError()) {
+ continue;
+ }
+ entry.file_id.date.unique_id = i;
+ entries.push_back(entry);
+ }
+ }
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(entries.size());
+}
+
+void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
+ bool is_autosaving{};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called, is_autosaving={}", is_autosaving);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_autosaving);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ const LoadAlbumScreenShotImageOutput image_output{
+ .width = 1280,
+ .height = 720,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = ScreenShotOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::vector<u8> image(image_output.height * image_output.width * STBI_rgb_alpha);
+
+ if (file_id.storage == AlbumStorage::Sd) {
+ LoadImage(image, sd_image_paths[file_id.date.unique_id],
+ static_cast<int>(image_output.width), static_cast<int>(image_output.height),
+ decoder_options.flags);
+ }
+
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ const LoadAlbumScreenShotImageOutput image_output{
+ .width = 320,
+ .height = 180,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = ScreenShotOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::vector<u8> image(image_output.height * image_output.width * STBI_rgb_alpha);
+
+ if (file_id.storage == AlbumStorage::Sd) {
+ LoadImage(image, sd_image_paths[file_id.date.unique_id],
+ static_cast<int>(image_output.width), static_cast<int>(image_output.height),
+ decoder_options.flags);
+ }
+
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IAlbumAccessorService::FindScreenshots() {
+ is_mounted = false;
+ sd_image_paths.clear();
+
+ // TODO: Swap this with a blocking operation.
+ const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
+ Common::FS::IterateDirEntries(
+ screenshots_dir,
+ [this](const std::filesystem::path& full_path) {
+ AlbumEntry entry;
+ // TODO: Implement proper indexing to allow more images
+ if (sd_image_paths.size() > 0xFF) {
+ return true;
+ }
+ if (GetAlbumEntry(entry, full_path).IsSuccess()) {
+ sd_image_paths.push_back(full_path);
+ }
+ return true;
+ },
+ Common::FS::DirEntryFilter::File);
+
+ is_mounted = true;
+}
+
+Result IAlbumAccessorService::GetAlbumEntry(AlbumEntry& out_entry,
+ const std::filesystem::path& path) {
+ std::istringstream line_stream(path.filename().string());
+ std::string date;
+ std::string application;
+ std::string time;
+
+ // Parse filename to obtain entry properties
+ std::getline(line_stream, application, '_');
+ std::getline(line_stream, date, '_');
+ std::getline(line_stream, time, '_');
+
+ std::istringstream date_stream(date);
+ std::istringstream time_stream(time);
+ std::string year;
+ std::string month;
+ std::string day;
+ std::string hour;
+ std::string minute;
+ std::string second;
+
+ std::getline(date_stream, year, '-');
+ std::getline(date_stream, month, '-');
+ std::getline(date_stream, day, '-');
+
+ std::getline(time_stream, hour, '-');
+ std::getline(time_stream, minute, '-');
+ std::getline(time_stream, second, '-');
+
+ try {
+ out_entry = {
+ .entry_size = 1,
+ .file_id{
+ .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
+ .date =
+ {
+ .year = static_cast<u16>(std::stoi(year)),
+ .month = static_cast<u8>(std::stoi(month)),
+ .day = static_cast<u8>(std::stoi(day)),
+ .hour = static_cast<u8>(std::stoi(hour)),
+ .minute = static_cast<u8>(std::stoi(minute)),
+ .second = static_cast<u8>(std::stoi(second)),
+ .unique_id = 0,
+ },
+ .storage = AlbumStorage::Sd,
+ .type = ContentType::Screenshot,
+ .unknown = 1,
+ },
+ };
+ } catch (const std::invalid_argument&) {
+ return ResultUnknown;
+ } catch (const std::out_of_range&) {
+ return ResultUnknown;
+ } catch (const std::exception&) {
+ return ResultUnknown;
+ }
+
+ return ResultSuccess;
+}
+
+Result IAlbumAccessorService::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
+ int width, int height, ScreenShotDecoderFlag flag) {
+ if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
+ return ResultUnknown;
+ }
+
+ const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ std::vector<u8> raw_file(db_file.GetSize());
+ if (db_file.Read(raw_file) != raw_file.size()) {
+ return ResultUnknown;
+ }
+
+ int filter_flag = STBIR_FILTER_DEFAULT;
+ int original_width, original_height, color_channels;
+ const auto dbi_image =
+ stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
+ &original_height, &color_channels, STBI_rgb_alpha);
+
+ if (dbi_image == nullptr) {
+ return ResultUnknown;
+ }
+
+ switch (flag) {
+ case ScreenShotDecoderFlag::EnableFancyUpsampling:
+ filter_flag = STBIR_FILTER_TRIANGLE;
+ break;
+ case ScreenShotDecoderFlag::EnableBlockSmoothing:
+ filter_flag = STBIR_FILTER_BOX;
+ break;
+ default:
+ filter_flag = STBIR_FILTER_DEFAULT;
+ break;
+ }
+
+ stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
+ height, 0, STBI_rgb_alpha, 3, filter_flag);
+
+ return ResultSuccess;
+}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 98a21a5ad..975c0ebdb 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -3,6 +3,7 @@
#pragma once
+#include "common/fs/fs.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -11,10 +12,121 @@ class System;
namespace Service::Capture {
-class CAPS_A final : public ServiceFramework<CAPS_A> {
+class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
public:
- explicit CAPS_A(Core::System& system_);
- ~CAPS_A() override;
+ explicit IAlbumAccessorService(Core::System& system_);
+ ~IAlbumAccessorService() override;
+
+private:
+ enum class ContentType : u8 {
+ Screenshot,
+ Movie,
+ ExtraMovie,
+ };
+
+ enum class AlbumStorage : u8 {
+ Nand,
+ Sd,
+
+ };
+
+ enum class ScreenShotDecoderFlag : u64 {
+ None = 0,
+ EnableFancyUpsampling = 1 << 0,
+ EnableBlockSmoothing = 1 << 1,
+ };
+
+ enum class ScreenShotOrientation : u32 {
+ None,
+ Rotate90,
+ Rotate180,
+ Rotate270,
+ };
+
+ struct ScreenShotAttribute {
+ u32 unknown_0;
+ ScreenShotOrientation orientation;
+ u32 unknown_1;
+ u32 unknown_2;
+ INSERT_PADDING_BYTES(0x30);
+ };
+ static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
+
+ struct ScreenShotDecodeOption {
+ ScreenShotDecoderFlag flags;
+ INSERT_PADDING_BYTES(0x18);
+ };
+ static_assert(sizeof(ScreenShotDecodeOption) == 0x20,
+ "ScreenShotDecodeOption is an invalid size");
+
+ struct AlbumFileDateTime {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 minute;
+ u8 second;
+ u8 unique_id;
+ };
+ static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime is an invalid size");
+
+ struct AlbumFileId {
+ u64 application_id;
+ AlbumFileDateTime date;
+ AlbumStorage storage;
+ ContentType type;
+ INSERT_PADDING_BYTES(0x5);
+ u8 unknown;
+ };
+ static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
+
+ struct AlbumEntry {
+ u64 entry_size;
+ AlbumFileId file_id;
+ };
+ static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry is an invalid size");
+
+ struct ApplicationData {
+ std::array<u8, 0x400> data;
+ u32 data_size;
+ };
+ static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
+
+ struct LoadAlbumScreenShotImageOutput {
+ s64 width;
+ s64 height;
+ ScreenShotAttribute attribute;
+ INSERT_PADDING_BYTES(0x400);
+ };
+ static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+ struct LoadAlbumScreenShotImageOutputForApplication {
+ s64 width;
+ s64 height;
+ ScreenShotAttribute attribute;
+ ApplicationData data;
+ INSERT_PADDING_BYTES(0xAC);
+ };
+ static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+ void DeleteAlbumFile(HLERequestContext& ctx);
+ void IsAlbumMounted(HLERequestContext& ctx);
+ void Unknown18(HLERequestContext& ctx);
+ void GetAlbumFileListEx0(HLERequestContext& ctx);
+ void GetAutoSavingStorage(HLERequestContext& ctx);
+ void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
+ void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
+
+private:
+ void FindScreenshots();
+ Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path);
+ Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
+ int height, ScreenShotDecoderFlag flag);
+
+ bool is_mounted{};
+ std::vector<std::filesystem::path> sd_image_paths{};
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 6e0baf0be..f9e0e272d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,6 +7,7 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
static const FunctionInfo functions[] = {
{11, nullptr, "CalculateApplicationOccupiedSize"},
{43, nullptr, "CheckSdCardMountStatus"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
+ {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
+ {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
{600, nullptr, "CountApplicationContentMeta"},
{601, nullptr, "ListApplicationContentMetaStatus"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
IContentManagementInterface::~IContentManagementInterface() = default;
+void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
+}
+
+void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
+}
+
IDocumentInterface::IDocumentInterface(Core::System& system_)
: ServiceFramework{system_, "IDocumentInterface"} {
// clang-format off
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 175dad780..34d2a45dc 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage
public:
explicit IContentManagementInterface(Core::System& system_);
~IContentManagementInterface() override;
+
+private:
+ void GetTotalSpaceSize(HLERequestContext& ctx);
+ void GetFreeSpaceSize(HLERequestContext& ctx);
};
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {