Kaynağa Gözat

Merge pull request #1939 from DarkLordZach/web-applet

applets: Implement HLE web browser applet (LibAppletOff)
bunnei 7 yıl önce
ebeveyn
işleme
83e8ad2331

+ 2 - 2
.travis/linux/docker.sh

@@ -1,12 +1,12 @@
 #!/bin/bash -ex
 #!/bin/bash -ex
 
 
 apt-get update
 apt-get update
-apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
+apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev qtwebengine5-dev wget cmake ninja-build ccache
 
 
 cd /yuzu
 cd /yuzu
 
 
 mkdir build && cd build
 mkdir build && cd build
-cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
+cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
 ninja
 ninja
 
 
 ccache -s
 ccache -s

+ 1 - 1
.travis/macos/build.sh

@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
 
 
 mkdir build && cd build
 mkdir build && cd build
 cmake --version
 cmake --version
-cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
+cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
 make -j4
 make -j4
 
 
 ccache -s
 ccache -s

+ 7 - 1
CMakeLists.txt

@@ -19,6 +19,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
 
 
 option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
 option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
 
 
+option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
+
 option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
 option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
 
 
 option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
 option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
@@ -302,7 +304,7 @@ endif()
 if (ENABLE_QT)
 if (ENABLE_QT)
     if (YUZU_USE_BUNDLED_QT)
     if (YUZU_USE_BUNDLED_QT)
         if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
         if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
-            set(QT_VER qt-5.10.0-msvc2015_64)
+            set(QT_VER qt-5.12.0-msvc2017_64)
         else()
         else()
             message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
             message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
         endif()
         endif()
@@ -319,6 +321,10 @@ if (ENABLE_QT)
     endif()
     endif()
 
 
     find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
     find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
+
+    if (YUZU_USE_QT_WEB_ENGINE)
+        find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets ${QT_PREFIX_HINT})
+    endif ()
 endif()
 endif()
 
 
 # Platform-specific library requirements
 # Platform-specific library requirements

+ 26 - 0
CMakeModules/CopyYuzuQt5Deps.cmake

@@ -5,6 +5,7 @@ function(copy_yuzu_Qt5_deps target_dir)
     set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
     set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
     set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
     set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
     set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
     set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
+    set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
     set(PLATFORMS ${DLL_DEST}platforms/)
     set(PLATFORMS ${DLL_DEST}platforms/)
     set(STYLES ${DLL_DEST}styles/)
     set(STYLES ${DLL_DEST}styles/)
     set(IMAGEFORMATS ${DLL_DEST}imageformats/)
     set(IMAGEFORMATS ${DLL_DEST}imageformats/)
@@ -17,6 +18,31 @@ function(copy_yuzu_Qt5_deps target_dir)
         Qt5OpenGL$<$<CONFIG:Debug>:d>.*
         Qt5OpenGL$<$<CONFIG:Debug>:d>.*
         Qt5Widgets$<$<CONFIG:Debug>:d>.*
         Qt5Widgets$<$<CONFIG:Debug>:d>.*
     )
     )
+
+    if (YUZU_USE_QT_WEB_ENGINE)
+        windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST} 
+            Qt5Network$<$<CONFIG:Debug>:d>.*
+            Qt5Positioning$<$<CONFIG:Debug>:d>.*
+            Qt5PrintSupport$<$<CONFIG:Debug>:d>.*
+            Qt5Qml$<$<CONFIG:Debug>:d>.*
+            Qt5Quick$<$<CONFIG:Debug>:d>.*
+            Qt5QuickWidgets$<$<CONFIG:Debug>:d>.*
+            Qt5WebChannel$<$<CONFIG:Debug>:d>.*
+            Qt5WebEngine$<$<CONFIG:Debug>:d>.*
+            Qt5WebEngineCore$<$<CONFIG:Debug>:d>.*
+            Qt5WebEngineWidgets$<$<CONFIG:Debug>:d>.*
+            QtWebEngineProcess$<$<CONFIG:Debug>:d>.*
+        )
+
+        windows_copy_files(${target_dir} ${Qt5_RESOURCES_DIR} ${DLL_DEST}
+            qtwebengine_resources.pak
+            qtwebengine_devtools_resources.pak
+            qtwebengine_resources_100p.pak
+            qtwebengine_resources_200p.pak
+            icudtl.dat
+        )
+    endif ()
+
     windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
     windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)

+ 2 - 1
appveyor.yml

@@ -42,7 +42,7 @@ before_build:
         $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
         $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
         if ($env:BUILD_TYPE -eq 'msvc') {
         if ($env:BUILD_TYPE -eq 'msvc') {
           # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
           # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
-          cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
+          cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
         } else {
         } else {
           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
         }
         }
@@ -94,6 +94,7 @@ after_build:
           Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
           Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
           rm "$RELEASE_DIST\*.exe"
           rm "$RELEASE_DIST\*.exe"
           Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
           Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
+          Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
           Copy-Item .\license.txt -Destination $RELEASE_DIST
           Copy-Item .\license.txt -Destination $RELEASE_DIST
           Copy-Item .\README.md -Destination $RELEASE_DIST
           Copy-Item .\README.md -Destination $RELEASE_DIST
           7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
           7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*

+ 4 - 0
src/core/CMakeLists.txt

@@ -88,6 +88,8 @@ add_library(core STATIC
     frontend/applets/profile_select.h
     frontend/applets/profile_select.h
     frontend/applets/software_keyboard.cpp
     frontend/applets/software_keyboard.cpp
     frontend/applets/software_keyboard.h
     frontend/applets/software_keyboard.h
+    frontend/applets/web_browser.cpp
+    frontend/applets/web_browser.h
     frontend/emu_window.cpp
     frontend/emu_window.cpp
     frontend/emu_window.h
     frontend/emu_window.h
     frontend/framebuffer_layout.cpp
     frontend/framebuffer_layout.cpp
@@ -173,6 +175,8 @@ add_library(core STATIC
     hle/service/am/applets/software_keyboard.h
     hle/service/am/applets/software_keyboard.h
     hle/service/am/applets/stub_applet.cpp
     hle/service/am/applets/stub_applet.cpp
     hle/service/am/applets/stub_applet.h
     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.cpp
     hle/service/am/idle.h
     hle/service/am/idle.h
     hle/service/am/omm.cpp
     hle/service/am/omm.cpp

+ 18 - 0
src/core/core.cpp

@@ -31,7 +31,9 @@
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
 #include "core/telemetry_session.h"
+#include "frontend/applets/profile_select.h"
 #include "frontend/applets/software_keyboard.h"
 #include "frontend/applets/software_keyboard.h"
+#include "frontend/applets/web_browser.h"
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
@@ -103,6 +105,8 @@ struct System::Impl {
             profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
             profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
         if (software_keyboard == nullptr)
         if (software_keyboard == nullptr)
             software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
             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");
         auto main_process = Kernel::Process::Create(kernel, "main");
         kernel.MakeCurrentProcess(main_process.get());
         kernel.MakeCurrentProcess(main_process.get());
@@ -199,6 +203,11 @@ struct System::Impl {
         // Close app loader
         // Close app loader
         app_loader.reset();
         app_loader.reset();
 
 
+        // Clear all applets
+        profile_selector.reset();
+        software_keyboard.reset();
+        web_browser.reset();
+
         LOG_DEBUG(Core, "Shutdown OK");
         LOG_DEBUG(Core, "Shutdown OK");
     }
     }
 
 
@@ -233,6 +242,7 @@ struct System::Impl {
     /// Frontend applets
     /// Frontend applets
     std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
     std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
     std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
     std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
+    std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
 
 
     /// Service manager
     /// Service manager
     std::shared_ptr<Service::SM::ServiceManager> service_manager;
     std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -443,6 +453,14 @@ const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() cons
     return *impl->software_keyboard;
     return *impl->software_keyboard;
 }
 }
 
 
+void System::SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet) {
+    impl->web_browser = std::move(applet);
+}
+
+const Core::Frontend::WebBrowserApplet& System::GetWebBrowser() const {
+    return *impl->web_browser;
+}
+
 System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
 System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     return impl->Init(*this, emu_window);
     return impl->Init(*this, emu_window);
 }
 }

+ 6 - 1
src/core/core.h

@@ -11,11 +11,12 @@
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/file_sys/vfs_types.h"
 #include "core/file_sys/vfs_types.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/object.h"
-#include "frontend/applets/profile_select.h"
 
 
 namespace Core::Frontend {
 namespace Core::Frontend {
 class EmuWindow;
 class EmuWindow;
+class ProfileSelectApplet;
 class SoftwareKeyboardApplet;
 class SoftwareKeyboardApplet;
+class WebBrowserApplet;
 } // namespace Core::Frontend
 } // namespace Core::Frontend
 
 
 namespace FileSys {
 namespace FileSys {
@@ -250,6 +251,10 @@ public:
 
 
     const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
     const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
 
 
+    void SetWebBrowser(std::unique_ptr<Core::Frontend::WebBrowserApplet> applet);
+
+    const Core::Frontend::WebBrowserApplet& GetWebBrowser() const;
+
 private:
 private:
     System();
     System();
 
 

+ 3 - 0
src/core/file_sys/romfs.cpp

@@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
 
 
     VirtualDir out = std::move(root);
     VirtualDir out = std::move(root);
 
 
+    if (type == RomFSExtractionType::SingleDiscard)
+        return out->GetSubdirectories().front();
+
     while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
     while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
         if (out->GetSubdirectories().front()->GetName() == "data" &&
         if (out->GetSubdirectories().front()->GetName() == "data" &&
             type == RomFSExtractionType::Truncated)
             type == RomFSExtractionType::Truncated)

+ 3 - 2
src/core/file_sys/romfs.h

@@ -33,8 +33,9 @@ struct IVFCHeader {
 static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
 static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
 
 
 enum class RomFSExtractionType {
 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
 // Converts a RomFS binary blob to VFS Filesystem

+ 24 - 0
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) const {
+    LOG_INFO(Service_AM,
+             "(STUBBED) called - No suitable web browser implementation found to open website page "
+             "at '{}'!",
+             filename);
+    finished_callback();
+}
+
+} // namespace Core::Frontend

+ 28 - 0
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) const = 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) const override;
+};
+
+} // namespace Core::Frontend

+ 8 - 4
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/profile_select.h"
 #include "core/hle/service/am/applets/software_keyboard.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/stub_applet.h"
+#include "core/hle/service/am/applets/web_browser.h"
 #include "core/hle/service/am/idle.h"
 #include "core/hle/service/am/idle.h"
 #include "core/hle/service/am/omm.h"
 #include "core/hle/service/am/omm.h"
 #include "core/hle/service/am/spsm.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 {
 enum class AppletId : u32 {
     ProfileSelect = 0x10,
     ProfileSelect = 0x10,
     SoftwareKeyboard = 0x11,
     SoftwareKeyboard = 0x11,
+    LibAppletOff = 0x17,
 };
 };
 
 
 constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
 constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
@@ -730,10 +732,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     IPC::RequestParser rp{ctx};
 
 
     const u64 offset{rp.Pop<u64>()};
     const u64 offset{rp.Pop<u64>()};
-    LOG_DEBUG(Service_AM, "called, offset={}", offset);
-
     const std::vector<u8> data{ctx.ReadBuffer()};
     const std::vector<u8> data{ctx.ReadBuffer()};
 
 
+    LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
+
     if (data.size() > backing.buffer.size() - offset) {
     if (data.size() > backing.buffer.size() - offset) {
         LOG_ERROR(Service_AM,
         LOG_ERROR(Service_AM,
                   "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
                   "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};
     IPC::RequestParser rp{ctx};
 
 
     const u64 offset{rp.Pop<u64>()};
     const u64 offset{rp.Pop<u64>()};
-    LOG_DEBUG(Service_AM, "called, offset={}", offset);
-
     const std::size_t size{ctx.GetWriteBufferSize()};
     const std::size_t size{ctx.GetWriteBufferSize()};
 
 
+    LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
+
     if (size > backing.buffer.size() - offset) {
     if (size > backing.buffer.size() - offset) {
         LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
         LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
                   backing.buffer.size(), 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>();
         return std::make_shared<Applets::ProfileSelect>();
     case AppletId::SoftwareKeyboard:
     case AppletId::SoftwareKeyboard:
         return std::make_shared<Applets::SoftwareKeyboard>();
         return std::make_shared<Applets::SoftwareKeyboard>();
+    case AppletId::LibAppletOff:
+        return std::make_shared<Applets::WebBrowser>();
     default:
     default:
         LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
         LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
                   static_cast<u32>(id));
                   static_cast<u32>(id));

+ 1 - 1
src/core/hle/service/am/applets/profile_select.cpp

@@ -7,7 +7,7 @@
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/string_util.h"
 #include "common/string_util.h"
 #include "core/core.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/am.h"
 #include "core/hle/service/am/applets/profile_select.h"
 #include "core/hle/service/am/applets/profile_select.h"
 
 

+ 184 - 0
src/core/hle/service/am/applets/web_browser.cpp

@@ -0,0 +1,184 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_paths.h"
+#include "common/hex_util.h"
+#include "common/logging/backend.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/romfs_factory.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;
+    }
+
+    const 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

+ 44 - 0
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

+ 6 - 0
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.pad_states.raw = pad_state.pad_states.raw;
         libnx_entry.pad.l_stick = pad_state.l_stick;
         libnx_entry.pad.l_stick = pad_state.l_stick;
         libnx_entry.pad.r_stick = pad_state.r_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(),
     std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
                 shared_memory_entries.size() * sizeof(NPadEntry));
                 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 {
 bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
     const bool support_handheld =
     const bool support_handheld =
         std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=
         std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=

+ 6 - 0
src/core/hle/service/hid/controllers/npad.h

@@ -124,6 +124,10 @@ public:
     void ConnectAllDisconnectedControllers();
     void ConnectAllDisconnectedControllers();
     void ClearAllControllers();
     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 std::size_t NPadIdToIndex(u32 npad_id);
     static u32 IndexToNPad(std::size_t index);
     static u32 IndexToNPad(std::size_t index);
 
 
@@ -292,6 +296,8 @@ private:
         bool is_connected;
         bool is_connected;
     };
     };
 
 
+    u32 press_state{};
+
     NPadType style{};
     NPadType style{};
     std::array<NPadEntry, 10> shared_memory_entries{};
     std::array<NPadEntry, 10> shared_memory_entries{};
     std::array<
     std::array<

+ 539 - 578
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 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
 constexpr u64 gyroscope_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;
 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> {
 class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
 public:
 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> {
 class HidDbg final : public ServiceFramework<HidDbg> {
 public:
 public:

+ 110 - 0
src/core/hle/service/hid/hid.h

@@ -4,12 +4,122 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "controllers/controller_base.h"
+#include "core/hle/service/service.h"
+
+namespace CoreTiming {
+struct EventType;
+}
+
+namespace Kernel {
+class SharedMemory;
+}
+
 namespace SM {
 namespace SM {
 class ServiceManager;
 class ServiceManager;
 }
 }
 
 
 namespace Service::HID {
 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
 /// Reload input devices. Used when input configuration changed
 void ReloadInputDevices();
 void ReloadInputDevices();
 
 

+ 9 - 0
src/core/loader/loader.h

@@ -259,6 +259,15 @@ public:
         return ResultStatus::ErrorNotImplemented;
         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:
 protected:
     FileSys::VirtualFile file;
     FileSys::VirtualFile file;
     bool is_loaded = false;
     bool is_loaded = false;

+ 8 - 0
src/core/loader/nsp.cpp

@@ -158,4 +158,12 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
     nacp = *nacp_file;
     nacp = *nacp_file;
     return ResultStatus::Success;
     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;
+}
 } // namespace Loader
 } // namespace Loader

+ 1 - 0
src/core/loader/nsp.h

@@ -44,6 +44,7 @@ public:
     ResultStatus ReadIcon(std::vector<u8>& buffer) override;
     ResultStatus ReadIcon(std::vector<u8>& buffer) override;
     ResultStatus ReadTitle(std::string& title) override;
     ResultStatus ReadTitle(std::string& title) override;
     ResultStatus ReadControlData(FileSys::NACP& nacp) override;
     ResultStatus ReadControlData(FileSys::NACP& nacp) override;
+    ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
 
 
 private:
 private:
     std::unique_ptr<FileSys::NSP> nsp;
     std::unique_ptr<FileSys::NSP> nsp;

+ 9 - 0
src/core/loader/xci.cpp

@@ -128,4 +128,13 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
     return ResultStatus::Success;
     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;
+}
+
 } // namespace Loader
 } // namespace Loader

+ 1 - 0
src/core/loader/xci.h

@@ -44,6 +44,7 @@ public:
     ResultStatus ReadIcon(std::vector<u8>& buffer) override;
     ResultStatus ReadIcon(std::vector<u8>& buffer) override;
     ResultStatus ReadTitle(std::string& title) override;
     ResultStatus ReadTitle(std::string& title) override;
     ResultStatus ReadControlData(FileSys::NACP& control) override;
     ResultStatus ReadControlData(FileSys::NACP& control) override;
+    ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
 
 
 private:
 private:
     std::unique_ptr<FileSys::XCI> xci;
     std::unique_ptr<FileSys::XCI> xci;

+ 7 - 0
src/yuzu/CMakeLists.txt

@@ -11,6 +11,8 @@ add_executable(yuzu
     applets/profile_select.h
     applets/profile_select.h
     applets/software_keyboard.cpp
     applets/software_keyboard.cpp
     applets/software_keyboard.h
     applets/software_keyboard.h
+    applets/web_browser.cpp
+    applets/web_browser.h
     bootmanager.cpp
     bootmanager.cpp
     bootmanager.h
     bootmanager.h
     compatibility_list.cpp
     compatibility_list.cpp
@@ -157,6 +159,11 @@ if (USE_DISCORD_PRESENCE)
     target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
     target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
 endif()
 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)
 if(UNIX AND NOT APPLE)
     install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
     install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
 endif()
 endif()

+ 113 - 0
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) const {
+    this->unpack_romfs_callback = unpack_romfs_callback;
+    this->finished_callback = 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();
+}

+ 53 - 0
src/yuzu/applets/web_browser.h

@@ -0,0 +1,53 @@
+// 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) const override;
+
+signals:
+    void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
+
+public slots:
+    void MainWindowUnpackRomFS();
+    void MainWindowFinishedBrowsing();
+
+private:
+    mutable std::function<void()> unpack_romfs_callback;
+    mutable std::function<void()> finished_callback;
+};

+ 151 - 0
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.
 // VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
 #include "applets/profile_select.h"
 #include "applets/profile_select.h"
 #include "applets/software_keyboard.h"
 #include "applets/software_keyboard.h"
+#include "applets/web_browser.h"
 #include "configuration/configure_per_general.h"
 #include "configuration/configure_per_general.h"
 #include "core/file_sys/vfs.h"
 #include "core/file_sys/vfs.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/am/applets/applets.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
 // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
 // defines.
 // defines.
@@ -96,6 +99,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "yuzu/discord_impl.h"
 #include "yuzu/discord_impl.h"
 #endif
 #endif
 
 
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+#endif
+
 #ifdef QT_STATICPLUGIN
 #ifdef QT_STATICPLUGIN
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
 #endif
 #endif
@@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
     emit SoftwareKeyboardFinishedCheckDialog();
     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() {
 void GMainWindow::InitializeWidgets() {
 #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
 #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
     ui.action_Report_Compatibility->setVisible(true);
     ui.action_Report_Compatibility->setVisible(true);
@@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
 
 
     system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
     system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
     system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*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())};
     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<Core::System::ResultStatus>("Core::System::ResultStatus");
     qRegisterMetaType<std::string>("std::string");
     qRegisterMetaType<std::string>("std::string");
     qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
     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);
     connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
 
 

+ 7 - 0
src/yuzu/main.h

@@ -13,6 +13,7 @@
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
 #include "ui_main.h"
 #include "ui_main.h"
 #include "yuzu/compatibility_list.h"
 #include "yuzu/compatibility_list.h"
 #include "yuzu/hotkeys.h"
 #include "yuzu/hotkeys.h"
@@ -26,6 +27,7 @@ class GraphicsSurfaceWidget;
 class GRenderWindow;
 class GRenderWindow;
 class MicroProfileDialog;
 class MicroProfileDialog;
 class ProfilerWidget;
 class ProfilerWidget;
+class QLabel;
 class WaitTreeWidget;
 class WaitTreeWidget;
 enum class GameListOpenTarget;
 enum class GameListOpenTarget;
 
 
@@ -103,11 +105,16 @@ signals:
     void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
     void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
     void SoftwareKeyboardFinishedCheckDialog();
     void SoftwareKeyboardFinishedCheckDialog();
 
 
+    void WebBrowserUnpackRomFS();
+    void WebBrowserFinishedBrowsing();
+
 public slots:
 public slots:
     void ProfileSelectorSelectProfile();
     void ProfileSelectorSelectProfile();
     void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
     void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
     void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
     void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
 
 
+    void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
+
 private:
 private:
     void InitializeWidgets();
     void InitializeWidgets();
     void InitializeDebugWidgets();
     void InitializeDebugWidgets();