mirror of
https://github.com/yuzu-mirror/yuzu.git
synced 2024-09-07 18:19:08 +01:00
service: caps: Implement album manager and reorganize service
This commit is contained in:
parent
dac53b4ba0
commit
8347e5cdb9
20 changed files with 856 additions and 522 deletions
|
@ -466,14 +466,18 @@ add_library(core STATIC
|
||||||
hle/service/caps/caps_a.h
|
hle/service/caps/caps_a.h
|
||||||
hle/service/caps/caps_c.cpp
|
hle/service/caps/caps_c.cpp
|
||||||
hle/service/caps/caps_c.h
|
hle/service/caps/caps_c.h
|
||||||
hle/service/caps/caps_u.cpp
|
hle/service/caps/caps_manager.cpp
|
||||||
hle/service/caps/caps_u.h
|
hle/service/caps/caps_manager.h
|
||||||
|
hle/service/caps/caps_result.h
|
||||||
hle/service/caps/caps_sc.cpp
|
hle/service/caps/caps_sc.cpp
|
||||||
hle/service/caps/caps_sc.h
|
hle/service/caps/caps_sc.h
|
||||||
hle/service/caps/caps_ss.cpp
|
hle/service/caps/caps_ss.cpp
|
||||||
hle/service/caps/caps_ss.h
|
hle/service/caps/caps_ss.h
|
||||||
hle/service/caps/caps_su.cpp
|
hle/service/caps/caps_su.cpp
|
||||||
hle/service/caps/caps_su.h
|
hle/service/caps/caps_su.h
|
||||||
|
hle/service/caps/caps_types.h
|
||||||
|
hle/service/caps/caps_u.cpp
|
||||||
|
hle/service/caps/caps_u.h
|
||||||
hle/service/erpt/erpt.cpp
|
hle/service/erpt/erpt.cpp
|
||||||
hle/service/erpt/erpt.h
|
hle/service/erpt/erpt.h
|
||||||
hle/service/es/es.cpp
|
hle/service/es/es.cpp
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
#include "core/hle/service/apm/apm_controller.h"
|
#include "core/hle/service/apm/apm_controller.h"
|
||||||
#include "core/hle/service/apm/apm_interface.h"
|
#include "core/hle/service/apm/apm_interface.h"
|
||||||
#include "core/hle/service/bcat/backend/backend.h"
|
#include "core/hle/service/bcat/backend/backend.h"
|
||||||
#include "core/hle/service/caps/caps.h"
|
#include "core/hle/service/caps/caps_types.h"
|
||||||
#include "core/hle/service/filesystem/filesystem.h"
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
#include "core/hle/service/ns/ns.h"
|
#include "core/hle/service/ns/ns.h"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "core/hle/service/caps/caps.h"
|
#include "core/hle/service/caps/caps.h"
|
||||||
#include "core/hle/service/caps/caps_a.h"
|
#include "core/hle/service/caps/caps_a.h"
|
||||||
#include "core/hle/service/caps/caps_c.h"
|
#include "core/hle/service/caps/caps_c.h"
|
||||||
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
#include "core/hle/service/caps/caps_sc.h"
|
#include "core/hle/service/caps/caps_sc.h"
|
||||||
#include "core/hle/service/caps/caps_ss.h"
|
#include "core/hle/service/caps/caps_ss.h"
|
||||||
#include "core/hle/service/caps/caps_su.h"
|
#include "core/hle/service/caps/caps_su.h"
|
||||||
|
@ -15,13 +16,21 @@ namespace Service::Capture {
|
||||||
|
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
auto album_manager = std::make_shared<AlbumManager>();
|
||||||
|
|
||||||
|
server_manager->RegisterNamedService(
|
||||||
|
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
||||||
|
server_manager->RegisterNamedService(
|
||||||
|
"caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
|
||||||
|
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:sc",
|
||||||
|
std::make_shared<IScreenShotControlService>(system));
|
||||||
|
server_manager->RegisterNamedService("caps:su",
|
||||||
|
std::make_shared<IScreenShotApplicationService>(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));
|
|
||||||
server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
|
|
||||||
server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
|
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,93 +3,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::SM {
|
|
||||||
class ServiceManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
enum class AlbumImageOrientation {
|
|
||||||
Orientation0 = 0,
|
|
||||||
Orientation1 = 1,
|
|
||||||
Orientation2 = 2,
|
|
||||||
Orientation3 = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class AlbumReportOption : s32 {
|
|
||||||
Disable = 0,
|
|
||||||
Enable = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ContentType : u8 {
|
|
||||||
Screenshot = 0,
|
|
||||||
Movie = 1,
|
|
||||||
ExtraMovie = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class AlbumStorage : u8 {
|
|
||||||
NAND = 0,
|
|
||||||
SD = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AlbumFileDateTime {
|
|
||||||
s16 year{};
|
|
||||||
s8 month{};
|
|
||||||
s8 day{};
|
|
||||||
s8 hour{};
|
|
||||||
s8 minute{};
|
|
||||||
s8 second{};
|
|
||||||
s8 uid{};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
|
|
||||||
|
|
||||||
struct AlbumEntry {
|
|
||||||
u64 size{};
|
|
||||||
u64 application_id{};
|
|
||||||
AlbumFileDateTime datetime{};
|
|
||||||
AlbumStorage storage{};
|
|
||||||
ContentType content{};
|
|
||||||
INSERT_PADDING_BYTES(6);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
|
|
||||||
|
|
||||||
struct AlbumFileEntry {
|
|
||||||
u64 size{}; // Size of the entry
|
|
||||||
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
|
|
||||||
AlbumFileDateTime datetime{};
|
|
||||||
AlbumStorage storage{};
|
|
||||||
ContentType content{};
|
|
||||||
INSERT_PADDING_BYTES(5);
|
|
||||||
u8 unknown{1}; // Set to 1 on official SW
|
|
||||||
};
|
|
||||||
static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
|
|
||||||
|
|
||||||
struct ApplicationAlbumEntry {
|
|
||||||
u64 size{}; // Size of the entry
|
|
||||||
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
|
|
||||||
AlbumFileDateTime datetime{};
|
|
||||||
AlbumStorage storage{};
|
|
||||||
ContentType content{};
|
|
||||||
INSERT_PADDING_BYTES(5);
|
|
||||||
u8 unknown{1}; // Set to 1 on official SW
|
|
||||||
};
|
|
||||||
static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
|
|
||||||
|
|
||||||
struct ApplicationAlbumFileEntry {
|
|
||||||
ApplicationAlbumEntry entry{};
|
|
||||||
AlbumFileDateTime datetime{};
|
|
||||||
u64 unknown{};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
|
|
||||||
"ApplicationAlbumFileEntry has incorrect size.");
|
|
||||||
|
|
||||||
void LoopProcess(Core::System& system);
|
void LoopProcess(Core::System& system);
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -1,40 +1,18 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <sstream>
|
#include "common/logging/log.h"
|
||||||
#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/caps/caps_a.h"
|
||||||
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
|
#include "core/hle/service/caps/caps_result.h"
|
||||||
|
#include "core/hle/service/caps/caps_types.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
|
IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
|
||||||
public:
|
std::shared_ptr<AlbumManager> album_manager)
|
||||||
explicit IAlbumAccessorSession(Core::System& system_)
|
: ServiceFramework{system_, "caps:a"}, manager{album_manager} {
|
||||||
: ServiceFramework{system_, "IAlbumAccessorSession"} {
|
|
||||||
// clang-format off
|
|
||||||
static const FunctionInfo functions[] = {
|
|
||||||
{2001, nullptr, "OpenAlbumMovieReadStream"},
|
|
||||||
{2002, nullptr, "CloseAlbumMovieReadStream"},
|
|
||||||
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
|
|
||||||
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
|
|
||||||
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
|
|
||||||
{2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
|
|
||||||
{2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
|
|
||||||
{2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
IAlbumAccessorService::IAlbumAccessorService(Core::System& system_)
|
|
||||||
: ServiceFramework{system_, "caps:a"} {
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, nullptr, "GetAlbumFileCount"},
|
{0, nullptr, "GetAlbumFileCount"},
|
||||||
|
@ -91,30 +69,25 @@ void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
|
||||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
|
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
|
||||||
file_id.application_id, file_id.storage, file_id.type);
|
file_id.application_id, file_id.storage, file_id.type);
|
||||||
|
|
||||||
if (file_id.storage == AlbumStorage::Sd) {
|
Result result = manager->DeleteAlbumFile(file_id);
|
||||||
if (!Common::FS::RemoveFile(sd_image_paths[file_id.date.unique_id])) {
|
result = TranslateResult(result);
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
|
||||||
rb.Push(ResultUnknown);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
|
void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto storage{rp.PopEnum<AlbumStorage>()};
|
const auto storage{rp.PopEnum<AlbumStorage>()};
|
||||||
|
|
||||||
LOG_INFO(Service_Capture, "called, storage={}, is_mounted={}", storage, is_mounted);
|
LOG_INFO(Service_Capture, "called, storage={}", storage);
|
||||||
|
|
||||||
if (storage == AlbumStorage::Sd) {
|
Result result = manager->IsAlbumMounted(storage);
|
||||||
FindScreenshots();
|
const bool is_mounted = result.IsSuccess();
|
||||||
}
|
result = TranslateResult(result);
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
rb.Push<u8>(is_mounted);
|
rb.Push<u8>(is_mounted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,38 +114,34 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto storage{rp.PopEnum<AlbumStorage>()};
|
const auto storage{rp.PopEnum<AlbumStorage>()};
|
||||||
const auto flags{rp.Pop<u8>()};
|
const auto flags{rp.Pop<u8>()};
|
||||||
|
const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
|
||||||
|
|
||||||
LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
|
LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
|
||||||
|
|
||||||
std::vector<AlbumEntry> entries{};
|
std::vector<AlbumEntry> entries;
|
||||||
|
Result result = manager->GetAlbumFileList(entries, storage, flags);
|
||||||
|
result = TranslateResult(result);
|
||||||
|
|
||||||
if (storage == AlbumStorage::Sd) {
|
entries.resize(std::min(album_entry_size, entries.size()));
|
||||||
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()) {
|
if (!entries.empty()) {
|
||||||
ctx.WriteBuffer(entries);
|
ctx.WriteBuffer(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
rb.Push(entries.size());
|
rb.Push(entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
|
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
|
||||||
bool is_autosaving{};
|
LOG_WARNING(Service_Capture, "(STUBBED) called");
|
||||||
|
|
||||||
LOG_WARNING(Service_Capture, "(STUBBED) called, is_autosaving={}", is_autosaving);
|
bool is_autosaving{};
|
||||||
|
Result result = manager->GetAutoSavingStorage(is_autosaving);
|
||||||
|
result = TranslateResult(result);
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
rb.Push<u8>(is_autosaving);
|
rb.Push<u8>(is_autosaving);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,35 +149,28 @@ void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx)
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto file_id{rp.PopRaw<AlbumFileId>()};
|
const auto file_id{rp.PopRaw<AlbumFileId>()};
|
||||||
const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
|
const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
|
||||||
|
const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
|
||||||
|
|
||||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
|
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);
|
file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
|
||||||
|
|
||||||
const LoadAlbumScreenShotImageOutput image_output{
|
std::vector<u8> image;
|
||||||
.width = 1280,
|
LoadAlbumScreenShotImageOutput image_output;
|
||||||
.height = 720,
|
Result result =
|
||||||
.attribute =
|
manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
|
||||||
{
|
result = TranslateResult(result);
|
||||||
.unknown_0{},
|
|
||||||
.orientation = ScreenShotOrientation::None,
|
|
||||||
.unknown_1{},
|
|
||||||
.unknown_2{},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<u8> image(image_output.height * image_output.width * STBI_rgb_alpha);
|
if (image.size() > image_buffer_size) {
|
||||||
|
result = ResultWorkMemoryError;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.IsSuccess()) {
|
||||||
ctx.WriteBuffer(image_output, 0);
|
ctx.WriteBuffer(image_output, 0);
|
||||||
ctx.WriteBuffer(image, 1);
|
ctx.WriteBuffer(image, 1);
|
||||||
|
}
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
|
void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
|
||||||
|
@ -219,157 +181,78 @@ void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestConte
|
||||||
LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
|
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);
|
file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
|
||||||
|
|
||||||
const LoadAlbumScreenShotImageOutput image_output{
|
std::vector<u8> image(ctx.GetWriteBufferSize(1));
|
||||||
.width = 320,
|
LoadAlbumScreenShotImageOutput image_output;
|
||||||
.height = 180,
|
Result result =
|
||||||
.attribute =
|
manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
|
||||||
{
|
result = TranslateResult(result);
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (result.IsSuccess()) {
|
||||||
ctx.WriteBuffer(image_output, 0);
|
ctx.WriteBuffer(image_output, 0);
|
||||||
ctx.WriteBuffer(image, 1);
|
ctx.WriteBuffer(image, 1);
|
||||||
|
}
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAlbumAccessorService::FindScreenshots() {
|
Result IAlbumAccessorService::TranslateResult(Result in_result) {
|
||||||
is_mounted = false;
|
if (in_result.IsSuccess()) {
|
||||||
sd_image_paths.clear();
|
return in_result;
|
||||||
|
|
||||||
// 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,
|
if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
|
||||||
const std::filesystem::path& path) {
|
if (in_result.description - 0x514 < 100) {
|
||||||
std::istringstream line_stream(path.filename().string());
|
return ResultInvalidFileData;
|
||||||
std::string date;
|
}
|
||||||
std::string application;
|
if (in_result.description - 0x5dc < 100) {
|
||||||
std::string time;
|
return ResultInvalidFileData;
|
||||||
|
|
||||||
// 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;
|
if (in_result.description - 0x578 < 100) {
|
||||||
|
if (in_result == ResultFileCountLimit) {
|
||||||
|
return ResultUnknown22;
|
||||||
|
}
|
||||||
|
return ResultUnknown25;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IAlbumAccessorService::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
|
if (in_result.raw < ResultUnknown1801.raw) {
|
||||||
int width, int height, ScreenShotDecoderFlag flag) {
|
if (in_result == ResultUnknown1202) {
|
||||||
if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
|
return ResultUnknown810;
|
||||||
return ResultUnknown;
|
}
|
||||||
|
if (in_result == ResultUnknown1203) {
|
||||||
|
return ResultUnknown810;
|
||||||
|
}
|
||||||
|
if (in_result == ResultUnknown1701) {
|
||||||
|
return ResultUnknown5;
|
||||||
|
}
|
||||||
|
} else if (in_result.raw < ResultUnknown1803.raw) {
|
||||||
|
if (in_result == ResultUnknown1801) {
|
||||||
|
return ResultUnknown5;
|
||||||
|
}
|
||||||
|
if (in_result == ResultUnknown1802) {
|
||||||
|
return ResultUnknown6;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (in_result == ResultUnknown1803) {
|
||||||
|
return ResultUnknown7;
|
||||||
|
}
|
||||||
|
if (in_result == ResultUnknown1804) {
|
||||||
|
return ResultOutOfRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResultUnknown1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
|
if (in_result.module == ErrorModule::FS) {
|
||||||
Common::FS::FileType::BinaryFile};
|
if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
|
||||||
|
(((in_result.description - 3000) >> 3) < 0x271)) {
|
||||||
std::vector<u8> raw_file(db_file.GetSize());
|
// TODO: Translate FS error
|
||||||
if (db_file.Read(raw_file) != raw_file.size()) {
|
return in_result;
|
||||||
return ResultUnknown;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int filter_flag = STBIR_FILTER_DEFAULT;
|
return in_result;
|
||||||
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
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/fs/fs.h"
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
@ -11,106 +10,15 @@ class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
class AlbumManager;
|
||||||
|
|
||||||
class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
|
class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
|
||||||
public:
|
public:
|
||||||
explicit IAlbumAccessorService(Core::System& system_);
|
explicit IAlbumAccessorService(Core::System& system_,
|
||||||
|
std::shared_ptr<AlbumManager> album_manager);
|
||||||
~IAlbumAccessorService() override;
|
~IAlbumAccessorService() override;
|
||||||
|
|
||||||
private:
|
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 DeleteAlbumFile(HLERequestContext& ctx);
|
||||||
void IsAlbumMounted(HLERequestContext& ctx);
|
void IsAlbumMounted(HLERequestContext& ctx);
|
||||||
void Unknown18(HLERequestContext& ctx);
|
void Unknown18(HLERequestContext& ctx);
|
||||||
|
@ -119,14 +27,9 @@ private:
|
||||||
void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
|
void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
|
||||||
void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
|
void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
|
||||||
|
|
||||||
private:
|
Result TranslateResult(Result in_result);
|
||||||
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::shared_ptr<AlbumManager> manager = nullptr;
|
||||||
std::vector<std::filesystem::path> sd_image_paths{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -3,53 +3,21 @@
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/service/caps/caps_c.h"
|
#include "core/hle/service/caps/caps_c.h"
|
||||||
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
|
#include "core/hle/service/caps/caps_result.h"
|
||||||
|
#include "core/hle/service/caps/caps_types.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
|
IAlbumControlService::IAlbumControlService(Core::System& system_,
|
||||||
public:
|
std::shared_ptr<AlbumManager> album_manager)
|
||||||
explicit IAlbumControlSession(Core::System& system_)
|
: ServiceFramework{system_, "caps:c"}, manager{album_manager} {
|
||||||
: ServiceFramework{system_, "IAlbumControlSession"} {
|
|
||||||
// clang-format off
|
|
||||||
static const FunctionInfo functions[] = {
|
|
||||||
{2001, nullptr, "OpenAlbumMovieReadStream"},
|
|
||||||
{2002, nullptr, "CloseAlbumMovieReadStream"},
|
|
||||||
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
|
|
||||||
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
|
|
||||||
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
|
|
||||||
{2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
|
|
||||||
{2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
|
|
||||||
{2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
|
|
||||||
{2401, nullptr, "OpenAlbumMovieWriteStream"},
|
|
||||||
{2402, nullptr, "FinishAlbumMovieWriteStream"},
|
|
||||||
{2403, nullptr, "CommitAlbumMovieWriteStream"},
|
|
||||||
{2404, nullptr, "DiscardAlbumMovieWriteStream"},
|
|
||||||
{2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
|
|
||||||
{2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
|
|
||||||
{2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
|
|
||||||
{2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
|
|
||||||
{2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
|
|
||||||
{2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
|
|
||||||
{2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
|
|
||||||
{2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
|
|
||||||
{2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
|
|
||||||
{2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
|
|
||||||
{2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
|
|
||||||
{2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{1, nullptr, "CaptureRawImage"},
|
{1, nullptr, "CaptureRawImage"},
|
||||||
{2, nullptr, "CaptureRawImageWithTimeout"},
|
{2, nullptr, "CaptureRawImageWithTimeout"},
|
||||||
{33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
{33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
||||||
{1001, nullptr, "RequestTakingScreenShot"},
|
{1001, nullptr, "RequestTakingScreenShot"},
|
||||||
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
|
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
|
||||||
{1011, nullptr, "NotifyTakingScreenShotRefused"},
|
{1011, nullptr, "NotifyTakingScreenShotRefused"},
|
||||||
|
@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPS_C::~CAPS_C() = default;
|
IAlbumControlService::~IAlbumControlService() = default;
|
||||||
|
|
||||||
void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) {
|
void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto library_version{rp.Pop<u64>()};
|
const auto library_version{rp.Pop<u64>()};
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
|
@ -10,14 +10,18 @@ class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
class AlbumManager;
|
||||||
|
|
||||||
class CAPS_C final : public ServiceFramework<CAPS_C> {
|
class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
|
||||||
public:
|
public:
|
||||||
explicit CAPS_C(Core::System& system_);
|
explicit IAlbumControlService(Core::System& system_,
|
||||||
~CAPS_C() override;
|
std::shared_ptr<AlbumManager> album_manager);
|
||||||
|
~IAlbumControlService() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetShimLibraryVersion(HLERequestContext& ctx);
|
void SetShimLibraryVersion(HLERequestContext& ctx);
|
||||||
|
|
||||||
|
std::shared_ptr<AlbumManager> manager = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
342
src/core/hle/service/caps/caps_manager.cpp
Normal file
342
src/core/hle/service/caps/caps_manager.cpp
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 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 "common/logging/log.h"
|
||||||
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
|
#include "core/hle/service/caps/caps_result.h"
|
||||||
|
|
||||||
|
namespace Service::Capture {
|
||||||
|
|
||||||
|
AlbumManager::AlbumManager() {}
|
||||||
|
|
||||||
|
AlbumManager::~AlbumManager() = default;
|
||||||
|
|
||||||
|
Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
|
||||||
|
if (file_id.storage > AlbumStorage::Sd) {
|
||||||
|
return ResultInvalidStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path path;
|
||||||
|
const auto result = GetFile(path, file_id);
|
||||||
|
|
||||||
|
if (result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Common::FS::RemoveFile(path)) {
|
||||||
|
return ResultFileNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
|
||||||
|
if (storage > AlbumStorage::Sd) {
|
||||||
|
return ResultInvalidStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_mounted = true;
|
||||||
|
|
||||||
|
if (storage == AlbumStorage::Sd) {
|
||||||
|
FindScreenshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_mounted ? ResultSuccess : ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
|
||||||
|
u8 flags) const {
|
||||||
|
if (storage > AlbumStorage::Sd) {
|
||||||
|
return ResultInvalidStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [file_id, path] : album_files) {
|
||||||
|
if (file_id.storage != storage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (out_entries.size() >= SdAlbumFileLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto entry_size = Common::FS::GetSize(path);
|
||||||
|
out_entries.push_back({
|
||||||
|
.entry_size = entry_size,
|
||||||
|
.file_id = file_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||||
|
ContentType contex_type, AlbumFileDateTime start_date,
|
||||||
|
AlbumFileDateTime end_date, u64 aruid) const {
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [file_id, path] : album_files) {
|
||||||
|
if (file_id.type != contex_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_id.date > start_date) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_id.date < end_date) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_entries.size() >= SdAlbumFileLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto entry_size = Common::FS::GetSize(path);
|
||||||
|
ApplicationAlbumFileEntry entry{.entry =
|
||||||
|
{
|
||||||
|
.size = entry_size,
|
||||||
|
.hash{},
|
||||||
|
.datetime = file_id.date,
|
||||||
|
.storage = file_id.storage,
|
||||||
|
.content = contex_type,
|
||||||
|
.unknown = 1,
|
||||||
|
},
|
||||||
|
.datetime = file_id.date,
|
||||||
|
.unknown = {}};
|
||||||
|
out_entries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
|
||||||
|
out_is_autosaving = false;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
|
||||||
|
std::vector<u8>& out_image,
|
||||||
|
const AlbumFileId& file_id,
|
||||||
|
const ScreenShotDecodeOption& decoder_options) const {
|
||||||
|
if (file_id.storage > AlbumStorage::Sd) {
|
||||||
|
return ResultInvalidStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_image_output = {
|
||||||
|
.width = 1280,
|
||||||
|
.height = 720,
|
||||||
|
.attribute =
|
||||||
|
{
|
||||||
|
.unknown_0{},
|
||||||
|
.orientation = AlbumImageOrientation::None,
|
||||||
|
.unknown_1{},
|
||||||
|
.unknown_2{},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::filesystem::path path;
|
||||||
|
const auto result = GetFile(path, file_id);
|
||||||
|
|
||||||
|
if (result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
|
||||||
|
|
||||||
|
return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
|
||||||
|
+static_cast<int>(out_image_output.height), decoder_options.flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::LoadAlbumScreenShotThumbnail(
|
||||||
|
LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
|
||||||
|
const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
|
||||||
|
if (file_id.storage > AlbumStorage::Sd) {
|
||||||
|
return ResultInvalidStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_image_output = {
|
||||||
|
.width = 320,
|
||||||
|
.height = 180,
|
||||||
|
.attribute =
|
||||||
|
{
|
||||||
|
.unknown_0{},
|
||||||
|
.orientation = AlbumImageOrientation::None,
|
||||||
|
.unknown_1{},
|
||||||
|
.unknown_2{},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::filesystem::path path;
|
||||||
|
const auto result = GetFile(path, file_id);
|
||||||
|
|
||||||
|
if (result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
|
||||||
|
|
||||||
|
return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
|
||||||
|
+static_cast<int>(out_image_output.height), decoder_options.flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
|
||||||
|
const auto file = album_files.find(file_id);
|
||||||
|
|
||||||
|
if (file == album_files.end()) {
|
||||||
|
return ResultFileNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_path = file->second;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumManager::FindScreenshots() {
|
||||||
|
is_mounted = false;
|
||||||
|
album_files.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;
|
||||||
|
if (GetAlbumEntry(entry, full_path).IsError()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
while (album_files.contains(entry.file_id)) {
|
||||||
|
if (++entry.file_id.date.unique_id == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
album_files[entry.file_id] = full_path;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Common::FS::DirEntryFilter::File);
|
||||||
|
|
||||||
|
is_mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
|
||||||
|
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 AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
|
||||||
|
int width, int height, ScreenShotDecoderFlag flag) const {
|
||||||
|
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
|
72
src/core/hle/service/caps/caps_manager.h
Normal file
72
src/core/hle/service/caps/caps_manager.h
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/caps/caps_types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
// Hash used to create lists from AlbumFileId data
|
||||||
|
template <>
|
||||||
|
struct hash<Service::Capture::AlbumFileId> {
|
||||||
|
size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
|
||||||
|
u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
|
||||||
|
hash_value ^= static_cast<u64>(pad_id.type);
|
||||||
|
return static_cast<size_t>(hash_value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
|
namespace Service::Capture {
|
||||||
|
|
||||||
|
class AlbumManager {
|
||||||
|
public:
|
||||||
|
explicit AlbumManager();
|
||||||
|
~AlbumManager();
|
||||||
|
|
||||||
|
Result DeleteAlbumFile(const AlbumFileId& file_id);
|
||||||
|
Result IsAlbumMounted(AlbumStorage storage);
|
||||||
|
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
|
||||||
|
u8 flags) const;
|
||||||
|
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||||
|
ContentType contex_type, AlbumFileDateTime start_date,
|
||||||
|
AlbumFileDateTime end_date, u64 aruid) const;
|
||||||
|
Result GetAutoSavingStorage(bool& out_is_autosaving) const;
|
||||||
|
Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
|
||||||
|
std::vector<u8>& out_image, const AlbumFileId& file_id,
|
||||||
|
const ScreenShotDecodeOption& decoder_options) const;
|
||||||
|
Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
|
||||||
|
std::vector<u8>& out_image, const AlbumFileId& file_id,
|
||||||
|
const ScreenShotDecodeOption& decoder_options) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::size_t NandAlbumFileLimit = 1000;
|
||||||
|
static constexpr std::size_t SdAlbumFileLimit = 10000;
|
||||||
|
|
||||||
|
void FindScreenshots();
|
||||||
|
Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool is_mounted{};
|
||||||
|
std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::Capture
|
35
src/core/hle/service/caps/caps_result.h
Normal file
35
src/core/hle/service/caps/caps_result.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Service::Capture {
|
||||||
|
|
||||||
|
constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
|
||||||
|
constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
|
||||||
|
constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
|
||||||
|
constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
|
||||||
|
constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
|
||||||
|
constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
|
||||||
|
constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
|
||||||
|
constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
|
||||||
|
constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
|
||||||
|
constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
|
||||||
|
constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
|
||||||
|
constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
|
||||||
|
constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
|
||||||
|
constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
|
||||||
|
constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
|
||||||
|
constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
|
||||||
|
constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
|
||||||
|
constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
|
||||||
|
constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
|
||||||
|
constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
|
||||||
|
constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
|
||||||
|
constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
|
||||||
|
constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
|
||||||
|
constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
|
||||||
|
|
||||||
|
} // namespace Service::Capture
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
|
IScreenShotControlService::IScreenShotControlService(Core::System& system_)
|
||||||
|
: ServiceFramework{system_, "caps:sc"} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{1, nullptr, "CaptureRawImage"},
|
{1, nullptr, "CaptureRawImage"},
|
||||||
|
@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPS_SC::~CAPS_SC() = default;
|
IScreenShotControlService::~IScreenShotControlService() = default;
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -11,10 +11,10 @@ class System;
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class CAPS_SC final : public ServiceFramework<CAPS_SC> {
|
class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
|
||||||
public:
|
public:
|
||||||
explicit CAPS_SC(Core::System& system_);
|
explicit IScreenShotControlService(Core::System& system_);
|
||||||
~CAPS_SC() override;
|
~IScreenShotControlService() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
|
IScreenShotService::IScreenShotService(Core::System& system_)
|
||||||
|
: ServiceFramework{system_, "caps:ss"} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{201, nullptr, "SaveScreenShot"},
|
{201, nullptr, "SaveScreenShot"},
|
||||||
|
@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPS_SS::~CAPS_SS() = default;
|
IScreenShotService::~IScreenShotService() = default;
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -11,10 +11,10 @@ class System;
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class CAPS_SS final : public ServiceFramework<CAPS_SS> {
|
class IScreenShotService final : public ServiceFramework<IScreenShotService> {
|
||||||
public:
|
public:
|
||||||
explicit CAPS_SS(Core::System& system_);
|
explicit IScreenShotService(Core::System& system_);
|
||||||
~CAPS_SS() override;
|
~IScreenShotService() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
|
IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
|
||||||
|
: ServiceFramework{system_, "caps:su"} {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
{32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
||||||
{201, nullptr, "SaveScreenShot"},
|
{201, nullptr, "SaveScreenShot"},
|
||||||
{203, nullptr, "SaveScreenShotEx0"},
|
{203, nullptr, "SaveScreenShotEx0"},
|
||||||
{205, nullptr, "SaveScreenShotEx1"},
|
{205, nullptr, "SaveScreenShotEx1"},
|
||||||
|
@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPS_SU::~CAPS_SU() = default;
|
IScreenShotApplicationService::~IScreenShotApplicationService() = default;
|
||||||
|
|
||||||
void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) {
|
void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto library_version{rp.Pop<u64>()};
|
const auto library_version{rp.Pop<u64>()};
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
|
@ -11,10 +11,10 @@ class System;
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class CAPS_SU final : public ServiceFramework<CAPS_SU> {
|
class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
|
||||||
public:
|
public:
|
||||||
explicit CAPS_SU(Core::System& system_);
|
explicit IScreenShotApplicationService(Core::System& system_);
|
||||||
~CAPS_SU() override;
|
~IScreenShotApplicationService() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetShimLibraryVersion(HLERequestContext& ctx);
|
void SetShimLibraryVersion(HLERequestContext& ctx);
|
||||||
|
|
184
src/core/hle/service/caps/caps_types.h
Normal file
184
src/core/hle/service/caps/caps_types.h
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Service::Capture {
|
||||||
|
|
||||||
|
// This is nn::album::ImageOrientation
|
||||||
|
enum class AlbumImageOrientation {
|
||||||
|
None,
|
||||||
|
Rotate90,
|
||||||
|
Rotate180,
|
||||||
|
Rotate270,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::album::AlbumReportOption
|
||||||
|
enum class AlbumReportOption : s32 {
|
||||||
|
Disable,
|
||||||
|
Enable,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ContentType : u8 {
|
||||||
|
Screenshot = 0,
|
||||||
|
Movie = 1,
|
||||||
|
ExtraMovie = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AlbumStorage : u8 {
|
||||||
|
Nand,
|
||||||
|
Sd,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ScreenShotDecoderFlag : u64 {
|
||||||
|
None = 0,
|
||||||
|
EnableFancyUpsampling = 1 << 0,
|
||||||
|
EnableBlockSmoothing = 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is nn::capsrv::AlbumFileDateTime
|
||||||
|
struct AlbumFileDateTime {
|
||||||
|
u16 year{};
|
||||||
|
u8 month{};
|
||||||
|
u8 day{};
|
||||||
|
u8 hour{};
|
||||||
|
u8 minute{};
|
||||||
|
u8 second{};
|
||||||
|
u8 unique_id{};
|
||||||
|
|
||||||
|
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
|
||||||
|
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
|
||||||
|
if (a.year > b.year) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.month > b.month) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.day > b.day) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.hour > b.hour) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.minute > b.minute) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return a.second > b.second;
|
||||||
|
};
|
||||||
|
friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
|
||||||
|
if (a.year < b.year) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.month < b.month) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.day < b.day) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.hour < b.hour) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a.minute < b.minute) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return a.second < b.second;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::album::AlbumEntry
|
||||||
|
struct AlbumFileEntry {
|
||||||
|
u64 size{}; // Size of the entry
|
||||||
|
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
|
||||||
|
AlbumFileDateTime datetime{};
|
||||||
|
AlbumStorage storage{};
|
||||||
|
ContentType content{};
|
||||||
|
INSERT_PADDING_BYTES(5);
|
||||||
|
u8 unknown{}; // Set to 1 on official SW
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
|
||||||
|
|
||||||
|
struct AlbumFileId {
|
||||||
|
u64 application_id{};
|
||||||
|
AlbumFileDateTime date{};
|
||||||
|
AlbumStorage storage{};
|
||||||
|
ContentType type{};
|
||||||
|
INSERT_PADDING_BYTES(0x5);
|
||||||
|
u8 unknown{};
|
||||||
|
|
||||||
|
friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
|
||||||
|
|
||||||
|
// This is nn::capsrv::AlbumEntry
|
||||||
|
struct AlbumEntry {
|
||||||
|
u64 entry_size{};
|
||||||
|
AlbumFileId file_id{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::capsrv::ApplicationAlbumEntry
|
||||||
|
struct ApplicationAlbumEntry {
|
||||||
|
u64 size{}; // Size of the entry
|
||||||
|
u64 hash{}; // AES256 with hardcoded key over AlbumEntry
|
||||||
|
AlbumFileDateTime datetime{};
|
||||||
|
AlbumStorage storage{};
|
||||||
|
ContentType content{};
|
||||||
|
INSERT_PADDING_BYTES(5);
|
||||||
|
u8 unknown{1}; // Set to 1 on official SW
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
|
||||||
|
|
||||||
|
// This is nn::capsrv::ApplicationAlbumFileEntry
|
||||||
|
struct ApplicationAlbumFileEntry {
|
||||||
|
ApplicationAlbumEntry entry{};
|
||||||
|
AlbumFileDateTime datetime{};
|
||||||
|
u64 unknown{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
|
||||||
|
"ApplicationAlbumFileEntry has incorrect size.");
|
||||||
|
|
||||||
|
struct ApplicationData {
|
||||||
|
std::array<u8, 0x400> data{};
|
||||||
|
u32 data_size{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
|
||||||
|
|
||||||
|
struct ScreenShotAttribute {
|
||||||
|
u32 unknown_0{};
|
||||||
|
AlbumImageOrientation 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 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");
|
||||||
|
|
||||||
|
} // namespace Service::Capture
|
|
@ -2,45 +2,29 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/service/caps/caps.h"
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
|
#include "core/hle/service/caps/caps_types.h"
|
||||||
#include "core/hle/service/caps/caps_u.h"
|
#include "core/hle/service/caps/caps_u.h"
|
||||||
#include "core/hle/service/ipc_helpers.h"
|
#include "core/hle/service/ipc_helpers.h"
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
class IAlbumAccessorApplicationSession final
|
IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
|
||||||
: public ServiceFramework<IAlbumAccessorApplicationSession> {
|
std::shared_ptr<AlbumManager> album_manager)
|
||||||
public:
|
: ServiceFramework{system_, "caps:u"}, manager{album_manager} {
|
||||||
explicit IAlbumAccessorApplicationSession(Core::System& system_)
|
|
||||||
: ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{2001, nullptr, "OpenAlbumMovieReadStream"},
|
{32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
||||||
{2002, nullptr, "CloseAlbumMovieReadStream"},
|
{102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
|
||||||
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
|
{103, nullptr, "DeleteAlbumFileByAruid"},
|
||||||
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
|
{104, nullptr, "GetAlbumFileSizeByAruid"},
|
||||||
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
|
|
||||||
// clang-format off
|
|
||||||
static const FunctionInfo functions[] = {
|
|
||||||
{32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
|
|
||||||
{102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
|
|
||||||
{103, nullptr, "DeleteAlbumContentsFileForApplication"},
|
|
||||||
{104, nullptr, "GetAlbumContentsFileSizeForApplication"},
|
|
||||||
{105, nullptr, "DeleteAlbumFileByAruidForDebug"},
|
{105, nullptr, "DeleteAlbumFileByAruidForDebug"},
|
||||||
{110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"},
|
{110, nullptr, "LoadAlbumScreenShotImageByAruid"},
|
||||||
{120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"},
|
{120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
|
||||||
{130, nullptr, "PrecheckToCreateContentsForApplication"},
|
{130, nullptr, "PrecheckToCreateContentsByAruid"},
|
||||||
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
|
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
|
||||||
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
|
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
|
||||||
{142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
|
{142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
|
||||||
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
|
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
|
||||||
{144, nullptr, "GetAllAlbumFileList3AaeAruid"},
|
{144, nullptr, "GetAllAlbumFileList3AaeAruid"},
|
||||||
{60002, nullptr, "OpenAccessorSessionForApplication"},
|
{60002, nullptr, "OpenAccessorSessionForApplication"},
|
||||||
|
@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPS_U::~CAPS_U() = default;
|
IAlbumApplicationService::~IAlbumApplicationService() = default;
|
||||||
|
|
||||||
void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
|
void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto library_version{rp.Pop<u64>()};
|
const auto library_version{rp.Pop<u64>()};
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
@ -64,10 +48,7 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
|
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
|
||||||
// Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
|
|
||||||
// u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
|
|
||||||
// output entries (which is copied to a s32 by official SW).
|
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto pid{rp.Pop<s32>()};
|
const auto pid{rp.Pop<s32>()};
|
||||||
const auto content_type{rp.PopEnum<ContentType>()};
|
const auto content_type{rp.PopEnum<ContentType>()};
|
||||||
|
@ -75,26 +56,49 @@ void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
|
||||||
const auto end_posix_time{rp.Pop<s64>()};
|
const auto end_posix_time{rp.Pop<s64>()};
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
||||||
// TODO: Update this when we implement the album.
|
LOG_WARNING(Service_Capture,
|
||||||
// Currently we do not have a method of accessing album entries, set this to 0 for now.
|
|
||||||
constexpr u32 total_entries_1{};
|
|
||||||
constexpr u32 total_entries_2{};
|
|
||||||
|
|
||||||
LOG_WARNING(
|
|
||||||
Service_Capture,
|
|
||||||
"(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
|
"(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
|
||||||
"end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
|
"end_posix_time={}, applet_resource_user_id={}",
|
||||||
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
|
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
|
||||||
total_entries_1, total_entries_2);
|
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 4};
|
// TODO: Translate posix to DateTime
|
||||||
rb.Push(ResultSuccess);
|
|
||||||
rb.Push(total_entries_1);
|
std::vector<ApplicationAlbumFileEntry> entries;
|
||||||
rb.Push(total_entries_2);
|
const Result result =
|
||||||
|
manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
|
||||||
|
|
||||||
|
if (!entries.empty()) {
|
||||||
|
ctx.WriteBuffer(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
GetAlbumContentsFileListForApplication(ctx);
|
rb.Push(result);
|
||||||
|
rb.Push<u64>(entries.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto pid{rp.Pop<s32>()};
|
||||||
|
const auto content_type{rp.PopEnum<ContentType>()};
|
||||||
|
const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
||||||
|
const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
||||||
|
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||||
|
|
||||||
|
LOG_WARNING(Service_Capture,
|
||||||
|
"(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
|
||||||
|
content_type, applet_resource_user_id);
|
||||||
|
|
||||||
|
std::vector<ApplicationAlbumFileEntry> entries;
|
||||||
|
const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
|
||||||
|
end_date_time, applet_resource_user_id);
|
||||||
|
|
||||||
|
if (!entries.empty()) {
|
||||||
|
ctx.WriteBuffer(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
|
rb.Push(result);
|
||||||
|
rb.Push<u64>(entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
|
@ -10,16 +10,20 @@ class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
class AlbumManager;
|
||||||
|
|
||||||
class CAPS_U final : public ServiceFramework<CAPS_U> {
|
class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
|
||||||
public:
|
public:
|
||||||
explicit CAPS_U(Core::System& system_);
|
explicit IAlbumApplicationService(Core::System& system_,
|
||||||
~CAPS_U() override;
|
std::shared_ptr<AlbumManager> album_manager);
|
||||||
|
~IAlbumApplicationService() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetShimLibraryVersion(HLERequestContext& ctx);
|
void SetShimLibraryVersion(HLERequestContext& ctx);
|
||||||
void GetAlbumContentsFileListForApplication(HLERequestContext& ctx);
|
void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
|
||||||
void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
|
void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
|
||||||
|
|
||||||
|
std::shared_ptr<AlbumManager> manager = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
|
Loading…
Reference in a new issue