diff options
Diffstat (limited to 'src')
48 files changed, 1651 insertions, 318 deletions
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp index 972d5e45b..ef301d8b4 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp @@ -77,6 +77,7 @@ void AudioRenderer::Wait() { "{}, got {}", Message::RenderResponse, msg); } + PostDSPClearCommandBuffer(); } void AudioRenderer::Send(Direction dir, u32 message) { @@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u command_buffers[session_id].reset_buffer = reset; } +void AudioRenderer::PostDSPClearCommandBuffer() noexcept { + for (auto& buffer : command_buffers) { + buffer.buffer = 0; + buffer.size = 0; + buffer.reset_buffer = false; + } +} + u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { return command_buffers[session_id].remaining_command_count; } diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index 85874d88a..57b89d9fe 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h @@ -85,6 +85,8 @@ private: */ void CreateSinkStreams(); + void PostDSPClearCommandBuffer() noexcept; + /// Core system Core::System& system; /// The output sink the AudioRenderer will send samples to diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 416203c59..8a1861051 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64) target_link_libraries(common PRIVATE xbyak::xbyak) endif() +if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX)) + target_sources(common + PRIVATE + arm64/native_clock.cpp + arm64/native_clock.h + ) +endif() + if (MSVC) target_compile_definitions(common PRIVATE # The standard library doesn't provide any replacement for codecvt yet diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp new file mode 100644 index 000000000..88fdba527 --- /dev/null +++ b/src/common/arm64/native_clock.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/arm64/native_clock.h" + +namespace Common::Arm64 { + +namespace { + +NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) { + return (static_cast<NativeClock::FactorType>(num) << 64) / den; +} + +u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) { + return static_cast<u64>((m * factor) >> 64); +} + +} // namespace + +NativeClock::NativeClock() { + const u64 host_cntfrq = GetHostCNTFRQ(); + ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); + us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); + ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); + guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq); + gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq); +} + +std::chrono::nanoseconds NativeClock::GetTimeNS() const { + return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)}; +} + +std::chrono::microseconds NativeClock::GetTimeUS() const { + return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)}; +} + +std::chrono::milliseconds NativeClock::GetTimeMS() const { + return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)}; +} + +u64 NativeClock::GetCNTPCT() const { + return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor); +} + +u64 NativeClock::GetGPUTick() const { + return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor); +} + +u64 NativeClock::GetHostTicksNow() const { + u64 cntvct_el0 = 0; + asm volatile("dsb ish\n\t" + "mrs %[cntvct_el0], cntvct_el0\n\t" + "dsb ish\n\t" + : [cntvct_el0] "=r"(cntvct_el0)); + return cntvct_el0; +} + +u64 NativeClock::GetHostTicksElapsed() const { + return GetHostTicksNow(); +} + +bool NativeClock::IsNative() const { + return true; +} + +u64 NativeClock::GetHostCNTFRQ() { + u64 cntfrq_el0 = 0; + asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); + return cntfrq_el0; +} + +} // namespace Common::Arm64 diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h new file mode 100644 index 000000000..a28b419f2 --- /dev/null +++ b/src/common/arm64/native_clock.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/wall_clock.h" + +namespace Common::Arm64 { + +class NativeClock final : public WallClock { +public: + explicit NativeClock(); + + std::chrono::nanoseconds GetTimeNS() const override; + + std::chrono::microseconds GetTimeUS() const override; + + std::chrono::milliseconds GetTimeMS() const override; + + u64 GetCNTPCT() const override; + + u64 GetGPUTick() const override; + + u64 GetHostTicksNow() const override; + + u64 GetHostTicksElapsed() const override; + + bool IsNative() const override; + + static u64 GetHostCNTFRQ(); + +public: + using FactorType = unsigned __int128; + + FactorType GetGuestCNTFRQFactor() const { + return guest_cntfrq_factor; + } + +private: + FactorType ns_cntfrq_factor; + FactorType us_cntfrq_factor; + FactorType ms_cntfrq_factor; + FactorType guest_cntfrq_factor; + FactorType gputick_cntfrq_factor; +}; + +} // namespace Common::Arm64 diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index 71e15ab4c..caca9a123 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -10,6 +10,10 @@ #include "common/x64/rdtsc.h" #endif +#if defined(ARCHITECTURE_arm64) && defined(__linux__) +#include "common/arm64/native_clock.h" +#endif + namespace Common { class StandardWallClock final : public WallClock { @@ -53,7 +57,7 @@ private: }; std::unique_ptr<WallClock> CreateOptimalClock() { -#ifdef ARCHITECTURE_x86_64 +#if defined(ARCHITECTURE_x86_64) const auto& caps = GetCPUCaps(); if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { @@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() { // - Is not more precise than 1 GHz (1ns resolution) return std::make_unique<StandardWallClock>(); } +#elif defined(ARCHITECTURE_arm64) && defined(__linux__) + return std::make_unique<Arm64::NativeClock>(); #else return std::make_unique<StandardWallClock>(); #endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e02ededfc..e4f499135 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 5b51edf30..1fbfbf31f 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -2949,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) KMemoryAttribute::Locked, nullptr)); } +Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size, + KMemoryPermission perm) { + R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer, + KMemoryState::FlagCanTransfer, KMemoryPermission::All, + KMemoryPermission::UserReadWrite, KMemoryAttribute::All, + KMemoryAttribute::None, perm, KMemoryAttribute::Locked)); +} + +Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size, + const KPageGroup& pg) { + R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer, + KMemoryState::FlagCanTransfer, KMemoryPermission::None, + KMemoryPermission::None, KMemoryAttribute::All, + KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, + KMemoryAttribute::Locked, std::addressof(pg))); +} + Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) { R_RETURN(this->LockMemoryAndOpen( out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index b9e8c6042..7da675f27 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -104,6 +104,9 @@ public: Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state); Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state); + Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size, + KMemoryPermission perm); + Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg); Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size); Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg); Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages, diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp index 13d34125c..0e2e11743 100644 --- a/src/core/hle/kernel/k_transfer_memory.cpp +++ b/src/core/hle/kernel/k_transfer_memory.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/scope_exit.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/k_transfer_memory.h" @@ -9,28 +10,50 @@ namespace Kernel { KTransferMemory::KTransferMemory(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer{kernel} {} + : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {} KTransferMemory::~KTransferMemory() = default; -Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size, - Svc::MemoryPermission owner_perm) { +Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size, + Svc::MemoryPermission own_perm) { // Set members. m_owner = GetCurrentProcessPointer(m_kernel); - // TODO(bunnei): Lock for transfer memory + // Get the owner page table. + auto& page_table = m_owner->GetPageTable(); + + // Construct the page group, guarding to make sure our state is valid on exit. + m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager()); + auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); }); + + // Lock the memory. + R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size, + ConvertToKMemoryPermission(own_perm))); // Set remaining tracking members. m_owner->Open(); - m_owner_perm = owner_perm; - m_address = address; - m_size = size; + m_owner_perm = own_perm; + m_address = addr; m_is_initialized = true; + m_is_mapped = false; + // We succeeded. + pg_guard.Cancel(); R_SUCCEED(); } -void KTransferMemory::Finalize() {} +void KTransferMemory::Finalize() { + // Unlock. + if (!m_is_mapped) { + const size_t size = m_page_group->GetNumPages() * PageSize; + ASSERT(R_SUCCEEDED( + m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group))); + } + + // Close the page group. + m_page_group->Close(); + m_page_group->Finalize(); +} void KTransferMemory::PostDestroy(uintptr_t arg) { KProcess* owner = reinterpret_cast<KProcess*>(arg); @@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) { owner->Close(); } +Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) { + // Validate the size. + R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); + + // Validate the permission. + R_UNLESS(m_owner_perm == map_perm, ResultInvalidState); + + // Lock ourselves. + KScopedLightLock lk(m_lock); + + // Ensure we're not already mapped. + R_UNLESS(!m_is_mapped, ResultInvalidState); + + // Map the memory. + const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None) + ? KMemoryState::Transfered + : KMemoryState::SharedTransfered; + R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup( + address, *m_page_group, state, KMemoryPermission::UserReadWrite)); + + // Mark ourselves as mapped. + m_is_mapped = true; + + R_SUCCEED(); +} + +Result KTransferMemory::Unmap(KProcessAddress address, size_t size) { + // Validate the size. + R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); + + // Lock ourselves. + KScopedLightLock lk(m_lock); + + // Unmap the memory. + const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None) + ? KMemoryState::Transfered + : KMemoryState::SharedTransfered; + R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state)); + + // Mark ourselves as unmapped. + ASSERT(m_is_mapped); + m_is_mapped = false; + + R_SUCCEED(); +} + +size_t KTransferMemory::GetSize() const { + return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0; +} + } // namespace Kernel diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h index 54f97ccb4..8a0b08761 100644 --- a/src/core/hle/kernel/k_transfer_memory.h +++ b/src/core/hle/kernel/k_transfer_memory.h @@ -3,6 +3,9 @@ #pragma once +#include <optional> + +#include "core/hle/kernel/k_page_group.h" #include "core/hle/kernel/slab_helpers.h" #include "core/hle/kernel/svc_types.h" #include "core/hle/result.h" @@ -48,16 +51,19 @@ public: return m_address; } - size_t GetSize() const { - return m_is_initialized ? m_size : 0; - } + size_t GetSize() const; + + Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm); + Result Unmap(KProcessAddress address, size_t size); private: + std::optional<KPageGroup> m_page_group{}; KProcess* m_owner{}; KProcessAddress m_address{}; + KLightLock m_lock; Svc::MemoryPermission m_owner_perm{}; - size_t m_size{}; bool m_is_initialized{}; + bool m_is_mapped{}; }; } // namespace Kernel diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp index 7d94e7f09..1f97121b3 100644 --- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp +++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp @@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64 } Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size, - MemoryPermission owner_perm) { - UNIMPLEMENTED(); - R_THROW(ResultNotImplemented); + MemoryPermission map_perm) { + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Validate the permission. + R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState); + + // Get the transfer memory. + KScopedAutoObject trmem = GetCurrentProcess(system.Kernel()) + .GetHandleTable() + .GetObject<KTransferMemory>(trmem_handle); + R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(GetCurrentProcess(system.Kernel()) + .GetPageTable() + .CanContain(address, size, KMemoryState::Transfered), + ResultInvalidMemoryRegion); + + // Map the transfer memory. + R_TRY(trmem->Map(address, size, map_perm)); + + // We succeeded. + R_SUCCEED(); } Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size) { - UNIMPLEMENTED(); - R_THROW(ResultNotImplemented); + // Validate the address/size. + R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); + R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize); + R_UNLESS(size > 0, ResultInvalidSize); + R_UNLESS((address < address + size), ResultInvalidCurrentMemory); + + // Get the transfer memory. + KScopedAutoObject trmem = GetCurrentProcess(system.Kernel()) + .GetHandleTable() + .GetObject<KTransferMemory>(trmem_handle); + R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle); + + // Verify that the mapping is in range. + R_UNLESS(GetCurrentProcess(system.Kernel()) + .GetPageTable() + .CanContain(address, size, KMemoryState::Transfered), + ResultInvalidMemoryRegion); + + // Unmap the transfer memory. + R_TRY(trmem->Unmap(address, size)); + + R_SUCCEED(); } Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address, diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 819dea6a7..ac376b55a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -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, diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 349482dcc..4a045cfd4 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -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(); diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index eb12312cc..e30e6478a 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -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"); diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp index b46ea840c..5d17c353f 100644 --- a/src/core/hle/service/am/applets/applet_error.cpp +++ b/src/core/hle/service/am/applets/applet_error.cpp @@ -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)); diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index 610fe9940..286f9fd10 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -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)); } diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h index 15f0ecfaa..58e9725b8 100644 --- a/src/core/hle/service/caps/caps.h +++ b/src/core/hle/service/caps/caps.h @@ -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 diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index 44267b284..e22f72bf6 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -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 diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h index 98a21a5ad..c90cff71e 100644 --- a/src/core/hle/service/caps/caps_a.h +++ b/src/core/hle/service/caps/caps_a.h @@ -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 diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp index fc77e35cd..1e7fe6474 100644 --- a/src/core/hle/service/caps/caps_c.cpp +++ b/src/core/hle/service/caps/caps_c.cpp @@ -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>()}; diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h index 537b3a2e3..92ba242db 100644 --- a/src/core/hle/service/caps/caps_c.h +++ b/src/core/hle/service/caps/caps_c.h @@ -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 diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp new file mode 100644 index 000000000..2df6a930a --- /dev/null +++ b/src/core/hle/service/caps/caps_manager.cpp @@ -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 diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h new file mode 100644 index 000000000..8337c655c --- /dev/null +++ b/src/core/hle/service/caps/caps_manager.h @@ -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 diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h new file mode 100644 index 000000000..c65e5fb9a --- /dev/null +++ b/src/core/hle/service/caps/caps_result.h @@ -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 diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp index 395b13da7..6117cb7c6 100644 --- a/src/core/hle/service/caps/caps_sc.cpp +++ b/src/core/hle/service/caps/caps_sc.cpp @@ -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 diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h index e5600f6d7..d555f4979 100644 --- a/src/core/hle/service/caps/caps_sc.h +++ b/src/core/hle/service/caps/caps_sc.h @@ -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 diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 62b9edd41..d0d1b5425 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -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 diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h index 718ade485..381e44fd4 100644 --- a/src/core/hle/service/caps/caps_ss.h +++ b/src/core/hle/service/caps/caps_ss.h @@ -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 diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index 3b11cc95c..cad173dc7 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -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>()}; diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index c6398858d..647e3059d 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -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); diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h new file mode 100644 index 000000000..bf6061273 --- /dev/null +++ b/src/core/hle/service/caps/caps_types.h @@ -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 diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp index bffe0f8d0..260f25490 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp @@ -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); + + // TODO: Translate posix to DateTime + + std::vector<ApplicationAlbumFileEntry> entries; + const Result result = + manager->GetAlbumFileList(entries, content_type, {}, {}, 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); + 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 diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h index e8dd037d7..9458c128e 100644 --- a/src/core/hle/service/caps/caps_u.h +++ b/src/core/hle/service/caps/caps_u.h @@ -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 diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 21b06d10b..22dc55a6d 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -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"}, diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index ae99c4695..b74b66438 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -35,6 +35,7 @@ private: void GetInternetConnectionStatus(HLERequestContext& ctx); void IsEthernetCommunicationEnabled(HLERequestContext& ctx); void IsAnyInternetRequestAccepted(HLERequestContext& ctx); + void IsAnyForegroundRequestAccepted(HLERequestContext& ctx); Network::RoomNetwork& network; }; diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 6e0baf0be..f9e0e272d 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -7,6 +7,7 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/glue_manager.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ns/errors.h" @@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) static const FunctionInfo functions[] = { {11, nullptr, "CalculateApplicationOccupiedSize"}, {43, nullptr, "CheckSdCardMountStatus"}, - {47, nullptr, "GetTotalSpaceSize"}, - {48, nullptr, "GetFreeSpaceSize"}, + {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"}, + {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"}, {600, nullptr, "CountApplicationContentMeta"}, {601, nullptr, "ListApplicationContentMetaStatus"}, {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, @@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) IContentManagementInterface::~IContentManagementInterface() = default; +void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum<FileSys::StorageId>()}; + + LOG_INFO(Service_Capture, "called, storage={}", storage); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage)); +} + +void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage{rp.PopEnum<FileSys::StorageId>()}; + + LOG_INFO(Service_Capture, "called, storage={}", storage); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage)); +} + IDocumentInterface::IDocumentInterface(Core::System& system_) : ServiceFramework{system_, "IDocumentInterface"} { // clang-format off diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index 175dad780..34d2a45dc 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage public: explicit IContentManagementInterface(Core::System& system_); ~IContentManagementInterface() override; + +private: + void GetTotalSpaceSize(HLERequestContext& ctx); + void GetFreeSpaceSize(HLERequestContext& ctx); }; class IDocumentInterface final : public ServiceFramework<IDocumentInterface> { diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp index 5db1703d1..938330dd0 100644 --- a/src/core/hle/service/pctl/pctl_module.cpp +++ b/src/core/hle/service/pctl/pctl_module.cpp @@ -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; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9e90c587c..9b2698fad 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() { it++; } - boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; + boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads; u64 total_size_bytes = 0; u64 largest_copy = 0; for (const IntervalSet& intervals : committed_ranges) { @@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { const u32 offset = buffer.Offset(binding.cpu_addr); const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; + + if (is_written) { + MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); + } + if constexpr (NEEDS_BIND_STORAGE_INDEX) { runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); ++binding_index; @@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) { const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); + const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0; + if (is_written) { + MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); + } + const u32 offset = buffer.Offset(binding.cpu_addr); const PixelFormat format = binding.format; if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { @@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() { const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); + MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); + const u32 offset = buffer.Offset(binding.cpu_addr); host_bindings.buffers.push_back(&buffer); host_bindings.offsets.push_back(offset); @@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() { const u32 offset = buffer.Offset(binding.cpu_addr); const bool is_written = ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; + + if (is_written) { + MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); + } + if constexpr (NEEDS_BIND_STORAGE_INDEX) { runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); ++binding_index; @@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() { const u32 size = binding.size; SynchronizeBuffer(buffer, binding.cpu_addr, size); + const bool is_written = + ((channel_state->written_compute_texture_buffers >> index) & 1) != 0; + if (is_written) { + MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size); + } + const u32 offset = buffer.Offset(binding.cpu_addr); const PixelFormat format = binding.format; if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { @@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) { template <class P> void BufferCache<P>::UpdateStorageBuffers(size_t stage) { - const u32 written_mask = channel_state->written_storage_buffers[stage]; ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { // Resolve buffer Binding& binding = channel_state->storage_buffers[stage][index]; const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); binding.buffer_id = buffer_id; - // Mark buffer as written if needed - if (((written_mask >> index) & 1) != 0) { - MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size); - } }); } @@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) { ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { Binding& binding = channel_state->texture_buffers[stage][index]; binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); - // Mark buffer as written if needed - if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) { - MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); - } }); } @@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) { .size = size, .buffer_id = buffer_id, }; - MarkWrittenBuffer(buffer_id, *cpu_addr, size); } template <class P> @@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() { // Resolve buffer Binding& binding = channel_state->compute_storage_buffers[index]; binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); - // Mark as written if needed - if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) { - MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); - } }); } @@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() { ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { Binding& binding = channel_state->compute_texture_buffers[index]; binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); - // Mark as written if needed - if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) { - MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size); - } }); } template <class P> void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { - if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) { - SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size); - } memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); const IntervalType base_interval{cpu_addr, cpu_addr + size}; diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 0a86ce139..8151cabf0 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -68,6 +68,7 @@ struct LevelInfo { Extent2D tile_size; u32 bpp_log2; u32 tile_width_spacing; + u32 num_levels; }; [[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) { @@ -118,11 +119,11 @@ template <u32 GOB_EXTENT> } [[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size, - u32 level) { + u32 level, u32 num_levels) { return { .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), - .depth = level == 0 + .depth = level == 0 && num_levels == 1 ? block_size.depth : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), }; @@ -166,13 +167,6 @@ template <u32 GOB_EXTENT> } [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { - if (level == 0) { - return Extent3D{ - .width = info.block.width, - .height = info.block.height, - .depth = info.block.depth, - }; - } const Extent3D blocks = NumLevelBlocks(info, level); return Extent3D{ .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), @@ -257,7 +251,7 @@ template <u32 GOB_EXTENT> } [[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block, - u32 tile_width_spacing) { + u32 tile_width_spacing, u32 num_levels) { const u32 bytes_per_block = BytesPerBlock(format); return { .size = @@ -270,16 +264,18 @@ template <u32 GOB_EXTENT> .tile_size = DefaultBlockSize(format), .bpp_log2 = BytesPerBlockLog2(bytes_per_block), .tile_width_spacing = tile_width_spacing, + .num_levels = num_levels, }; } [[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) { - return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing); + return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing, + info.resources.levels); } [[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block, u32 tile_width_spacing, u32 level) { - const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing); + const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level); u32 offset = 0; for (u32 current_level = 0; current_level < level; ++current_level) { offset += CalculateLevelSize(info, current_level); @@ -466,7 +462,7 @@ template <u32 GOB_EXTENT> }; const u32 bpp_log2 = BytesPerBlockLog2(info.format); const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing); - const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0); + const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels); return Extent3D{ .width = Common::AlignUpLog2(num_tiles.width, alignment), .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height), @@ -533,7 +529,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr UNIMPLEMENTED_IF(copy.image_extent != level_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); - const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + const Extent3D block = + AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); size_t host_offset = copy.buffer_offset; @@ -698,7 +695,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) { const Extent2D tile_size = DefaultBlockSize(info.format); const Extent3D level_size = AdjustMipSize(info.size, level); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); - const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level); + const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels); const u32 bpp_log2 = BytesPerBlockLog2(info.format); return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing); } @@ -887,7 +884,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory .image_extent = level_size, }; const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); - const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + const Extent3D block = + AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); size_t guest_layer_offset = 0; @@ -1041,7 +1039,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) { const Extent2D tile_size = DefaultBlockSize(info.format); const Extent3D level_size = AdjustMipSize(info.size, level); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); - return AdjustMipBlockSize(num_tiles, level_info.block, level); + return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); } boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) { @@ -1063,7 +1061,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I for (s32 level = 0; level < num_levels; ++level) { const Extent3D level_size = AdjustMipSize(size, level); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); - const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); + const Extent3D block = + AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); params[level] = SwizzleParameters{ .num_tiles = num_tiles, .block = block, @@ -1292,11 +1291,11 @@ u32 MapSizeBytes(const ImageBase& image) { } } -static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == +static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) == 0x7f8000); -static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000); +static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x4000); -static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000); +static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x4000); static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == 0x2afc00); diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 82767fdf0..8dd1667f3 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -66,9 +66,10 @@ struct Range { switch (usage) { case MemoryUsage::Upload: case MemoryUsage::Stream: - return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + return VMA_ALLOCATION_CREATE_MAPPED_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; case MemoryUsage::Download: - return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; case MemoryUsage::DeviceLocal: return {}; } @@ -252,8 +253,7 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const { vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { const VmaAllocationCreateInfo alloc_ci = { - .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | - MemoryUsageVmaFlags(usage), + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), .usage = MemoryUsageVma(usage), .requiredFlags = 0, .preferredFlags = MemoryUsagePreferedVmaFlags(usage), diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index d15559518..ca0e14fad 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -23,6 +23,7 @@ #include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/main.h" +#include "yuzu/util/controller_navigation.h" namespace { @@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + ui->labelError->setVisible(false); + // Setup/load everything prior to setting up connections. // This avoids unintentionally changing the states of elements while loading them in. SetSupportedControllers(); @@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( LoadConfiguration(); + controller_navigation = new ControllerNavigation(system.HIDCore(), this); + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { SetExplainText(i); UpdateControllerIcon(i); @@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { if (checked) { + // Hide eventual error message about number of controllers + ui->labelError->setVisible(false); for (std::size_t index = 0; index <= i; ++index) { connected_controller_checkboxes[index]->setChecked(checked); } @@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QtControllerSelectorDialog::ApplyConfiguration); + connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, + [this](Qt::Key key) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); + QCoreApplication::postEvent(this, event); + }); + // Enhancement: Check if the parameters have already been met before disconnecting controllers. // If all the parameters are met AND only allows a single player, // stop the constructor here as we do not need to continue. @@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( } QtControllerSelectorDialog::~QtControllerSelectorDialog() { + controller_navigation->UnloadController(); system.HIDCore().DisableAllControllerConfiguration(); } @@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { dialog.exec(); } +void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) { + const auto num_connected_players = static_cast<int>( + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [](const QGroupBox* player) { return player->isChecked(); })); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) { + // Display error message when trying to validate using "Enter" and "OK" button is disabled + ui->labelError->setVisible(true); + return; + } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) { + // Remove a player if possible + connected_controller_checkboxes[num_connected_players - 1]->setChecked(false); + return; + } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) { + // Add a player, if possible + ui->labelError->setVisible(false); + connected_controller_checkboxes[num_connected_players]->setChecked(true); + return; + } + QDialog::keyPressEvent(evt); +} + bool QtControllerSelectorDialog::CheckIfParametersMet() { // Here, we check and validate the current configuration against all applicable parameters. const auto num_connected_players = static_cast<int>( diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 2fdc35857..7f0673d06 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -34,6 +34,8 @@ class HIDCore; enum class NpadStyleIndex : u8; } // namespace Core::HID +class ControllerNavigation; + class QtControllerSelectorDialog final : public QDialog { Q_OBJECT @@ -46,6 +48,8 @@ public: int exec() override; + void keyPressEvent(QKeyEvent* evt) override; + private: // Applies the current configuration. void ApplyConfiguration(); @@ -110,6 +114,8 @@ private: Core::System& system; + ControllerNavigation* controller_navigation = nullptr; + // This is true if and only if all parameters are met. Otherwise, this is false. // This determines whether the "OK" button can be clicked to exit the applet. bool parameters_met{false}; diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui index 729e921ee..6f7cb3c13 100644 --- a/src/yuzu/applets/qt_controller.ui +++ b/src/yuzu/applets/qt_controller.ui @@ -2624,13 +2624,53 @@ </spacer> </item> <item alignment="Qt::AlignBottom"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> + <widget class="QWidget" name="closeButtons" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_46"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelError"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="styleSheet"> + <string notr="true">QLabel { color : red; }</string> + </property> + <property name="text"> + <string>Not enough controllers</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 89361fa3f..5427758c1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -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(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index c1872ecd4..2346eb3bd 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -374,6 +374,7 @@ private slots: void ResetWindowSize720(); void ResetWindowSize900(); void ResetWindowSize1080(); + void OnAlbum(); void OnCabinet(Service::NFP::CabinetMode mode); void OnMiiEdit(); void OnCaptureScreenshot(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 31c3de9ef..88684ffb5 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -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>&Capture Screenshot</string> </property> </action> + <action name="action_Load_Album"> + <property name="text"> + <string>Open &Album</string> + </property> + </action> <action name="action_Load_Cabinet_Nickname_Owner"> <property name="text"> <string>&Set Nickname and Owner</string> |