summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/quaternion.h2
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h5
-rw-r--r--src/core/file_sys/control_metadata.cpp28
-rw-r--r--src/core/file_sys/control_metadata.h30
-rw-r--r--src/core/file_sys/savedata_factory.cpp30
-rw-r--r--src/core/file_sys/savedata_factory.h8
-rw-r--r--src/core/file_sys/vfs.h4
-rw-r--r--src/core/frontend/applets/profile_select.cpp19
-rw-r--r--src/core/frontend/applets/profile_select.h27
-rw-r--r--src/core/frontend/framebuffer_layout.cpp15
-rw-r--r--src/core/frontend/framebuffer_layout.h7
-rw-r--r--src/core/hle/kernel/errors.h2
-rw-r--r--src/core/hle/kernel/svc.cpp38
-rw-r--r--src/core/hle/kernel/thread.cpp24
-rw-r--r--src/core/hle/kernel/thread.h14
-rw-r--r--src/core/hle/kernel/vm_manager.cpp1
-rw-r--r--src/core/hle/service/am/am.cpp51
-rw-r--r--src/core/hle/service/am/am.h2
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp77
-rw-r--r--src/core/hle/service/am/applets/profile_select.h50
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp47
-rw-r--r--src/core/hle/service/filesystem/filesystem.h6
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp46
-rw-r--r--src/core/hle/service/hid/hid.cpp207
-rw-r--r--src/core/loader/loader.h10
-rw-r--r--src/core/loader/nsp.cpp4
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp5
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/video_core/engines/shader_bytecode.h2
-rw-r--r--src/video_core/renderer_base.cpp12
-rw-r--r--src/video_core/renderer_base.h27
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp41
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h9
-rw-r--r--src/video_core/video_core.cpp8
-rw-r--r--src/video_core/video_core.h2
-rw-r--r--src/yuzu/CMakeLists.txt5
-rw-r--r--src/yuzu/applets/profile_select.cpp168
-rw-r--r--src/yuzu/applets/profile_select.h73
-rw-r--r--src/yuzu/bootmanager.cpp18
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure.ui52
-rw-r--r--src/yuzu/configuration/configure_input.cpp60
-rw-r--r--src/yuzu/configuration/configure_input.h8
-rw-r--r--src/yuzu/configuration/configure_input.ui48
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp1
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp137
-rw-r--r--src/yuzu/configuration/configure_input_simple.h40
-rw-r--r--src/yuzu/configuration/configure_input_simple.ui97
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp10
-rw-r--r--src/yuzu/debugger/wait_tree.cpp5
-rw-r--r--src/yuzu/main.cpp52
-rw-r--r--src/yuzu/main.h3
-rw-r--r--src/yuzu/main.ui88
-rw-r--r--src/yuzu/ui_settings.h7
60 files changed, 1535 insertions, 242 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 d63b7f19b..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);
@@ -132,4 +134,32 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
}
}
+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/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/svc.cpp b/src/core/hle/kernel/svc.cpp
index 28268e112..2e80b48c2 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -932,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;
}
@@ -960,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();
@@ -1245,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/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 d6e7981d3..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>;
@@ -371,6 +377,12 @@ public:
return affinity_mask;
}
+ ThreadActivity GetActivity() const {
+ return activity;
+ }
+
+ void SetActivity(ThreadActivity value);
+
private:
explicit Thread(KernelCore& kernel);
~Thread() override;
@@ -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 27c31aad2..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,
};
@@ -775,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:
@@ -861,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"},
@@ -1039,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/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2ec38c726..268409257 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -306,7 +306,10 @@ private:
std::shared_ptr<IAppletResource> applet_resource;
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
if (applet_resource == nullptr) {
applet_resource = std::make_shared<IAppletResource>();
@@ -318,7 +321,12 @@ private:
}
void ActivateXpad(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto basic_xpad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
+ basic_xpad_id, applet_resource_user_id);
applet_resource->ActivateController(HidController::XPad);
IPC::ResponseBuilder rb{ctx, 2};
@@ -326,7 +334,10 @@ private:
}
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
@@ -334,7 +345,10 @@ private:
}
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
@@ -342,7 +356,10 @@ private:
}
void ActivateMouse(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
@@ -350,7 +367,10 @@ private:
}
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
@@ -358,7 +378,12 @@ private:
}
void ActivateGesture(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
+ applet_resource_user_id);
applet_resource->ActivateController(HidController::Gesture);
IPC::ResponseBuilder rb{ctx, 2};
@@ -367,7 +392,12 @@ private:
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
// Should have no effect with how our npad sets up the data
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
+ applet_resource_user_id);
applet_resource->ActivateController(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
@@ -376,22 +406,37 @@ private:
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto handle = rp.PopRaw<u32>();
- LOG_WARNING(Service_HID, "(STUBBED) called with handle={}", handle);
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto drift_mode{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}",
+ handle, drift_mode, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -401,8 +446,9 @@ private:
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto supported_styleset = rp.PopRaw<u32>();
- LOG_DEBUG(Service_HID, "called with supported_styleset={}", supported_styleset);
+ const auto supported_styleset{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedStyleSet({supported_styleset});
@@ -412,7 +458,10 @@ private:
}
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
@@ -422,7 +471,10 @@ private:
}
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
@@ -431,7 +483,10 @@ private:
}
void ActivateNpad(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -440,8 +495,12 @@ private:
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto npad_id = rp.PopRaw<u32>();
- LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id);
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto unknown{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
+ npad_id, applet_resource_user_id, unknown);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
@@ -451,8 +510,11 @@ private:
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto npad_id = rp.PopRaw<u32>();
- LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id);
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
+ applet_resource_user_id);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.DisconnectNPad(npad_id);
@@ -462,8 +524,9 @@ private:
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto npad_id = rp.PopRaw<u32>();
- LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id);
+ const auto npad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -474,8 +537,11 @@ private:
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto hold_type = rp.PopRaw<u64>();
- LOG_DEBUG(Service_HID, "called with hold_type={}", hold_type);
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto hold_type{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
+ applet_resource_user_id, hold_type);
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
@@ -485,7 +551,10 @@ private:
}
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
const auto& controller =
applet_resource->GetController<Controller_NPad>(HidController::NPad);
@@ -496,15 +565,21 @@ private:
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto npad_id = rp.PopRaw<u32>();
- LOG_WARNING(Service_HID, "(STUBBED) called with npad_id={}", npad_id);
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ npad_id, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetVibrationEnabled(true);
@@ -523,9 +598,12 @@ private:
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto controller_id = rp.PopRaw<u32>();
- const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>();
- LOG_DEBUG(Service_HID, "called with controller_id={}", controller_id);
+ const auto controller_id{rp.Pop<u32>()};
+ const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}",
+ controller_id, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -535,7 +613,10 @@ private:
}
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
const auto controllers = ctx.ReadBuffer(0);
const auto vibrations = ctx.ReadBuffer(1);
@@ -557,7 +638,12 @@ private:
}
void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::RequestParser rp{ctx};
+ const auto controller_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}",
+ controller_id, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
@@ -568,8 +654,11 @@ private:
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id = rp.PopRaw<u32>();
- LOG_DEBUG(Service_HID, "called with npad_id={}", npad_id);
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
+ applet_resource_user_id);
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
@@ -579,7 +668,14 @@ private:
}
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto unknown_1{rp.Pop<u32>()};
+ const auto unknown_2{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
+ unknown_1, unknown_2, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -587,8 +683,11 @@ private:
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto mode = rp.PopRaw<u32>();
- LOG_WARNING(Service_HID, "(STUBBED) called with mode={}", mode);
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto mode{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
+ applet_resource_user_id, mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -612,35 +711,55 @@ private:
}
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto unknown{rp.Pop<u32>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}",
+ applet_resource_user_id, unknown);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
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/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index eb703bb5a..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.
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_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 3640d2b89..a1cef99ae 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1779,7 +1779,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];
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index f02415139..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: \
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/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/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>&amp;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;