Merge pull request #11650 from german77/lle_album

service: am: Add support for LLE Album Applet
This commit is contained in:
Fernando S 2023-10-10 11:47:13 +02:00 committed by GitHub
commit 8151a4d301
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 12216 additions and 247 deletions

View file

@ -466,14 +466,18 @@ add_library(core STATIC
hle/service/caps/caps_a.h
hle/service/caps/caps_c.cpp
hle/service/caps/caps_c.h
hle/service/caps/caps_u.cpp
hle/service/caps/caps_u.h
hle/service/caps/caps_manager.cpp
hle/service/caps/caps_manager.h
hle/service/caps/caps_result.h
hle/service/caps/caps_sc.cpp
hle/service/caps/caps_sc.h
hle/service/caps/caps_ss.cpp
hle/service/caps/caps_ss.h
hle/service/caps/caps_su.cpp
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.h
hle/service/es/es.cpp

View file

@ -31,7 +31,7 @@
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/apm/apm_interface.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/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
@ -764,6 +764,66 @@ void AppletMessageQueue::OperationModeChanged() {
on_operation_mode_changed->Signal();
}
ILockAccessor::ILockAccessor(Core::System& system_)
: ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &ILockAccessor::TryLock, "TryLock"},
{2, &ILockAccessor::Unlock, "Unlock"},
{3, &ILockAccessor::GetEvent, "GetEvent"},
{4,&ILockAccessor::IsLocked, "IsLocked"},
};
// clang-format on
RegisterHandlers(functions);
lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
}
ILockAccessor::~ILockAccessor() = default;
void ILockAccessor::TryLock(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto return_handle = rp.Pop<bool>();
LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
// TODO: When return_handle is true this function should return the lock handle
is_locked = true;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u8>(is_locked);
}
void ILockAccessor::Unlock(HLERequestContext& ctx) {
LOG_INFO(Service_AM, "called");
is_locked = false;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void ILockAccessor::GetEvent(HLERequestContext& ctx) {
LOG_INFO(Service_AM, "called");
lock_event->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(lock_event->GetReadableEvent());
}
void ILockAccessor::IsLocked(HLERequestContext& ctx) {
LOG_INFO(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
rb.Push<u8>(is_locked);
}
ICommonStateGetter::ICommonStateGetter(Core::System& system_,
std::shared_ptr<AppletMessageQueue> msg_queue_)
: ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
@ -787,7 +847,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{14, nullptr, "GetWakeupCount"},
{20, nullptr, "PushToGeneralChannel"},
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
{31, nullptr, "GetReaderLockAccessorEx"},
{31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
{32, nullptr, "GetWriterLockAccessorEx"},
{40, nullptr, "GetCradleFwVersion"},
{50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@ -805,7 +865,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{65, nullptr, "GetApplicationIdByContentActionName"},
{66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
{67, nullptr, "CancelCpuBoostMode"},
{68, nullptr, "GetBuiltInDisplayType"},
{68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
{80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
@ -886,6 +946,18 @@ void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto unknown = rp.Pop<u32>();
LOG_INFO(Service_AM, "called, unknown={}", unknown);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<ILockAccessor>(system);
}
void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "called");
@ -970,6 +1042,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
apm_sys->SetCpuBoostMode(ctx);
}
void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(0);
}
void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto system_button{rp.PopEnum<SystemButtonType>()};
@ -1493,6 +1573,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
case Applets::AppletId::MiiEdit:
PushInShowMiiEditData();
break;
case Applets::AppletId::PhotoViewer:
PushInShowAlbum();
break;
default:
break;
}
@ -1569,6 +1652,23 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
rb.PushRaw(applet_info);
}
void ILibraryAppletSelfAccessor::PushInShowAlbum() {
const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3,
.size = Applets::CommonArgumentSize::Version3,
.library_version = 1,
.theme_color = Applets::ThemeColor::BasicBlack,
.play_startup_sound = true,
.system_tick = system.CoreTiming().GetClockTicks(),
};
std::vector<u8> argument_data(sizeof(arguments));
std::vector<u8> settings_data{2};
std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
queue_data.emplace_back(std::move(argument_data));
queue_data.emplace_back(std::move(settings_data));
}
void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3,

View file

@ -195,6 +195,23 @@ private:
ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
class ILockAccessor final : public ServiceFramework<ILockAccessor> {
public:
explicit ILockAccessor(Core::System& system_);
~ILockAccessor() override;
private:
void TryLock(HLERequestContext& ctx);
void Unlock(HLERequestContext& ctx);
void GetEvent(HLERequestContext& ctx);
void IsLocked(HLERequestContext& ctx);
bool is_locked{};
Kernel::KEvent* lock_event;
KernelHelpers::ServiceContext service_context;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
explicit ICommonStateGetter(Core::System& system_,
@ -237,6 +254,7 @@ private:
void GetCurrentFocusState(HLERequestContext& ctx);
void RequestToAcquireSleepLock(HLERequestContext& ctx);
void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
void GetReaderLockAccessorEx(HLERequestContext& ctx);
void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
void GetOperationMode(HLERequestContext& ctx);
void GetPerformanceMode(HLERequestContext& ctx);
@ -248,6 +266,7 @@ private:
void EndVrModeEx(HLERequestContext& ctx);
void GetDefaultDisplayResolution(HLERequestContext& ctx);
void SetCpuBoostMode(HLERequestContext& ctx);
void GetBuiltInDisplayType(HLERequestContext& ctx);
void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
void GetSettingsPlatformRegion(HLERequestContext& ctx);
void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
@ -327,6 +346,7 @@ private:
void ExitProcessAndReturn(HLERequestContext& ctx);
void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
void PushInShowAlbum();
void PushInShowCabinetData();
void PushInShowMiiEditData();

View file

@ -28,8 +28,8 @@ public:
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
{21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
{22, nullptr, "GetHomeMenuFunctions"},
{23, nullptr, "GetGlobalStateController"},
{22, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@ -110,6 +110,22 @@ private:
rb.PushIpcInterface<IAppletCommonFunctions>(system);
}
void GetHomeMenuFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IHomeMenuFunctions>(system);
}
void GetGlobalStateController(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IGlobalStateController>(system);
}
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");

View file

@ -138,6 +138,10 @@ void Error::Initialize() {
CopyArgumentData(data, args->application_error);
error_code = Result(args->application_error.error_code);
break;
case ErrorAppletMode::ShowErrorPctl:
CopyArgumentData(data, args->error_record);
error_code = Decode64BitError(args->error_record.error_code_64);
break;
case ErrorAppletMode::ShowErrorRecord:
CopyArgumentData(data, args->error_record);
error_code = Decode64BitError(args->error_record.error_code_64);
@ -191,6 +195,7 @@ void Error::Execute() {
frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
break;
}
case ErrorAppletMode::ShowErrorPctl:
case ErrorAppletMode::ShowErrorRecord:
reporter.SaveErrorReport(title_id, error_code,
fmt::format("{:016X}", args->error_record.posix_time));

View file

@ -4,6 +4,7 @@
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/caps/caps_a.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_ss.h"
#include "core/hle/service/caps/caps_su.h"
@ -15,13 +16,21 @@ namespace Service::Capture {
void LoopProcess(Core::System& 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<CAPS_A>(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));
}

View file

@ -3,93 +3,12 @@
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Service::SM {
class ServiceManager;
}
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);
} // namespace Service::Capture

View file

@ -1,40 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/hle/service/caps/caps_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"
namespace Service::Capture {
class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
public:
explicit IAlbumAccessorSession(Core::System& system_)
: 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);
}
};
CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
std::shared_ptr<AlbumManager> album_manager)
: ServiceFramework{system_, "caps:a"}, manager{album_manager} {
// 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 +33,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 +60,199 @@ 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);
Result result = manager->DeleteAlbumFile(file_id);
result = TranslateResult(result);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto storage{rp.PopEnum<AlbumStorage>()};
LOG_INFO(Service_Capture, "called, storage={}", storage);
Result result = manager->IsAlbumMounted(storage);
const bool is_mounted = result.IsSuccess();
result = TranslateResult(result);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
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>()};
const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
std::vector<AlbumEntry> entries;
Result result = manager->GetAlbumFileList(entries, storage, flags);
result = TranslateResult(result);
entries.resize(std::min(album_entry_size, entries.size()));
if (!entries.empty()) {
ctx.WriteBuffer(entries);
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(entries.size());
}
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
LOG_WARNING(Service_Capture, "(STUBBED) called");
bool is_autosaving{};
Result result = manager->GetAutoSavingStorage(is_autosaving);
result = TranslateResult(result);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
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>()};
const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
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);
std::vector<u8> image;
LoadAlbumScreenShotImageOutput image_output;
Result result =
manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
result = TranslateResult(result);
if (image.size() > image_buffer_size) {
result = ResultWorkMemoryError;
}
if (result.IsSuccess()) {
ctx.WriteBuffer(image_output, 0);
ctx.WriteBuffer(image, 1);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
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);
std::vector<u8> image(ctx.GetWriteBufferSize(1));
LoadAlbumScreenShotImageOutput image_output;
Result result =
manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
result = TranslateResult(result);
if (result.IsSuccess()) {
ctx.WriteBuffer(image_output, 0);
ctx.WriteBuffer(image, 1);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
Result IAlbumAccessorService::TranslateResult(Result in_result) {
if (in_result.IsSuccess()) {
return in_result;
}
if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
if (in_result.description - 0x514 < 100) {
return ResultInvalidFileData;
}
if (in_result.description - 0x5dc < 100) {
return ResultInvalidFileData;
}
if (in_result.description - 0x578 < 100) {
if (in_result == ResultFileCountLimit) {
return ResultUnknown22;
}
return ResultUnknown25;
}
if (in_result.raw < ResultUnknown1801.raw) {
if (in_result == ResultUnknown1202) {
return ResultUnknown810;
}
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;
}
if (in_result.module == ErrorModule::FS) {
if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
(((in_result.description - 3000) >> 3) < 0x271)) {
// TODO: Translate FS error
return in_result;
}
}
return in_result;
}
} // namespace Service::Capture

View file

@ -10,11 +10,26 @@ class System;
}
namespace Service::Capture {
class AlbumManager;
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_,
std::shared_ptr<AlbumManager> album_manager);
~IAlbumAccessorService() override;
private:
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);
Result TranslateResult(Result in_result);
std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture

View file

@ -3,53 +3,21 @@
#include "common/logging/log.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"
namespace Service::Capture {
class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
public:
explicit IAlbumControlSession(Core::System& system_)
: 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"} {
IAlbumControlService::IAlbumControlService(Core::System& system_,
std::shared_ptr<AlbumManager> album_manager)
: ServiceFramework{system_, "caps:c"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
{33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
{33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
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};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};

View file

@ -10,14 +10,18 @@ class System;
}
namespace Service::Capture {
class AlbumManager;
class CAPS_C final : public ServiceFramework<CAPS_C> {
class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
public:
explicit CAPS_C(Core::System& system_);
~CAPS_C() override;
explicit IAlbumControlService(Core::System& system_,
std::shared_ptr<AlbumManager> album_manager);
~IAlbumControlService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture

View 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

View 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

View 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

View file

@ -5,7 +5,8 @@
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
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
RegisterHandlers(functions);
}
CAPS_SC::~CAPS_SC() = default;
IScreenShotControlService::~IScreenShotControlService() = default;
} // namespace Service::Capture

View file

@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
class CAPS_SC final : public ServiceFramework<CAPS_SC> {
class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
public:
explicit CAPS_SC(Core::System& system_);
~CAPS_SC() override;
explicit IScreenShotControlService(Core::System& system_);
~IScreenShotControlService() override;
};
} // namespace Service::Capture

View file

@ -5,7 +5,8 @@
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
static const FunctionInfo functions[] = {
{201, nullptr, "SaveScreenShot"},
@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
RegisterHandlers(functions);
}
CAPS_SS::~CAPS_SS() = default;
IScreenShotService::~IScreenShotService() = default;
} // namespace Service::Capture

View file

@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
class CAPS_SS final : public ServiceFramework<CAPS_SS> {
class IScreenShotService final : public ServiceFramework<IScreenShotService> {
public:
explicit CAPS_SS(Core::System& system_);
~CAPS_SS() override;
explicit IScreenShotService(Core::System& system_);
~IScreenShotService() override;
};
} // namespace Service::Capture

View file

@ -7,10 +7,11 @@
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
static const FunctionInfo functions[] = {
{32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
{32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{201, nullptr, "SaveScreenShot"},
{203, nullptr, "SaveScreenShotEx0"},
{205, nullptr, "SaveScreenShotEx1"},
@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
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};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};

View file

@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
class CAPS_SU final : public ServiceFramework<CAPS_SU> {
class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
public:
explicit CAPS_SU(Core::System& system_);
~CAPS_SU() override;
explicit IScreenShotApplicationService(Core::System& system_);
~IScreenShotApplicationService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);

View 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

View file

@ -2,45 +2,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#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/ipc_helpers.h"
namespace Service::Capture {
class IAlbumAccessorApplicationSession final
: public ServiceFramework<IAlbumAccessorApplicationSession> {
public:
explicit IAlbumAccessorApplicationSession(Core::System& system_)
: ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
{2002, nullptr, "CloseAlbumMovieReadStream"},
{2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
{2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
{2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
};
// clang-format on
RegisterHandlers(functions);
}
};
CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
std::shared_ptr<AlbumManager> album_manager)
: ServiceFramework{system_, "caps:u"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
{102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
{103, nullptr, "DeleteAlbumContentsFileForApplication"},
{104, nullptr, "GetAlbumContentsFileSizeForApplication"},
{32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
{103, nullptr, "DeleteAlbumFileByAruid"},
{104, nullptr, "GetAlbumFileSizeByAruid"},
{105, nullptr, "DeleteAlbumFileByAruidForDebug"},
{110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"},
{120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"},
{130, nullptr, "PrecheckToCreateContentsForApplication"},
{110, nullptr, "LoadAlbumScreenShotImageByAruid"},
{120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
{130, nullptr, "PrecheckToCreateContentsByAruid"},
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
{142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{144, nullptr, "GetAllAlbumFileList3AaeAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
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};
const auto library_version{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);
}
void CAPS_U::GetAlbumContentsFileListForApplication(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).
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto pid{rp.Pop<s32>()};
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 applet_resource_user_id{rp.Pop<u64>()};
// TODO: Update this when we implement the album.
// 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={}, "
"end_posix_time={}, applet_resource_user_id={}",
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
LOG_WARNING(
Service_Capture,
"(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
"end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
total_entries_1, total_entries_2);
// TODO: Translate posix to DateTime
std::vector<ApplicationAlbumFileEntry> entries;
const Result result =
manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
if (!entries.empty()) {
ctx.WriteBuffer(entries);
}
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push(total_entries_1);
rb.Push(total_entries_2);
rb.Push(result);
rb.Push<u64>(entries.size());
}
void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
GetAlbumContentsFileListForApplication(ctx);
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

View file

@ -10,16 +10,20 @@ class System;
}
namespace Service::Capture {
class AlbumManager;
class CAPS_U final : public ServiceFramework<CAPS_U> {
class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
public:
explicit CAPS_U(Core::System& system_);
~CAPS_U() override;
explicit IAlbumApplicationService(Core::System& system_,
std::shared_ptr<AlbumManager> album_manager);
~IAlbumApplicationService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
void GetAlbumContentsFileListForApplication(HLERequestContext& ctx);
void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture

View file

@ -545,6 +545,16 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) {
}
}
void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
const bool is_accepted{};
LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u8>(is_accepted);
}
IGeneralService::IGeneralService(Core::System& system_)
: ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
// clang-format off
@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
{19, nullptr, "SetEthernetCommunicationEnabled"},
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
{22, nullptr, "IsAnyForegroundRequestAccepted"},
{22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
{23, nullptr, "PutToSleep"},
{24, nullptr, "WakeUp"},
{25, nullptr, "GetSsidListVersion"},

View file

@ -35,6 +35,7 @@ private:
void GetInternetConnectionStatus(HLERequestContext& ctx);
void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
Network::RoomNetwork& network;
};

View file

@ -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

View file

@ -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> {

View file

@ -33,7 +33,7 @@ public:
{1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
{1002, nullptr, "ConfirmLaunchApplicationPermission"},
{1003, nullptr, "ConfirmResumeApplicationPermission"},
{1004, nullptr, "ConfirmSnsPostPermission"},
{1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
{1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@ -236,6 +236,13 @@ private:
states.free_communication = true;
}
void ConfirmSnsPostPermission(HLERequestContext& ctx) {
LOG_WARNING(Service_PCTL, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(Error::ResultNoFreeCommunication);
}
void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
const bool is_temporary_unlocked = false;

View file

@ -1560,6 +1560,7 @@ void GMainWindow::ConnectMenuEvents() {
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
[this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
connect_menu(ui->action_Load_Cabinet_Eraser,
@ -1597,6 +1598,7 @@ void GMainWindow::UpdateMenuState() {
};
const std::array applet_actions{
ui->action_Load_Album,
ui->action_Load_Cabinet_Nickname_Owner,
ui->action_Load_Cabinet_Eraser,
ui->action_Load_Cabinet_Restorer,
@ -4224,6 +4226,29 @@ void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
void GMainWindow::OnAlbum() {
constexpr u64 AlbumId = 0x010000000000100Dull;
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"),
tr("Please install the firmware to use the Album applet."));
return;
}
auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
if (!album_nca) {
QMessageBox::warning(this, tr("Album Applet"),
tr("Album applet is not available. Please reinstall firmware."));
return;
}
system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
const auto filename = QString::fromStdString(album_nca->GetFullPath());
UISettings::values.roms_path = QFileInfo(filename).path();
BootGame(filename);
}
void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
constexpr u64 CabinetId = 0x0100000000001002ull;
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();

View file

@ -374,6 +374,7 @@ private slots:
void ResetWindowSize720();
void ResetWindowSize900();
void ResetWindowSize1080();
void OnAlbum();
void OnCabinet(Service::NFP::CabinetMode mode);
void OnMiiEdit();
void OnCaptureScreenshot();

View file

@ -160,6 +160,7 @@
<addaction name="action_Verify_installed_contents"/>
<addaction name="separator"/>
<addaction name="menu_cabinet_applet"/>
<addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
@ -380,6 +381,11 @@
<string>&amp;Capture Screenshot</string>
</property>
</action>
<action name="action_Load_Album">
<property name="text">
<string>Open &amp;Album</string>
</property>
</action>
<action name="action_Load_Cabinet_Nickname_Owner">
<property name="text">
<string>&amp;Set Nickname and Owner</string>