diff options
Diffstat (limited to 'src')
62 files changed, 2356 insertions, 1235 deletions
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp index 2fe0b3aef..726591fce 100644 --- a/src/audio_core/time_stretch.cpp +++ b/src/audio_core/time_stretch.cpp @@ -53,8 +53,8 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out, const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale); m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio); - // Place a lower limit of 5% speed. When a game boots up, there will be - // many silence samples. These do not need to be timestretched. + // Place a lower limit of 5% speed. When a game boots up, there will be + // many silence samples. These do not need to be timestretched. m_stretch_ratio = std::max(m_stretch_ratio, 0.05); m_sound_touch.setTempo(m_stretch_ratio); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8f2db5bea..aa9e05089 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -88,6 +88,8 @@ add_library(core STATIC frontend/applets/profile_select.h frontend/applets/software_keyboard.cpp frontend/applets/software_keyboard.h + frontend/applets/web_browser.cpp + frontend/applets/web_browser.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp @@ -173,6 +175,8 @@ add_library(core STATIC hle/service/am/applets/software_keyboard.h hle/service/am/applets/stub_applet.cpp hle/service/am/applets/stub_applet.h + hle/service/am/applets/web_browser.cpp + hle/service/am/applets/web_browser.h hle/service/am/idle.cpp hle/service/am/idle.h hle/service/am/omm.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index fd10199ec..572814e4b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -30,8 +30,11 @@ #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" +#include "core/settings.h" #include "core/telemetry_session.h" +#include "frontend/applets/profile_select.h" #include "frontend/applets/software_keyboard.h" +#include "frontend/applets/web_browser.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" @@ -94,6 +97,11 @@ struct System::Impl { CoreTiming::Init(); kernel.Initialize(); + const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()); + Settings::values.custom_rtc_differential = + Settings::values.custom_rtc.value_or(current_time) - current_time; + // Create a default fs if one doesn't already exist. if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); @@ -103,6 +111,8 @@ struct System::Impl { profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>(); if (software_keyboard == nullptr) software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); + if (web_browser == nullptr) + web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); auto main_process = Kernel::Process::Create(kernel, "main"); kernel.MakeCurrentProcess(main_process.get()); @@ -199,6 +209,11 @@ struct System::Impl { // Close app loader app_loader.reset(); + // Clear all applets + profile_selector.reset(); + software_keyboard.reset(); + web_browser.reset(); + LOG_DEBUG(Core, "Shutdown OK"); } @@ -233,6 +248,7 @@ struct System::Impl { /// Frontend applets std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; + std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser; /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; @@ -427,22 +443,34 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } -void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) { +void System::SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet) { impl->profile_selector = std::move(applet); } -const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const { +const Frontend::ProfileSelectApplet& System::GetProfileSelector() const { return *impl->profile_selector; } -void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) { +void System::SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet) { impl->software_keyboard = std::move(applet); } -const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { +const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { return *impl->software_keyboard; } +void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) { + impl->web_browser = std::move(applet); +} + +Frontend::WebBrowserApplet& System::GetWebBrowser() { + return *impl->web_browser; +} + +const Frontend::WebBrowserApplet& System::GetWebBrowser() const { + return *impl->web_browser; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(*this, emu_window); } diff --git a/src/core/core.h b/src/core/core.h index 869921493..511a5ad3a 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -11,11 +11,12 @@ #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; +class ProfileSelectApplet; class SoftwareKeyboardApplet; +class WebBrowserApplet; } // namespace Core::Frontend namespace FileSys { @@ -242,13 +243,18 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; - void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet); + void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); - const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const; + const Frontend::ProfileSelectApplet& GetProfileSelector() const; - void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet); + void SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet); - const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; + const Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; + + void SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet); + + Frontend::WebBrowserApplet& GetWebBrowser(); + const Frontend::WebBrowserApplet& GetWebBrowser() const; private: System(); diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 19b6f8600..5aa3b600b 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -359,6 +359,8 @@ bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTable dirs.push_back(std::move(npfs)); if (IsDirectoryExeFS(dirs.back())) exefs = dirs.back(); + else if (IsDirectoryLogoPartition(dirs.back())) + logo = dirs.back(); } else { if (has_rights_id) status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; @@ -546,4 +548,8 @@ u64 NCA::GetBaseIVFCOffset() const { return ivfc_offset; } +VirtualDir NCA::GetLogoPartition() const { + return logo; +} + } // namespace FileSys diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 99294cbb4..5d4d05c82 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -74,6 +74,13 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } +inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { + // NintendoLogo is the static image in the top left corner while StartupMovie is the animation + // in the bottom right corner. + return pfs->GetFile("NintendoLogo.png") != nullptr && + pfs->GetFile("StartupMovie.gif") != nullptr; +} + // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { @@ -102,6 +109,8 @@ public: // Returns the base ivfc offset used in BKTR patching. u64 GetBaseIVFCOffset() const; + VirtualDir GetLogoPartition() const; + private: bool CheckSupportedNCA(const NCAHeader& header); bool HandlePotentialHeaderDecryption(); @@ -122,6 +131,7 @@ private: VirtualFile romfs = nullptr; VirtualDir exefs = nullptr; + VirtualDir logo = nullptr; VirtualFile file; VirtualFile bktr_base_romfs; u64 ivfc_offset = 0; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 6690aa575..7b5c509fb 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -39,27 +39,4 @@ static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x31 static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry."); static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry."); -class DirectoryBackend : NonCopyable { -public: - DirectoryBackend() {} - virtual ~DirectoryBackend() {} - - /** - * List files contained in the directory - * @param count Number of entries to return at once in entries - * @param entries Buffer to read data into - * @return Number of entries listed - */ - virtual u64 Read(const u64 count, Entry* entries) = 0; - - /// Returns the number of entries still left to read. - virtual u64 GetEntryCount() const = 0; - - /** - * Close the directory - * @return true if the directory closed correctly - */ - virtual bool Close() const = 0; -}; - } // namespace FileSys diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index 81e1f66ac..ebbdf081e 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { VirtualDir out = std::move(root); + if (type == RomFSExtractionType::SingleDiscard) + return out->GetSubdirectories().front(); + while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) { if (out->GetSubdirectories().front()->GetName() == "data" && type == RomFSExtractionType::Truncated) diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index 0ec404731..0f35639bc 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -33,8 +33,9 @@ struct IVFCHeader { static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); enum class RomFSExtractionType { - Full, // Includes data directory - Truncated, // Traverses into data directory + Full, // Includes data directory + Truncated, // Traverses into data directory + SingleDiscard, // Traverses into the first subdirectory of root }; // Converts a RomFS binary blob to VFS Filesystem diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp new file mode 100644 index 000000000..3a3d3d0bf --- /dev/null +++ b/src/core/frontend/applets/web_browser.cpp @@ -0,0 +1,24 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/frontend/applets/web_browser.h" + +namespace Core::Frontend { + +WebBrowserApplet::~WebBrowserApplet() = default; + +DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; + +void DefaultWebBrowserApplet::OpenPage(std::string_view filename, + std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) { + LOG_INFO(Service_AM, + "(STUBBED) called - No suitable web browser implementation found to open website page " + "at '{}'!", + filename); + finished_callback(); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h new file mode 100644 index 000000000..f952856af --- /dev/null +++ b/src/core/frontend/applets/web_browser.h @@ -0,0 +1,28 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <string_view> + +namespace Core::Frontend { + +class WebBrowserApplet { +public: + virtual ~WebBrowserApplet(); + + virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) = 0; +}; + +class DefaultWebBrowserApplet final : public WebBrowserApplet { +public: + ~DefaultWebBrowserApplet() override; + + void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) override; +}; + +} // namespace Core::Frontend diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 7a5e9d216..d1cbe0e44 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -23,6 +23,7 @@ #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/applets/web_browser.h" #include "core/hle/service/am/idle.h" #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" @@ -44,6 +45,7 @@ constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; enum class AppletId : u32 { ProfileSelect = 0x10, SoftwareKeyboard = 0x11, + LibAppletOff = 0x17, }; constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; @@ -730,10 +732,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 offset{rp.Pop<u64>()}; - LOG_DEBUG(Service_AM, "called, offset={}", offset); - const std::vector<u8> data{ctx.ReadBuffer()}; + LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); + if (data.size() > backing.buffer.size() - offset) { LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", @@ -753,10 +755,10 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 offset{rp.Pop<u64>()}; - LOG_DEBUG(Service_AM, "called, offset={}", offset); - const std::size_t size{ctx.GetWriteBufferSize()}; + LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); + if (size > backing.buffer.size() - offset) { LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", backing.buffer.size(), size, offset); @@ -791,6 +793,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { return std::make_shared<Applets::ProfileSelect>(); case AppletId::SoftwareKeyboard: return std::make_shared<Applets::SoftwareKeyboard>(); + case AppletId::LibAppletOff: + return std::make_shared<Applets::WebBrowser>(); default: LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!", static_cast<u32>(id)); diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 4c7b45454..14e2a1fee 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -7,7 +7,7 @@ #include "common/assert.h" #include "common/string_util.h" #include "core/core.h" -#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/profile_select.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/profile_select.h" diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp new file mode 100644 index 000000000..9b0aa7f5f --- /dev/null +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -0,0 +1,190 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstring> +#include <vector> + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/mode.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/vfs_types.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/am/applets/web_browser.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" + +namespace Service::AM::Applets { + +// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not +// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant, +// but some may be worth an implementation. +constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6; + +struct WebBufferHeader { + u16 count; + INSERT_PADDING_BYTES(6); +}; +static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size."); + +struct WebArgumentHeader { + u16 type; + u16 size; + u32 offset; +}; +static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size."); + +struct WebArgumentResult { + u32 result_code; + std::array<char, 0x1000> last_url; + u64 last_url_size; +}; +static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size."); + +static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) { + WebBufferHeader header; + ASSERT(sizeof(WebBufferHeader) <= data.size()); + std::memcpy(&header, data.data(), sizeof(WebBufferHeader)); + + u64 offset = sizeof(WebBufferHeader); + for (u16 i = 0; i < header.count; ++i) { + WebArgumentHeader arg; + ASSERT(offset + sizeof(WebArgumentHeader) <= data.size()); + std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader)); + offset += sizeof(WebArgumentHeader); + + if (arg.type == type) { + std::vector<u8> out(arg.size); + offset += arg.offset; + ASSERT(offset + arg.size <= data.size()); + std::memcpy(out.data(), data.data() + offset, out.size()); + return out; + } + + offset += arg.offset + arg.size; + } + + return {}; +} + +static FileSys::VirtualFile GetManualRomFS() { + auto& loader{Core::System::GetInstance().GetAppLoader()}; + + FileSys::VirtualFile out; + if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success) + return out; + + const auto& installed{FileSystem::GetUnionContents()}; + const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(), + FileSys::ContentRecordType::Manual); + + if (res != nullptr) + return res->GetRomFS(); + return nullptr; +} + +WebBrowser::WebBrowser() = default; + +WebBrowser::~WebBrowser() = default; + +void WebBrowser::Initialize() { + Applet::Initialize(); + + complete = false; + temporary_dir.clear(); + filename.clear(); + status = RESULT_SUCCESS; + + const auto web_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(web_arg_storage != nullptr); + const auto& web_arg = web_arg_storage->GetData(); + + const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE); + filename = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(url_data.data()), url_data.size()); + + temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + + "web_applet_manual", + FileUtil::DirectorySeparator::PlatformDefault); + FileUtil::DeleteDirRecursively(temporary_dir); + + manual_romfs = GetManualRomFS(); + if (manual_romfs == nullptr) { + status = ResultCode(-1); + LOG_ERROR(Service_AM, "Failed to find manual for current process!"); + } + + filename = + FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename, + FileUtil::DirectorySeparator::PlatformDefault); +} + +bool WebBrowser::TransactionComplete() const { + return complete; +} + +ResultCode WebBrowser::GetStatus() const { + return status; +} + +void WebBrowser::ExecuteInteractive() { + UNIMPLEMENTED_MSG("Unexpected interactive data recieved!"); +} + +void WebBrowser::Execute() { + if (complete) + return; + + if (status != RESULT_SUCCESS) { + complete = true; + return; + } + + auto& frontend{Core::System::GetInstance().GetWebBrowser()}; + + frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); +} + +void WebBrowser::UnpackRomFS() { + if (unpacked) + return; + + ASSERT(manual_romfs != nullptr); + const auto dir = + FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard); + const auto& vfs{Core::System::GetInstance().GetFilesystem()}; + const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); + FileSys::VfsRawCopyD(dir, temp_dir); + + unpacked = true; +} + +void WebBrowser::Finalize() { + complete = true; + + WebArgumentResult out{}; + out.result_code = 0; + out.last_url_size = 0; + + std::vector<u8> data(sizeof(WebArgumentResult)); + std::memcpy(data.data(), &out, sizeof(WebArgumentResult)); + + broker.PushNormalDataFromApplet(IStorage{data}); + broker.SignalStateChanged(); + + FileUtil::DeleteDirRecursively(temporary_dir); +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h new file mode 100644 index 000000000..b9e228fac --- /dev/null +++ b/src/core/hle/service/am/applets/web_browser.h @@ -0,0 +1,44 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +class WebBrowser final : public Applet { +public: + WebBrowser(); + ~WebBrowser() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + // Callback to be fired when the frontend needs the manual RomFS unpacked to temporary + // directory. This is a blocking call and may take a while as some manuals can be up to 100MB in + // size. Attempting to access files at filename before invocation is likely to not work. + void UnpackRomFS(); + + // Callback to be fired when the frontend is finished browsing. This will delete the temporary + // manual RomFS extracted files, so ensure this is only called at actual finalization. + void Finalize(); + +private: + bool complete = false; + bool unpacked = false; + ResultCode status = RESULT_SUCCESS; + + FileSys::VirtualFile manual_romfs; + std::string temporary_dir; + std::string filename; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 75fdb861a..04c8c35a8 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -410,6 +410,8 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; libnx_entry.pad.l_stick = pad_state.l_stick; libnx_entry.pad.r_stick = pad_state.r_stick; + + press_state |= static_cast<u32>(pad_state.pad_states.raw); } std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), shared_memory_entries.size() * sizeof(NPadEntry)); @@ -636,6 +638,10 @@ void Controller_NPad::ClearAllControllers() { }); } +u32 Controller_NPad::GetAndResetPressState() { + return std::exchange(press_state, 0); +} + bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { const bool support_handheld = std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) != diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 29851f16a..106cf58c8 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -124,6 +124,10 @@ public: void ConnectAllDisconnectedControllers(); void ClearAllControllers(); + // Logical OR for all buttons presses on all controllers + // Specifically for cheat engine and other features. + u32 GetAndResetPressState(); + static std::size_t NPadIdToIndex(u32 npad_id); static u32 IndexToNPad(std::size_t index); @@ -292,6 +296,8 @@ private: bool is_connected; }; + u32 press_state{}; + NPadType style{}; std::array<NPadEntry, 10> shared_memory_entries{}; std::array< diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 268409257..008bf3f02 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -40,119 +40,82 @@ constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 66; constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; -enum class HidController : std::size_t { - DebugPad, - Touchscreen, - Mouse, - Keyboard, - XPad, - Unknown1, - Unknown2, - Unknown3, - SixAxisSensor, - NPad, - Gesture, - - MaxControllers, -}; - -class IAppletResource final : public ServiceFramework<IAppletResource> { -public: - IAppletResource() : ServiceFramework("IAppletResource") { - static const FunctionInfo functions[] = { - {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, - }; - RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); - shared_mem = Kernel::SharedMemory::Create( - kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, - Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); - - MakeController<Controller_DebugPad>(HidController::DebugPad); - MakeController<Controller_Touchscreen>(HidController::Touchscreen); - MakeController<Controller_Mouse>(HidController::Mouse); - MakeController<Controller_Keyboard>(HidController::Keyboard); - MakeController<Controller_XPad>(HidController::XPad); - MakeController<Controller_Stubbed>(HidController::Unknown1); - MakeController<Controller_Stubbed>(HidController::Unknown2); - MakeController<Controller_Stubbed>(HidController::Unknown3); - MakeController<Controller_Stubbed>(HidController::SixAxisSensor); - MakeController<Controller_NPad>(HidController::NPad); - MakeController<Controller_Gesture>(HidController::Gesture); - - // Homebrew doesn't try to activate some controllers, so we activate them by default - GetController<Controller_NPad>(HidController::NPad).ActivateController(); - GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController(); - - GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00); - GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00); - GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); - - // Register update callbacks - pad_update_event = CoreTiming::RegisterEvent( - "HID::UpdatePadCallback", - [this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); }); - - // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) - - CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); - - ReloadInputDevices(); - } - - void ActivateController(HidController controller) { - controllers[static_cast<size_t>(controller)]->ActivateController(); - } - - void DeactivateController(HidController controller) { - controllers[static_cast<size_t>(controller)]->DeactivateController(); - } +IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") { + static const FunctionInfo functions[] = { + {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, + }; + RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + shared_mem = Kernel::SharedMemory::Create( + kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); + + MakeController<Controller_DebugPad>(HidController::DebugPad); + MakeController<Controller_Touchscreen>(HidController::Touchscreen); + MakeController<Controller_Mouse>(HidController::Mouse); + MakeController<Controller_Keyboard>(HidController::Keyboard); + MakeController<Controller_XPad>(HidController::XPad); + MakeController<Controller_Stubbed>(HidController::Unknown1); + MakeController<Controller_Stubbed>(HidController::Unknown2); + MakeController<Controller_Stubbed>(HidController::Unknown3); + MakeController<Controller_Stubbed>(HidController::SixAxisSensor); + MakeController<Controller_NPad>(HidController::NPad); + MakeController<Controller_Gesture>(HidController::Gesture); + + // Homebrew doesn't try to activate some controllers, so we activate them by default + GetController<Controller_NPad>(HidController::NPad).ActivateController(); + GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController(); + + GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00); + GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00); + GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); + + // Register update callbacks + pad_update_event = + CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) { + UpdateControllers(userdata, cycles_late); + }); + + // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) + + CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); + + ReloadInputDevices(); +} - template <typename T> - void MakeController(HidController controller) { - controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(); - } +void IAppletResource::ActivateController(HidController controller) { + controllers[static_cast<size_t>(controller)]->ActivateController(); +} - template <typename T> - T& GetController(HidController controller) { - return static_cast<T&>(*controllers[static_cast<size_t>(controller)]); - } +void IAppletResource::DeactivateController(HidController controller) { + controllers[static_cast<size_t>(controller)]->DeactivateController(); +} - ~IAppletResource() { - CoreTiming::UnscheduleEvent(pad_update_event, 0); - } +IAppletResource ::~IAppletResource() { + CoreTiming::UnscheduleEvent(pad_update_event, 0); +} -private: - void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); +void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(shared_mem); - } + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(shared_mem); +} - void UpdateControllers(u64 userdata, int cycles_late) { - const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); - for (const auto& controller : controllers) { - if (should_reload) { - controller->OnLoadInputDevices(); - } - controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE); +void IAppletResource::UpdateControllers(u64 userdata, int cycles_late) { + const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); + for (const auto& controller : controllers) { + if (should_reload) { + controller->OnLoadInputDevices(); } - - CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); + controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE); } - // Handle to shared memory region designated to HID service - Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; - - // CoreTiming update events - CoreTiming::EventType* pad_update_event; - - std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> - controllers{}; -}; + CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); +} class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { public: @@ -172,599 +135,597 @@ private: } }; -class Hid final : public ServiceFramework<Hid> { -public: - Hid() : ServiceFramework("hid") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &Hid::CreateAppletResource, "CreateAppletResource"}, - {1, &Hid::ActivateDebugPad, "ActivateDebugPad"}, - {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"}, - {21, &Hid::ActivateMouse, "ActivateMouse"}, - {31, &Hid::ActivateKeyboard, "ActivateKeyboard"}, - {32, nullptr, "SendKeyboardLockKeyEvent"}, - {40, nullptr, "AcquireXpadIdEventHandle"}, - {41, nullptr, "ReleaseXpadIdEventHandle"}, - {51, &Hid::ActivateXpad, "ActivateXpad"}, - {55, nullptr, "GetXpadIds"}, - {56, nullptr, "ActivateJoyXpad"}, - {58, nullptr, "GetJoyXpadLifoHandle"}, - {59, nullptr, "GetJoyXpadIds"}, - {60, nullptr, "ActivateSixAxisSensor"}, - {61, nullptr, "DeactivateSixAxisSensor"}, - {62, nullptr, "GetSixAxisSensorLifoHandle"}, - {63, nullptr, "ActivateJoySixAxisSensor"}, - {64, nullptr, "DeactivateJoySixAxisSensor"}, - {65, nullptr, "GetJoySixAxisSensorLifoHandle"}, - {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, - {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, - {68, nullptr, "IsSixAxisSensorFusionEnabled"}, - {69, nullptr, "EnableSixAxisSensorFusion"}, - {70, nullptr, "SetSixAxisSensorFusionParameters"}, - {71, nullptr, "GetSixAxisSensorFusionParameters"}, - {72, nullptr, "ResetSixAxisSensorFusionParameters"}, - {73, nullptr, "SetAccelerometerParameters"}, - {74, nullptr, "GetAccelerometerParameters"}, - {75, nullptr, "ResetAccelerometerParameters"}, - {76, nullptr, "SetAccelerometerPlayMode"}, - {77, nullptr, "GetAccelerometerPlayMode"}, - {78, nullptr, "ResetAccelerometerPlayMode"}, - {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"}, - {80, nullptr, "GetGyroscopeZeroDriftMode"}, - {81, nullptr, "ResetGyroscopeZeroDriftMode"}, - {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, - {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"}, - {91, &Hid::ActivateGesture, "ActivateGesture"}, - {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, - {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, - {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, - {103, &Hid::ActivateNpad, "ActivateNpad"}, - {104, nullptr, "DeactivateNpad"}, - {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, - {107, &Hid::DisconnectNpad, "DisconnectNpad"}, - {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, - {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"}, - {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"}, - {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, - {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"}, - {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"}, - {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, - {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"}, - {126, nullptr, "StartLrAssignmentMode"}, - {127, nullptr, "StopLrAssignmentMode"}, - {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, - {129, nullptr, "GetNpadHandheldActivationMode"}, - {130, nullptr, "SwapNpadAssignment"}, - {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, - {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, - {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, - {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"}, - {201, &Hid::SendVibrationValue, "SendVibrationValue"}, - {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, - {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, - {204, nullptr, "PermitVibration"}, - {205, nullptr, "IsVibrationPermitted"}, - {206, &Hid::SendVibrationValues, "SendVibrationValues"}, - {207, nullptr, "SendVibrationGcErmCommand"}, - {208, nullptr, "GetActualVibrationGcErmCommand"}, - {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, - {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, - {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, - {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, - {302, nullptr, "StopConsoleSixAxisSensor"}, - {303, nullptr, "ActivateSevenSixAxisSensor"}, - {304, nullptr, "StartSevenSixAxisSensor"}, - {305, nullptr, "StopSevenSixAxisSensor"}, - {306, nullptr, "InitializeSevenSixAxisSensor"}, - {307, nullptr, "FinalizeSevenSixAxisSensor"}, - {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, - {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, - {310, nullptr, "ResetSevenSixAxisSensorTimestamp"}, - {400, nullptr, "IsUsbFullKeyControllerEnabled"}, - {401, nullptr, "EnableUsbFullKeyController"}, - {402, nullptr, "IsUsbFullKeyControllerConnected"}, - {403, nullptr, "HasBattery"}, - {404, nullptr, "HasLeftRightBattery"}, - {405, nullptr, "GetNpadInterfaceType"}, - {406, nullptr, "GetNpadLeftRightInterfaceType"}, - {500, nullptr, "GetPalmaConnectionHandle"}, - {501, nullptr, "InitializePalma"}, - {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, - {503, nullptr, "GetPalmaOperationInfo"}, - {504, nullptr, "PlayPalmaActivity"}, - {505, nullptr, "SetPalmaFrModeType"}, - {506, nullptr, "ReadPalmaStep"}, - {507, nullptr, "EnablePalmaStep"}, - {508, nullptr, "ResetPalmaStep"}, - {509, nullptr, "ReadPalmaApplicationSection"}, - {510, nullptr, "WritePalmaApplicationSection"}, - {511, nullptr, "ReadPalmaUniqueCode"}, - {512, nullptr, "SetPalmaUniqueCodeInvalid"}, - {513, nullptr, "WritePalmaActivityEntry"}, - {514, nullptr, "WritePalmaRgbLedPatternEntry"}, - {515, nullptr, "WritePalmaWaveEntry"}, - {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, - {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, - {518, nullptr, "SuspendPalmaFeature"}, - {519, nullptr, "GetPalmaOperationResult"}, - {520, nullptr, "ReadPalmaPlayLog"}, - {521, nullptr, "ResetPalmaPlayLog"}, - {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, - {523, nullptr, "SetIsPalmaPairedConnectable"}, - {524, nullptr, "PairPalma"}, - {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, - {1000, nullptr, "SetNpadCommunicationMode"}, - {1001, nullptr, "GetNpadCommunicationMode"}, - }; - // clang-format on - - RegisterHandlers(functions); +std::shared_ptr<IAppletResource> Hid::GetAppletResource() { + if (applet_resource == nullptr) { + applet_resource = std::make_shared<IAppletResource>(); } - ~Hid() = default; -private: - std::shared_ptr<IAppletResource> applet_resource; + return applet_resource; +} - void CreateAppletResource(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; +Hid::Hid() : ServiceFramework("hid") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &Hid::CreateAppletResource, "CreateAppletResource"}, + {1, &Hid::ActivateDebugPad, "ActivateDebugPad"}, + {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"}, + {21, &Hid::ActivateMouse, "ActivateMouse"}, + {31, &Hid::ActivateKeyboard, "ActivateKeyboard"}, + {32, nullptr, "SendKeyboardLockKeyEvent"}, + {40, nullptr, "AcquireXpadIdEventHandle"}, + {41, nullptr, "ReleaseXpadIdEventHandle"}, + {51, &Hid::ActivateXpad, "ActivateXpad"}, + {55, nullptr, "GetXpadIds"}, + {56, nullptr, "ActivateJoyXpad"}, + {58, nullptr, "GetJoyXpadLifoHandle"}, + {59, nullptr, "GetJoyXpadIds"}, + {60, nullptr, "ActivateSixAxisSensor"}, + {61, nullptr, "DeactivateSixAxisSensor"}, + {62, nullptr, "GetSixAxisSensorLifoHandle"}, + {63, nullptr, "ActivateJoySixAxisSensor"}, + {64, nullptr, "DeactivateJoySixAxisSensor"}, + {65, nullptr, "GetJoySixAxisSensorLifoHandle"}, + {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, + {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, + {68, nullptr, "IsSixAxisSensorFusionEnabled"}, + {69, nullptr, "EnableSixAxisSensorFusion"}, + {70, nullptr, "SetSixAxisSensorFusionParameters"}, + {71, nullptr, "GetSixAxisSensorFusionParameters"}, + {72, nullptr, "ResetSixAxisSensorFusionParameters"}, + {73, nullptr, "SetAccelerometerParameters"}, + {74, nullptr, "GetAccelerometerParameters"}, + {75, nullptr, "ResetAccelerometerParameters"}, + {76, nullptr, "SetAccelerometerPlayMode"}, + {77, nullptr, "GetAccelerometerPlayMode"}, + {78, nullptr, "ResetAccelerometerPlayMode"}, + {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"}, + {80, nullptr, "GetGyroscopeZeroDriftMode"}, + {81, nullptr, "ResetGyroscopeZeroDriftMode"}, + {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, + {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"}, + {91, &Hid::ActivateGesture, "ActivateGesture"}, + {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, + {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, + {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, + {103, &Hid::ActivateNpad, "ActivateNpad"}, + {104, nullptr, "DeactivateNpad"}, + {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, + {107, &Hid::DisconnectNpad, "DisconnectNpad"}, + {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, + {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"}, + {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"}, + {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, + {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"}, + {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"}, + {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, + {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"}, + {126, nullptr, "StartLrAssignmentMode"}, + {127, nullptr, "StopLrAssignmentMode"}, + {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, + {129, nullptr, "GetNpadHandheldActivationMode"}, + {130, nullptr, "SwapNpadAssignment"}, + {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, + {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, + {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, + {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"}, + {201, &Hid::SendVibrationValue, "SendVibrationValue"}, + {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, + {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, + {204, nullptr, "PermitVibration"}, + {205, nullptr, "IsVibrationPermitted"}, + {206, &Hid::SendVibrationValues, "SendVibrationValues"}, + {207, nullptr, "SendVibrationGcErmCommand"}, + {208, nullptr, "GetActualVibrationGcErmCommand"}, + {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, + {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, + {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, + {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, + {302, nullptr, "StopConsoleSixAxisSensor"}, + {303, nullptr, "ActivateSevenSixAxisSensor"}, + {304, nullptr, "StartSevenSixAxisSensor"}, + {305, nullptr, "StopSevenSixAxisSensor"}, + {306, nullptr, "InitializeSevenSixAxisSensor"}, + {307, nullptr, "FinalizeSevenSixAxisSensor"}, + {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, + {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, + {310, nullptr, "ResetSevenSixAxisSensorTimestamp"}, + {400, nullptr, "IsUsbFullKeyControllerEnabled"}, + {401, nullptr, "EnableUsbFullKeyController"}, + {402, nullptr, "IsUsbFullKeyControllerConnected"}, + {403, nullptr, "HasBattery"}, + {404, nullptr, "HasLeftRightBattery"}, + {405, nullptr, "GetNpadInterfaceType"}, + {406, nullptr, "GetNpadLeftRightInterfaceType"}, + {500, nullptr, "GetPalmaConnectionHandle"}, + {501, nullptr, "InitializePalma"}, + {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, + {503, nullptr, "GetPalmaOperationInfo"}, + {504, nullptr, "PlayPalmaActivity"}, + {505, nullptr, "SetPalmaFrModeType"}, + {506, nullptr, "ReadPalmaStep"}, + {507, nullptr, "EnablePalmaStep"}, + {508, nullptr, "ResetPalmaStep"}, + {509, nullptr, "ReadPalmaApplicationSection"}, + {510, nullptr, "WritePalmaApplicationSection"}, + {511, nullptr, "ReadPalmaUniqueCode"}, + {512, nullptr, "SetPalmaUniqueCodeInvalid"}, + {513, nullptr, "WritePalmaActivityEntry"}, + {514, nullptr, "WritePalmaRgbLedPatternEntry"}, + {515, nullptr, "WritePalmaWaveEntry"}, + {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, + {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, + {518, nullptr, "SuspendPalmaFeature"}, + {519, nullptr, "GetPalmaOperationResult"}, + {520, nullptr, "ReadPalmaPlayLog"}, + {521, nullptr, "ResetPalmaPlayLog"}, + {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, + {523, nullptr, "SetIsPalmaPairedConnectable"}, + {524, nullptr, "PairPalma"}, + {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, + {1000, nullptr, "SetNpadCommunicationMode"}, + {1001, nullptr, "GetNpadCommunicationMode"}, + }; + // clang-format on + + RegisterHandlers(functions); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +Hid::~Hid() = default; - if (applet_resource == nullptr) { - applet_resource = std::make_shared<IAppletResource>(); - } +void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAppletResource>(applet_resource); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + if (applet_resource == nullptr) { + applet_resource = std::make_shared<IAppletResource>(); } - void ActivateXpad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto basic_xpad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IAppletResource>(applet_resource); +} - LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", - basic_xpad_id, applet_resource_user_id); +void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto basic_xpad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::XPad); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id, + applet_resource_user_id); - void ActivateDebugPad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::XPad); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::DebugPad); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void ActivateTouchScreen(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::DebugPad); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::Touchscreen); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void ActivateMouse(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Touchscreen); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::Mouse); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void ActivateKeyboard(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Mouse); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::Keyboard); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void ActivateGesture(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Keyboard); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); +void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto unknown{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::Gesture); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, + applet_resource_user_id); - void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { - // Should have no effect with how our npad sets up the data - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Gesture); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); +void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + // Should have no effect with how our npad sets up the data + IPC::RequestParser rp{ctx}; + const auto unknown{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->ActivateController(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, + applet_resource_user_id); - void StartSixAxisSensor(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); +void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); - void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { - 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>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, - "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}", - handle, drift_mode, applet_resource_user_id); +void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { + 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>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, + "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}", handle, + drift_mode, applet_resource_user_id); - void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); +void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. - rb.Push(true); - } + LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); - void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto supported_styleset{rp.Pop<u32>()}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. + rb.Push(true); +} - LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); +void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto supported_styleset{rp.Pop<u32>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSupportedStyleSet({supported_styleset}); + LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedStyleSet({supported_styleset}); - void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(controller.GetSupportedStyleSet().raw); - } + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(controller.GetSupportedStyleSet().raw); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void ActivateNpad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - applet_resource->ActivateController(HidController::NPad); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto unknown{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + applet_resource->ActivateController(HidController::NPad); +} - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", - npad_id, applet_resource_user_id, unknown); +void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto unknown{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetStyleSetChangedEvent()); - } + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id, + applet_resource_user_id, unknown); - void DisconnectNpad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetStyleSetChangedEvent()); +} - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); +void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .DisconnectNPad(npad_id); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, + applet_resource_user_id); - void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id); +void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetLedPattern(npad_id) - .raw); - } + LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id); - void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto hold_type{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLedPattern(npad_id) + .raw); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}", - applet_resource_user_id, hold_type); +void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto hold_type{rp.Pop<u64>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}", + applet_resource_user_id, hold_type); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); - void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto& controller = - applet_resource->GetController<Controller_NPad>(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); +} - LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", - npad_id, applet_resource_user_id); +void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id, + applet_resource_user_id); - void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetVibrationEnabled(true); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetVibrationEnabled(false); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } +void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); - void SendVibrationValue(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - 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>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", - controller_id, applet_resource_user_id); +void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + 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>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, + applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController({controller_id}, {vibration_values}); - } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); - void SendVibrationValues(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateController({controller_id}, {vibration_values}); +} - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); +void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto controllers = ctx.ReadBuffer(0); - const auto vibrations = ctx.ReadBuffer(1); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - std::vector<u32> controller_list(controllers.size() / sizeof(u32)); - std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() / - sizeof(Controller_NPad::Vibration)); + const auto controllers = ctx.ReadBuffer(0); + const auto vibrations = ctx.ReadBuffer(1); - std::memcpy(controller_list.data(), controllers.data(), controllers.size()); - std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); - std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(), - [](u32 controller_id) { return controller_id - 3; }); + std::vector<u32> controller_list(controllers.size() / sizeof(u32)); + std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() / + sizeof(Controller_NPad::Vibration)); - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController(controller_list, vibration_list); + std::memcpy(controller_list.data(), controllers.data(), controllers.size()); + std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); + std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(), + [](u32 controller_id) { return controller_id - 3; }); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateController(controller_list, vibration_list); - void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", - controller_id, applet_resource_user_id); +void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto controller_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<Controller_NPad::Vibration>( - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetLastVibration()); - } + LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, + applet_resource_user_id); - void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<Controller_NPad::Vibration>( + applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration()); +} - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); +void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, + applet_resource_user_id); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); - void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { - 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>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, - "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}", - unknown_1, unknown_2, applet_resource_user_id); +void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { + 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>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, + "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}", + unknown_1, unknown_2, applet_resource_user_id); - void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto mode{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}", - applet_resource_user_id, mode); +void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto mode{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}", + applet_resource_user_id, mode); - void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); - rb.Push<u32>(0); - } +void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); - void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(1); + rb.Push<u32>(0); +} - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IActiveVibrationDeviceList>(); - } +void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); - void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IActiveVibrationDeviceList>(); +} - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", - applet_resource_user_id); +void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + applet_resource_user_id); - void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); +void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, + applet_resource_user_id); - void StopSixAxisSensor(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle); +void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto handle{rp.Pop<u32>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle); - void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto unknown{rp.Pop<u32>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}", - applet_resource_user_id, unknown); +void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto unknown{rp.Pop<u32>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}", + applet_resource_user_id, unknown); - void SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} - LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown); +void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto unknown{rp.Pop<u32>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - } -}; + LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} class HidDbg final : public ServiceFramework<HidDbg> { public: diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 773035460..eca27c056 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,12 +4,122 @@ #pragma once +#include "controllers/controller_base.h" +#include "core/hle/service/service.h" + +namespace CoreTiming { +struct EventType; +} + +namespace Kernel { +class SharedMemory; +} + namespace SM { class ServiceManager; } namespace Service::HID { +enum class HidController : std::size_t { + DebugPad, + Touchscreen, + Mouse, + Keyboard, + XPad, + Unknown1, + Unknown2, + Unknown3, + SixAxisSensor, + NPad, + Gesture, + + MaxControllers, +}; + +class IAppletResource final : public ServiceFramework<IAppletResource> { +public: + IAppletResource(); + ~IAppletResource() override; + + void ActivateController(HidController controller); + void DeactivateController(HidController controller); + + template <typename T> + T& GetController(HidController controller) { + return static_cast<T&>(*controllers[static_cast<size_t>(controller)]); + } + + template <typename T> + const T& GetController(HidController controller) const { + return static_cast<T&>(*controllers[static_cast<size_t>(controller)]); + } + +private: + template <typename T> + void MakeController(HidController controller) { + controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(); + } + + void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); + void UpdateControllers(u64 userdata, int cycles_late); + + Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; + + CoreTiming::EventType* pad_update_event; + + std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> + controllers{}; +}; + +class Hid final : public ServiceFramework<Hid> { +public: + Hid(); + ~Hid() override; + + std::shared_ptr<IAppletResource> GetAppletResource(); + +private: + void CreateAppletResource(Kernel::HLERequestContext& ctx); + void ActivateXpad(Kernel::HLERequestContext& ctx); + void ActivateDebugPad(Kernel::HLERequestContext& ctx); + void ActivateTouchScreen(Kernel::HLERequestContext& ctx); + void ActivateMouse(Kernel::HLERequestContext& ctx); + void ActivateKeyboard(Kernel::HLERequestContext& ctx); + void ActivateGesture(Kernel::HLERequestContext& ctx); + void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); + void StartSixAxisSensor(Kernel::HLERequestContext& ctx); + void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); + void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); + void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); + void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); + void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx); + void ActivateNpad(Kernel::HLERequestContext& ctx); + void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx); + void DisconnectNpad(Kernel::HLERequestContext& ctx); + void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); + void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx); + void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx); + void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx); + void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); + void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); + void SendVibrationValue(Kernel::HLERequestContext& ctx); + void SendVibrationValues(Kernel::HLERequestContext& ctx); + void GetActualVibrationValue(Kernel::HLERequestContext& ctx); + void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx); + void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx); + void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); + void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); + void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); + void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); + void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); + void StopSixAxisSensor(Kernel::HLERequestContext& ctx); + void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); + void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); + + std::shared_ptr<IAppletResource> applet_resource; +}; + /// Reload input devices. Used when input configuration changed void ReloadInputDevices(); diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 16564de24..c13640ad8 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -12,9 +12,16 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" +#include "core/settings.h" namespace Service::Time { +static std::chrono::seconds GetSecondsSinceEpoch() { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()) + + Settings::values.custom_rtc_differential; +} + static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time, CalendarAdditionalInfo& additional_info, [[maybe_unused]] const TimeZoneRule& /*rule*/) { @@ -68,9 +75,7 @@ public: private: void GetCurrentTime(Kernel::HLERequestContext& ctx) { - const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>( - std::chrono::system_clock::now().time_since_epoch()) - .count()}; + const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -266,10 +271,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto initial_type = rp.PopRaw<u8>(); - const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>( - std::chrono::system_clock::now().time_since_epoch()) - .count()}; - + const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; const std::time_t time(time_since_epoch); const std::tm* tm = std::localtime(&time); if (tm == nullptr) { diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 1bdf19e13..70c933934 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -32,6 +32,9 @@ namespace Service::VI { +constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; +constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6}; + struct DisplayInfo { /// The name of this particular display. char display_name[0x40]{"Default"}; @@ -874,6 +877,22 @@ public: ~IApplicationDisplayService() = default; private: + enum class ConvertedScaleMode : u64 { + Freeze = 0, + ScaleToWindow = 1, + ScaleAndCrop = 2, + None = 3, + PreserveAspectRatio = 4, + }; + + enum class NintendoScaleMode : u32 { + None = 0, + Freeze = 1, + ScaleToWindow = 2, + ScaleAndCrop = 3, + PreserveAspectRatio = 4, + }; + void GetRelayService(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); @@ -974,13 +993,27 @@ private: void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const u32 scaling_mode = rp.Pop<u32>(); + const auto scaling_mode = rp.PopEnum<NintendoScaleMode>(); const u64 unknown = rp.Pop<u64>(); - LOG_WARNING(Service_VI, "(STUBBED) called. scaling_mode=0x{:08X}, unknown=0x{:016X}", - scaling_mode, unknown); + LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}", + static_cast<u32>(scaling_mode), unknown); IPC::ResponseBuilder rb{ctx, 2}; + + if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) { + LOG_ERROR(Service_VI, "Invalid scaling mode provided."); + rb.Push(ERR_OPERATION_FAILED); + return; + } + + if (scaling_mode != NintendoScaleMode::ScaleToWindow && + scaling_mode != NintendoScaleMode::PreserveAspectRatio) { + LOG_ERROR(Service_VI, "Unsupported scaling mode supplied."); + rb.Push(ERR_UNSUPPORTED); + return; + } + rb.Push(RESULT_SUCCESS); } @@ -1061,51 +1094,37 @@ private: rb.PushCopyObjects(vsync_event); } - enum class ConvertedScaleMode : u64 { - None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no - // scaling/default - Freeze = 1, - ScaleToWindow = 2, - Crop = 3, - NoCrop = 4, - }; - - // This struct is different, currently it's 1:1 but this might change in the future. - enum class NintendoScaleMode : u32 { - None = 0, - Freeze = 1, - ScaleToWindow = 2, - Crop = 3, - NoCrop = 4, - }; - void ConvertScalingMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto mode = rp.PopEnum<NintendoScaleMode>(); + const auto mode = rp.PopEnum<NintendoScaleMode>(); LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode)); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); + const auto converted_mode = ConvertScalingModeImpl(mode); + + if (converted_mode.Succeeded()) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(*converted_mode); + } else { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(converted_mode.Code()); + } + } + + static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) { switch (mode) { case NintendoScaleMode::None: - rb.PushEnum(ConvertedScaleMode::None); - break; + return MakeResult(ConvertedScaleMode::None); case NintendoScaleMode::Freeze: - rb.PushEnum(ConvertedScaleMode::Freeze); - break; + return MakeResult(ConvertedScaleMode::Freeze); case NintendoScaleMode::ScaleToWindow: - rb.PushEnum(ConvertedScaleMode::ScaleToWindow); - break; - case NintendoScaleMode::Crop: - rb.PushEnum(ConvertedScaleMode::Crop); - break; - case NintendoScaleMode::NoCrop: - rb.PushEnum(ConvertedScaleMode::NoCrop); - break; + return MakeResult(ConvertedScaleMode::ScaleToWindow); + case NintendoScaleMode::ScaleAndCrop: + return MakeResult(ConvertedScaleMode::ScaleAndCrop); + case NintendoScaleMode::PreserveAspectRatio: + return MakeResult(ConvertedScaleMode::PreserveAspectRatio); default: - UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode)); - rb.PushEnum(ConvertedScaleMode::None); - break; + return ERR_OPERATION_FAILED; } } diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 30eacd803..bb925f4a6 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -178,6 +178,8 @@ public: /** * Get the banner (typically banner section) of the application + * In the context of NX, this is the animation that displays in the bottom right of the screen + * when a game boots. Stored in GIF format. * @param buffer Reference to buffer to store data * @return ResultStatus result of function */ @@ -187,6 +189,8 @@ public: /** * Get the logo (typically logo section) of the application + * In the context of NX, this is the static image that displays in the top left of the screen + * when a game boots. Stored in JPEG format. * @param buffer Reference to buffer to store data * @return ResultStatus result of function */ @@ -259,6 +263,15 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the RomFS of the manual of the application + * @param file The raw manual RomFS of the game + * @return ResultStatus result of function + */ + virtual ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileSys::VirtualFile file; bool is_loaded = false; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index a093e3d36..93a970d10 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -79,4 +79,13 @@ u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } + +ResultStatus AppLoader_NAX::ReadBanner(std::vector<u8>& buffer) { + return nca_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) { + return nca_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 0a97511b8..f40079574 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -39,6 +39,9 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::NAX> nax; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 7e1b0d84f..ce8196fcf 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -84,4 +84,23 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { return ResultStatus::Success; } +ResultStatus AppLoader_NCA::ReadBanner(std::vector<u8>& buffer) { + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) + return ResultStatus::ErrorNotInitialized; + const auto logo = nca->GetLogoPartition(); + if (logo == nullptr) + return ResultStatus::ErrorNoIcon; + buffer = logo->GetFile("StartupMovie.gif")->ReadAllBytes(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) { + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) + return ResultStatus::ErrorNotInitialized; + const auto logo = nca->GetLogoPartition(); + if (logo == nullptr) + return ResultStatus::ErrorNoIcon; + buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index cbbe701d2..b9f077468 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -39,6 +39,9 @@ public: u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; + private: std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 4d4b44571..7da1f8960 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -158,4 +158,21 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) { nacp = *nacp_file; return ResultStatus::Success; } + +ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) { + const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual); + if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr) + return ResultStatus::ErrorNoRomFS; + file = nca->GetRomFS(); + return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success; +} + +ResultStatus AppLoader_NSP::ReadBanner(std::vector<u8>& buffer) { + return secondary_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) { + return secondary_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 32eb0193d..953a1b508 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -44,6 +44,10 @@ public: ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; ResultStatus ReadControlData(FileSys::NACP& nacp) override; + ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override; + + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; private: std::unique_ptr<FileSys::NSP> nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index e67e43c69..89f7bbf77 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -128,4 +128,21 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) { return ResultStatus::Success; } +ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) { + const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(), + FileSys::ContentRecordType::Manual); + if (xci->GetStatus() != ResultStatus::Success || nca == nullptr) + return ResultStatus::ErrorXCIMissingPartition; + file = nca->GetRomFS(); + return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success; +} + +ResultStatus AppLoader_XCI::ReadBanner(std::vector<u8>& buffer) { + return nca_loader->ReadBanner(buffer); +} + +ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) { + return nca_loader->ReadLogo(buffer); +} + } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 9d3923f62..d6995b61e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -44,6 +44,10 @@ public: ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; ResultStatus ReadControlData(FileSys::NACP& control) override; + ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override; + + ResultStatus ReadBanner(std::vector<u8>& buffer) override; + ResultStatus ReadLogo(std::vector<u8>& buffer) override; private: std::unique_ptr<FileSys::XCI> xci; diff --git a/src/core/settings.h b/src/core/settings.h index de01b05c0..29ce98983 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,6 +6,7 @@ #include <array> #include <atomic> +#include <chrono> #include <map> #include <optional> #include <string> @@ -350,6 +351,11 @@ struct Values { bool use_docked_mode; bool enable_nfc; std::optional<u32> rng_seed; + // Measured in seconds since epoch + std::optional<std::chrono::seconds> custom_rtc; + // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` + std::chrono::seconds custom_rtc_differential; + s32 current_user; s32 language_index; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 0406fbcd9..327db68a5 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -30,6 +30,8 @@ add_library(video_core STATIC renderer_base.h renderer_opengl/gl_buffer_cache.cpp renderer_opengl/gl_buffer_cache.h + renderer_opengl/gl_global_cache.cpp + renderer_opengl/gl_global_cache.h renderer_opengl/gl_primitive_assembler.cpp renderer_opengl/gl_primitive_assembler.h renderer_opengl/gl_rasterizer.cpp diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 46a6c0308..bd2b30e77 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -14,7 +14,7 @@ namespace OpenGL { OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size) - : RasterizerCache{rasterizer}, stream_buffer(GL_ARRAY_BUFFER, size) {} + : RasterizerCache{rasterizer}, stream_buffer(size, true) {} GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment, bool cache) { diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp new file mode 100644 index 000000000..7992b82c4 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_global_cache.cpp @@ -0,0 +1,24 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <glad/glad.h> + +#include "video_core/renderer_opengl/gl_global_cache.h" +#include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/utils.h" + +namespace OpenGL { + +CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} { + buffer.Create(); + // Bind and unbind the buffer so it gets allocated by the driver + glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory"); +} + +GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer) + : RasterizerCache{rasterizer} {} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h new file mode 100644 index 000000000..406a735bc --- /dev/null +++ b/src/video_core/renderer_opengl/gl_global_cache.h @@ -0,0 +1,60 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <glad/glad.h> + +#include "common/common_types.h" +#include "video_core/rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" + +namespace OpenGL { + +namespace GLShader { +class GlobalMemoryEntry; +} // namespace GLShader + +class RasterizerOpenGL; +class CachedGlobalRegion; +using GlobalRegion = std::shared_ptr<CachedGlobalRegion>; + +class CachedGlobalRegion final : public RasterizerCacheObject { +public: + explicit CachedGlobalRegion(VAddr addr, u32 size); + + /// Gets the address of the shader in guest memory, required for cache management + VAddr GetAddr() const { + return addr; + } + + /// Gets the size of the shader in guest memory, required for cache management + std::size_t GetSizeInBytes() const { + return size; + } + + /// Gets the GL program handle for the buffer + GLuint GetBufferHandle() const { + return buffer.handle; + } + + // TODO(Rodrigo): When global memory is written (STG), implement flushing + void Flush() override { + UNIMPLEMENTED(); + } + +private: + VAddr addr{}; + u32 size{}; + + OGLBuffer buffer; +}; + +class GlobalRegionCacheOpenGL final : public RasterizerCache<GlobalRegion> { +public: + explicit GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer); +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aa8dab0c2..2bf086902 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -101,7 +101,7 @@ struct FramebufferCacheKey { RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) : res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info}, - buffer_cache(*this, STREAM_BUFFER_SIZE) { + buffer_cache(*this, STREAM_BUFFER_SIZE), global_cache{*this} { // Create sampler objects for (std::size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); @@ -135,27 +135,31 @@ void RasterizerOpenGL::CheckExtensions() { } } -void RasterizerOpenGL::SetupVertexFormat() { +GLuint RasterizerOpenGL::SetupVertexFormat() { auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; - if (!gpu.dirty_flags.vertex_attrib_format) - return; + if (!gpu.dirty_flags.vertex_attrib_format) { + return state.draw.vertex_array; + } gpu.dirty_flags.vertex_attrib_format = false; MICROPROFILE_SCOPE(OpenGL_VAO); auto [iter, is_cache_miss] = vertex_array_cache.try_emplace(regs.vertex_attrib_format); - auto& VAO = iter->second; + auto& vao_entry = iter->second; if (is_cache_miss) { - VAO.Create(); - state.draw.vertex_array = VAO.handle; - state.ApplyVertexBufferState(); + vao_entry.Create(); + const GLuint vao = vao_entry.handle; - // The index buffer binding is stored within the VAO. Stupid OpenGL, but easy to work - // around. - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_cache.GetHandle()); + // Eventhough we are using DSA to create this vertex array, there is a bug on Intel's blob + // that fails to properly create the vertex array if it's not bound even after creating it + // with glCreateVertexArrays + state.draw.vertex_array = vao; + state.ApplyVertexArrayState(); + + glVertexArrayElementBuffer(vao, buffer_cache.GetHandle()); // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. // Enables the first 16 vertex attributes always, as we don't know which ones are actually @@ -163,7 +167,7 @@ void RasterizerOpenGL::SetupVertexFormat() { // for now to avoid OpenGL errors. // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't // assume every shader uses them all. - for (unsigned index = 0; index < 16; ++index) { + for (u32 index = 0; index < 16; ++index) { const auto& attrib = regs.vertex_attrib_format[index]; // Ignore invalid attributes. @@ -178,28 +182,29 @@ void RasterizerOpenGL::SetupVertexFormat() { ASSERT(buffer.IsEnabled()); - glEnableVertexAttribArray(index); + glEnableVertexArrayAttrib(vao, index); if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt || attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) { - glVertexAttribIFormat(index, attrib.ComponentCount(), - MaxwellToGL::VertexType(attrib), attrib.offset); + glVertexArrayAttribIFormat(vao, index, attrib.ComponentCount(), + MaxwellToGL::VertexType(attrib), attrib.offset); } else { - glVertexAttribFormat(index, attrib.ComponentCount(), - MaxwellToGL::VertexType(attrib), - attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); + glVertexArrayAttribFormat( + vao, index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); } - glVertexAttribBinding(index, attrib.buffer); + glVertexArrayAttribBinding(vao, index, attrib.buffer); } } - state.draw.vertex_array = VAO.handle; - state.ApplyVertexBufferState(); // Rebinding the VAO invalidates the vertex buffer bindings. gpu.dirty_flags.vertex_array = 0xFFFFFFFF; + + state.draw.vertex_array = vao_entry.handle; + return vao_entry.handle; } -void RasterizerOpenGL::SetupVertexBuffer() { +void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; @@ -217,7 +222,7 @@ void RasterizerOpenGL::SetupVertexBuffer() { if (!vertex_array.IsEnabled()) continue; - Tegra::GPUVAddr start = vertex_array.StartAddress(); + const Tegra::GPUVAddr start = vertex_array.StartAddress(); const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); ASSERT(end > start); @@ -225,21 +230,18 @@ void RasterizerOpenGL::SetupVertexBuffer() { const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size); // Bind the vertex array to the buffer at the current offset. - glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset, - vertex_array.stride); + glVertexArrayVertexBuffer(vao, index, buffer_cache.GetHandle(), vertex_buffer_offset, + vertex_array.stride); if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { // Enable vertex buffer instancing with the specified divisor. - glVertexBindingDivisor(index, vertex_array.divisor); + glVertexArrayBindingDivisor(vao, index, vertex_array.divisor); } else { // Disable the vertex buffer instancing. - glVertexBindingDivisor(index, 0); + glVertexArrayBindingDivisor(vao, index, 0); } } - // Implicit set by glBindVertexBuffer. Stupid glstate handling... - state.draw.vertex_buffer = buffer_cache.GetHandle(); - gpu.dirty_flags.vertex_array = 0; } @@ -365,7 +367,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the // clip distances only when it's written by a shader stage. for (std::size_t i = 0; i < Maxwell::NumClipDistances; ++i) { - clip_distances[i] |= shader->GetShaderEntries().clip_distances[i]; + clip_distances[i] = clip_distances[i] || shader->GetShaderEntries().clip_distances[i]; } // When VertexA is enabled, we have dual vertex shaders @@ -703,9 +705,6 @@ void RasterizerOpenGL::DrawArrays() { // Draw the vertex batch const bool is_indexed = accelerate_draw == AccelDraw::Indexed; - state.draw.vertex_buffer = buffer_cache.GetHandle(); - state.ApplyVertexBufferState(); - std::size_t buffer_size = CalculateVertexArraysSize(); // Add space for index buffer (keeping in mind non-core primitives) @@ -735,8 +734,9 @@ void RasterizerOpenGL::DrawArrays() { gpu.dirty_flags.vertex_array = 0xFFFFFFFF; } - SetupVertexFormat(); - SetupVertexBuffer(); + const GLuint vao = SetupVertexFormat(); + SetupVertexBuffer(vao); + DrawParameters params = SetupDraw(); SetupShaders(params.primitive_mode); @@ -775,6 +775,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.InvalidateRegion(addr, size); shader_cache.InvalidateRegion(addr, size); + global_cache.InvalidateRegion(addr, size); buffer_cache.InvalidateRegion(addr, size); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 45c988816..21c51f874 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -23,6 +23,7 @@ #include "video_core/rasterizer_cache.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" +#include "video_core/renderer_opengl/gl_global_cache.h" #include "video_core/renderer_opengl/gl_primitive_assembler.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -66,6 +67,10 @@ public: static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0, "The maximum size of a constbuffer must be a multiple of the size of GLvec4"); + static constexpr std::size_t MaxGlobalMemorySize = 0x10000; + static_assert(MaxGlobalMemorySize % sizeof(float) == 0, + "The maximum size of a global memory must be a multiple of the size of float"); + private: class SamplerInfo { public: @@ -122,7 +127,7 @@ private: bool using_depth_fb = true, bool preserve_contents = true, std::optional<std::size_t> single_color_target = {}); - /* + /** * Configures the current constbuffers to use for the draw command. * @param stage The shader stage to configure buffers for. * @param shader The shader object that contains the specified stage. @@ -132,7 +137,7 @@ private: u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, GLenum primitive_mode, u32 current_bindpoint); - /* + /** * Configures the current textures to use for the draw command. * @param stage The shader stage to configure textures for. * @param shader The shader object that contains the specified stage. @@ -202,6 +207,7 @@ private: RasterizerCacheOpenGL res_cache; ShaderCacheOpenGL shader_cache; + GlobalRegionCacheOpenGL global_cache; Core::Frontend::EmuWindow& emu_window; @@ -227,8 +233,10 @@ private: std::size_t CalculateIndexBufferSize() const; - void SetupVertexFormat(); - void SetupVertexBuffer(); + /// Updates and returns a vertex array object representing current vertex format + GLuint SetupVertexFormat(); + + void SetupVertexBuffer(GLuint vao); DrawParameters SetupDraw(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index d26a5f4de..a05b8b936 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -452,7 +452,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes); glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); - glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW); + glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_COPY); if (source_format.compressed) { glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, static_cast<GLsizei>(src_params.size_in_bytes), nullptr); diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index c17d5ac00..1da744158 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -117,7 +117,7 @@ void OGLBuffer::Create() { return; MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - glGenBuffers(1, &handle); + glCreateBuffers(1, &handle); } void OGLBuffer::Release() { @@ -126,7 +126,6 @@ void OGLBuffer::Release() { MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); glDeleteBuffers(1, &handle); - OpenGLState::GetCurState().ResetBuffer(handle).Apply(); handle = 0; } @@ -152,7 +151,7 @@ void OGLVertexArray::Create() { return; MICROPROFILE_SCOPE(OpenGL_ResourceCreation); - glGenVertexArrays(1, &handle); + glCreateVertexArrays(1, &handle); } void OGLVertexArray::Release() { diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index dc0a5ed5e..b7ba59350 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -83,8 +83,6 @@ OpenGLState::OpenGLState() { draw.read_framebuffer = 0; draw.draw_framebuffer = 0; draw.vertex_array = 0; - draw.vertex_buffer = 0; - draw.uniform_buffer = 0; draw.shader_program = 0; draw.program_pipeline = 0; @@ -505,7 +503,6 @@ void OpenGLState::ApplySamplers() const { } void OpenGLState::ApplyFramebufferState() const { - // Framebuffer if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); } @@ -514,16 +511,10 @@ void OpenGLState::ApplyFramebufferState() const { } } -void OpenGLState::ApplyVertexBufferState() const { - // Vertex array +void OpenGLState::ApplyVertexArrayState() const { if (draw.vertex_array != cur_state.draw.vertex_array) { glBindVertexArray(draw.vertex_array); } - - // Vertex buffer - if (draw.vertex_buffer != cur_state.draw.vertex_buffer) { - glBindBuffer(GL_ARRAY_BUFFER, draw.vertex_buffer); - } } void OpenGLState::ApplyDepthClamp() const { @@ -543,11 +534,7 @@ void OpenGLState::ApplyDepthClamp() const { void OpenGLState::Apply() const { ApplyFramebufferState(); - ApplyVertexBufferState(); - // Uniform buffer - if (draw.uniform_buffer != cur_state.draw.uniform_buffer) { - glBindBuffer(GL_UNIFORM_BUFFER, draw.uniform_buffer); - } + ApplyVertexArrayState(); // Shader program if (draw.shader_program != cur_state.draw.shader_program) { @@ -638,16 +625,6 @@ OpenGLState& OpenGLState::ResetPipeline(GLuint handle) { return *this; } -OpenGLState& OpenGLState::ResetBuffer(GLuint handle) { - if (draw.vertex_buffer == handle) { - draw.vertex_buffer = 0; - } - if (draw.uniform_buffer == handle) { - draw.uniform_buffer = 0; - } - return *this; -} - OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) { if (draw.vertex_array == handle) { draw.vertex_array = 0; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 439bfbc98..a5a7c0920 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -154,8 +154,6 @@ public: GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING - GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING - GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING } draw; @@ -206,10 +204,10 @@ public: } /// Apply this state as the current OpenGL state void Apply() const; - /// Apply only the state afecting the framebuffer + /// Apply only the state affecting the framebuffer void ApplyFramebufferState() const; - /// Apply only the state afecting the vertex buffer - void ApplyVertexBufferState() const; + /// Apply only the state affecting the vertex array + void ApplyVertexArrayState() const; /// Set the initial OpenGL state static void ApplyDefaultState(); /// Resets any references to the given resource @@ -217,7 +215,6 @@ public: OpenGLState& ResetSampler(GLuint handle); OpenGLState& ResetProgram(GLuint handle); OpenGLState& ResetPipeline(GLuint handle); - OpenGLState& ResetBuffer(GLuint handle); OpenGLState& ResetVertexArray(GLuint handle); OpenGLState& ResetFramebuffer(GLuint handle); void EmulateViewportWithScissor(); diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index b97b895a4..d0b14b3f6 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -15,13 +15,12 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning", namespace OpenGL { -OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent) - : gl_target(target), buffer_size(size) { +OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent) + : buffer_size(size) { gl_buffer.Create(); - glBindBuffer(gl_target, gl_buffer.handle); GLsizeiptr allocate_size = size; - if (target == GL_ARRAY_BUFFER) { + if (vertex_data_usage) { // On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer // read position is near the end and is an out-of-bound access to the vertex buffer. This is // probably a bug in the driver and is related to the usage of vec3<byte> attributes in the @@ -35,18 +34,17 @@ OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coh coherent = prefer_coherent; const GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0); - glBufferStorage(gl_target, allocate_size, nullptr, flags); - mapped_ptr = static_cast<u8*>(glMapBufferRange( - gl_target, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT))); + glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags); + mapped_ptr = static_cast<u8*>(glMapNamedBufferRange( + gl_buffer.handle, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT))); } else { - glBufferData(gl_target, allocate_size, nullptr, GL_STREAM_DRAW); + glNamedBufferData(gl_buffer.handle, allocate_size, nullptr, GL_STREAM_DRAW); } } OGLStreamBuffer::~OGLStreamBuffer() { if (persistent) { - glBindBuffer(gl_target, gl_buffer.handle); - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } gl_buffer.Release(); } @@ -74,7 +72,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a invalidate = true; if (persistent) { - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } } @@ -84,7 +82,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a (coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) | (invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT); mapped_ptr = static_cast<u8*>( - glMapBufferRange(gl_target, buffer_pos, buffer_size - buffer_pos, flags)); + glMapNamedBufferRange(gl_buffer.handle, buffer_pos, buffer_size - buffer_pos, flags)); mapped_offset = buffer_pos; } @@ -95,11 +93,11 @@ void OGLStreamBuffer::Unmap(GLsizeiptr size) { ASSERT(size <= mapped_size); if (!coherent && size > 0) { - glFlushMappedBufferRange(gl_target, buffer_pos - mapped_offset, size); + glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos - mapped_offset, size); } if (!persistent) { - glUnmapBuffer(gl_target); + glUnmapNamedBuffer(gl_buffer.handle); } buffer_pos += size; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index ae7961bd7..3d18ecb4d 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -13,7 +13,7 @@ namespace OpenGL { class OGLStreamBuffer : private NonCopyable { public: - explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent = false); + explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false); ~OGLStreamBuffer(); GLuint GetHandle() const; @@ -33,7 +33,6 @@ public: private: OGLBuffer gl_buffer; - GLenum gl_target; bool coherent = false; bool persistent = false; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 235732d86..c268c9686 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -245,20 +245,20 @@ void RendererOpenGL::InitOpenGLObjects() { // Generate VAO vertex_array.Create(); - state.draw.vertex_array = vertex_array.handle; - state.draw.vertex_buffer = vertex_buffer.handle; - state.draw.uniform_buffer = 0; - state.Apply(); // Attach vertex data to VAO - glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); - glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), - (GLvoid*)offsetof(ScreenRectVertex, position)); - glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), - (GLvoid*)offsetof(ScreenRectVertex, tex_coord)); - glEnableVertexAttribArray(attrib_position); - glEnableVertexAttribArray(attrib_tex_coord); + glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); + glVertexArrayAttribFormat(vertex_array.handle, attrib_position, 2, GL_FLOAT, GL_FALSE, + offsetof(ScreenRectVertex, position)); + glVertexArrayAttribFormat(vertex_array.handle, attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, + offsetof(ScreenRectVertex, tex_coord)); + glVertexArrayAttribBinding(vertex_array.handle, attrib_position, 0); + glVertexArrayAttribBinding(vertex_array.handle, attrib_tex_coord, 0); + glEnableVertexArrayAttrib(vertex_array.handle, attrib_position); + glEnableVertexArrayAttrib(vertex_array.handle, attrib_tex_coord); + glVertexArrayVertexBuffer(vertex_array.handle, 0, vertex_buffer.handle, 0, + sizeof(ScreenRectVertex)); // Allocate textures for the screen screen_info.texture.resource.Create(); @@ -370,14 +370,12 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, state.texture_units[0].texture = screen_info.display_texture; state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; // Workaround brigthness problems in SMO by enabling sRGB in the final output - // if it has been used in the frame - // Needed because of this bug in QT - // QTBUG-50987 + // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); state.Apply(); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - // restore default state + // Restore default state state.framebuffer_srgb.enabled = false; state.texture_units[0].texture = 0; state.Apply(); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 17ecaafde..1f852df4b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -11,6 +11,8 @@ add_executable(yuzu applets/profile_select.h applets/software_keyboard.cpp applets/software_keyboard.h + applets/web_browser.cpp + applets/web_browser.h bootmanager.cpp bootmanager.h compatibility_list.cpp @@ -37,6 +39,8 @@ add_executable(yuzu configuration/configure_input_simple.h configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h + configuration/configure_profile_manager.cpp + configuration/configure_profile_manager.h configuration/configure_system.cpp configuration/configure_system.h configuration/configure_per_general.cpp @@ -94,6 +98,7 @@ set(UIS configuration/configure_input_simple.ui configuration/configure_mouse_advanced.ui configuration/configure_per_general.ui + configuration/configure_profile_manager.ui configuration/configure_system.ui configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui @@ -154,6 +159,11 @@ if (USE_DISCORD_PRESENCE) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) endif() +if (YUZU_USE_QT_WEB_ENGINE) + target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets) + target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) +endif () + if(UNIX AND NOT APPLE) install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") endif() diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp new file mode 100644 index 000000000..6a9138d53 --- /dev/null +++ b/src/yuzu/applets/web_browser.cpp @@ -0,0 +1,113 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> + +#include <QKeyEvent> + +#include "core/hle/lock.h" +#include "yuzu/applets/web_browser.h" +#include "yuzu/main.h" + +#ifdef YUZU_USE_QT_WEB_ENGINE + +constexpr char NX_SHIM_INJECT_SCRIPT[] = R"( + window.nx = {}; + window.nx.playReport = {}; + window.nx.playReport.setCounterSetIdentifier = function () { + console.log("nx.playReport.setCounterSetIdentifier called - unimplemented"); + }; + + window.nx.playReport.incrementCounter = function () { + console.log("nx.playReport.incrementCounter called - unimplemented"); + }; + + window.nx.footer = {}; + window.nx.footer.unsetAssign = function () { + console.log("nx.footer.unsetAssign called - unimplemented"); + }; + + var yuzu_key_callbacks = []; + window.nx.footer.setAssign = function(key, discard1, func, discard2) { + switch (key) { + case 'A': + yuzu_key_callbacks[0] = func; + break; + case 'B': + yuzu_key_callbacks[1] = func; + break; + case 'X': + yuzu_key_callbacks[2] = func; + break; + case 'Y': + yuzu_key_callbacks[3] = func; + break; + case 'L': + yuzu_key_callbacks[6] = func; + break; + case 'R': + yuzu_key_callbacks[7] = func; + break; + } + }; + + var applet_done = false; + window.nx.endApplet = function() { + applet_done = true; + }; +)"; + +QString GetNXShimInjectionScript() { + return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); +} + +NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} + +void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { + parent()->event(event); +} + +void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { + parent()->event(event); +} + +#endif + +QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { + connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage, + Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this, + &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this, + &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection); +} + +QtWebBrowser::~QtWebBrowser() = default; + +void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) { + this->unpack_romfs_callback = std::move(unpack_romfs_callback); + this->finished_callback = std::move(finished_callback); + + const auto index = url.find('?'); + if (index == std::string::npos) { + emit MainWindowOpenPage(url, ""); + } else { + const auto front = url.substr(0, index); + const auto back = url.substr(index); + emit MainWindowOpenPage(front, back); + } +} + +void QtWebBrowser::MainWindowUnpackRomFS() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + unpack_romfs_callback(); +} + +void QtWebBrowser::MainWindowFinishedBrowsing() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + finished_callback(); +} diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h new file mode 100644 index 000000000..1a3d67353 --- /dev/null +++ b/src/yuzu/applets/web_browser.h @@ -0,0 +1,52 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <QObject> + +#ifdef YUZU_USE_QT_WEB_ENGINE +#include <QWebEngineView> +#endif + +#include "core/frontend/applets/web_browser.h" + +class GMainWindow; + +#ifdef YUZU_USE_QT_WEB_ENGINE + +QString GetNXShimInjectionScript(); + +class NXInputWebEngineView : public QWebEngineView { +public: + explicit NXInputWebEngineView(QWidget* parent = nullptr); + +protected: + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; +}; + +#endif + +class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { + Q_OBJECT + +public: + explicit QtWebBrowser(GMainWindow& main_window); + ~QtWebBrowser() override; + + void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) override; + +signals: + void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; + +private: + void MainWindowUnpackRomFS(); + void MainWindowFinishedBrowsing(); + + std::function<void()> unpack_romfs_callback; + std::function<void()> finished_callback; +}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 165d70e9c..ddf4cf552 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -419,13 +419,21 @@ void Config::ReadValues() { Settings::values.language_index = qt_config->value("language_index", 1).toInt(); - const auto enabled = qt_config->value("rng_seed_enabled", false).toBool(); - if (enabled) { + const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool(); + if (rng_seed_enabled) { Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong(); } else { Settings::values.rng_seed = std::nullopt; } + const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool(); + if (custom_rtc_enabled) { + Settings::values.custom_rtc = + std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong()); + } else { + Settings::values.custom_rtc = std::nullopt; + } + qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); @@ -653,6 +661,11 @@ void Config::SaveValues() { qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0)); + qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value()); + qt_config->setValue("custom_rtc", + QVariant::fromValue<long long>( + Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count())); + qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index ce833b6c8..3f03f0b77 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -52,6 +52,11 @@ <string>System</string> </attribute> </widget> + <widget class="ConfigureProfileManager" name="profileManagerTab"> + <attribute name="title"> + <string>Profiles</string> + </attribute> + </widget> <widget class="ConfigureInputSimple" name="inputTab"> <attribute name="title"> <string>Input</string> @@ -104,6 +109,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureProfileManager</class> + <extends>QWidget</extends> + <header>configuration/configure_profile_manager.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> <header>configuration/configure_audio.h</header> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 90d7c6372..d802443d0 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -32,6 +32,7 @@ void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); ui->gameListTab->applyConfiguration(); ui->systemTab->applyConfiguration(); + ui->profileManagerTab->applyConfiguration(); ui->inputTab->applyConfiguration(); ui->graphicsTab->applyConfiguration(); ui->audioTab->applyConfiguration(); @@ -43,7 +44,7 @@ void ConfigureDialog::applyConfiguration() { void ConfigureDialog::PopulateSelectionList() { const std::array<std::pair<QString, QStringList>, 4> items{ {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, - {tr("System"), {tr("System"), tr("Audio")}}, + {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, {tr("Graphics"), {tr("Graphics")}}, {tr("Controls"), {tr("Input")}}}}; @@ -60,11 +61,15 @@ void ConfigureDialog::UpdateVisibleTabs() { if (items.isEmpty()) return; - const std::map<QString, QWidget*> widgets = { - {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab}, - {tr("Input"), ui->inputTab}, {tr("Graphics"), ui->graphicsTab}, - {tr("Audio"), ui->audioTab}, {tr("Debug"), ui->debugTab}, - {tr("Web"), ui->webTab}, {tr("Game List"), ui->gameListTab}}; + const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab}, + {tr("System"), ui->systemTab}, + {tr("Profiles"), ui->profileManagerTab}, + {tr("Input"), ui->inputTab}, + {tr("Graphics"), ui->graphicsTab}, + {tr("Audio"), ui->audioTab}, + {tr("Debug"), ui->debugTab}, + {tr("Web"), ui->webTab}, + {tr("Game List"), ui->gameListTab}}; ui->tabWidget->clear(); diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp new file mode 100644 index 000000000..41663e39a --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -0,0 +1,300 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <QFileDialog> +#include <QGraphicsItem> +#include <QGraphicsScene> +#include <QHeaderView> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" +#include "ui_configure_profile_manager.h" +#include "yuzu/configuration/configure_profile_manager.h" +#include "yuzu/util/limitable_input_dialog.h" + +namespace { +// 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 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); +} + +QString GetAccountUsername(const Service::Account::ProfileManager& manager, + Service::Account::UUID uuid) { + Service::Account::ProfileBase profile; + if (!manager.GetProfileBase(uuid, profile)) { + return {}; + } + + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + return QString::fromStdString(text); +} + +QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { + return ConfigureProfileManager::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())); +} + +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); +} + +QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { + return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"), + description_text, 1, + static_cast<int>(Service::Account::profile_username_size)); +} +} // Anonymous namespace + +ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigureProfileManager), + profile_manager(std::make_unique<Service::Account::ProfileManager>()) { + ui->setupUi(this); + + 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); + + ui->scrollArea->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser); + + connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser); + connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser); + connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser); + connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage); + + scene = new QGraphicsScene; + ui->current_user_icon->setScene(scene); + + this->setConfiguration(); +} + +ConfigureProfileManager::~ConfigureProfileManager() = default; + +void ConfigureProfileManager::setConfiguration() { + enabled = !Core::System::GetInstance().IsPoweredOn(); + item_model->removeRows(0, item_model->rowCount()); + list_items.clear(); + + PopulateUserList(); + UpdateCurrentUser(); +} + +void ConfigureProfileManager::PopulateUserList() { + 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); +} + +void ConfigureProfileManager::UpdateCurrentUser() { + ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); + + const auto& current_user = profile_manager->GetUser(Settings::values.current_user); + ASSERT(current_user); + const auto username = GetAccountUsername(*profile_manager, *current_user); + + scene->clear(); + scene->addPixmap( + GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + ui->current_user_username->setText(username); +} + +void ConfigureProfileManager::applyConfiguration() { + if (!enabled) + return; + + Settings::Apply(); +} + +void ConfigureProfileManager::SelectUser(const QModelIndex& index) { + Settings::values.current_user = + std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); + + UpdateCurrentUser(); + + ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); + ui->pm_rename->setEnabled(true); + ui->pm_set_image->setEnabled(true); +} + +void ConfigureProfileManager::AddUser() { + const auto username = + GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); + if (username.isEmpty()) { + return; + } + + const auto uuid = Service::Account::UUID::Generate(); + profile_manager->CreateNewUser(uuid, username.toStdString()); + + item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); +} + +void ConfigureProfileManager::RenameUser() { + const auto user = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(user); + ASSERT(uuid); + + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(*uuid, profile)) + return; + + const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); + if (new_username.isEmpty()) { + return; + } + + const auto username_std = new_username.toStdString(); + std::fill(profile.username.begin(), profile.username.end(), '\0'); + std::copy(username_std.begin(), username_std.end(), profile.username.begin()); + + profile_manager->SetProfileBase(*uuid, profile); + + item_model->setItem( + user, 0, + new QStandardItem{GetIcon(*uuid), + FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); + UpdateCurrentUser(); +} + +void ConfigureProfileManager::DeleteUser() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid); + const auto username = GetAccountUsername(*profile_manager, *uuid); + + const auto confirm = QMessageBox::question( + this, tr("Confirm Delete"), + tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); + + if (confirm == QMessageBox::No) + return; + + if (Settings::values.current_user == tree_view->currentIndex().row()) + Settings::values.current_user = 0; + UpdateCurrentUser(); + + if (!profile_manager->RemoveUser(*uuid)) + return; + + item_model->removeRows(tree_view->currentIndex().row(), 1); + tree_view->clearSelection(); + + ui->pm_remove->setEnabled(false); + ui->pm_rename->setEnabled(false); +} + +void ConfigureProfileManager::SetUserImage() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid); + + const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), + tr("JPEG Images (*.jpg *.jpeg)")); + + if (file.isEmpty()) { + return; + } + + const auto image_path = GetImagePath(*uuid); + if (QFile::exists(image_path) && !QFile::remove(image_path)) { + QMessageBox::warning( + this, tr("Error deleting image"), + tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); + return; + } + + const auto raw_path = QString::fromStdString( + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); + const QFileInfo raw_info{raw_path}; + if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { + QMessageBox::warning(this, tr("Error deleting file"), + tr("Unable to delete existing file: %1.").arg(raw_path)); + return; + } + + const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); + if (!QDir{raw_path}.mkpath(absolute_dst_path)) { + QMessageBox::warning( + this, tr("Error creating user image directory"), + tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); + return; + } + + if (!QFile::copy(file, image_path)) { + QMessageBox::warning(this, tr("Error copying user image"), + tr("Unable to copy image from %1 to %2").arg(file, image_path)); + return; + } + + const auto username = GetAccountUsername(*profile_manager, *uuid); + item_model->setItem(index, 0, + new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); + UpdateCurrentUser(); +} diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h new file mode 100644 index 000000000..7fe95a2a8 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -0,0 +1,57 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QList> +#include <QWidget> + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Service::Account { +class ProfileManager; +} + +namespace Ui { +class ConfigureProfileManager; +} + +class ConfigureProfileManager : public QWidget { + Q_OBJECT + +public: + explicit ConfigureProfileManager(QWidget* parent = nullptr); + ~ConfigureProfileManager() override; + + void applyConfiguration(); + void setConfiguration(); + +private: + void PopulateUserList(); + void UpdateCurrentUser(); + + void SelectUser(const QModelIndex& index); + void AddUser(); + void RenameUser(); + void DeleteUser(); + void SetUserImage(); + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + std::unique_ptr<Ui::ConfigureProfileManager> ui; + bool enabled = false; + + std::unique_ptr<Service::Account::ProfileManager> profile_manager; +}; diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui new file mode 100644 index 000000000..dedba4998 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.ui @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureProfileManager</class> + <widget class="QWidget" name="ConfigureProfileManager"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>366</width> + <height>483</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Profile Manager</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetNoConstraint</enum> + </property> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Current User</string> + </property> + </widget> + </item> + <item> + <widget class="QGraphicsView" name="current_user_icon"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="current_user_username"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Username</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="widgetResizable"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="pm_set_image"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Set Image</string> + </property> + </widget> + </item> + <item> + <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> + <widget class="QPushButton" name="pm_add"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_rename"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Rename</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_remove"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_disable_info"> + <property name="text"> + <string>Profile management is available only when game is not running.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index ab5d46492..94e27349d 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -15,7 +15,6 @@ #include "common/file_util.h" #include "common/string_util.h" #include "core/core.h" -#include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configure_system.h" @@ -36,64 +35,9 @@ constexpr std::array<int, 12> days_in_month = {{ 30, 31, }}; - -// 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 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); -} - -QString GetAccountUsername(const Service::Account::ProfileManager& manager, - Service::Account::UUID uuid) { - Service::Account::ProfileBase profile; - if (!manager.GetProfileBase(uuid, profile)) { - return {}; - } - - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); - return QString::fromStdString(text); -} - -QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { - return ConfigureSystem::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())); -} - -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); -} - -QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { - return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"), - description_text, 1, - static_cast<int>(Service::Account::profile_username_size)); -} } // Anonymous namespace -ConfigureSystem::ConfigureSystem(QWidget* parent) - : QWidget(parent), ui(new Ui::ConfigureSystem), - profile_manager(std::make_unique<Service::Account::ProfileManager>()) { +ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { ui->setupUi(this); connect(ui->combo_birthmonth, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, @@ -101,50 +45,17 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::RefreshConsoleID); - 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); - - ui->scrollArea->setLayout(layout); - - connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser); - - connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser); - connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser); - connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser); - connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage); - connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { ui->rng_seed_edit->setEnabled(checked); if (!checked) ui->rng_seed_edit->setText(QStringLiteral("00000000")); }); - scene = new QGraphicsScene; - ui->current_user_icon->setScene(scene); + connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { + ui->custom_rtc_edit->setEnabled(checked); + if (!checked) + ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); + }); this->setConfiguration(); } @@ -156,49 +67,19 @@ void ConfigureSystem::setConfiguration() { ui->combo_language->setCurrentIndex(Settings::values.language_index); - item_model->removeRows(0, item_model->rowCount()); - list_items.clear(); - - PopulateUserList(); - UpdateCurrentUser(); - ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); const auto rng_seed = QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper(); ui->rng_seed_edit->setText(rng_seed); -} - -void ConfigureSystem::PopulateUserList() { - 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); -} - -void ConfigureSystem::UpdateCurrentUser() { - ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); - - const auto& current_user = profile_manager->GetUser(Settings::values.current_user); - ASSERT(current_user); - const auto username = GetAccountUsername(*profile_manager, *current_user); + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); - scene->clear(); - scene->addPixmap( - GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - ui->current_user_username->setText(username); + const auto rtc_time = Settings::values.custom_rtc.value_or( + std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); } void ConfigureSystem::ReadSystemSettings() {} @@ -214,6 +95,12 @@ void ConfigureSystem::applyConfiguration() { else Settings::values.rng_seed = std::nullopt; + if (ui->custom_rtc_checkbox->isChecked()) + Settings::values.custom_rtc = + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); + else + Settings::values.custom_rtc = std::nullopt; + Settings::Apply(); } @@ -256,130 +143,3 @@ void ConfigureSystem::RefreshConsoleID() { ui->label_console_id->setText( tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } - -void ConfigureSystem::SelectUser(const QModelIndex& index) { - Settings::values.current_user = - std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); - - UpdateCurrentUser(); - - ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); - ui->pm_rename->setEnabled(true); - ui->pm_set_image->setEnabled(true); -} - -void ConfigureSystem::AddUser() { - const auto username = - GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); - if (username.isEmpty()) { - return; - } - - const auto uuid = Service::Account::UUID::Generate(); - profile_manager->CreateNewUser(uuid, username.toStdString()); - - item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); -} - -void ConfigureSystem::RenameUser() { - const auto user = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(user); - ASSERT(uuid); - - Service::Account::ProfileBase profile; - if (!profile_manager->GetProfileBase(*uuid, profile)) - return; - - const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); - if (new_username.isEmpty()) { - return; - } - - const auto username_std = new_username.toStdString(); - std::fill(profile.username.begin(), profile.username.end(), '\0'); - std::copy(username_std.begin(), username_std.end(), profile.username.begin()); - - profile_manager->SetProfileBase(*uuid, profile); - - item_model->setItem( - user, 0, - new QStandardItem{GetIcon(*uuid), - FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); - UpdateCurrentUser(); -} - -void ConfigureSystem::DeleteUser() { - const auto index = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(index); - ASSERT(uuid); - const auto username = GetAccountUsername(*profile_manager, *uuid); - - const auto confirm = QMessageBox::question( - this, tr("Confirm Delete"), - tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); - - if (confirm == QMessageBox::No) - return; - - if (Settings::values.current_user == tree_view->currentIndex().row()) - Settings::values.current_user = 0; - UpdateCurrentUser(); - - if (!profile_manager->RemoveUser(*uuid)) - return; - - item_model->removeRows(tree_view->currentIndex().row(), 1); - tree_view->clearSelection(); - - ui->pm_remove->setEnabled(false); - ui->pm_rename->setEnabled(false); -} - -void ConfigureSystem::SetUserImage() { - const auto index = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(index); - ASSERT(uuid); - - const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), - tr("JPEG Images (*.jpg *.jpeg)")); - - if (file.isEmpty()) { - return; - } - - const auto image_path = GetImagePath(*uuid); - if (QFile::exists(image_path) && !QFile::remove(image_path)) { - QMessageBox::warning( - this, tr("Error deleting image"), - tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); - return; - } - - const auto raw_path = QString::fromStdString( - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); - const QFileInfo raw_info{raw_path}; - if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { - QMessageBox::warning(this, tr("Error deleting file"), - tr("Unable to delete existing file: %1.").arg(raw_path)); - return; - } - - const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); - if (!QDir{raw_path}.mkpath(absolute_dst_path)) { - QMessageBox::warning( - this, tr("Error creating user image directory"), - tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); - return; - } - - if (!QFile::copy(file, image_path)) { - QMessageBox::warning(this, tr("Error copying user image"), - tr("Unable to copy image from %1 to %2").arg(file, image_path)); - return; - } - - const auto username = GetAccountUsername(*profile_manager, *uuid); - item_model->setItem(index, 0, - new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); - UpdateCurrentUser(); -} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 07764e1f7..cf1e54de5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -9,16 +9,6 @@ #include <QList> #include <QWidget> -class QGraphicsScene; -class QStandardItem; -class QStandardItemModel; -class QTreeView; -class QVBoxLayout; - -namespace Service::Account { -class ProfileManager; -} - namespace Ui { class ConfigureSystem; } @@ -39,21 +29,6 @@ private: void UpdateBirthdayComboBox(int birthmonth_index); void RefreshConsoleID(); - void PopulateUserList(); - void UpdateCurrentUser(); - void SelectUser(const QModelIndex& index); - void AddUser(); - void RenameUser(); - void DeleteUser(); - void SetUserImage(); - - QVBoxLayout* layout; - QTreeView* tree_view; - QStandardItemModel* item_model; - QGraphicsScene* scene; - - std::vector<QList<QStandardItem*>> list_items; - std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; @@ -61,6 +36,4 @@ private: int birthday = 0; int language_index = 0; int sound_index = 0; - - std::unique_ptr<Service::Account::ProfileManager> profile_manager; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index a91580893..073327298 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -22,6 +22,13 @@ <string>System Settings</string> </property> <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0"> + <widget class="QLabel" name="label_sound"> + <property name="text"> + <string>Sound output mode</string> + </property> + </widget> + </item> <item row="1" column="1"> <widget class="QComboBox" name="combo_language"> <property name="toolTip"> @@ -114,27 +121,6 @@ </item> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_console_id"> - <property name="text"> - <string>Console ID:</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_sound"> - <property name="text"> - <string>Sound output mode</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_birthday"> - <property name="text"> - <string>Birthday</string> - </property> - </widget> - </item> <item row="0" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> <item> @@ -206,6 +192,20 @@ </item> </layout> </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_console_id"> + <property name="text"> + <string>Console ID:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_birthday"> + <property name="text"> + <string>Birthday</string> + </property> + </widget> + </item> <item row="3" column="1"> <widget class="QPushButton" name="button_regenerate_console_id"> <property name="sizePolicy"> @@ -241,21 +241,21 @@ </item> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_language"> + <item row="5" column="0"> + <widget class="QCheckBox" name="rng_seed_checkbox"> <property name="text"> - <string>Language</string> + <string>RNG Seed</string> </property> </widget> </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="rng_seed_checkbox"> + <item row="1" column="0"> + <widget class="QLabel" name="label_language"> <property name="text"> - <string>RNG Seed</string> + <string>Language</string> </property> </widget> </item> - <item row="4" column="1"> + <item row="5" column="1"> <widget class="QLineEdit" name="rng_seed_edit"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> @@ -276,147 +276,44 @@ </property> </widget> </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="gridGroupBox"> - <property name="title"> - <string>Profile Manager</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <property name="sizeConstraint"> - <enum>QLayout::SetNoConstraint</enum> - </property> - <item row="0" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Current User</string> - </property> - </widget> - </item> - <item> - <widget class="QGraphicsView" name="current_user_icon"> - <property name="minimumSize"> - <size> - <width>48</width> - <height>48</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>48</width> - <height>48</height> - </size> - </property> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="interactive"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="current_user_username"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Username</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QScrollArea" name="scrollArea"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="4" column="0"> + <widget class="QCheckBox" name="custom_rtc_checkbox"> + <property name="text"> + <string>Custom RTC</string> </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> + </widget> + </item> + <item row="4" column="1"> + <widget class="QDateTimeEdit" name="custom_rtc_edit"> + <property name="minimumDate"> + <date> + <year>1970</year> + <month>1</month> + <day>1</day> + </date> </property> - <property name="widgetResizable"> - <bool>false</bool> + <property name="displayFormat"> + <string>d MMM yyyy h:mm:ss AP</string> </property> </widget> </item> - <item row="2" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QPushButton" name="pm_set_image"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Set Image</string> - </property> - </widget> - </item> - <item> - <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> - <widget class="QPushButton" name="pm_add"> - <property name="text"> - <string>Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pm_rename"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Rename</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pm_remove"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - </layout> - </item> </layout> </widget> </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> + <item> <widget class="QLabel" name="label_disable_info"> <property name="text"> <string>System settings are available only when game is not running.</string> diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 3c2ccb76f..18566d028 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -89,12 +89,11 @@ void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); - ui->button_verify_login->setText(tr("Verifying")); - verify_watcher.setFuture( - QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), - token = ui->edit_token->text().toStdString()]() { - return Core::VerifyLogin(username, token); - })); + ui->button_verify_login->setText(tr("Verifying...")); + verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(), + token = ui->edit_token->text().toStdString()] { + return Core::VerifyLogin(username, token); + })); } void ConfigureWeb::OnLoginVerified() { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 8e9524fd6..c0e3c5fa9 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -376,7 +376,7 @@ void GameList::LoadCompatibilityList() { QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); QJsonArray arr = json.array(); - for (const QJsonValueRef& value : arr) { + for (const QJsonValueRef value : arr) { QJsonObject game = value.toObject(); if (game.contains("compatibility") && game["compatibility"].isDouble()) { @@ -384,7 +384,7 @@ void GameList::LoadCompatibilityList() { QString directory = game["directory"].toString(); QJsonArray ids = game["releases"].toArray(); - for (const QJsonValueRef& id_ref : ids) { + for (const QJsonValueRef id_ref : ids) { QJsonObject id_object = id_ref.toObject(); QString id = id_object["id"].toString(); compatibility_list.emplace( diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1d5a2b51a..f564de994 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -10,11 +10,14 @@ // 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 "applets/web_browser.h" #include "configuration/configure_per_general.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. @@ -96,6 +99,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/discord_impl.h" #endif +#ifdef YUZU_USE_QT_WEB_ENGINE +#include <QWebEngineProfile> +#include <QWebEngineScript> +#include <QWebEngineScriptCollection> +#include <QWebEngineSettings> +#include <QWebEngineView> +#endif + #ifdef QT_STATICPLUGIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif @@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message emit SoftwareKeyboardFinishedCheckDialog(); } +#ifdef YUZU_USE_QT_WEB_ENGINE + +void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { + NXInputWebEngineView web_browser_view(this); + + // Scope to contain the QProgressDialog for initalization + { + QProgressDialog progress(this); + progress.setMinimumDuration(200); + progress.setLabelText(tr("Loading Web Applet...")); + progress.setRange(0, 4); + progress.setValue(0); + progress.show(); + + auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); }); + + while (!future.isFinished()) + QApplication::processEvents(); + + progress.setValue(1); + + // Load the special shim script to handle input and exit. + QWebEngineScript nx_shim; + nx_shim.setSourceCode(GetNXShimInjectionScript()); + nx_shim.setWorldId(QWebEngineScript::MainWorld); + nx_shim.setName("nx_inject.js"); + nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); + nx_shim.setRunsOnSubFrames(true); + web_browser_view.page()->profile()->scripts()->insert(nx_shim); + + web_browser_view.load( + QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() + + QString::fromStdString(std::string(additional_args)))); + + progress.setValue(2); + + render_window->hide(); + web_browser_view.setFocus(); + + const auto& layout = render_window->GetFramebufferLayout(); + web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); + web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); + web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / + Layout::ScreenUndocked::Width); + web_browser_view.settings()->setAttribute( + QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + + web_browser_view.show(); + + progress.setValue(3); + + QApplication::processEvents(); + + progress.setValue(4); + } + + bool finished = false; + QAction* exit_action = new QAction(tr("Exit Web Applet"), this); + connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; }); + ui.menubar->addAction(exit_action); + + auto& npad = + Core::System::GetInstance() + .ServiceManager() + .GetService<Service::HID::Hid>("hid") + ->GetAppletResource() + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + + const auto fire_js_keypress = [&web_browser_view](u32 key_code) { + web_browser_view.page()->runJavaScript( + QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") + .arg(QString::fromStdString(std::to_string(key_code)))); + }; + + bool running_exit_check = false; + while (!finished) { + QApplication::processEvents(); + + if (!running_exit_check) { + web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"), + [&](const QVariant& res) { + running_exit_check = false; + if (res.toBool()) + finished = true; + }); + running_exit_check = true; + } + + const auto input = npad.GetAndResetPressState(); + for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) { + if ((input & (1 << i)) != 0) { + LOG_DEBUG(Frontend, "firing input for button id={:02X}", i); + web_browser_view.page()->runJavaScript( + QStringLiteral("yuzu_key_callbacks[%1]();").arg(i)); + } + } + + if (input & 0x00888000) // RStick Down | LStick Down | DPad Down + fire_js_keypress(40); // Down Arrow Key + else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right + fire_js_keypress(39); // Right Arrow Key + else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up + fire_js_keypress(38); // Up Arrow Key + else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left + fire_js_keypress(37); // Left Arrow Key + else if (input & 0x00000001) // A Button + fire_js_keypress(13); // Enter Key + } + + web_browser_view.hide(); + render_window->show(); + render_window->setFocus(); + ui.menubar->removeAction(exit_action); + + // Needed to update render window focus/show and remove menubar action + QApplication::processEvents(); + emit WebBrowserFinishedBrowsing(); +} + +#else + +void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { + QMessageBox::warning( + this, tr("Web Applet"), + tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot " + "properly display the game manual or web page requested."), + QMessageBox::Ok, QMessageBox::Ok); + + LOG_INFO(Frontend, + "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at " + "'{}' with arguments '{}'!", + filename, additional_args); + + emit WebBrowserFinishedBrowsing(); +} + +#endif + void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui.action_Report_Compatibility->setVisible(true); @@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this)); system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); + system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this)); const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -1325,6 +1475,7 @@ void GMainWindow::OnStartGame() { qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); + qRegisterMetaType<std::string_view>("std::string_view"); connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index d560bf75b..2d705ad54 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -13,6 +13,7 @@ #include "common/common_types.h" #include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" #include "ui_main.h" #include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" @@ -26,6 +27,7 @@ class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; +class QLabel; class WaitTreeWidget; enum class GameListOpenTarget; @@ -103,11 +105,16 @@ signals: void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); + void WebBrowserUnpackRomFS(); + void WebBrowserFinishedBrowsing(); + public slots: void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); + void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); + private: void InitializeWidgets(); void InitializeDebugWidgets(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index fe0d1eebf..7a77f76e8 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -325,13 +325,21 @@ void Config::ReadValues() { Settings::values.current_user = std::clamp<int>( sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); - const auto enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); - if (enabled) { + const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); + if (rng_seed_enabled) { Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); } else { Settings::values.rng_seed = std::nullopt; } + const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); + if (custom_rtc_enabled) { + Settings::values.custom_rtc = + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); + } else { + Settings::values.custom_rtc = std::nullopt; + } + // Core Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 0f3f8da50..ba51a4a51 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -183,6 +183,12 @@ enable_nfc = rng_seed_enabled = rng_seed = +# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service +# This will auto-increment, with the time set being the time the game is started +# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used +custom_rtc_enabled = +custom_rtc = + # Sets the account username, max length is 32 characters # yuzu (default) username = yuzu |