diff options
Diffstat (limited to 'src')
75 files changed, 1481 insertions, 232 deletions
diff --git a/src/common/quaternion.h b/src/common/quaternion.h index ea39298c1..c528c0b68 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -12,7 +12,7 @@ template <typename T> class Quaternion { public: Math::Vec3<T> xyz; - T w; + T w{}; Quaternion<decltype(-T{})> Inverse() const { return {-xyz, w}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 882c9ab59..93f5ba3fe 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -83,6 +83,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/profile_select.cpp + frontend/applets/profile_select.h frontend/applets/software_keyboard.cpp frontend/applets/software_keyboard.h frontend/emu_window.cpp @@ -162,6 +164,8 @@ add_library(core STATIC hle/service/am/applet_oe.h hle/service/am/applets/applets.cpp hle/service/am/applets/applets.h + hle/service/am/applets/profile_select.cpp + hle/service/am/applets/profile_select.h hle/service/am/applets/software_keyboard.cpp hle/service/am/applets/software_keyboard.h hle/service/am/applets/stub_applet.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index ce7851538..fd10199ec 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -99,6 +99,8 @@ struct System::Impl { virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); /// Create default implementations of applets if one is not provided. + if (profile_selector == nullptr) + profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>(); if (software_keyboard == nullptr) software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); @@ -229,6 +231,7 @@ struct System::Impl { bool is_powered_on = false; /// Frontend applets + std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; /// Service manager @@ -424,6 +427,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) { + impl->profile_selector = std::move(applet); +} + +const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const { + return *impl->profile_selector; +} + void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) { impl->software_keyboard = std::move(applet); } diff --git a/src/core/core.h b/src/core/core.h index 71031dfcf..869921493 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" +#include "frontend/applets/profile_select.h" namespace Core::Frontend { class EmuWindow; @@ -241,6 +242,10 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet); + + const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const; + void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet); const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index e065e592f..83c184750 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -36,18 +36,20 @@ std::string LanguageEntry::GetDeveloperName() const { developer_name.size()); } -NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { - file->ReadObject(raw.get()); +NACP::NACP() = default; + +NACP::NACP(VirtualFile file) { + file->ReadObject(&raw); } NACP::~NACP() = default; const LanguageEntry& NACP::GetLanguageEntry(Language language) const { if (language != Language::Default) { - return raw->language_entries.at(static_cast<u8>(language)); + return raw.language_entries.at(static_cast<u8>(language)); } - for (const auto& language_entry : raw->language_entries) { + for (const auto& language_entry : raw.language_entries) { if (!language_entry.GetApplicationName().empty()) return language_entry; } @@ -65,21 +67,29 @@ std::string NACP::GetDeveloperName(Language language) const { } u64 NACP::GetTitleId() const { - return raw->title_id; + return raw.title_id; } u64 NACP::GetDLCBaseTitleId() const { - return raw->dlc_base_title_id; + return raw.dlc_base_title_id; } std::string NACP::GetVersionString() const { - return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), - raw->version_string.size()); + return Common::StringFromFixedZeroTerminatedBuffer(raw.version_string.data(), + raw.version_string.size()); +} + +u64 NACP::GetDefaultNormalSaveSize() const { + return raw.normal_save_data_size; +} + +u64 NACP::GetDefaultJournalSaveSize() const { + return raw.journal_sava_data_size; } std::vector<u8> NACP::GetRawBytes() const { std::vector<u8> out(sizeof(RawNACP)); - std::memcpy(out.data(), raw.get(), sizeof(RawNACP)); + std::memcpy(out.data(), &raw, sizeof(RawNACP)); return out; } } // namespace FileSys diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index bfaad46b4..7b9cdc910 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -28,17 +28,30 @@ static_assert(sizeof(LanguageEntry) == 0x300, "LanguageEntry has incorrect size. // The raw file format of a NACP file. struct RawNACP { std::array<LanguageEntry, 16> language_entries; - INSERT_PADDING_BYTES(0x38); + std::array<u8, 0x25> isbn; + u8 startup_user_account; + INSERT_PADDING_BYTES(2); + u32_le application_attribute; + u32_le supported_languages; + u32_le parental_control; + bool screenshot_enabled; + u8 video_capture_mode; + bool data_loss_confirmation; + INSERT_PADDING_BYTES(1); u64_le title_id; - INSERT_PADDING_BYTES(0x20); + std::array<u8, 0x20> rating_age; std::array<char, 0x10> version_string; u64_le dlc_base_title_id; u64_le title_id_2; - INSERT_PADDING_BYTES(0x28); + u64_le normal_save_data_size; + u64_le journal_sava_data_size; + INSERT_PADDING_BYTES(0x18); u64_le product_code; - u64_le title_id_3; - std::array<u64_le, 0x7> title_id_array; - INSERT_PADDING_BYTES(0x8); + std::array<u64_le, 0x8> local_communication; + u8 logo_type; + u8 logo_handling; + bool runtime_add_on_content_install; + INSERT_PADDING_BYTES(5); u64_le title_id_update; std::array<u8, 0x40> bcat_passphrase; INSERT_PADDING_BYTES(0xEC0); @@ -72,6 +85,7 @@ extern const std::array<const char*, 15> LANGUAGE_NAMES; // These store application name, dev name, title id, and other miscellaneous data. class NACP { public: + explicit NACP(); explicit NACP(VirtualFile file); ~NACP(); @@ -81,10 +95,12 @@ public: u64 GetTitleId() const; u64 GetDLCBaseTitleId() const; std::string GetVersionString() const; + u64 GetDefaultNormalSaveSize() const; + u64 GetDefaultJournalSaveSize() const; std::vector<u8> GetRawBytes() const; private: - std::unique_ptr<RawNACP> raw; + RawNACP raw{}; }; } // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index bd50fedc7..1913dc956 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -13,6 +13,8 @@ namespace FileSys { +constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size"; + std::string SaveDataDescriptor::DebugInfo() const { return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}]", static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); @@ -128,7 +130,36 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ return fmt::format("{}save/cache/{:016X}", out, title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); + return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id); } } +SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, + u128 user_id) const { + const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto dir = GetOrCreateDirectoryRelative(this->dir, path); + + const auto size_file = dir->GetFile(SAVE_DATA_SIZE_FILENAME); + if (size_file == nullptr || size_file->GetSize() < sizeof(SaveDataSize)) + return {0, 0}; + + SaveDataSize out; + if (size_file->ReadObject(&out) != sizeof(SaveDataSize)) + return {0, 0}; + return out; +} + +void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, + SaveDataSize new_value) { + const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto dir = GetOrCreateDirectoryRelative(this->dir, path); + + const auto size_file = dir->CreateFile(SAVE_DATA_SIZE_FILENAME); + if (size_file == nullptr) + return; + + size_file->Resize(sizeof(SaveDataSize)); + size_file->WriteObject(new_value); +} + } // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index bd4919610..3a1caf292 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -46,6 +46,11 @@ struct SaveDataDescriptor { }; static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size."); +struct SaveDataSize { + u64 normal; + u64 journal; +}; + /// File system interface to the SaveData archive class SaveDataFactory { public: @@ -60,6 +65,9 @@ public: static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, u64 save_id); + SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; + void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value); + private: VirtualDir dir; }; diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index e5641b255..954094772 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -150,7 +150,7 @@ public: template <typename T> std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); - return Write(data, number_elements * sizeof(T), offset); + return Write(reinterpret_cast<const u8*>(data), number_elements * sizeof(T), offset); } // Writes size bytes starting at memory location data to offset in file. @@ -166,7 +166,7 @@ public: template <typename T> std::size_t WriteObject(const T& data, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); - return Write(&data, sizeof(T), offset); + return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset); } // Renames the file to name. Returns whether or not the operation was successsful. diff --git a/src/core/frontend/applets/profile_select.cpp b/src/core/frontend/applets/profile_select.cpp new file mode 100644 index 000000000..fbf5f2a9e --- /dev/null +++ b/src/core/frontend/applets/profile_select.cpp @@ -0,0 +1,19 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/applets/profile_select.h" +#include "core/settings.h" + +namespace Core::Frontend { + +ProfileSelectApplet::~ProfileSelectApplet() = default; + +void DefaultProfileSelectApplet::SelectProfile( + std::function<void(std::optional<Service::Account::UUID>)> callback) const { + Service::Account::ProfileManager manager; + callback(manager.GetUser(Settings::values.current_user).value_or(Service::Account::UUID{})); + LOG_INFO(Service_ACC, "called, selecting current user instead of prompting..."); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/profile_select.h b/src/core/frontend/applets/profile_select.h new file mode 100644 index 000000000..fc8f7ae94 --- /dev/null +++ b/src/core/frontend/applets/profile_select.h @@ -0,0 +1,27 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include "core/hle/service/acc/profile_manager.h" + +namespace Core::Frontend { + +class ProfileSelectApplet { +public: + virtual ~ProfileSelectApplet(); + + virtual void SelectProfile( + std::function<void(std::optional<Service::Account::UUID>)> callback) const = 0; +}; + +class DefaultProfileSelectApplet final : public ProfileSelectApplet { +public: + void SelectProfile( + std::function<void(std::optional<Service::Account::UUID>)> callback) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 8f07aedc9..f8662d193 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" namespace Layout { @@ -42,4 +43,18 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) { return res; } +FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) { + int width, height; + + if (Settings::values.use_docked_mode) { + width = ScreenDocked::WidthDocked * res_scale; + height = ScreenDocked::HeightDocked * res_scale; + } else { + width = ScreenUndocked::Width * res_scale; + height = ScreenUndocked::Height * res_scale; + } + + return DefaultFrameLayout(width, height); +} + } // namespace Layout diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index f3fddfa94..e06647794 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -9,6 +9,7 @@ namespace Layout { enum ScreenUndocked : unsigned { Width = 1280, Height = 720 }; +enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 }; /// Describes the layout of the window framebuffer struct FramebufferLayout { @@ -34,4 +35,10 @@ struct FramebufferLayout { */ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height); +/** + * Convenience method to get frame layout by resolution scale + * @param res_scale resolution scale factor + */ +FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale); + } // namespace Layout diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index e6b5171ee..a1cad4fcb 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -201,11 +201,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) { modules.push_back(std::move(module)); } -static Kernel::Thread* FindThreadById(int id) { +static Kernel::Thread* FindThreadById(s64 id) { for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (auto& thread : threads) { - if (thread->GetThreadID() == static_cast<u32>(id)) { + if (thread->GetThreadID() == static_cast<u64>(id)) { current_core = core; return thread.get(); } diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index 8b58d701d..d8240ec6d 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -27,7 +27,7 @@ constexpr ResultCode ERR_SYNCHRONIZATION_CANCELED{ErrorModule::Kernel, 118}; constexpr ResultCode ERR_OUT_OF_RANGE{ErrorModule::Kernel, 119}; constexpr ResultCode ERR_INVALID_ENUM_VALUE{ErrorModule::Kernel, 120}; constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121}; -constexpr ResultCode ERR_ALREADY_REGISTERED{ErrorModule::Kernel, 122}; +constexpr ResultCode ERR_BUSY{ErrorModule::Kernel, 122}; constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123}; constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125}; constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132}; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index e441c5bc6..1c2290651 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -112,7 +112,7 @@ struct KernelCore::Impl { void Shutdown() { next_object_id = 0; - next_process_id = 10; + next_process_id = Process::ProcessIDMin; next_thread_id = 1; process_list.clear(); @@ -153,10 +153,8 @@ struct KernelCore::Impl { } std::atomic<u32> next_object_id{0}; - // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are - // reserved for low-level services - std::atomic<u32> next_process_id{10}; - std::atomic<u32> next_thread_id{1}; + std::atomic<u64> next_process_id{Process::ProcessIDMin}; + std::atomic<u64> next_thread_id{1}; // Lists all processes that exist in the current session. std::vector<SharedPtr<Process>> process_list; @@ -242,11 +240,11 @@ u32 KernelCore::CreateNewObjectID() { return impl->next_object_id++; } -u32 KernelCore::CreateNewThreadID() { +u64 KernelCore::CreateNewThreadID() { return impl->next_thread_id++; } -u32 KernelCore::CreateNewProcessID() { +u64 KernelCore::CreateNewProcessID() { return impl->next_process_id++; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index ea00c89f5..58c9d108b 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -88,10 +88,10 @@ private: u32 CreateNewObjectID(); /// Creates a new process ID, incrementing the internal process ID counter; - u32 CreateNewProcessID(); + u64 CreateNewProcessID(); /// Creates a new thread ID, incrementing the internal thread ID counter. - u32 CreateNewThreadID(); + u64 CreateNewThreadID(); /// Creates a timer callback handle for the given timer. ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer); diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index 0ea851a74..806078638 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -32,6 +32,7 @@ bool Object::IsWaitable() const { } UNREACHABLE(); + return false; } } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 459eedfa6..7da367251 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -120,6 +120,18 @@ struct CodeSet final { class Process final : public WaitObject { public: + enum : u64 { + /// Lowest allowed process ID for a kernel initial process. + InitialKIPIDMin = 1, + /// Highest allowed process ID for a kernel initial process. + InitialKIPIDMax = 80, + + /// Lowest allowed process ID for a userland process. + ProcessIDMin = 81, + /// Highest allowed process ID for a userland process. + ProcessIDMax = 0xFFFFFFFFFFFFFFFF, + }; + static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name); @@ -162,7 +174,7 @@ public: } /// Gets the unique ID that identifies this particular process. - u32 GetProcessID() const { + u64 GetProcessID() const { return process_id; } @@ -288,10 +300,10 @@ private: ProcessStatus status; /// The ID of this process - u32 process_id = 0; + u64 process_id = 0; /// Title ID corresponding to the process - u64 program_id; + u64 program_id = 0; /// Resource limit descriptor for this process SharedPtr<ResourceLimit> resource_limit; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index c826dfd96..2e80b48c2 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -391,7 +391,7 @@ static ResultCode SendSyncRequest(Handle handle) { } /// Get the ID for the specified thread. -static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { +static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); @@ -405,20 +405,33 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { return RESULT_SUCCESS; } -/// Get the ID of the specified process -static ResultCode GetProcessId(u32* process_id, Handle process_handle) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); +/// Gets the ID of the specified process or a specified thread's owning process. +static ResultCode GetProcessId(u64* process_id, Handle handle) { + LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); - const SharedPtr<Process> process = handle_table.Get<Process>(process_handle); - if (!process) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", - process_handle); - return ERR_INVALID_HANDLE; + const SharedPtr<Process> process = handle_table.Get<Process>(handle); + if (process) { + *process_id = process->GetProcessID(); + return RESULT_SUCCESS; } - *process_id = process->GetProcessID(); - return RESULT_SUCCESS; + const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle); + if (thread) { + const Process* const owner_process = thread->GetOwnerProcess(); + if (!owner_process) { + LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered."); + return ERR_INVALID_HANDLE; + } + + *process_id = owner_process->GetProcessID(); + return RESULT_SUCCESS; + } + + // NOTE: This should also handle debug objects before returning. + + LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle); + return ERR_INVALID_HANDLE; } /// Default thread wakeup callback for WaitSynchronization @@ -919,8 +932,35 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) } /// Sets the thread activity -static ResultCode SetThreadActivity(Handle handle, u32 unknown) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, unknown=0x{:08X}", handle, unknown); +static ResultCode SetThreadActivity(Handle handle, u32 activity) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity); + if (activity > static_cast<u32>(ThreadActivity::Paused)) { + return ERR_INVALID_ENUM_VALUE; + } + + const auto* current_process = Core::CurrentProcess(); + const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); + if (!thread) { + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle); + return ERR_INVALID_HANDLE; + } + + if (thread->GetOwnerProcess() != current_process) { + LOG_ERROR(Kernel_SVC, + "The current process does not own the current thread, thread_handle={:08X} " + "thread_pid={}, " + "current_process_pid={}", + handle, thread->GetOwnerProcess()->GetProcessID(), + current_process->GetProcessID()); + return ERR_INVALID_HANDLE; + } + + if (thread == GetCurrentThread()) { + LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); + return ERR_BUSY; + } + + thread->SetActivity(static_cast<ThreadActivity>(activity)); return RESULT_SUCCESS; } @@ -947,7 +987,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { if (thread == GetCurrentThread()) { LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); - return ERR_ALREADY_REGISTERED; + return ERR_BUSY; } Core::ARM_Interface::ThreadContext ctx = thread->GetContext(); @@ -1232,7 +1272,10 @@ static ResultCode StartThread(Handle thread_handle) { ASSERT(thread->GetStatus() == ThreadStatus::Dormant); thread->ResumeFromWait(); - Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule(); + + if (thread->GetStatus() == ThreadStatus::Ready) { + Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule(); + } return RESULT_SUCCESS; } diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 2f758b959..2a2c2c5ea 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -73,7 +73,15 @@ void SvcWrap() { template <ResultCode func(u32*, u64)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, Param(1)).raw; + const u32 retval = func(¶m_1, Param(1)).raw; + Core::CurrentArmInterface().SetReg(1, param_1); + FuncReturn(retval); +} + +template <ResultCode func(u64*, u32)> +void SvcWrap() { + u64 param_1 = 0; + const u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 63f8923fd..434655638 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -50,7 +50,7 @@ void Thread::Stop() { // Clean up thread from ready queue // This is only needed when the thread is terminated forcefully (SVC TerminateProcess) - if (status == ThreadStatus::Ready) { + if (status == ThreadStatus::Ready || status == ThreadStatus::Paused) { scheduler->UnscheduleThread(this, current_priority); } @@ -140,6 +140,11 @@ void Thread::ResumeFromWait() { wakeup_callback = nullptr; + if (activity == ThreadActivity::Paused) { + status = ThreadStatus::Paused; + return; + } + status = ThreadStatus::Ready; ChangeScheduler(); @@ -391,6 +396,23 @@ bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> t return wakeup_callback(reason, std::move(thread), std::move(object), index); } +void Thread::SetActivity(ThreadActivity value) { + activity = value; + + if (value == ThreadActivity::Paused) { + // Set status if not waiting + if (status == ThreadStatus::Ready) { + status = ThreadStatus::Paused; + } else if (status == ThreadStatus::Running) { + status = ThreadStatus::Paused; + Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); + } + } else if (status == ThreadStatus::Paused) { + // Ready to reschedule + ResumeFromWait(); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 77aec099a..fe5398d56 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -45,6 +45,7 @@ enum ThreadProcessorId : s32 { enum class ThreadStatus { Running, ///< Currently running Ready, ///< Ready to run + Paused, ///< Paused by SetThreadActivity or debug WaitHLEEvent, ///< Waiting for hle event to finish WaitSleep, ///< Waiting due to a SleepThread SVC WaitIPC, ///< Waiting for the reply from an IPC request @@ -61,6 +62,11 @@ enum class ThreadWakeupReason { Timeout // The thread was woken up due to a wait timeout. }; +enum class ThreadActivity : u32 { + Normal = 0, + Paused = 1, +}; + class Thread final : public WaitObject { public: using TLSMemory = std::vector<u8>; @@ -151,7 +157,7 @@ public: * Gets the thread's thread ID * @return The thread's ID */ - u32 GetThreadID() const { + u64 GetThreadID() const { return thread_id; } @@ -371,6 +377,12 @@ public: return affinity_mask; } + ThreadActivity GetActivity() const { + return activity; + } + + void SetActivity(ThreadActivity value); + private: explicit Thread(KernelCore& kernel); ~Thread() override; @@ -379,7 +391,7 @@ private: Core::ARM_Interface::ThreadContext context{}; - u32 thread_id = 0; + u64 thread_id = 0; ThreadStatus status = ThreadStatus::Dormant; @@ -439,6 +451,8 @@ private: TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>(); std::string name; + + ThreadActivity activity = ThreadActivity::Normal; }; /** diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index f39e096ca..10ad94aa6 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -190,6 +190,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) { vma.type = VMAType::Free; vma.permissions = VMAPermission::None; vma.state = MemoryState::Unmapped; + vma.attribute = MemoryAttribute::None; vma.backing_block = nullptr; vma.offset = 0; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index f83730cd6..d13ce4dca 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -8,6 +8,7 @@ #include <stack> #include "audio_core/audio_renderer.h" #include "core/core.h" +#include "core/file_sys/savedata_factory.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -19,6 +20,7 @@ #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/profile_select.h" #include "core/hle/service/am/applets/software_keyboard.h" #include "core/hle/service/am/applets/stub_applet.h" #include "core/hle/service/am/idle.h" @@ -39,6 +41,7 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2}; constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; enum class AppletId : u32 { + ProfileSelect = 0x10, SoftwareKeyboard = 0x11, }; @@ -71,10 +74,13 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") { IWindowController::~IWindowController() = default; void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); + + LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0); + rb.Push<u64>(process_id); } void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) { @@ -772,6 +778,8 @@ ILibraryAppletCreator::~ILibraryAppletCreator() = default; static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { switch (id) { + case AppletId::ProfileSelect: + return std::make_shared<Applets::ProfileSelect>(); case AppletId::SoftwareKeyboard: return std::make_shared<Applets::SoftwareKeyboard>(); default: @@ -858,8 +866,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"}, {23, &IApplicationFunctions::GetDisplayVersion, "GetDisplayVersion"}, {24, nullptr, "GetLaunchStorageInfoForDebug"}, - {25, nullptr, "ExtendSaveData"}, - {26, nullptr, "GetSaveDataSize"}, + {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, + {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, {32, &IApplicationFunctions::BeginBlockingHomeButton, "BeginBlockingHomeButton"}, @@ -1036,6 +1044,48 @@ void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) { rb.Push<u64>(0); } +void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto type{rp.PopRaw<FileSys::SaveDataType>()}; + rp.Skip(1, false); + const auto user_id{rp.PopRaw<u128>()}; + const auto new_normal_size{rp.PopRaw<u64>()}; + const auto new_journal_size{rp.PopRaw<u64>()}; + + LOG_DEBUG(Service_AM, + "called with type={:02X}, user_id={:016X}{:016X}, new_normal={:016X}, " + "new_journal={:016X}", + static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); + + FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, + {new_normal_size, new_journal_size}); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + + // The following value is used upon failure to help the system recover. + // Since we always succeed, this should be 0. + rb.Push<u64>(0); +} + +void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto type{rp.PopRaw<FileSys::SaveDataType>()}; + rp.Skip(1, false); + const auto user_id{rp.PopRaw<u128>()}; + + LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), + user_id[1], user_id[0]); + + const auto size = + FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.Push(size.normal); + rb.Push(size.journal); +} + void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nvflinger) { auto message_queue = std::make_shared<AppletMessageQueue>(); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 34c45fadf..b6113cfdd 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -206,6 +206,8 @@ private: void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx); void NotifyRunning(Kernel::HLERequestContext& ctx); void GetPseudoDeviceId(Kernel::HLERequestContext& ctx); + void ExtendSaveData(Kernel::HLERequestContext& ctx); + void GetSaveDataSize(Kernel::HLERequestContext& ctx); void BeginBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx); void EndBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx); void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp new file mode 100644 index 000000000..4c7b45454 --- /dev/null +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -0,0 +1,77 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/profile_select.h" + +namespace Service::AM::Applets { + +constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; + +ProfileSelect::ProfileSelect() = default; +ProfileSelect::~ProfileSelect() = default; + +void ProfileSelect::Initialize() { + complete = false; + status = RESULT_SUCCESS; + final_data.clear(); + + Applet::Initialize(); + + const auto user_config_storage = broker.PopNormalDataToApplet(); + ASSERT(user_config_storage != nullptr); + const auto& user_config = user_config_storage->GetData(); + + ASSERT(user_config.size() >= sizeof(UserSelectionConfig)); + std::memcpy(&config, user_config.data(), sizeof(UserSelectionConfig)); +} + +bool ProfileSelect::TransactionComplete() const { + return complete; +} + +ResultCode ProfileSelect::GetStatus() const { + return status; +} + +void ProfileSelect::ExecuteInteractive() { + UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); +} + +void ProfileSelect::Execute() { + if (complete) { + broker.PushNormalDataFromApplet(IStorage{final_data}); + return; + } + + const auto& frontend{Core::System::GetInstance().GetProfileSelector()}; + + frontend.SelectProfile([this](std::optional<Account::UUID> uuid) { SelectionComplete(uuid); }); +} + +void ProfileSelect::SelectionComplete(std::optional<Account::UUID> uuid) { + UserSelectionOutput output{}; + + if (uuid.has_value() && uuid->uuid != Account::INVALID_UUID) { + output.result = 0; + output.uuid_selected = uuid->uuid; + } else { + status = ERR_USER_CANCELLED_SELECTION; + output.result = ERR_USER_CANCELLED_SELECTION.raw; + output.uuid_selected = Account::INVALID_UUID; + } + + final_data = std::vector<u8>(sizeof(UserSelectionOutput)); + std::memcpy(final_data.data(), &output, final_data.size()); + broker.PushNormalDataFromApplet(IStorage{final_data}); + broker.SignalStateChanged(); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h new file mode 100644 index 000000000..787485f22 --- /dev/null +++ b/src/core/hle/service/am/applets/profile_select.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "common/common_funcs.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +struct UserSelectionConfig { + // TODO(DarkLordZach): RE this structure + // It seems to be flags and the like that determine the UI of the applet on the switch... from + // my research this is safe to ignore for now. + INSERT_PADDING_BYTES(0xA0); +}; +static_assert(sizeof(UserSelectionConfig) == 0xA0, "UserSelectionConfig has incorrect size."); + +struct UserSelectionOutput { + u64 result; + u128 uuid_selected; +}; +static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has incorrect size."); + +class ProfileSelect final : public Applet { +public: + ProfileSelect(); + ~ProfileSelect() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void SelectionComplete(std::optional<Account::UUID> uuid); + +private: + UserSelectionConfig config; + bool complete = false; + ResultCode status = RESULT_SUCCESS; + std::vector<u8> final_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index b1490e6fa..c6da2df43 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -8,18 +8,23 @@ #include "common/file_util.h" #include "core/core.h" #include "core/file_sys/bis_factory.h" +#include "core/file_sys/control_metadata.h" #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_offset.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_ldr.h" #include "core/hle/service/filesystem/fsp_pr.h" #include "core/hle/service/filesystem/fsp_srv.h" +#include "core/loader/loader.h" namespace Service::FileSystem { @@ -28,6 +33,10 @@ namespace Service::FileSystem { // TODO(DarkLordZach): Eventually make this configurable in settings. constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000; +// A default size for normal/journal save data size if application control metadata cannot be found. +// This should be large enough to satisfy even the most extreme requirements (~4.2GB) +constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000; + static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, std::string_view dir_name_) { std::string dir_name(FileUtil::SanitizePath(dir_name_)); @@ -341,6 +350,44 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } +FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id) { + if (save_data_factory == nullptr) { + return {0, 0}; + } + + const auto value = save_data_factory->ReadSaveDataSize(type, title_id, user_id); + + if (value.normal == 0 && value.journal == 0) { + FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE}; + + FileSys::NACP nacp; + const auto res = Core::System::GetInstance().GetAppLoader().ReadControlData(nacp); + + if (res != Loader::ResultStatus::Success) { + FileSys::PatchManager pm{Core::CurrentProcess()->GetTitleID()}; + auto [nacp_unique, discard] = pm.GetControlMetadata(); + + if (nacp_unique != nullptr) { + new_size = {nacp_unique->GetDefaultNormalSaveSize(), + nacp_unique->GetDefaultJournalSaveSize()}; + } + } else { + new_size = {nacp.GetDefaultNormalSaveSize(), nacp.GetDefaultJournalSaveSize()}; + } + + WriteSaveDataSize(type, title_id, user_id, new_size); + return new_size; + } + + return value; +} + +void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, + FileSys::SaveDataSize new_value) { + if (save_data_factory != nullptr) + save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value); +} + FileSys::RegisteredCacheUnion GetUnionContents() { return FileSys::RegisteredCacheUnion{ {GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}}; diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 965414be0..6fd5e7b23 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -21,9 +21,11 @@ class SDMCFactory; enum class ContentRecordType : u8; enum class Mode : u32; enum class SaveDataSpaceId : u8; +enum class SaveDataType : u8; enum class StorageId : u8; struct SaveDataDescriptor; +struct SaveDataSize; } // namespace FileSys namespace Service { @@ -48,6 +50,10 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space); ResultVal<FileSys::VirtualDir> OpenSDMC(); +FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id); +void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, + FileSys::SaveDataSize new_value); + FileSys::RegisteredCacheUnion GetUnionContents(); FileSys::RegisteredCache* GetSystemNANDContents(); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index d6829d0b8..75fdb861a 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -339,52 +339,6 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index]; auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; - if (hold_type == NpadHoldType::Horizontal) { - ControllerPadState state{}; - AnalogPosition temp_lstick_entry{}; - AnalogPosition temp_rstick_entry{}; - if (controller_type == NPadControllerType::JoyLeft) { - state.d_down.Assign(pad_state.pad_states.d_left.Value()); - state.d_left.Assign(pad_state.pad_states.d_up.Value()); - state.d_right.Assign(pad_state.pad_states.d_down.Value()); - state.d_up.Assign(pad_state.pad_states.d_right.Value()); - state.l.Assign(pad_state.pad_states.l.Value() | - pad_state.pad_states.left_sl.Value()); - state.r.Assign(pad_state.pad_states.r.Value() | - pad_state.pad_states.left_sr.Value()); - - state.zl.Assign(pad_state.pad_states.zl.Value()); - state.plus.Assign(pad_state.pad_states.minus.Value()); - - temp_lstick_entry = pad_state.l_stick; - temp_rstick_entry = pad_state.r_stick; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_lstick_entry.y *= -1; - } else if (controller_type == NPadControllerType::JoyRight) { - state.x.Assign(pad_state.pad_states.a.Value()); - state.a.Assign(pad_state.pad_states.b.Value()); - state.b.Assign(pad_state.pad_states.y.Value()); - state.y.Assign(pad_state.pad_states.b.Value()); - - state.l.Assign(pad_state.pad_states.l.Value() | - pad_state.pad_states.right_sl.Value()); - state.r.Assign(pad_state.pad_states.r.Value() | - pad_state.pad_states.right_sr.Value()); - state.zr.Assign(pad_state.pad_states.zr.Value()); - state.plus.Assign(pad_state.pad_states.plus.Value()); - - temp_lstick_entry = pad_state.l_stick; - temp_rstick_entry = pad_state.r_stick; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_rstick_entry.x *= -1; - } - pad_state.pad_states.raw = state.raw; - pad_state.l_stick = temp_lstick_entry; - pad_state.r_stick = temp_rstick_entry; - } - libnx_entry.connection_status.raw = 0; switch (controller_type) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 0838e303b..cfd67adc0 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -15,6 +15,10 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/vfs.h" +namespace FileSys { +class NACP; +} // namespace FileSys + namespace Kernel { struct AddressMapping; class Process; @@ -245,11 +249,11 @@ public: } /** - * Get the developer of the application - * @param developer Reference to store the application developer into + * Get the control data (CNMT) of the application + * @param control Reference to store the application control data into * @return ResultStatus result of function */ - virtual ResultStatus ReadDeveloper(std::string& developer) { + virtual ResultStatus ReadControlData(FileSys::NACP& control) { return ResultStatus::ErrorNotImplemented; } diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index b4ab88ae8..4d4b44571 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -152,10 +152,10 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) { return ResultStatus::Success; } -ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) { +ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) { if (nacp_file == nullptr) return ResultStatus::ErrorNoControl; - developer = nacp_file->GetDeveloperName(); + nacp = *nacp_file; return ResultStatus::Success; } } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 2b1e0719b..32eb0193d 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -43,7 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; - ResultStatus ReadDeveloper(std::string& developer) override; + ResultStatus ReadControlData(FileSys::NACP& nacp) override; private: std::unique_ptr<FileSys::NSP> nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index bd5a83b49..e67e43c69 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -121,10 +121,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) { return ResultStatus::Success; } -ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) { +ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) { if (nacp_file == nullptr) return ResultStatus::ErrorNoControl; - developer = nacp_file->GetDeveloperName(); + control = *nacp_file; return ResultStatus::Success; } + } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 15d1b1a23..9d3923f62 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -43,7 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; - ResultStatus ReadDeveloper(std::string& developer) override; + ResultStatus ReadControlData(FileSys::NACP& control) override; private: std::unique_ptr<FileSys::XCI> xci; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 643afdee8..e9166dbd9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -187,6 +187,7 @@ T Read(const VAddr vaddr) { default: UNREACHABLE(); } + return {}; } template <typename T> diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 25bb7604a..0faff6fdf 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -164,6 +164,7 @@ public: return 3; default: UNREACHABLE(); + return 1; } } @@ -871,6 +872,7 @@ public: return 4; } UNREACHABLE(); + return 1; } GPUVAddr StartAddress() const { diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 2efeb6e1a..e53c77f2b 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -1049,7 +1049,7 @@ union Instruction { BitField<49, 1, u64> nodep_flag; BitField<50, 3, u64> component_mask_selector; BitField<53, 4, u64> texture_info; - BitField<60, 1, u64> fp32_flag; + BitField<59, 1, u64> fp32_flag; TextureType GetTextureType() const { // The TEXS instruction has a weird encoding for the texture type. @@ -1065,6 +1065,7 @@ union Instruction { LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", static_cast<u32>(texture_info.Value())); UNREACHABLE(); + return TextureType::Texture1D; } TextureProcessMode GetTextureProcessMode() const { @@ -1145,6 +1146,7 @@ union Instruction { LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", static_cast<u32>(texture_info.Value())); UNREACHABLE(); + return TextureType::Texture1D; } TextureProcessMode GetTextureProcessMode() const { diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 88c45a423..08cf6268f 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -102,6 +102,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { return 1; default: UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format)); + return 1; } } @@ -119,6 +120,7 @@ u32 DepthFormatBytesPerPixel(DepthFormat format) { return 2; default: UNIMPLEMENTED_MSG("Unimplemented Depth format {}", static_cast<u32>(format)); + return 1; } } diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index 9c55e9f1e..64f75db43 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp @@ -171,6 +171,7 @@ u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) default: UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation)); + return 0; } } @@ -268,6 +269,7 @@ bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value) return value != 0; } UNREACHABLE(); + return true; } } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index a310491a8..47e76d8fe 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -192,6 +192,7 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor return linear_to_morton_fns[static_cast<std::size_t>(format)]; } UNREACHABLE(); + return morton_to_linear_fns[static_cast<std::size_t>(format)]; } /// 8x8 Z-Order coordinate from 2D coordinates diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 1482cdb40..94223f45f 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -27,4 +27,16 @@ void RendererBase::UpdateCurrentFramebufferLayout() { render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height); } +void RendererBase::RequestScreenshot(void* data, std::function<void()> callback, + const Layout::FramebufferLayout& layout) { + if (renderer_settings.screenshot_requested) { + LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request"); + return; + } + renderer_settings.screenshot_bits = data; + renderer_settings.screenshot_complete_callback = std::move(callback); + renderer_settings.screenshot_framebuffer_layout = layout; + renderer_settings.screenshot_requested = true; +} + } // namespace VideoCore diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 669e26e15..1d54c3723 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -9,6 +9,7 @@ #include <optional> #include "common/common_types.h" +#include "core/frontend/emu_window.h" #include "video_core/gpu.h" #include "video_core/rasterizer_interface.h" @@ -21,6 +22,12 @@ namespace VideoCore { struct RendererSettings { std::atomic_bool use_framelimiter{false}; std::atomic_bool set_background_color{false}; + + // Screenshot + std::atomic<bool> screenshot_requested{false}; + void* screenshot_bits; + std::function<void()> screenshot_complete_callback; + Layout::FramebufferLayout screenshot_framebuffer_layout; }; class RendererBase : NonCopyable { @@ -57,9 +64,29 @@ public: return *rasterizer; } + Core::Frontend::EmuWindow& GetRenderWindow() { + return render_window; + } + + const Core::Frontend::EmuWindow& GetRenderWindow() const { + return render_window; + } + + RendererSettings& Settings() { + return renderer_settings; + } + + const RendererSettings& Settings() const { + return renderer_settings; + } + /// Refreshes the settings common to all renderers void RefreshBaseSettings(); + /// Request a screenshot of the next frame + void RequestScreenshot(void* data, std::function<void()> callback, + const Layout::FramebufferLayout& layout); + protected: Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. std::unique_ptr<RasterizerInterface> rasterizer; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 5f4cdd119..7ea07631a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -101,8 +101,18 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), params.srgb_conversion); + + if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) { + // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled, + // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also + // causes GetFormatType to properly return 'Depth' below). + params.pixel_format = PixelFormat::Z16; + } + params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); params.type = GetFormatType(params.pixel_format); + UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled); + params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); params.unaligned_height = config.tic.Height(); @@ -257,7 +267,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U - {GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI + {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float, false}, // R11FG11FB10F {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index b4ef6030d..de3671acf 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -67,6 +67,7 @@ public: 6, "ShaderTrianglesAdjacency"); default: UNREACHABLE_MSG("Unknown primitive mode."); + return LazyGeometryProgram(geometry_programs.points, "points", 1, "ShaderPoints"); } } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index a7d8c404d..76d9b3538 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -364,6 +364,7 @@ public: return value; default: UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size)); + return value; } } @@ -626,6 +627,7 @@ public: return "floatBitsToInt(" + value + ')'; } else { UNREACHABLE(); + return value; } } @@ -1753,7 +1755,7 @@ private: instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL; constexpr std::array<const char*, 4> coord_container{ - {"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}}; + {"", "int coords = (", "ivec2 coords = ivec2(", "ivec3 coords = ivec3("}}; std::string coord = coord_container[total_coord_count]; @@ -2062,6 +2064,8 @@ private: std::to_string(instr.alu.GetSignedImm20_20())}; default: UNREACHABLE(); + return {regs.GetRegisterAsInteger(instr.gpr39, 0, false), + std::to_string(instr.alu.GetSignedImm20_20())}; } }(); const std::string offset = '(' + packed_shift + " & 0xff)"; @@ -3312,6 +3316,7 @@ private: return std::to_string(instr.r2p.immediate_mask); default: UNREACHABLE(); + return std::to_string(instr.r2p.immediate_mask); } }(); const std::string mask = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + @@ -3775,7 +3780,10 @@ private: } break; } - default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); } + default: { + UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); + break; + } } break; @@ -3930,4 +3938,4 @@ std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u return {}; } -} // namespace OpenGL::GLShader::Decompiler
\ No newline at end of file +} // namespace OpenGL::GLShader::Decompiler diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 4fd0d66c5..235732d86 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -138,7 +138,12 @@ void RendererOpenGL::SwapBuffers( // Load the framebuffer from memory, draw it to the screen, and swap buffers LoadFBToScreenInfo(*framebuffer); - DrawScreen(); + + if (renderer_settings.screenshot_requested) + CaptureScreenshot(); + + DrawScreen(render_window.GetFramebufferLayout()); + render_window.SwapBuffers(); } @@ -383,14 +388,13 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, /** * Draws the emulated screens to the emulator window. */ -void RendererOpenGL::DrawScreen() { +void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { if (renderer_settings.set_background_color) { // Update background color before drawing glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); } - const auto& layout = render_window.GetFramebufferLayout(); const auto& screen = layout.screen; glViewport(0, 0, layout.width, layout.height); @@ -414,6 +418,37 @@ void RendererOpenGL::DrawScreen() { /// Updates the framerate void RendererOpenGL::UpdateFramerate() {} +void RendererOpenGL::CaptureScreenshot() { + // Draw the current frame to the screenshot framebuffer + screenshot_framebuffer.Create(); + GLuint old_read_fb = state.draw.read_framebuffer; + GLuint old_draw_fb = state.draw.draw_framebuffer; + state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; + state.Apply(); + + Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; + + GLuint renderbuffer; + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + + DrawScreen(layout); + + glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + renderer_settings.screenshot_bits); + + screenshot_framebuffer.Release(); + state.draw.read_framebuffer = old_read_fb; + state.draw.draw_framebuffer = old_draw_fb; + state.Apply(); + glDeleteRenderbuffers(1, &renderbuffer); + + renderer_settings.screenshot_complete_callback(); + renderer_settings.screenshot_requested = false; +} + static const char* GetSource(GLenum source) { #define RET(s) \ case GL_DEBUG_SOURCE_##s: \ @@ -427,6 +462,7 @@ static const char* GetSource(GLenum source) { RET(OTHER); default: UNREACHABLE(); + return "Unknown source"; } #undef RET } @@ -445,6 +481,7 @@ static const char* GetType(GLenum type) { RET(MARKER); default: UNREACHABLE(); + return "Unknown type"; } #undef RET } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index c0868c0e4..b85cc262f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -16,6 +16,10 @@ namespace Core::Frontend { class EmuWindow; } +namespace Layout { +struct FramebufferLayout; +} + namespace OpenGL { /// Structure used for storing information about the textures for the Switch screen @@ -66,10 +70,12 @@ private: void ConfigureFramebufferTexture(TextureInfo& texture, const Tegra::FramebufferConfig& framebuffer); - void DrawScreen(); + void DrawScreen(const Layout::FramebufferLayout& layout); void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); + void CaptureScreenshot(); + // Loads framebuffer from emulated memory into the display information structure void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); // Fills active OpenGL texture with the given RGBA color. @@ -82,6 +88,7 @@ private: OGLVertexArray vertex_array; OGLBuffer vertex_buffer; OGLProgram shader; + OGLFramebuffer screenshot_framebuffer; /// Display information for Switch screen ScreenInfo screen_info; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 9582dd2ca..a97b1562b 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -65,6 +65,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) { default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); + return PixelFormat::S8Z24; } } @@ -141,6 +142,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); + return PixelFormat::RGBA8_SRGB; } } @@ -327,6 +329,7 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format, LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format), static_cast<u32>(component_type)); UNREACHABLE(); + return PixelFormat::ABGR8U; } } @@ -346,6 +349,7 @@ ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) { default: LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type)); UNREACHABLE(); + return ComponentType::UNorm; } } @@ -393,6 +397,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) { default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); + return ComponentType::UNorm; } } @@ -403,6 +408,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); + return PixelFormat::ABGR8U; } } @@ -418,6 +424,7 @@ ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) { default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); + return ComponentType::UNorm; } } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index bbae9285f..5db75de22 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -226,7 +226,7 @@ u32 BytesPerPixel(TextureFormat format) { return 8; default: UNIMPLEMENTED_MSG("Format not implemented"); - break; + return 1; } } diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 07e3a7d24..f7de3471b 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <memory> +#include "core/core.h" +#include "core/settings.h" #include "video_core/renderer_base.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/video_core.h" @@ -13,4 +15,10 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind return std::make_unique<OpenGL::RendererOpenGL>(emu_window); } +u16 GetResolutionScaleFactor(const RendererBase& renderer) { + return !Settings::values.resolution_factor + ? renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio() + : Settings::values.resolution_factor; +} + } // namespace VideoCore diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index f79f85dfe..5b373bcb1 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -22,4 +22,6 @@ class RendererBase; */ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window); +u16 GetResolutionScaleFactor(const RendererBase& renderer); + } // namespace VideoCore diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3232aa8fb..17ecaafde 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu Info.plist about_dialog.cpp about_dialog.h + applets/profile_select.cpp + applets/profile_select.h applets/software_keyboard.cpp applets/software_keyboard.h bootmanager.cpp @@ -31,6 +33,8 @@ add_executable(yuzu configuration/configure_input.h configuration/configure_input_player.cpp configuration/configure_input_player.h + configuration/configure_input_simple.cpp + configuration/configure_input_simple.h configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h configuration/configure_system.cpp @@ -87,6 +91,7 @@ set(UIS configuration/configure_graphics.ui configuration/configure_input.ui configuration/configure_input_player.ui + configuration/configure_input_simple.ui configuration/configure_mouse_advanced.ui configuration/configure_per_general.ui configuration/configure_system.ui diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp new file mode 100644 index 000000000..5c1b65a2c --- /dev/null +++ b/src/yuzu/applets/profile_select.cpp @@ -0,0 +1,168 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <QDialogButtonBox> +#include <QLabel> +#include <QLineEdit> +#include <QScrollArea> +#include <QStandardItemModel> +#include <QVBoxLayout> +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/hle/lock.h" +#include "yuzu/applets/profile_select.h" +#include "yuzu/main.h" + +// Same backup JPEG used by acc IProfile::GetImage if no jpeg found +constexpr std::array<u8, 107> backup_jpeg{ + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, + 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, + 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, + 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, + 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, + 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, +}; + +QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { + return QtProfileSelectionDialog::tr( + "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " + "00112233-4455-6677-8899-AABBCCDDEEFF))") + .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QString GetImagePath(Service::Account::UUID uuid) { + const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; + return QString::fromStdString(path); +} + +QPixmap GetIcon(Service::Account::UUID uuid) { + QPixmap icon{GetImagePath(uuid)}; + + if (!icon) { + icon.fill(Qt::black); + icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); + } + + return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) + : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) { + outer_layout = new QVBoxLayout; + + instruction_label = new QLabel(tr("Select a user:")); + + scroll_area = new QScrollArea; + + buttons = new QDialogButtonBox; + buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole); + + connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); + + outer_layout->addWidget(instruction_label); + outer_layout->addWidget(scroll_area); + outer_layout->addWidget(buttons); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setIconSize({64, 64}); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 1); + item_model->setHeaderData(0, Qt::Horizontal, "Users"); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + scroll_area->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + + const auto& profiles = profile_manager->GetAllUsers(); + for (const auto& user : profiles) { + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(user, profile)) + continue; + + const auto username = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + + list_items.push_back(QList<QStandardItem*>{new QStandardItem{ + GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); + } + + for (const auto& item : list_items) + item_model->appendRow(item); + + setLayout(outer_layout); + setWindowTitle(tr("Profile Selector")); + resize(550, 400); +} + +QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; + +void QtProfileSelectionDialog::accept() { + ok = true; + QDialog::accept(); +} + +void QtProfileSelectionDialog::reject() { + ok = false; + user_index = 0; + QDialog::reject(); +} + +bool QtProfileSelectionDialog::GetStatus() const { + return ok; +} + +u32 QtProfileSelectionDialog::GetIndex() const { + return user_index; +} + +void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { + user_index = index.row(); +} + +QtProfileSelector::QtProfileSelector(GMainWindow& parent) { + connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, + &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); + connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, + &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); +} + +QtProfileSelector::~QtProfileSelector() = default; + +void QtProfileSelector::SelectProfile( + std::function<void(std::optional<Service::Account::UUID>)> callback) const { + this->callback = std::move(callback); + emit MainWindowSelectProfile(); +} + +void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + callback(uuid); +} diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h new file mode 100644 index 000000000..868573324 --- /dev/null +++ b/src/yuzu/applets/profile_select.h @@ -0,0 +1,73 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <QDialog> +#include <QList> +#include "core/frontend/applets/profile_select.h" + +class GMainWindow; +class QDialogButtonBox; +class QGraphicsScene; +class QLabel; +class QScrollArea; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +class QtProfileSelectionDialog final : public QDialog { + Q_OBJECT + +public: + explicit QtProfileSelectionDialog(QWidget* parent); + ~QtProfileSelectionDialog() override; + + void accept() override; + void reject() override; + + bool GetStatus() const; + u32 GetIndex() const; + +private: + bool ok = false; + u32 user_index = 0; + + void SelectUser(const QModelIndex& index); + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + QVBoxLayout* outer_layout; + QLabel* instruction_label; + QScrollArea* scroll_area; + QDialogButtonBox* buttons; + + std::unique_ptr<Service::Account::ProfileManager> profile_manager; +}; + +class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { + Q_OBJECT + +public: + explicit QtProfileSelector(GMainWindow& parent); + ~QtProfileSelector() override; + + void SelectProfile( + std::function<void(std::optional<Service::Account::UUID>)> callback) const override; + +signals: + void MainWindowSelectProfile() const; + +private: + void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid); + + mutable std::function<void(std::optional<Service::Account::UUID>)> callback; +}; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 384e17921..40db7a5e9 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -14,6 +14,8 @@ #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h" #include "yuzu/bootmanager.h" EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} @@ -333,6 +335,22 @@ void GRenderWindow::InitRenderTarget() { BackupGeometry(); } +void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { + auto& renderer = Core::System::GetInstance().Renderer(); + + if (!res_scale) + res_scale = VideoCore::GetResolutionScaleFactor(renderer); + + const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; + screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); + renderer.RequestScreenshot(screenshot_image.bits(), + [=] { + screenshot_image.mirrored(false, true).save(screenshot_path); + LOG_INFO(Frontend, "The screenshot is saved."); + }, + layout); +} + void GRenderWindow::OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 873985564..4e3028215 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -8,6 +8,7 @@ #include <condition_variable> #include <mutex> #include <QGLWidget> +#include <QImage> #include <QThread> #include "common/thread.h" #include "core/core.h" @@ -139,6 +140,8 @@ public: void InitRenderTarget(); + void CaptureScreenshot(u16 res_scale, const QString& screenshot_path); + public slots: void moveContext(); // overridden @@ -165,6 +168,9 @@ private: EmuThread* emu_thread; + /// Temporary storage of the screenshot taken + QImage screenshot_image; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index eb2077b0f..c4349ccc8 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,6 +4,7 @@ #include <QSettings> #include "common/file_util.h" +#include "configure_input_simple.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" @@ -339,6 +340,13 @@ void Config::ReadTouchscreenValues() { qt_config->endGroup(); } +void Config::ApplyDefaultProfileIfInputInvalid() { + if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), + [](const Settings::PlayerInput& in) { return in.connected; })) { + ApplyInputProfileConfiguration(UISettings::values.profile_index); + } +} + void Config::ReadValues() { qt_config->beginGroup("Controls"); @@ -460,6 +468,8 @@ void Config::ReadValues() { UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); UISettings::values.enable_discord_presence = qt_config->value("enable_discord_presence", true).toBool(); + UISettings::values.screenshot_resolution_factor = + static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt()); qt_config->beginGroup("UIGameList"); UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); @@ -518,6 +528,9 @@ void Config::ReadValues() { UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); + UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt(); + + ApplyDefaultProfileIfInputInvalid(); qt_config->endGroup(); } @@ -678,6 +691,8 @@ void Config::SaveValues() { qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); + qt_config->setValue("screenshot_resolution_factor", + UISettings::values.screenshot_resolution_factor); qt_config->beginGroup("UIGameList"); qt_config->setValue("show_unknown", UISettings::values.show_unknown); @@ -699,6 +714,7 @@ void Config::SaveValues() { qt_config->beginGroup("Paths"); qt_config->setValue("romsPath", UISettings::values.roms_path); qt_config->setValue("symbolsPath", UISettings::values.symbols_path); + qt_config->setValue("screenshotPath", UISettings::values.screenshot_path); qt_config->setValue("gameListRootDir", UISettings::values.gamedir); qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); qt_config->setValue("recentFiles", UISettings::values.recent_files); @@ -720,6 +736,7 @@ void Config::SaveValues() { qt_config->setValue("firstStart", UISettings::values.first_start); qt_config->setValue("calloutFlags", UISettings::values.callout_flags); qt_config->setValue("showConsole", UISettings::values.show_console); + qt_config->setValue("profileIndex", UISettings::values.profile_index); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index a1c27bbf9..e73ad19bb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -34,6 +34,7 @@ private: void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); + void ApplyDefaultProfileIfInputInvalid(); void SaveValues(); void SavePlayerValues(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9b297df28..8706b80d2 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>461</width> - <height>500</height> + <height>659</height> </rect> </property> <property name="windowTitle"> @@ -24,17 +24,17 @@ <string>General</string> </attribute> </widget> - <widget class="ConfigureGameList" name="gameListTab"> - <attribute name="title"> - <string>Game List</string> - </attribute> - </widget> + <widget class="ConfigureGameList" name="gameListTab"> + <attribute name="title"> + <string>Game List</string> + </attribute> + </widget> <widget class="ConfigureSystem" name="systemTab"> <attribute name="title"> <string>System</string> </attribute> </widget> - <widget class="ConfigureInput" name="inputTab"> + <widget class="ConfigureInputSimple" name="inputTab"> <attribute name="title"> <string>Input</string> </attribute> @@ -54,11 +54,11 @@ <string>Debug</string> </attribute> </widget> - <widget class="ConfigureWeb" name="webTab"> - <attribute name="title"> - <string>Web</string> - </attribute> - </widget> + <widget class="ConfigureWeb" name="webTab"> + <attribute name="title"> + <string>Web</string> + </attribute> + </widget> </widget> </item> <item> @@ -77,12 +77,12 @@ <header>configuration/configure_general.h</header> <container>1</container> </customwidget> - <customwidget> - <class>ConfigureGameList</class> - <extends>QWidget</extends> - <header>configuration/configure_gamelist.h</header> - <container>1</container> - </customwidget> + <customwidget> + <class>ConfigureGameList</class> + <extends>QWidget</extends> + <header>configuration/configure_gamelist.h</header> + <container>1</container> + </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> @@ -102,9 +102,9 @@ <container>1</container> </customwidget> <customwidget> - <class>ConfigureInput</class> + <class>ConfigureInputSimple</class> <extends>QWidget</extends> - <header>configuration/configure_input.h</header> + <header>configuration/configure_input_simple.h</header> <container>1</container> </customwidget> <customwidget> @@ -113,12 +113,12 @@ <header>configuration/configure_graphics.h</header> <container>1</container> </customwidget> - <customwidget> - <class>ConfigureWeb</class> - <extends>QWidget</extends> - <header>configuration/configure_web.h</header> - <container>1</container> - </customwidget> + <customwidget> + <class>ConfigureWeb</class> + <extends>QWidget</extends> + <header>configuration/configure_web.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 830d26115..f39d57998 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,33 @@ #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_mouse_advanced.h" +void OnDockedModeChanged(bool last_state, bool new_state) { + if (last_state == new_state) { + return; + } + + Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return; + } + Service::SM::ServiceManager& sm = system.ServiceManager(); + + // Message queue is shared between these services, we just need to signal an operation + // change to one and it will handle both automatically + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->OperationModeChanged(); + has_signalled = true; + } + + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->OperationModeChanged(); + } +} + namespace { template <typename Dialog, typename... Args> void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { @@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { } // Anonymous namespace ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) { ui->setupUi(this); players_controller = { @@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { - if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) { - ui->handheld_connected->setChecked(false); - } - - if (last_state == new_state) { - return; - } - - Core::System& system{Core::System::GetInstance()}; - if (!system.IsPoweredOn()) { - return; - } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - // Message queue is shared between these services, we just need to signal an operation - // change to one and it will handle both automatically - auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); - auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); - bool has_signalled = false; - - if (applet_oe != nullptr) { - applet_oe->GetMessageQueue()->OperationModeChanged(); - has_signalled = true; - } - - if (applet_ae != nullptr && !has_signalled) { - applet_ae->GetMessageQueue()->OperationModeChanged(); - } -} - void ConfigureInput::applyConfiguration() { for (std::size_t i = 0; i < players_controller.size(); ++i) { const auto controller_type_index = players_controller[i]->currentIndex(); diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 1649e4c0b..b8e62cc2b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,8 +7,8 @@ #include <array> #include <memory> +#include <QDialog> #include <QKeyEvent> -#include <QWidget> #include "ui_configure_input.h" @@ -20,7 +20,9 @@ namespace Ui { class ConfigureInput; } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog { Q_OBJECT public: @@ -33,8 +35,6 @@ public: private: void updateUIEnabled(); - void OnDockedModeChanged(bool last_state, bool new_state); - /// Load configuration settings. void loadConfiguration(); /// Restore all buttons to their default values. diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index dae8277bc..0a2d9f024 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ConfigureInput</class> - <widget class="QWidget" name="ConfigureInput"> + <widget class="QDialog" name="ConfigureInput"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>473</width> - <height>685</height> + <width>384</width> + <height>576</height> </rect> </property> <property name="windowTitle"> @@ -478,6 +478,13 @@ </property> </spacer> </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> </layout> </item> </layout> @@ -485,5 +492,38 @@ </layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInput</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>553</y> + </hint> + <hint type="destinationlabel"> + <x>191</x> + <y>287</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureInput</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>553</y> + </hint> + <hint type="destinationlabel"> + <x>191</x> + <y>287</y> + </hint> + </hints> + </connection> + </connections> </ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7dadd83c1..ba2b32c4f 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -6,6 +6,7 @@ #include <memory> #include <utility> #include <QColorDialog> +#include <QGridLayout> #include <QMenu> #include <QMessageBox> #include <QTimer> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp new file mode 100644 index 000000000..07d71e9d1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -0,0 +1,137 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <tuple> + +#include "ui_configure_input_simple.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_simple.h" +#include "yuzu/ui_settings.h" + +namespace { + +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { + caller->applyConfiguration(); + Dialog dialog(caller, std::forward<Args>(args)...); + + const auto res = dialog.exec(); + if (res == QDialog::Accepted) { + dialog.applyConfiguration(); + } +} + +// OnProfileSelect functions should (when applicable): +// - Set controller types +// - Set controller enabled +// - Set docked mode +// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) +// +// OnProfileSelect function should NOT however: +// - Reset any button mappings +// - Open any dialogs +// - Block in any way + +constexpr std::size_t HANDHELD_INDEX = 8; + +void HandheldOnProfileSelect() { + Settings::values.players[HANDHELD_INDEX].connected = true; + Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; + + for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { + Settings::values.players[player].connected = false; + } + + Settings::values.use_docked_mode = false; + Settings::values.keyboard_enabled = false; + Settings::values.mouse_enabled = false; + Settings::values.debug_pad_enabled = false; + Settings::values.touchscreen.enabled = true; +} + +void DualJoyconsDockedOnProfileSelect() { + Settings::values.players[0].connected = true; + Settings::values.players[0].type = Settings::ControllerType::DualJoycon; + + for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { + Settings::values.players[player].connected = false; + } + + Settings::values.use_docked_mode = true; + Settings::values.keyboard_enabled = false; + Settings::values.mouse_enabled = false; + Settings::values.debug_pad_enabled = false; + Settings::values.touchscreen.enabled = false; +} + +// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure +// is clicked) +using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>; + +constexpr std::array<InputProfile, 3> INPUT_PROFILES{{ + {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, + [](ConfigureInputSimple* caller) { + CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); + }}, + {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect, + [](ConfigureInputSimple* caller) { + CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false); + }}, + {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, +}}; + +} // namespace + +void ApplyInputProfileConfiguration(int profile_index) { + std::get<1>( + INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); +} + +ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { + ui->setupUi(this); + + for (const auto& profile : INPUT_PROFILES) { + const QString label = tr(std::get<0>(profile)); + ui->profile_combobox->addItem(label, label); + } + + connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureInputSimple::OnSelectProfile); + connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); + + this->loadConfiguration(); +} + +ConfigureInputSimple::~ConfigureInputSimple() = default; + +void ConfigureInputSimple::applyConfiguration() { + auto index = ui->profile_combobox->currentIndex(); + // Make the stored index for "Custom" very large so that if new profiles are added it + // doesn't change. + if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) + index = std::numeric_limits<int>::max(); + + UISettings::values.profile_index = index; +} + +void ConfigureInputSimple::loadConfiguration() { + const auto index = UISettings::values.profile_index; + if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) + ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); + else + ui->profile_combobox->setCurrentIndex(index); +} + +void ConfigureInputSimple::OnSelectProfile(int index) { + const auto old_docked = Settings::values.use_docked_mode; + ApplyInputProfileConfiguration(index); + OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); +} + +void ConfigureInputSimple::OnConfigure() { + std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); +} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h new file mode 100644 index 000000000..5b6b69994 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.h @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QWidget> + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputSimple; +} + +// Used by configuration loader to apply a profile if the input is invalid. +void ApplyInputProfileConfiguration(int profile_index); + +class ConfigureInputSimple : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInputSimple(QWidget* parent = nullptr); + ~ConfigureInputSimple() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + /// Load configuration settings. + void loadConfiguration(); + + void OnSelectProfile(int index); + void OnConfigure(); + + std::unique_ptr<Ui::ConfigureInputSimple> ui; +}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui new file mode 100644 index 000000000..c4889caa9 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputSimple</class> + <widget class="QWidget" name="ConfigureInputSimple"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>473</width> + <height>685</height> + </rect> + </property> + <property name="windowTitle"> + <string>ConfigureInputSimple</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Profile</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="2"> + <widget class="QPushButton" name="profile_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="profile_combobox"> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Choose a controller configuration:</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp index 80109b434..dffaba5ed 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -47,8 +47,8 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) tree_view->setContextMenuPolicy(Qt::NoContextMenu); item_model->insertColumns(0, 2); - item_model->setHeaderData(0, Qt::Horizontal, "Patch Name"); - item_model->setHeaderData(1, Qt::Horizontal, "Version"); + item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name")); + item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. @@ -108,9 +108,9 @@ void ConfigurePerGameGeneral::loadConfiguration() { if (loader->ReadTitle(title) == Loader::ResultStatus::Success) ui->display_name->setText(QString::fromStdString(title)); - std::string developer; - if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) - ui->display_developer->setText(QString::fromStdString(developer)); + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); ui->display_version->setText(QStringLiteral("1.0.0")); } diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 707747422..209798521 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -30,6 +30,7 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat( return Tegra::Texture::TextureFormat::A2B10G10R10; default: UNIMPLEMENTED_MSG("Unimplemented RT format"); + return Tegra::Texture::TextureFormat::A8R8G8B8; } } diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 6b3a757e0..1adf6e330 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -221,6 +221,9 @@ QString WaitTreeThread::GetText() const { case Kernel::ThreadStatus::Ready: status = tr("ready"); break; + case Kernel::ThreadStatus::Paused: + status = tr("paused"); + break; case Kernel::ThreadStatus::WaitHLEEvent: status = tr("waiting for HLE return"); break; @@ -262,6 +265,8 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkGreen); case Kernel::ThreadStatus::Ready: return QColor(Qt::GlobalColor::darkBlue); + case Kernel::ThreadStatus::Paused: + return QColor(Qt::GlobalColor::lightGray); case Kernel::ThreadStatus::WaitHLEEvent: case Kernel::ThreadStatus::WaitIPC: return QColor(Qt::GlobalColor::darkRed); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 90b212ba5..01a0f94ab 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,6 +8,7 @@ #include <thread> // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/profile_select.h" #include "applets/software_keyboard.h" #include "configuration/configure_per_general.h" #include "core/file_sys/vfs.h" @@ -208,6 +209,28 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::ProfileSelectorSelectProfile() { + QtProfileSelectionDialog dialog(this); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + if (!dialog.GetStatus()) { + emit ProfileSelectorFinishedSelection(std::nullopt); + return; + } + + Service::Account::ProfileManager manager; + const auto uuid = manager.GetUser(dialog.GetIndex()); + if (!uuid.has_value()) { + emit ProfileSelectorFinishedSelection(std::nullopt); + return; + } + + emit ProfileSelectorFinishedSelection(uuid); +} + void GMainWindow::SoftwareKeyboardGetText( const Core::Frontend::SoftwareKeyboardParameters& parameters) { QtSoftwareKeyboardDialog dialog(this, parameters); @@ -335,6 +358,9 @@ void GMainWindow::InitializeHotkeys() { Qt::ApplicationShortcut); hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), Qt::ApplicationShortcut); + hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot", + QKeySequence(QKeySequence::Print)); + hotkey_registry.LoadHotkeys(); connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, @@ -394,6 +420,12 @@ void GMainWindow::InitializeHotkeys() { OnLoadAmiibo(); } }); + connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this), + &QShortcut::activated, this, [&] { + if (emu_thread->IsRunning()) { + OnCaptureScreenshot(); + } + }); } void GMainWindow::SetDefaultUIGeometry() { @@ -491,6 +523,10 @@ void GMainWindow::ConnectMenuEvents() { hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key()); connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); + // Movie + connect(ui.action_Capture_Screenshot, &QAction::triggered, this, + &GMainWindow::OnCaptureScreenshot); + // Help connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder); connect(ui.action_Rederive, &QAction::triggered, this, @@ -574,6 +610,7 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetGPUDebugContext(debug_context); + system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this)); system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -727,6 +764,7 @@ void GMainWindow::ShutdownGame() { ui.action_Restart->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); ui.action_Load_Amiibo->setEnabled(false); + ui.action_Capture_Screenshot->setEnabled(false); render_window->hide(); game_list->show(); game_list->setFilterFocus(); @@ -1290,6 +1328,7 @@ void GMainWindow::OnStartGame() { discord_rpc->Update(); ui.action_Load_Amiibo->setEnabled(true); + ui.action_Capture_Screenshot->setEnabled(true); } void GMainWindow::OnPauseGame() { @@ -1298,6 +1337,7 @@ void GMainWindow::OnPauseGame() { ui.action_Start->setEnabled(true); ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(true); + ui.action_Capture_Screenshot->setEnabled(false); } void GMainWindow::OnStopGame() { @@ -1460,6 +1500,18 @@ void GMainWindow::OnToggleFilterBar() { } } +void GMainWindow::OnCaptureScreenshot() { + OnPauseGame(); + const QString path = + QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), + UISettings::values.screenshot_path, tr("PNG Image (*.png)")); + if (!path.isEmpty()) { + UISettings::values.screenshot_path = QFileInfo(path).path(); + render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path); + } + OnStartGame(); +} + void GMainWindow::UpdateStatusBar() { if (emu_thread == nullptr) { status_bar_update_timer.stop(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index ca9c50367..4e37f6a2d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -99,10 +99,12 @@ signals: // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); + void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid); void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); public slots: + void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); @@ -187,6 +189,7 @@ private slots: void ShowFullscreen(); void HideFullscreen(); void ToggleWindowMode(); + void OnCaptureScreenshot(); void OnCoreError(Core::System::ResultStatus, std::string); void OnReinitializeKeys(ReinitializeKeyBehavior behavior); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 75e96387f..ffcabb495 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -101,11 +101,13 @@ <addaction name="action_Show_Status_Bar"/> <addaction name="menu_View_Debugging"/> </widget> - <widget class ="QMenu" name="menu_Tools"> + <widget class="QMenu" name="menu_Tools"> <property name="title"> <string>Tools</string> </property> - <addaction name="action_Rederive" /> + <addaction name="action_Rederive"/> + <addaction name="separator"/> + <addaction name="action_Capture_Screenshot"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -118,7 +120,7 @@ <addaction name="menu_File"/> <addaction name="menu_Emulation"/> <addaction name="menu_View"/> - <addaction name="menu_Tools" /> + <addaction name="menu_Tools"/> <addaction name="menu_Help"/> </widget> <action name="action_Install_File_NAND"> @@ -173,11 +175,11 @@ <string>&Stop</string> </property> </action> - <action name="action_Rederive"> - <property name="text"> - <string>Reinitialize keys...</string> - </property> - </action> + <action name="action_Rederive"> + <property name="text"> + <string>Reinitialize keys...</string> + </property> + </action> <action name="action_About"> <property name="text"> <string>About yuzu</string> @@ -252,39 +254,47 @@ <string>Fullscreen</string> </property> </action> - <action name="action_Restart"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Restart</string> - </property> - </action> + <action name="action_Restart"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Restart</string> + </property> + </action> <action name="action_Load_Amiibo"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Load Amiibo...</string> - </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Load Amiibo...</string> + </property> </action> - <action name="action_Report_Compatibility"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Report Compatibility</string> - </property> - <property name="visible"> - <bool>false</bool> - </property> - </action> - <action name="action_Open_yuzu_Folder"> - <property name="text"> - <string>Open yuzu Folder</string> - </property> - </action> - </widget> + <action name="action_Report_Compatibility"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Report Compatibility</string> + </property> + <property name="visible"> + <bool>false</bool> + </property> + </action> + <action name="action_Open_yuzu_Folder"> + <property name="text"> + <string>Open yuzu Folder</string> + </property> + </action> + <action name="action_Capture_Screenshot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Capture Screenshot</string> + </property> + </action> + </widget> <resources/> <connections/> </ui> diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index e80aebc0a..58ba240fd 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -10,6 +10,7 @@ #include <QByteArray> #include <QString> #include <QStringList> +#include "common/common_types.h" namespace UISettings { @@ -42,8 +43,11 @@ struct Values { // Discord RPC bool enable_discord_presence; + u16 screenshot_resolution_factor; + QString roms_path; QString symbols_path; + QString screenshot_path; QString gamedir; bool gamedir_deepscan; QStringList recent_files; @@ -58,6 +62,9 @@ struct Values { // logging bool show_console; + // Controllers + int profile_index; + // Game List bool show_unknown; bool show_add_ons; |