summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-x.travis/linux/docker.sh4
-rwxr-xr-x.travis/macos/build.sh2
-rw-r--r--CMakeLists.txt8
-rw-r--r--CMakeModules/CopyYuzuQt5Deps.cmake26
-rw-r--r--appveyor.yml3
-rw-r--r--src/audio_core/time_stretch.cpp4
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/core.cpp36
-rw-r--r--src/core/core.h16
-rw-r--r--src/core/file_sys/content_archive.cpp6
-rw-r--r--src/core/file_sys/content_archive.h10
-rw-r--r--src/core/file_sys/directory.h23
-rw-r--r--src/core/file_sys/romfs.cpp3
-rw-r--r--src/core/file_sys/romfs.h5
-rw-r--r--src/core/frontend/applets/web_browser.cpp24
-rw-r--r--src/core/frontend/applets/web_browser.h28
-rw-r--r--src/core/hle/service/am/am.cpp12
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp2
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp190
-rw-r--r--src/core/hle/service/am/applets/web_browser.h44
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/npad.h6
-rw-r--r--src/core/hle/service/hid/hid.cpp1117
-rw-r--r--src/core/hle/service/hid/hid.h110
-rw-r--r--src/core/hle/service/time/time.cpp16
-rw-r--r--src/core/hle/service/vi/vi.cpp97
-rw-r--r--src/core/loader/loader.h13
-rw-r--r--src/core/loader/nax.cpp9
-rw-r--r--src/core/loader/nax.h3
-rw-r--r--src/core/loader/nca.cpp19
-rw-r--r--src/core/loader/nca.h3
-rw-r--r--src/core/loader/nsp.cpp17
-rw-r--r--src/core/loader/nsp.h4
-rw-r--r--src/core/loader/xci.cpp17
-rw-r--r--src/core/loader/xci.h4
-rw-r--r--src/core/settings.h6
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/maxwell_3d.cpp8
-rw-r--r--src/video_core/engines/maxwell_3d.h3
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.cpp24
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.h60
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp9
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h10
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h4
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/web_browser.cpp113
-rw-r--r--src/yuzu/applets/web_browser.h52
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp17
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp300
-rw-r--r--src/yuzu/configuration/configure_profile_manager.h57
-rw-r--r--src/yuzu/configuration/configure_profile_manager.ui172
-rw-r--r--src/yuzu/configuration/configure_system.cpp274
-rw-r--r--src/yuzu/configuration/configure_system.h27
-rw-r--r--src/yuzu/configuration/configure_system.ui217
-rw-r--r--src/yuzu/configuration/configure_web.cpp11
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/main.cpp151
-rw-r--r--src/yuzu/main.h7
-rw-r--r--src/yuzu_cmd/config.cpp12
-rw-r--r--src/yuzu_cmd/default_ini.h6
64 files changed, 2344 insertions, 1139 deletions
diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh
index 4fe3326f9..8b7e65911 100755
--- a/.travis/linux/docker.sh
+++ b/.travis/linux/docker.sh
@@ -1,12 +1,12 @@
#!/bin/bash -ex
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
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
ccache -s
diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh
index dce12099b..4a14837fc 100755
--- a/.travis/macos/build.sh
+++ b/.travis/macos/build.sh
@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
mkdir build && cd build
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
ccache -s
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f71f9fd9..871e0ca1a 100644
--- a/CMakeLists.txt
+++ b/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_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
+
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
@@ -302,7 +304,7 @@ endif()
if (ENABLE_QT)
if (YUZU_USE_BUNDLED_QT)
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()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
@@ -319,6 +321,10 @@ if (ENABLE_QT)
endif()
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()
# Platform-specific library requirements
diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake
index aaf80b77b..4fef66659 100644
--- a/CMakeModules/CopyYuzuQt5Deps.cmake
+++ b/CMakeModules/CopyYuzuQt5Deps.cmake
@@ -5,6 +5,7 @@ function(copy_yuzu_Qt5_deps target_dir)
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
+ set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
set(PLATFORMS ${DLL_DEST}platforms/)
set(STYLES ${DLL_DEST}styles/)
set(IMAGEFORMATS ${DLL_DEST}imageformats/)
@@ -17,6 +18,31 @@ function(copy_yuzu_Qt5_deps target_dir)
Qt5OpenGL$<$<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_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
diff --git a/appveyor.yml b/appveyor.yml
index d6a69fbc2..cef19c259 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -42,7 +42,7 @@ before_build:
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
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
- 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 {
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
rm "$RELEASE_DIST\*.exe"
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 .\README.md -Destination $RELEASE_DIST
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
index 2fe0b3aef..726591fce 100644
--- a/src/audio_core/time_stretch.cpp
+++ b/src/audio_core/time_stretch.cpp
@@ -53,8 +53,8 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
- // Place a lower limit of 5% speed. When a game boots up, there will be
- // many silence samples. These do not need to be timestretched.
+ // Place a lower limit of 5% speed. When a game boots up, there will be
+ // many silence samples. These do not need to be timestretched.
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
m_sound_touch.setTempo(m_stretch_ratio);
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8f2db5bea..aa9e05089 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -88,6 +88,8 @@ add_library(core STATIC
frontend/applets/profile_select.h
frontend/applets/software_keyboard.cpp
frontend/applets/software_keyboard.h
+ frontend/applets/web_browser.cpp
+ frontend/applets/web_browser.h
frontend/emu_window.cpp
frontend/emu_window.h
frontend/framebuffer_layout.cpp
@@ -173,6 +175,8 @@ add_library(core STATIC
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/stub_applet.cpp
hle/service/am/applets/stub_applet.h
+ hle/service/am/applets/web_browser.cpp
+ hle/service/am/applets/web_browser.h
hle/service/am/idle.cpp
hle/service/am/idle.h
hle/service/am/omm.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index fd10199ec..572814e4b 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -30,8 +30,11 @@
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
+#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "frontend/applets/profile_select.h"
#include "frontend/applets/software_keyboard.h"
+#include "frontend/applets/web_browser.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@@ -94,6 +97,11 @@ struct System::Impl {
CoreTiming::Init();
kernel.Initialize();
+ const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch());
+ Settings::values.custom_rtc_differential =
+ Settings::values.custom_rtc.value_or(current_time) - current_time;
+
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -103,6 +111,8 @@ struct System::Impl {
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
if (software_keyboard == nullptr)
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
+ if (web_browser == nullptr)
+ web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
auto main_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(main_process.get());
@@ -199,6 +209,11 @@ struct System::Impl {
// Close app loader
app_loader.reset();
+ // Clear all applets
+ profile_selector.reset();
+ software_keyboard.reset();
+ web_browser.reset();
+
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -233,6 +248,7 @@ struct System::Impl {
/// Frontend applets
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
+ std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -427,22 +443,34 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
-void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) {
+void System::SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet) {
impl->profile_selector = std::move(applet);
}
-const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
+const Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
return *impl->profile_selector;
}
-void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {
+void System::SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet) {
impl->software_keyboard = std::move(applet);
}
-const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
+const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
return *impl->software_keyboard;
}
+void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
+ impl->web_browser = std::move(applet);
+}
+
+Frontend::WebBrowserApplet& System::GetWebBrowser() {
+ return *impl->web_browser;
+}
+
+const Frontend::WebBrowserApplet& System::GetWebBrowser() const {
+ return *impl->web_browser;
+}
+
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}
diff --git a/src/core/core.h b/src/core/core.h
index 869921493..511a5ad3a 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -11,11 +11,12 @@
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
-#include "frontend/applets/profile_select.h"
namespace Core::Frontend {
class EmuWindow;
+class ProfileSelectApplet;
class SoftwareKeyboardApplet;
+class WebBrowserApplet;
} // namespace Core::Frontend
namespace FileSys {
@@ -242,13 +243,18 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
- void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet);
+ void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
- const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const;
+ const Frontend::ProfileSelectApplet& GetProfileSelector() const;
- void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);
+ void SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet);
- const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
+ const Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
+
+ void SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet);
+
+ Frontend::WebBrowserApplet& GetWebBrowser();
+ const Frontend::WebBrowserApplet& GetWebBrowser() const;
private:
System();
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 19b6f8600..5aa3b600b 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -359,6 +359,8 @@ bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTable
dirs.push_back(std::move(npfs));
if (IsDirectoryExeFS(dirs.back()))
exefs = dirs.back();
+ else if (IsDirectoryLogoPartition(dirs.back()))
+ logo = dirs.back();
} else {
if (has_rights_id)
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
@@ -546,4 +548,8 @@ u64 NCA::GetBaseIVFCOffset() const {
return ivfc_offset;
}
+VirtualDir NCA::GetLogoPartition() const {
+ return logo;
+}
+
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 99294cbb4..5d4d05c82 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -74,6 +74,13 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
+inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
+ // NintendoLogo is the static image in the top left corner while StartupMovie is the animation
+ // in the bottom right corner.
+ return pfs->GetFile("NintendoLogo.png") != nullptr &&
+ pfs->GetFile("StartupMovie.gif") != nullptr;
+}
+
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
@@ -102,6 +109,8 @@ public:
// Returns the base ivfc offset used in BKTR patching.
u64 GetBaseIVFCOffset() const;
+ VirtualDir GetLogoPartition() const;
+
private:
bool CheckSupportedNCA(const NCAHeader& header);
bool HandlePotentialHeaderDecryption();
@@ -122,6 +131,7 @@ private:
VirtualFile romfs = nullptr;
VirtualDir exefs = nullptr;
+ VirtualDir logo = nullptr;
VirtualFile file;
VirtualFile bktr_base_romfs;
u64 ivfc_offset = 0;
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index 6690aa575..7b5c509fb 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -39,27 +39,4 @@ static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x31
static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
-class DirectoryBackend : NonCopyable {
-public:
- DirectoryBackend() {}
- virtual ~DirectoryBackend() {}
-
- /**
- * List files contained in the directory
- * @param count Number of entries to return at once in entries
- * @param entries Buffer to read data into
- * @return Number of entries listed
- */
- virtual u64 Read(const u64 count, Entry* entries) = 0;
-
- /// Returns the number of entries still left to read.
- virtual u64 GetEntryCount() const = 0;
-
- /**
- * Close the directory
- * @return true if the directory closed correctly
- */
- virtual bool Close() const = 0;
-};
-
} // namespace FileSys
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 81e1f66ac..ebbdf081e 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
VirtualDir out = std::move(root);
+ if (type == RomFSExtractionType::SingleDiscard)
+ return out->GetSubdirectories().front();
+
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
if (out->GetSubdirectories().front()->GetName() == "data" &&
type == RomFSExtractionType::Truncated)
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 0ec404731..0f35639bc 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -33,8 +33,9 @@ struct IVFCHeader {
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
enum class RomFSExtractionType {
- Full, // Includes data directory
- Truncated, // Traverses into data directory
+ Full, // Includes data directory
+ Truncated, // Traverses into data directory
+ SingleDiscard, // Traverses into the first subdirectory of root
};
// Converts a RomFS binary blob to VFS Filesystem
diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp
new file mode 100644
index 000000000..3a3d3d0bf
--- /dev/null
+++ b/src/core/frontend/applets/web_browser.cpp
@@ -0,0 +1,24 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/frontend/applets/web_browser.h"
+
+namespace Core::Frontend {
+
+WebBrowserApplet::~WebBrowserApplet() = default;
+
+DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
+
+void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
+ std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) {
+ LOG_INFO(Service_AM,
+ "(STUBBED) called - No suitable web browser implementation found to open website page "
+ "at '{}'!",
+ filename);
+ finished_callback();
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h
new file mode 100644
index 000000000..f952856af
--- /dev/null
+++ b/src/core/frontend/applets/web_browser.h
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <string_view>
+
+namespace Core::Frontend {
+
+class WebBrowserApplet {
+public:
+ virtual ~WebBrowserApplet();
+
+ virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) = 0;
+};
+
+class DefaultWebBrowserApplet final : public WebBrowserApplet {
+public:
+ ~DefaultWebBrowserApplet() override;
+
+ void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) override;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7a5e9d216..d1cbe0e44 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -23,6 +23,7 @@
#include "core/hle/service/am/applets/profile_select.h"
#include "core/hle/service/am/applets/software_keyboard.h"
#include "core/hle/service/am/applets/stub_applet.h"
+#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/am/idle.h"
#include "core/hle/service/am/omm.h"
#include "core/hle/service/am/spsm.h"
@@ -44,6 +45,7 @@ constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
+ LibAppletOff = 0x17,
};
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
@@ -730,10 +732,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
- LOG_DEBUG(Service_AM, "called, offset={}", offset);
-
const std::vector<u8> data{ctx.ReadBuffer()};
+ LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
+
if (data.size() > backing.buffer.size() - offset) {
LOG_ERROR(Service_AM,
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
@@ -753,10 +755,10 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
- LOG_DEBUG(Service_AM, "called, offset={}", offset);
-
const std::size_t size{ctx.GetWriteBufferSize()};
+ LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
+
if (size > backing.buffer.size() - offset) {
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
backing.buffer.size(), size, offset);
@@ -791,6 +793,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
return std::make_shared<Applets::ProfileSelect>();
case AppletId::SoftwareKeyboard:
return std::make_shared<Applets::SoftwareKeyboard>();
+ case AppletId::LibAppletOff:
+ return std::make_shared<Applets::WebBrowser>();
default:
LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
static_cast<u32>(id));
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 4c7b45454..14e2a1fee 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -7,7 +7,7 @@
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
-#include "core/frontend/applets/software_keyboard.h"
+#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/profile_select.h"
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
new file mode 100644
index 000000000..9b0aa7f5f
--- /dev/null
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -0,0 +1,190 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstring>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/frontend/applets/web_browser.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/am/applets/web_browser.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/loader.h"
+
+namespace Service::AM::Applets {
+
+// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
+// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
+// but some may be worth an implementation.
+constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
+
+struct WebBufferHeader {
+ u16 count;
+ INSERT_PADDING_BYTES(6);
+};
+static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
+
+struct WebArgumentHeader {
+ u16 type;
+ u16 size;
+ u32 offset;
+};
+static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
+
+struct WebArgumentResult {
+ u32 result_code;
+ std::array<char, 0x1000> last_url;
+ u64 last_url_size;
+};
+static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
+
+static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
+ WebBufferHeader header;
+ ASSERT(sizeof(WebBufferHeader) <= data.size());
+ std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
+
+ u64 offset = sizeof(WebBufferHeader);
+ for (u16 i = 0; i < header.count; ++i) {
+ WebArgumentHeader arg;
+ ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
+ std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
+ offset += sizeof(WebArgumentHeader);
+
+ if (arg.type == type) {
+ std::vector<u8> out(arg.size);
+ offset += arg.offset;
+ ASSERT(offset + arg.size <= data.size());
+ std::memcpy(out.data(), data.data() + offset, out.size());
+ return out;
+ }
+
+ offset += arg.offset + arg.size;
+ }
+
+ return {};
+}
+
+static FileSys::VirtualFile GetManualRomFS() {
+ auto& loader{Core::System::GetInstance().GetAppLoader()};
+
+ FileSys::VirtualFile out;
+ if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
+ return out;
+
+ const auto& installed{FileSystem::GetUnionContents()};
+ const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
+ FileSys::ContentRecordType::Manual);
+
+ if (res != nullptr)
+ return res->GetRomFS();
+ return nullptr;
+}
+
+WebBrowser::WebBrowser() = default;
+
+WebBrowser::~WebBrowser() = default;
+
+void WebBrowser::Initialize() {
+ Applet::Initialize();
+
+ complete = false;
+ temporary_dir.clear();
+ filename.clear();
+ status = RESULT_SUCCESS;
+
+ const auto web_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(web_arg_storage != nullptr);
+ const auto& web_arg = web_arg_storage->GetData();
+
+ const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
+ filename = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(url_data.data()), url_data.size());
+
+ temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
+ "web_applet_manual",
+ FileUtil::DirectorySeparator::PlatformDefault);
+ FileUtil::DeleteDirRecursively(temporary_dir);
+
+ manual_romfs = GetManualRomFS();
+ if (manual_romfs == nullptr) {
+ status = ResultCode(-1);
+ LOG_ERROR(Service_AM, "Failed to find manual for current process!");
+ }
+
+ filename =
+ FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
+ FileUtil::DirectorySeparator::PlatformDefault);
+}
+
+bool WebBrowser::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode WebBrowser::GetStatus() const {
+ return status;
+}
+
+void WebBrowser::ExecuteInteractive() {
+ UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
+}
+
+void WebBrowser::Execute() {
+ if (complete)
+ return;
+
+ if (status != RESULT_SUCCESS) {
+ complete = true;
+ return;
+ }
+
+ auto& frontend{Core::System::GetInstance().GetWebBrowser()};
+
+ frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
+}
+
+void WebBrowser::UnpackRomFS() {
+ if (unpacked)
+ return;
+
+ ASSERT(manual_romfs != nullptr);
+ const auto dir =
+ FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
+ const auto& vfs{Core::System::GetInstance().GetFilesystem()};
+ const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
+ FileSys::VfsRawCopyD(dir, temp_dir);
+
+ unpacked = true;
+}
+
+void WebBrowser::Finalize() {
+ complete = true;
+
+ WebArgumentResult out{};
+ out.result_code = 0;
+ out.last_url_size = 0;
+
+ std::vector<u8> data(sizeof(WebArgumentResult));
+ std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
+
+ broker.PushNormalDataFromApplet(IStorage{data});
+ broker.SignalStateChanged();
+
+ FileUtil::DeleteDirRecursively(temporary_dir);
+}
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
new file mode 100644
index 000000000..b9e228fac
--- /dev/null
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -0,0 +1,44 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Service::AM::Applets {
+
+class WebBrowser final : public Applet {
+public:
+ WebBrowser();
+ ~WebBrowser() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ // Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
+ // directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
+ // size. Attempting to access files at filename before invocation is likely to not work.
+ void UnpackRomFS();
+
+ // Callback to be fired when the frontend is finished browsing. This will delete the temporary
+ // manual RomFS extracted files, so ensure this is only called at actual finalization.
+ void Finalize();
+
+private:
+ bool complete = false;
+ bool unpacked = false;
+ ResultCode status = RESULT_SUCCESS;
+
+ FileSys::VirtualFile manual_romfs;
+ std::string temporary_dir;
+ std::string filename;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 75fdb861a..04c8c35a8 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -410,6 +410,8 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw;
libnx_entry.pad.l_stick = pad_state.l_stick;
libnx_entry.pad.r_stick = pad_state.r_stick;
+
+ press_state |= static_cast<u32>(pad_state.pad_states.raw);
}
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
shared_memory_entries.size() * sizeof(NPadEntry));
@@ -636,6 +638,10 @@ void Controller_NPad::ClearAllControllers() {
});
}
+u32 Controller_NPad::GetAndResetPressState() {
+ return std::exchange(press_state, 0);
+}
+
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
const bool support_handheld =
std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 29851f16a..106cf58c8 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -124,6 +124,10 @@ public:
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
+ // Logical OR for all buttons presses on all controllers
+ // Specifically for cheat engine and other features.
+ u32 GetAndResetPressState();
+
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
@@ -292,6 +296,8 @@ private:
bool is_connected;
};
+ u32 press_state{};
+
NPadType style{};
std::array<NPadEntry, 10> shared_memory_entries{};
std::array<
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 268409257..008bf3f02 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -40,119 +40,82 @@ constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 66;
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
-enum class HidController : std::size_t {
- DebugPad,
- Touchscreen,
- Mouse,
- Keyboard,
- XPad,
- Unknown1,
- Unknown2,
- Unknown3,
- SixAxisSensor,
- NPad,
- Gesture,
-
- MaxControllers,
-};
-
-class IAppletResource final : public ServiceFramework<IAppletResource> {
-public:
- IAppletResource() : ServiceFramework("IAppletResource") {
- static const FunctionInfo functions[] = {
- {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
- };
- RegisterHandlers(functions);
- auto& kernel = Core::System::GetInstance().Kernel();
- shared_mem = Kernel::SharedMemory::Create(
- kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
- Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
-
- MakeController<Controller_DebugPad>(HidController::DebugPad);
- MakeController<Controller_Touchscreen>(HidController::Touchscreen);
- MakeController<Controller_Mouse>(HidController::Mouse);
- MakeController<Controller_Keyboard>(HidController::Keyboard);
- MakeController<Controller_XPad>(HidController::XPad);
- MakeController<Controller_Stubbed>(HidController::Unknown1);
- MakeController<Controller_Stubbed>(HidController::Unknown2);
- MakeController<Controller_Stubbed>(HidController::Unknown3);
- MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
- MakeController<Controller_NPad>(HidController::NPad);
- MakeController<Controller_Gesture>(HidController::Gesture);
-
- // Homebrew doesn't try to activate some controllers, so we activate them by default
- GetController<Controller_NPad>(HidController::NPad).ActivateController();
- GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
-
- GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
- GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
- GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
-
- // Register update callbacks
- pad_update_event = CoreTiming::RegisterEvent(
- "HID::UpdatePadCallback",
- [this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); });
-
- // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
-
- CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
-
- ReloadInputDevices();
- }
-
- void ActivateController(HidController controller) {
- controllers[static_cast<size_t>(controller)]->ActivateController();
- }
-
- void DeactivateController(HidController controller) {
- controllers[static_cast<size_t>(controller)]->DeactivateController();
- }
+IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") {
+ static const FunctionInfo functions[] = {
+ {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
+ };
+ RegisterHandlers(functions);
+
+ auto& kernel = Core::System::GetInstance().Kernel();
+ shared_mem = Kernel::SharedMemory::Create(
+ kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
+ Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
+
+ MakeController<Controller_DebugPad>(HidController::DebugPad);
+ MakeController<Controller_Touchscreen>(HidController::Touchscreen);
+ MakeController<Controller_Mouse>(HidController::Mouse);
+ MakeController<Controller_Keyboard>(HidController::Keyboard);
+ MakeController<Controller_XPad>(HidController::XPad);
+ MakeController<Controller_Stubbed>(HidController::Unknown1);
+ MakeController<Controller_Stubbed>(HidController::Unknown2);
+ MakeController<Controller_Stubbed>(HidController::Unknown3);
+ MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
+ MakeController<Controller_NPad>(HidController::NPad);
+ MakeController<Controller_Gesture>(HidController::Gesture);
+
+ // Homebrew doesn't try to activate some controllers, so we activate them by default
+ GetController<Controller_NPad>(HidController::NPad).ActivateController();
+ GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
+
+ GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
+ GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
+ GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
+
+ // Register update callbacks
+ pad_update_event =
+ CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) {
+ UpdateControllers(userdata, cycles_late);
+ });
+
+ // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
+
+ CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
+
+ ReloadInputDevices();
+}
- template <typename T>
- void MakeController(HidController controller) {
- controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
- }
+void IAppletResource::ActivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->ActivateController();
+}
- template <typename T>
- T& GetController(HidController controller) {
- return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
- }
+void IAppletResource::DeactivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->DeactivateController();
+}
- ~IAppletResource() {
- CoreTiming::UnscheduleEvent(pad_update_event, 0);
- }
+IAppletResource ::~IAppletResource() {
+ CoreTiming::UnscheduleEvent(pad_update_event, 0);
+}
-private:
- void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(shared_mem);
- }
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(shared_mem);
+}
- void UpdateControllers(u64 userdata, int cycles_late) {
- const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
- for (const auto& controller : controllers) {
- if (should_reload) {
- controller->OnLoadInputDevices();
- }
- controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
+void IAppletResource::UpdateControllers(u64 userdata, int cycles_late) {
+ const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
+ for (const auto& controller : controllers) {
+ if (should_reload) {
+ controller->OnLoadInputDevices();
}
-
- CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
+ controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
- // Handle to shared memory region designated to HID service
- Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
-
- // CoreTiming update events
- CoreTiming::EventType* pad_update_event;
-
- std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
- controllers{};
-};
+ CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
+}
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
public:
@@ -172,599 +135,597 @@ private:
}
};
-class Hid final : public ServiceFramework<Hid> {
-public:
- Hid() : ServiceFramework("hid") {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &Hid::CreateAppletResource, "CreateAppletResource"},
- {1, &Hid::ActivateDebugPad, "ActivateDebugPad"},
- {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
- {21, &Hid::ActivateMouse, "ActivateMouse"},
- {31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
- {32, nullptr, "SendKeyboardLockKeyEvent"},
- {40, nullptr, "AcquireXpadIdEventHandle"},
- {41, nullptr, "ReleaseXpadIdEventHandle"},
- {51, &Hid::ActivateXpad, "ActivateXpad"},
- {55, nullptr, "GetXpadIds"},
- {56, nullptr, "ActivateJoyXpad"},
- {58, nullptr, "GetJoyXpadLifoHandle"},
- {59, nullptr, "GetJoyXpadIds"},
- {60, nullptr, "ActivateSixAxisSensor"},
- {61, nullptr, "DeactivateSixAxisSensor"},
- {62, nullptr, "GetSixAxisSensorLifoHandle"},
- {63, nullptr, "ActivateJoySixAxisSensor"},
- {64, nullptr, "DeactivateJoySixAxisSensor"},
- {65, nullptr, "GetJoySixAxisSensorLifoHandle"},
- {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
- {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
- {68, nullptr, "IsSixAxisSensorFusionEnabled"},
- {69, nullptr, "EnableSixAxisSensorFusion"},
- {70, nullptr, "SetSixAxisSensorFusionParameters"},
- {71, nullptr, "GetSixAxisSensorFusionParameters"},
- {72, nullptr, "ResetSixAxisSensorFusionParameters"},
- {73, nullptr, "SetAccelerometerParameters"},
- {74, nullptr, "GetAccelerometerParameters"},
- {75, nullptr, "ResetAccelerometerParameters"},
- {76, nullptr, "SetAccelerometerPlayMode"},
- {77, nullptr, "GetAccelerometerPlayMode"},
- {78, nullptr, "ResetAccelerometerPlayMode"},
- {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
- {80, nullptr, "GetGyroscopeZeroDriftMode"},
- {81, nullptr, "ResetGyroscopeZeroDriftMode"},
- {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
- {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
- {91, &Hid::ActivateGesture, "ActivateGesture"},
- {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
- {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
- {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
- {103, &Hid::ActivateNpad, "ActivateNpad"},
- {104, nullptr, "DeactivateNpad"},
- {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
- {107, &Hid::DisconnectNpad, "DisconnectNpad"},
- {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
- {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"},
- {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
- {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
- {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
- {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
- {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
- {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
- {126, nullptr, "StartLrAssignmentMode"},
- {127, nullptr, "StopLrAssignmentMode"},
- {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
- {129, nullptr, "GetNpadHandheldActivationMode"},
- {130, nullptr, "SwapNpadAssignment"},
- {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
- {132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
- {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
- {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
- {201, &Hid::SendVibrationValue, "SendVibrationValue"},
- {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
- {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
- {204, nullptr, "PermitVibration"},
- {205, nullptr, "IsVibrationPermitted"},
- {206, &Hid::SendVibrationValues, "SendVibrationValues"},
- {207, nullptr, "SendVibrationGcErmCommand"},
- {208, nullptr, "GetActualVibrationGcErmCommand"},
- {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
- {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
- {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
- {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
- {302, nullptr, "StopConsoleSixAxisSensor"},
- {303, nullptr, "ActivateSevenSixAxisSensor"},
- {304, nullptr, "StartSevenSixAxisSensor"},
- {305, nullptr, "StopSevenSixAxisSensor"},
- {306, nullptr, "InitializeSevenSixAxisSensor"},
- {307, nullptr, "FinalizeSevenSixAxisSensor"},
- {308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
- {309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
- {310, nullptr, "ResetSevenSixAxisSensorTimestamp"},
- {400, nullptr, "IsUsbFullKeyControllerEnabled"},
- {401, nullptr, "EnableUsbFullKeyController"},
- {402, nullptr, "IsUsbFullKeyControllerConnected"},
- {403, nullptr, "HasBattery"},
- {404, nullptr, "HasLeftRightBattery"},
- {405, nullptr, "GetNpadInterfaceType"},
- {406, nullptr, "GetNpadLeftRightInterfaceType"},
- {500, nullptr, "GetPalmaConnectionHandle"},
- {501, nullptr, "InitializePalma"},
- {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
- {503, nullptr, "GetPalmaOperationInfo"},
- {504, nullptr, "PlayPalmaActivity"},
- {505, nullptr, "SetPalmaFrModeType"},
- {506, nullptr, "ReadPalmaStep"},
- {507, nullptr, "EnablePalmaStep"},
- {508, nullptr, "ResetPalmaStep"},
- {509, nullptr, "ReadPalmaApplicationSection"},
- {510, nullptr, "WritePalmaApplicationSection"},
- {511, nullptr, "ReadPalmaUniqueCode"},
- {512, nullptr, "SetPalmaUniqueCodeInvalid"},
- {513, nullptr, "WritePalmaActivityEntry"},
- {514, nullptr, "WritePalmaRgbLedPatternEntry"},
- {515, nullptr, "WritePalmaWaveEntry"},
- {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
- {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
- {518, nullptr, "SuspendPalmaFeature"},
- {519, nullptr, "GetPalmaOperationResult"},
- {520, nullptr, "ReadPalmaPlayLog"},
- {521, nullptr, "ResetPalmaPlayLog"},
- {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
- {523, nullptr, "SetIsPalmaPairedConnectable"},
- {524, nullptr, "PairPalma"},
- {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
- {1000, nullptr, "SetNpadCommunicationMode"},
- {1001, nullptr, "GetNpadCommunicationMode"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
+std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
+ if (applet_resource == nullptr) {
+ applet_resource = std::make_shared<IAppletResource>();
}
- ~Hid() = default;
-private:
- std::shared_ptr<IAppletResource> applet_resource;
+ return applet_resource;
+}
- void CreateAppletResource(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+Hid::Hid() : ServiceFramework("hid") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &Hid::CreateAppletResource, "CreateAppletResource"},
+ {1, &Hid::ActivateDebugPad, "ActivateDebugPad"},
+ {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
+ {21, &Hid::ActivateMouse, "ActivateMouse"},
+ {31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
+ {32, nullptr, "SendKeyboardLockKeyEvent"},
+ {40, nullptr, "AcquireXpadIdEventHandle"},
+ {41, nullptr, "ReleaseXpadIdEventHandle"},
+ {51, &Hid::ActivateXpad, "ActivateXpad"},
+ {55, nullptr, "GetXpadIds"},
+ {56, nullptr, "ActivateJoyXpad"},
+ {58, nullptr, "GetJoyXpadLifoHandle"},
+ {59, nullptr, "GetJoyXpadIds"},
+ {60, nullptr, "ActivateSixAxisSensor"},
+ {61, nullptr, "DeactivateSixAxisSensor"},
+ {62, nullptr, "GetSixAxisSensorLifoHandle"},
+ {63, nullptr, "ActivateJoySixAxisSensor"},
+ {64, nullptr, "DeactivateJoySixAxisSensor"},
+ {65, nullptr, "GetJoySixAxisSensorLifoHandle"},
+ {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
+ {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
+ {68, nullptr, "IsSixAxisSensorFusionEnabled"},
+ {69, nullptr, "EnableSixAxisSensorFusion"},
+ {70, nullptr, "SetSixAxisSensorFusionParameters"},
+ {71, nullptr, "GetSixAxisSensorFusionParameters"},
+ {72, nullptr, "ResetSixAxisSensorFusionParameters"},
+ {73, nullptr, "SetAccelerometerParameters"},
+ {74, nullptr, "GetAccelerometerParameters"},
+ {75, nullptr, "ResetAccelerometerParameters"},
+ {76, nullptr, "SetAccelerometerPlayMode"},
+ {77, nullptr, "GetAccelerometerPlayMode"},
+ {78, nullptr, "ResetAccelerometerPlayMode"},
+ {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
+ {80, nullptr, "GetGyroscopeZeroDriftMode"},
+ {81, nullptr, "ResetGyroscopeZeroDriftMode"},
+ {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
+ {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
+ {91, &Hid::ActivateGesture, "ActivateGesture"},
+ {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
+ {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
+ {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
+ {103, &Hid::ActivateNpad, "ActivateNpad"},
+ {104, nullptr, "DeactivateNpad"},
+ {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
+ {107, &Hid::DisconnectNpad, "DisconnectNpad"},
+ {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
+ {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"},
+ {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
+ {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
+ {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
+ {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
+ {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
+ {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
+ {126, nullptr, "StartLrAssignmentMode"},
+ {127, nullptr, "StopLrAssignmentMode"},
+ {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
+ {129, nullptr, "GetNpadHandheldActivationMode"},
+ {130, nullptr, "SwapNpadAssignment"},
+ {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
+ {132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
+ {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
+ {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
+ {201, &Hid::SendVibrationValue, "SendVibrationValue"},
+ {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
+ {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
+ {204, nullptr, "PermitVibration"},
+ {205, nullptr, "IsVibrationPermitted"},
+ {206, &Hid::SendVibrationValues, "SendVibrationValues"},
+ {207, nullptr, "SendVibrationGcErmCommand"},
+ {208, nullptr, "GetActualVibrationGcErmCommand"},
+ {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
+ {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
+ {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
+ {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
+ {302, nullptr, "StopConsoleSixAxisSensor"},
+ {303, nullptr, "ActivateSevenSixAxisSensor"},
+ {304, nullptr, "StartSevenSixAxisSensor"},
+ {305, nullptr, "StopSevenSixAxisSensor"},
+ {306, nullptr, "InitializeSevenSixAxisSensor"},
+ {307, nullptr, "FinalizeSevenSixAxisSensor"},
+ {308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
+ {309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
+ {310, nullptr, "ResetSevenSixAxisSensorTimestamp"},
+ {400, nullptr, "IsUsbFullKeyControllerEnabled"},
+ {401, nullptr, "EnableUsbFullKeyController"},
+ {402, nullptr, "IsUsbFullKeyControllerConnected"},
+ {403, nullptr, "HasBattery"},
+ {404, nullptr, "HasLeftRightBattery"},
+ {405, nullptr, "GetNpadInterfaceType"},
+ {406, nullptr, "GetNpadLeftRightInterfaceType"},
+ {500, nullptr, "GetPalmaConnectionHandle"},
+ {501, nullptr, "InitializePalma"},
+ {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
+ {503, nullptr, "GetPalmaOperationInfo"},
+ {504, nullptr, "PlayPalmaActivity"},
+ {505, nullptr, "SetPalmaFrModeType"},
+ {506, nullptr, "ReadPalmaStep"},
+ {507, nullptr, "EnablePalmaStep"},
+ {508, nullptr, "ResetPalmaStep"},
+ {509, nullptr, "ReadPalmaApplicationSection"},
+ {510, nullptr, "WritePalmaApplicationSection"},
+ {511, nullptr, "ReadPalmaUniqueCode"},
+ {512, nullptr, "SetPalmaUniqueCodeInvalid"},
+ {513, nullptr, "WritePalmaActivityEntry"},
+ {514, nullptr, "WritePalmaRgbLedPatternEntry"},
+ {515, nullptr, "WritePalmaWaveEntry"},
+ {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
+ {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
+ {518, nullptr, "SuspendPalmaFeature"},
+ {519, nullptr, "GetPalmaOperationResult"},
+ {520, nullptr, "ReadPalmaPlayLog"},
+ {521, nullptr, "ResetPalmaPlayLog"},
+ {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
+ {523, nullptr, "SetIsPalmaPairedConnectable"},
+ {524, nullptr, "PairPalma"},
+ {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
+ {1000, nullptr, "SetNpadCommunicationMode"},
+ {1001, nullptr, "GetNpadCommunicationMode"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+Hid::~Hid() = default;
- if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>();
- }
+void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAppletResource>(applet_resource);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ if (applet_resource == nullptr) {
+ applet_resource = std::make_shared<IAppletResource>();
}
- void ActivateXpad(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto basic_xpad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IAppletResource>(applet_resource);
+}
- LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
- basic_xpad_id, applet_resource_user_id);
+void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto basic_xpad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::XPad);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id,
+ applet_resource_user_id);
- void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::XPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::DebugPad);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::DebugPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::Touchscreen);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void ActivateMouse(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Touchscreen);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::Mouse);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Mouse);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::Keyboard);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void ActivateGesture(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Keyboard);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::Gesture);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
+ applet_resource_user_id);
- void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
- // Should have no effect with how our npad sets up the data
- IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Gesture);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
+ // Should have no effect with how our npad sets up the data
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->ActivateController(HidController::NPad);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
+ applet_resource_user_id);
- void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
- void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto drift_mode{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID,
- "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}",
- handle, drift_mode, applet_resource_user_id);
+void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto drift_mode{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, handle={}, drift_mode={}, applet_resource_user_id={}", handle,
+ drift_mode, applet_resource_user_id);
- void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- // TODO (Hexagon12): Properly implement reading gyroscope values from controllers.
- rb.Push(true);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
- void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto supported_styleset{rp.Pop<u32>()};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ // TODO (Hexagon12): Properly implement reading gyroscope values from controllers.
+ rb.Push(true);
+}
- LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
+void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto supported_styleset{rp.Pop<u32>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedStyleSet({supported_styleset});
+ LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedStyleSet({supported_styleset});
- void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(controller.GetSupportedStyleSet().raw);
- }
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(controller.GetSupportedStyleSet().raw);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void ActivateNpad(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- applet_resource->ActivateController(HidController::NPad);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto unknown{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ applet_resource->ActivateController(HidController::NPad);
+}
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
- npad_id, applet_resource_user_id, unknown);
+void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto unknown{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetStyleSetChangedEvent());
- }
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id,
+ applet_resource_user_id, unknown);
- void DisconnectNpad(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetStyleSetChangedEvent());
+}
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .DisconnectNPad(npad_id);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
+ applet_resource_user_id);
- void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
+void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.Pop<u32>()};
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLedPattern(npad_id)
- .raw);
- }
+ LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
- void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto hold_type{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLedPattern(npad_id)
+ .raw);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
- applet_resource_user_id, hold_type);
+void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto hold_type{rp.Pop<u64>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
+ applet_resource_user_id, hold_type);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
- void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto& controller =
- applet_resource->GetController<Controller_NPad>(HidController::NPad);
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
- npad_id, applet_resource_user_id);
+void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id,
+ applet_resource_user_id);
- void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetVibrationEnabled(true);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetVibrationEnabled(false);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
- void SendVibrationValue(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
- const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}",
- controller_id, applet_resource_user_id);
+void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto controller_id{rp.Pop<u32>()};
+ const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
+ applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController({controller_id}, {vibration_values});
- }
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
- void SendVibrationValues(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController({controller_id}, {vibration_values});
+}
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto controllers = ctx.ReadBuffer(0);
- const auto vibrations = ctx.ReadBuffer(1);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- std::vector<u32> controller_list(controllers.size() / sizeof(u32));
- std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
- sizeof(Controller_NPad::Vibration));
+ const auto controllers = ctx.ReadBuffer(0);
+ const auto vibrations = ctx.ReadBuffer(1);
- std::memcpy(controller_list.data(), controllers.data(), controllers.size());
- std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
- std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
- [](u32 controller_id) { return controller_id - 3; });
+ std::vector<u32> controller_list(controllers.size() / sizeof(u32));
+ std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
+ sizeof(Controller_NPad::Vibration));
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController(controller_list, vibration_list);
+ std::memcpy(controller_list.data(), controllers.data(), controllers.size());
+ std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
+ std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
+ [](u32 controller_id) { return controller_id - 3; });
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController(controller_list, vibration_list);
- void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}",
- controller_id, applet_resource_user_id);
+void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto controller_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw<Controller_NPad::Vibration>(
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLastVibration());
- }
+ LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
+ applet_resource_user_id);
- void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<Controller_NPad::Vibration>(
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration());
+}
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
+ applet_resource_user_id);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
- void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto unknown_1{rp.Pop<u32>()};
- const auto unknown_2{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- unknown_1, unknown_2, applet_resource_user_id);
+void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto unknown_1{rp.Pop<u32>()};
+ const auto unknown_2{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
+ unknown_1, unknown_2, applet_resource_user_id);
- void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto mode{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
- applet_resource_user_id, mode);
+void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto mode{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
+ applet_resource_user_id, mode);
- void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(1);
- rb.Push<u32>(0);
- }
+void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
- void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(1);
+ rb.Push<u32>(0);
+}
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IActiveVibrationDeviceList>();
- }
+void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
- void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IActiveVibrationDeviceList>();
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
+void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
- void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
+ applet_resource_user_id);
- void StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle);
+void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto handle{rp.Pop<u32>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, handle={}", handle);
- void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto unknown{rp.Pop<u32>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}",
- applet_resource_user_id, unknown);
+void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto unknown{rp.Pop<u32>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, unknown={}",
+ applet_resource_user_id, unknown);
- void SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
- LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown);
+void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto unknown{rp.Pop<u32>()};
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
-};
+ LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}", unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
class HidDbg final : public ServiceFramework<HidDbg> {
public:
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 773035460..eca27c056 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,12 +4,122 @@
#pragma once
+#include "controllers/controller_base.h"
+#include "core/hle/service/service.h"
+
+namespace CoreTiming {
+struct EventType;
+}
+
+namespace Kernel {
+class SharedMemory;
+}
+
namespace SM {
class ServiceManager;
}
namespace Service::HID {
+enum class HidController : std::size_t {
+ DebugPad,
+ Touchscreen,
+ Mouse,
+ Keyboard,
+ XPad,
+ Unknown1,
+ Unknown2,
+ Unknown3,
+ SixAxisSensor,
+ NPad,
+ Gesture,
+
+ MaxControllers,
+};
+
+class IAppletResource final : public ServiceFramework<IAppletResource> {
+public:
+ IAppletResource();
+ ~IAppletResource() override;
+
+ void ActivateController(HidController controller);
+ void DeactivateController(HidController controller);
+
+ template <typename T>
+ T& GetController(HidController controller) {
+ return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
+ }
+
+ template <typename T>
+ const T& GetController(HidController controller) const {
+ return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
+ }
+
+private:
+ template <typename T>
+ void MakeController(HidController controller) {
+ controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
+ }
+
+ void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
+ void UpdateControllers(u64 userdata, int cycles_late);
+
+ Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
+
+ CoreTiming::EventType* pad_update_event;
+
+ std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
+ controllers{};
+};
+
+class Hid final : public ServiceFramework<Hid> {
+public:
+ Hid();
+ ~Hid() override;
+
+ std::shared_ptr<IAppletResource> GetAppletResource();
+
+private:
+ void CreateAppletResource(Kernel::HLERequestContext& ctx);
+ void ActivateXpad(Kernel::HLERequestContext& ctx);
+ void ActivateDebugPad(Kernel::HLERequestContext& ctx);
+ void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
+ void ActivateMouse(Kernel::HLERequestContext& ctx);
+ void ActivateKeyboard(Kernel::HLERequestContext& ctx);
+ void ActivateGesture(Kernel::HLERequestContext& ctx);
+ void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
+ void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
+ void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
+ void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
+ void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
+ void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
+ void ActivateNpad(Kernel::HLERequestContext& ctx);
+ void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
+ void DisconnectNpad(Kernel::HLERequestContext& ctx);
+ void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
+ void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
+ void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
+ void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void SendVibrationValue(Kernel::HLERequestContext& ctx);
+ void SendVibrationValues(Kernel::HLERequestContext& ctx);
+ void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx);
+ void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx);
+ void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
+ void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
+ void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
+ void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
+ void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<IAppletResource> applet_resource;
+};
+
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 16564de24..c13640ad8 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -12,9 +12,16 @@
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
+#include "core/settings.h"
namespace Service::Time {
+static std::chrono::seconds GetSecondsSinceEpoch() {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()) +
+ Settings::values.custom_rtc_differential;
+}
+
static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time,
CalendarAdditionalInfo& additional_info,
[[maybe_unused]] const TimeZoneRule& /*rule*/) {
@@ -68,9 +75,7 @@ public:
private:
void GetCurrentTime(Kernel::HLERequestContext& ctx) {
- const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
- std::chrono::system_clock::now().time_since_epoch())
- .count()};
+ const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -266,10 +271,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto initial_type = rp.PopRaw<u8>();
- const s64 time_since_epoch{std::chrono::duration_cast<std::chrono::seconds>(
- std::chrono::system_clock::now().time_since_epoch())
- .count()};
-
+ const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
const std::time_t time(time_since_epoch);
const std::tm* tm = std::localtime(&time);
if (tm == nullptr) {
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 1bdf19e13..70c933934 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -32,6 +32,9 @@
namespace Service::VI {
+constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
+constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
+
struct DisplayInfo {
/// The name of this particular display.
char display_name[0x40]{"Default"};
@@ -874,6 +877,22 @@ public:
~IApplicationDisplayService() = default;
private:
+ enum class ConvertedScaleMode : u64 {
+ Freeze = 0,
+ ScaleToWindow = 1,
+ ScaleAndCrop = 2,
+ None = 3,
+ PreserveAspectRatio = 4,
+ };
+
+ enum class NintendoScaleMode : u32 {
+ None = 0,
+ Freeze = 1,
+ ScaleToWindow = 2,
+ ScaleAndCrop = 3,
+ PreserveAspectRatio = 4,
+ };
+
void GetRelayService(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_VI, "(STUBBED) called");
@@ -974,13 +993,27 @@ private:
void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const u32 scaling_mode = rp.Pop<u32>();
+ const auto scaling_mode = rp.PopEnum<NintendoScaleMode>();
const u64 unknown = rp.Pop<u64>();
- LOG_WARNING(Service_VI, "(STUBBED) called. scaling_mode=0x{:08X}, unknown=0x{:016X}",
- scaling_mode, unknown);
+ LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}",
+ static_cast<u32>(scaling_mode), unknown);
IPC::ResponseBuilder rb{ctx, 2};
+
+ if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
+ LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
+ rb.Push(ERR_OPERATION_FAILED);
+ return;
+ }
+
+ if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
+ scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
+ LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
+ rb.Push(ERR_UNSUPPORTED);
+ return;
+ }
+
rb.Push(RESULT_SUCCESS);
}
@@ -1061,51 +1094,37 @@ private:
rb.PushCopyObjects(vsync_event);
}
- enum class ConvertedScaleMode : u64 {
- None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no
- // scaling/default
- Freeze = 1,
- ScaleToWindow = 2,
- Crop = 3,
- NoCrop = 4,
- };
-
- // This struct is different, currently it's 1:1 but this might change in the future.
- enum class NintendoScaleMode : u32 {
- None = 0,
- Freeze = 1,
- ScaleToWindow = 2,
- Crop = 3,
- NoCrop = 4,
- };
-
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto mode = rp.PopEnum<NintendoScaleMode>();
+ const auto mode = rp.PopEnum<NintendoScaleMode>();
LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
+ const auto converted_mode = ConvertScalingModeImpl(mode);
+
+ if (converted_mode.Succeeded()) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(*converted_mode);
+ } else {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(converted_mode.Code());
+ }
+ }
+
+ static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
- rb.PushEnum(ConvertedScaleMode::None);
- break;
+ return MakeResult(ConvertedScaleMode::None);
case NintendoScaleMode::Freeze:
- rb.PushEnum(ConvertedScaleMode::Freeze);
- break;
+ return MakeResult(ConvertedScaleMode::Freeze);
case NintendoScaleMode::ScaleToWindow:
- rb.PushEnum(ConvertedScaleMode::ScaleToWindow);
- break;
- case NintendoScaleMode::Crop:
- rb.PushEnum(ConvertedScaleMode::Crop);
- break;
- case NintendoScaleMode::NoCrop:
- rb.PushEnum(ConvertedScaleMode::NoCrop);
- break;
+ return MakeResult(ConvertedScaleMode::ScaleToWindow);
+ case NintendoScaleMode::ScaleAndCrop:
+ return MakeResult(ConvertedScaleMode::ScaleAndCrop);
+ case NintendoScaleMode::PreserveAspectRatio:
+ return MakeResult(ConvertedScaleMode::PreserveAspectRatio);
default:
- UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode));
- rb.PushEnum(ConvertedScaleMode::None);
- break;
+ return ERR_OPERATION_FAILED;
}
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 30eacd803..bb925f4a6 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -178,6 +178,8 @@ public:
/**
* Get the banner (typically banner section) of the application
+ * In the context of NX, this is the animation that displays in the bottom right of the screen
+ * when a game boots. Stored in GIF format.
* @param buffer Reference to buffer to store data
* @return ResultStatus result of function
*/
@@ -187,6 +189,8 @@ public:
/**
* Get the logo (typically logo section) of the application
+ * In the context of NX, this is the static image that displays in the top left of the screen
+ * when a game boots. Stored in JPEG format.
* @param buffer Reference to buffer to store data
* @return ResultStatus result of function
*/
@@ -259,6 +263,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
+ /**
+ * Get the RomFS of the manual of the application
+ * @param file The raw manual RomFS of the game
+ * @return ResultStatus result of function
+ */
+ virtual ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) {
+ return ResultStatus::ErrorNotImplemented;
+ }
+
protected:
FileSys::VirtualFile file;
bool is_loaded = false;
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index a093e3d36..93a970d10 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -79,4 +79,13 @@ u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
return nca_loader->ReadProgramId(out_program_id);
}
+
+ResultStatus AppLoader_NAX::ReadBanner(std::vector<u8>& buffer) {
+ return nca_loader->ReadBanner(buffer);
+}
+
+ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) {
+ return nca_loader->ReadLogo(buffer);
+}
+
} // namespace Loader
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index 0a97511b8..f40079574 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -39,6 +39,9 @@ public:
u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
+ ResultStatus ReadBanner(std::vector<u8>& buffer) override;
+ ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+
private:
std::unique_ptr<FileSys::NAX> nax;
std::unique_ptr<AppLoader_NCA> nca_loader;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 7e1b0d84f..ce8196fcf 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -84,4 +84,23 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
return ResultStatus::Success;
}
+ResultStatus AppLoader_NCA::ReadBanner(std::vector<u8>& buffer) {
+ if (nca == nullptr || nca->GetStatus() != ResultStatus::Success)
+ return ResultStatus::ErrorNotInitialized;
+ const auto logo = nca->GetLogoPartition();
+ if (logo == nullptr)
+ return ResultStatus::ErrorNoIcon;
+ buffer = logo->GetFile("StartupMovie.gif")->ReadAllBytes();
+ return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) {
+ if (nca == nullptr || nca->GetStatus() != ResultStatus::Success)
+ return ResultStatus::ErrorNotInitialized;
+ const auto logo = nca->GetLogoPartition();
+ if (logo == nullptr)
+ return ResultStatus::ErrorNoIcon;
+ buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes();
+ return ResultStatus::Success;
+}
} // namespace Loader
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index cbbe701d2..b9f077468 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -39,6 +39,9 @@ public:
u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
+ ResultStatus ReadBanner(std::vector<u8>& buffer) override;
+ ResultStatus ReadLogo(std::vector<u8>& buffer) override;
+
private:
std::unique_ptr<FileSys::NCA> nca;
std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 4d4b44571..7da1f8960 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -158,4 +158,21 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
nacp = *nacp_file;
return ResultStatus::Success;
}
+
+ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) {
+ const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual);
+ if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr)
+ return ResultStatus::ErrorNoRomFS;
+ file = nca->GetRomFS();
+ return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NSP::ReadBanner(std::vector<u8>& buffer) {
+ return secondary_loader->ReadBanner(buffer);
+}
+
+ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) {
+ return secondary_loader->ReadLogo(buffer);
+}
+
} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 32eb0193d..953a1b508 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -44,6 +44,10 @@ public:
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadControlData(FileSys::NACP& nacp) override;
+ ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
+
+ ResultStatus ReadBanner(std::vector<u8>& buffer) override;
+ ResultStatus ReadLogo(std::vector<u8>& buffer) override;
private:
std::unique_ptr<FileSys::NSP> nsp;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index e67e43c69..89f7bbf77 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -128,4 +128,21 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
return ResultStatus::Success;
}
+ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) {
+ const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(),
+ FileSys::ContentRecordType::Manual);
+ if (xci->GetStatus() != ResultStatus::Success || nca == nullptr)
+ return ResultStatus::ErrorXCIMissingPartition;
+ file = nca->GetRomFS();
+ return file == nullptr ? ResultStatus::ErrorNoRomFS : ResultStatus::Success;
+}
+
+ResultStatus AppLoader_XCI::ReadBanner(std::vector<u8>& buffer) {
+ return nca_loader->ReadBanner(buffer);
+}
+
+ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) {
+ return nca_loader->ReadLogo(buffer);
+}
+
} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 9d3923f62..d6995b61e 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -44,6 +44,10 @@ public:
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadControlData(FileSys::NACP& control) override;
+ ResultStatus ReadManualRomFS(FileSys::VirtualFile& file) override;
+
+ ResultStatus ReadBanner(std::vector<u8>& buffer) override;
+ ResultStatus ReadLogo(std::vector<u8>& buffer) override;
private:
std::unique_ptr<FileSys::XCI> xci;
diff --git a/src/core/settings.h b/src/core/settings.h
index de01b05c0..29ce98983 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -6,6 +6,7 @@
#include <array>
#include <atomic>
+#include <chrono>
#include <map>
#include <optional>
#include <string>
@@ -350,6 +351,11 @@ struct Values {
bool use_docked_mode;
bool enable_nfc;
std::optional<u32> rng_seed;
+ // Measured in seconds since epoch
+ std::optional<std::chrono::seconds> custom_rtc;
+ // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
+ std::chrono::seconds custom_rtc_differential;
+
s32 current_user;
s32 language_index;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 0406fbcd9..327db68a5 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -30,6 +30,8 @@ add_library(video_core STATIC
renderer_base.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
+ renderer_opengl/gl_global_cache.cpp
+ renderer_opengl/gl_global_cache.h
renderer_opengl/gl_primitive_assembler.cpp
renderer_opengl/gl_primitive_assembler.h
renderer_opengl/gl_rasterizer.cpp
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index b19b3a75a..0ed7bc5d8 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -135,6 +135,14 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
if (regs.reg_array[method_call.method] != method_call.argument) {
regs.reg_array[method_call.method] = method_call.argument;
+ // Shader
+ constexpr u32 shader_registers_count =
+ sizeof(regs.shader_config[0]) * Regs::MaxShaderProgram / sizeof(u32);
+ if (method_call.method >= MAXWELL3D_REG_INDEX(shader_config[0]) &&
+ method_call.method < MAXWELL3D_REG_INDEX(shader_config[0]) + shader_registers_count) {
+ dirty_flags.shaders = true;
+ }
+
// Vertex format
if (method_call.method >= MAXWELL3D_REG_INDEX(vertex_attrib_format) &&
method_call.method <
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 0faff6fdf..d50e5a126 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1089,10 +1089,13 @@ public:
MemoryManager& memory_manager;
struct DirtyFlags {
+ bool shaders = true;
+
bool vertex_attrib_format = true;
u32 vertex_array = 0xFFFFFFFF;
void OnMemoryWrite() {
+ shaders = true;
vertex_array = 0xFFFFFFFF;
}
};
diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp
new file mode 100644
index 000000000..7992b82c4
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_global_cache.cpp
@@ -0,0 +1,24 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <glad/glad.h>
+
+#include "video_core/renderer_opengl/gl_global_cache.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
+#include "video_core/renderer_opengl/utils.h"
+
+namespace OpenGL {
+
+CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} {
+ buffer.Create();
+ // Bind and unbind the buffer so it gets allocated by the driver
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
+ LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory");
+}
+
+GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
+ : RasterizerCache{rasterizer} {}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h
new file mode 100644
index 000000000..406a735bc
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_global_cache.h
@@ -0,0 +1,60 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <glad/glad.h>
+
+#include "common/common_types.h"
+#include "video_core/rasterizer_cache.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+
+namespace OpenGL {
+
+namespace GLShader {
+class GlobalMemoryEntry;
+} // namespace GLShader
+
+class RasterizerOpenGL;
+class CachedGlobalRegion;
+using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
+
+class CachedGlobalRegion final : public RasterizerCacheObject {
+public:
+ explicit CachedGlobalRegion(VAddr addr, u32 size);
+
+ /// Gets the address of the shader in guest memory, required for cache management
+ VAddr GetAddr() const {
+ return addr;
+ }
+
+ /// Gets the size of the shader in guest memory, required for cache management
+ std::size_t GetSizeInBytes() const {
+ return size;
+ }
+
+ /// Gets the GL program handle for the buffer
+ GLuint GetBufferHandle() const {
+ return buffer.handle;
+ }
+
+ // TODO(Rodrigo): When global memory is written (STG), implement flushing
+ void Flush() override {
+ UNIMPLEMENTED();
+ }
+
+private:
+ VAddr addr{};
+ u32 size{};
+
+ OGLBuffer buffer;
+};
+
+class GlobalRegionCacheOpenGL final : public RasterizerCache<GlobalRegion> {
+public:
+ explicit GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer);
+};
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 61ccfa104..0c2a3265b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -101,7 +101,7 @@ struct FramebufferCacheKey {
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info},
- buffer_cache(*this, STREAM_BUFFER_SIZE) {
+ buffer_cache(*this, STREAM_BUFFER_SIZE), global_cache{*this} {
// Create sampler objects
for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -295,7 +295,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
@@ -367,7 +367,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
// (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the
// clip distances only when it's written by a shader stage.
for (std::size_t i = 0; i < Maxwell::NumClipDistances; ++i) {
- clip_distances[i] |= shader->GetShaderEntries().clip_distances[i];
+ clip_distances[i] = clip_distances[i] || shader->GetShaderEntries().clip_distances[i];
}
// When VertexA is enabled, we have dual vertex shaders
@@ -378,6 +378,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
SyncClipEnabled(clip_distances);
+
+ gpu.dirty_flags.shaders = false;
}
void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
@@ -761,6 +763,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.InvalidateRegion(addr, size);
shader_cache.InvalidateRegion(addr, size);
+ global_cache.InvalidateRegion(addr, size);
buffer_cache.InvalidateRegion(addr, size);
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index bac5de91e..fe230083f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -23,6 +23,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
+#include "video_core/renderer_opengl/gl_global_cache.h"
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -66,6 +67,10 @@ public:
static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0,
"The maximum size of a constbuffer must be a multiple of the size of GLvec4");
+ static constexpr std::size_t MaxGlobalMemorySize = 0x10000;
+ static_assert(MaxGlobalMemorySize % sizeof(float) == 0,
+ "The maximum size of a global memory must be a multiple of the size of float");
+
private:
class SamplerInfo {
public:
@@ -105,7 +110,7 @@ private:
bool using_depth_fb = true, bool preserve_contents = true,
std::optional<std::size_t> single_color_target = {});
- /*
+ /**
* Configures the current constbuffers to use for the draw command.
* @param stage The shader stage to configure buffers for.
* @param shader The shader object that contains the specified stage.
@@ -115,7 +120,7 @@ private:
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
GLenum primitive_mode, u32 current_bindpoint);
- /*
+ /**
* Configures the current textures to use for the draw command.
* @param stage The shader stage to configure textures for.
* @param shader The shader object that contains the specified stage.
@@ -185,6 +190,7 @@ private:
RasterizerCacheOpenGL res_cache;
ShaderCacheOpenGL shader_cache;
+ GlobalRegionCacheOpenGL global_cache;
Core::Frontend::EmuWindow& emu_window;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index d3dcb9a46..bff0c65cd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -452,7 +452,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
- glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW);
+ glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_COPY);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index aea6bf1af..c785fffa3 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -188,6 +188,10 @@ void CachedShader::CalculateProperties() {
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
+ if (!Core::System::GetInstance().GPU().Maxwell3D().dirty_flags.shaders) {
+ return last_shaders[static_cast<u32>(program)];
+ }
+
const VAddr program_addr{GetShaderAddress(program)};
// Look up shader in the cache based on address
@@ -199,7 +203,7 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
Register(shader);
}
- return shader;
+ return last_shaders[static_cast<u32>(program)] = shader;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index de3671acf..768747968 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <map>
#include <memory>
@@ -115,6 +116,9 @@ public:
/// Gets the current specified shader stage program
Shader GetStageProgram(Maxwell::ShaderProgram program);
+
+private:
+ std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
};
} // namespace OpenGL
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 17ecaafde..1f852df4b 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -11,6 +11,8 @@ add_executable(yuzu
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
+ applets/web_browser.cpp
+ applets/web_browser.h
bootmanager.cpp
bootmanager.h
compatibility_list.cpp
@@ -37,6 +39,8 @@ add_executable(yuzu
configuration/configure_input_simple.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
+ configuration/configure_profile_manager.cpp
+ configuration/configure_profile_manager.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_per_general.cpp
@@ -94,6 +98,7 @@ set(UIS
configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
+ configuration/configure_profile_manager.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
@@ -154,6 +159,11 @@ if (USE_DISCORD_PRESENCE)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
+if (YUZU_USE_QT_WEB_ENGINE)
+ target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets)
+ target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
+endif ()
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
new file mode 100644
index 000000000..6a9138d53
--- /dev/null
+++ b/src/yuzu/applets/web_browser.cpp
@@ -0,0 +1,113 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <mutex>
+
+#include <QKeyEvent>
+
+#include "core/hle/lock.h"
+#include "yuzu/applets/web_browser.h"
+#include "yuzu/main.h"
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(
+ window.nx = {};
+ window.nx.playReport = {};
+ window.nx.playReport.setCounterSetIdentifier = function () {
+ console.log("nx.playReport.setCounterSetIdentifier called - unimplemented");
+ };
+
+ window.nx.playReport.incrementCounter = function () {
+ console.log("nx.playReport.incrementCounter called - unimplemented");
+ };
+
+ window.nx.footer = {};
+ window.nx.footer.unsetAssign = function () {
+ console.log("nx.footer.unsetAssign called - unimplemented");
+ };
+
+ var yuzu_key_callbacks = [];
+ window.nx.footer.setAssign = function(key, discard1, func, discard2) {
+ switch (key) {
+ case 'A':
+ yuzu_key_callbacks[0] = func;
+ break;
+ case 'B':
+ yuzu_key_callbacks[1] = func;
+ break;
+ case 'X':
+ yuzu_key_callbacks[2] = func;
+ break;
+ case 'Y':
+ yuzu_key_callbacks[3] = func;
+ break;
+ case 'L':
+ yuzu_key_callbacks[6] = func;
+ break;
+ case 'R':
+ yuzu_key_callbacks[7] = func;
+ break;
+ }
+ };
+
+ var applet_done = false;
+ window.nx.endApplet = function() {
+ applet_done = true;
+ };
+)";
+
+QString GetNXShimInjectionScript() {
+ return QString::fromStdString(NX_SHIM_INJECT_SCRIPT);
+}
+
+NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {}
+
+void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) {
+ parent()->event(event);
+}
+
+void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) {
+ parent()->event(event);
+}
+
+#endif
+
+QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
+ connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage,
+ Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this,
+ &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this,
+ &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection);
+}
+
+QtWebBrowser::~QtWebBrowser() = default;
+
+void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) {
+ this->unpack_romfs_callback = std::move(unpack_romfs_callback);
+ this->finished_callback = std::move(finished_callback);
+
+ const auto index = url.find('?');
+ if (index == std::string::npos) {
+ emit MainWindowOpenPage(url, "");
+ } else {
+ const auto front = url.substr(0, index);
+ const auto back = url.substr(index);
+ emit MainWindowOpenPage(front, back);
+ }
+}
+
+void QtWebBrowser::MainWindowUnpackRomFS() {
+ // Acquire the HLE mutex
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ unpack_romfs_callback();
+}
+
+void QtWebBrowser::MainWindowFinishedBrowsing() {
+ // Acquire the HLE mutex
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ finished_callback();
+}
diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h
new file mode 100644
index 000000000..1a3d67353
--- /dev/null
+++ b/src/yuzu/applets/web_browser.h
@@ -0,0 +1,52 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <QObject>
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineView>
+#endif
+
+#include "core/frontend/applets/web_browser.h"
+
+class GMainWindow;
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+QString GetNXShimInjectionScript();
+
+class NXInputWebEngineView : public QWebEngineView {
+public:
+ explicit NXInputWebEngineView(QWidget* parent = nullptr);
+
+protected:
+ void keyPressEvent(QKeyEvent* event) override;
+ void keyReleaseEvent(QKeyEvent* event) override;
+};
+
+#endif
+
+class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
+ Q_OBJECT
+
+public:
+ explicit QtWebBrowser(GMainWindow& main_window);
+ ~QtWebBrowser() override;
+
+ void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) override;
+
+signals:
+ void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
+
+private:
+ void MainWindowUnpackRomFS();
+ void MainWindowFinishedBrowsing();
+
+ std::function<void()> unpack_romfs_callback;
+ std::function<void()> finished_callback;
+};
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 165d70e9c..ddf4cf552 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -419,13 +419,21 @@ void Config::ReadValues() {
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
- const auto enabled = qt_config->value("rng_seed_enabled", false).toBool();
- if (enabled) {
+ const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
+ if (rng_seed_enabled) {
Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
} else {
Settings::values.rng_seed = std::nullopt;
}
+ const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
+ if (custom_rtc_enabled) {
+ Settings::values.custom_rtc =
+ std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
+ } else {
+ Settings::values.custom_rtc = std::nullopt;
+ }
+
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
@@ -653,6 +661,11 @@ void Config::SaveValues() {
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
+ qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
+ qt_config->setValue("custom_rtc",
+ QVariant::fromValue<long long>(
+ Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
+
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index ce833b6c8..3f03f0b77 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -52,6 +52,11 @@
<string>System</string>
</attribute>
</widget>
+ <widget class="ConfigureProfileManager" name="profileManagerTab">
+ <attribute name="title">
+ <string>Profiles</string>
+ </attribute>
+ </widget>
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
@@ -104,6 +109,12 @@
<container>1</container>
</customwidget>
<customwidget>
+ <class>ConfigureProfileManager</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_profile_manager.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
<header>configuration/configure_audio.h</header>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 90d7c6372..d802443d0 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -32,6 +32,7 @@ void ConfigureDialog::applyConfiguration() {
ui->generalTab->applyConfiguration();
ui->gameListTab->applyConfiguration();
ui->systemTab->applyConfiguration();
+ ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration();
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
@@ -43,7 +44,7 @@ void ConfigureDialog::applyConfiguration() {
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QStringList>, 4> items{
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
- {tr("System"), {tr("System"), tr("Audio")}},
+ {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}},
{tr("Controls"), {tr("Input")}}}};
@@ -60,11 +61,15 @@ void ConfigureDialog::UpdateVisibleTabs() {
if (items.isEmpty())
return;
- const std::map<QString, QWidget*> widgets = {
- {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab},
- {tr("Input"), ui->inputTab}, {tr("Graphics"), ui->graphicsTab},
- {tr("Audio"), ui->audioTab}, {tr("Debug"), ui->debugTab},
- {tr("Web"), ui->webTab}, {tr("Game List"), ui->gameListTab}};
+ const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab},
+ {tr("System"), ui->systemTab},
+ {tr("Profiles"), ui->profileManagerTab},
+ {tr("Input"), ui->inputTab},
+ {tr("Graphics"), ui->graphicsTab},
+ {tr("Audio"), ui->audioTab},
+ {tr("Debug"), ui->debugTab},
+ {tr("Web"), ui->webTab},
+ {tr("Game List"), ui->gameListTab}};
ui->tabWidget->clear();
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
new file mode 100644
index 000000000..41663e39a
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -0,0 +1,300 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <QFileDialog>
+#include <QGraphicsItem>
+#include <QGraphicsScene>
+#include <QHeaderView>
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/assert.h"
+#include "common/file_util.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/settings.h"
+#include "ui_configure_profile_manager.h"
+#include "yuzu/configuration/configure_profile_manager.h"
+#include "yuzu/util/limitable_input_dialog.h"
+
+namespace {
+// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
+constexpr std::array<u8, 107> backup_jpeg{
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
+ 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
+ 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
+ 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
+ 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+};
+
+QString GetImagePath(Service::Account::UUID uuid) {
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
+ return QString::fromStdString(path);
+}
+
+QString GetAccountUsername(const Service::Account::ProfileManager& manager,
+ Service::Account::UUID uuid) {
+ Service::Account::ProfileBase profile;
+ if (!manager.GetProfileBase(uuid, profile)) {
+ return {};
+ }
+
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+ return QString::fromStdString(text);
+}
+
+QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
+ return ConfigureProfileManager::tr("%1\n%2",
+ "%1 is the profile username, %2 is the formatted UUID (e.g. "
+ "00112233-4455-6677-8899-AABBCCDDEEFF))")
+ .arg(username, QString::fromStdString(uuid.FormatSwitch()));
+}
+
+QPixmap GetIcon(Service::Account::UUID uuid) {
+ QPixmap icon{GetImagePath(uuid)};
+
+ if (!icon) {
+ icon.fill(Qt::black);
+ icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
+ }
+
+ return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+}
+
+QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
+ return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"),
+ description_text, 1,
+ static_cast<int>(Service::Account::profile_username_size));
+}
+} // Anonymous namespace
+
+ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
+ : QWidget(parent), ui(new Ui::ConfigureProfileManager),
+ profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
+ ui->setupUi(this);
+
+ layout = new QVBoxLayout;
+ tree_view = new QTreeView;
+ item_model = new QStandardItemModel(tree_view);
+ tree_view->setModel(item_model);
+
+ tree_view->setAlternatingRowColors(true);
+ tree_view->setSelectionMode(QHeaderView::SingleSelection);
+ tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+ tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setSortingEnabled(true);
+ tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+ tree_view->setUniformRowHeights(true);
+ tree_view->setIconSize({64, 64});
+ tree_view->setContextMenuPolicy(Qt::NoContextMenu);
+
+ item_model->insertColumns(0, 1);
+ item_model->setHeaderData(0, Qt::Horizontal, "Users");
+
+ // We must register all custom types with the Qt Automoc system so that we are able to use it
+ // with signals/slots. In this case, QList falls under the umbrells of custom types.
+ qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tree_view);
+
+ ui->scrollArea->setLayout(layout);
+
+ connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser);
+
+ connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser);
+ connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser);
+ connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser);
+ connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage);
+
+ scene = new QGraphicsScene;
+ ui->current_user_icon->setScene(scene);
+
+ this->setConfiguration();
+}
+
+ConfigureProfileManager::~ConfigureProfileManager() = default;
+
+void ConfigureProfileManager::setConfiguration() {
+ enabled = !Core::System::GetInstance().IsPoweredOn();
+ item_model->removeRows(0, item_model->rowCount());
+ list_items.clear();
+
+ PopulateUserList();
+ UpdateCurrentUser();
+}
+
+void ConfigureProfileManager::PopulateUserList() {
+ const auto& profiles = profile_manager->GetAllUsers();
+ for (const auto& user : profiles) {
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(user, profile))
+ continue;
+
+ const auto username = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+
+ list_items.push_back(QList<QStandardItem*>{new QStandardItem{
+ GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
+ }
+
+ for (const auto& item : list_items)
+ item_model->appendRow(item);
+}
+
+void ConfigureProfileManager::UpdateCurrentUser() {
+ ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
+
+ const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
+ ASSERT(current_user);
+ const auto username = GetAccountUsername(*profile_manager, *current_user);
+
+ scene->clear();
+ scene->addPixmap(
+ GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ ui->current_user_username->setText(username);
+}
+
+void ConfigureProfileManager::applyConfiguration() {
+ if (!enabled)
+ return;
+
+ Settings::Apply();
+}
+
+void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
+ Settings::values.current_user =
+ std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
+
+ UpdateCurrentUser();
+
+ ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
+ ui->pm_rename->setEnabled(true);
+ ui->pm_set_image->setEnabled(true);
+}
+
+void ConfigureProfileManager::AddUser() {
+ const auto username =
+ GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
+ if (username.isEmpty()) {
+ return;
+ }
+
+ const auto uuid = Service::Account::UUID::Generate();
+ profile_manager->CreateNewUser(uuid, username.toStdString());
+
+ item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
+}
+
+void ConfigureProfileManager::RenameUser() {
+ const auto user = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(user);
+ ASSERT(uuid);
+
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(*uuid, profile))
+ return;
+
+ const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
+ if (new_username.isEmpty()) {
+ return;
+ }
+
+ const auto username_std = new_username.toStdString();
+ std::fill(profile.username.begin(), profile.username.end(), '\0');
+ std::copy(username_std.begin(), username_std.end(), profile.username.begin());
+
+ profile_manager->SetProfileBase(*uuid, profile);
+
+ item_model->setItem(
+ user, 0,
+ new QStandardItem{GetIcon(*uuid),
+ FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
+ UpdateCurrentUser();
+}
+
+void ConfigureProfileManager::DeleteUser() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+
+ const auto confirm = QMessageBox::question(
+ this, tr("Confirm Delete"),
+ tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
+
+ if (confirm == QMessageBox::No)
+ return;
+
+ if (Settings::values.current_user == tree_view->currentIndex().row())
+ Settings::values.current_user = 0;
+ UpdateCurrentUser();
+
+ if (!profile_manager->RemoveUser(*uuid))
+ return;
+
+ item_model->removeRows(tree_view->currentIndex().row(), 1);
+ tree_view->clearSelection();
+
+ ui->pm_remove->setEnabled(false);
+ ui->pm_rename->setEnabled(false);
+}
+
+void ConfigureProfileManager::SetUserImage() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+
+ const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
+ tr("JPEG Images (*.jpg *.jpeg)"));
+
+ if (file.isEmpty()) {
+ return;
+ }
+
+ const auto image_path = GetImagePath(*uuid);
+ if (QFile::exists(image_path) && !QFile::remove(image_path)) {
+ QMessageBox::warning(
+ this, tr("Error deleting image"),
+ tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
+ return;
+ }
+
+ const auto raw_path = QString::fromStdString(
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
+ const QFileInfo raw_info{raw_path};
+ if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
+ QMessageBox::warning(this, tr("Error deleting file"),
+ tr("Unable to delete existing file: %1.").arg(raw_path));
+ return;
+ }
+
+ const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
+ if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
+ QMessageBox::warning(
+ this, tr("Error creating user image directory"),
+ tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
+ return;
+ }
+
+ if (!QFile::copy(file, image_path)) {
+ QMessageBox::warning(this, tr("Error copying user image"),
+ tr("Unable to copy image from %1 to %2").arg(file, image_path));
+ return;
+ }
+
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+ item_model->setItem(index, 0,
+ new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
+ UpdateCurrentUser();
+}
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h
new file mode 100644
index 000000000..7fe95a2a8
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include <QList>
+#include <QWidget>
+
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Service::Account {
+class ProfileManager;
+}
+
+namespace Ui {
+class ConfigureProfileManager;
+}
+
+class ConfigureProfileManager : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureProfileManager(QWidget* parent = nullptr);
+ ~ConfigureProfileManager() override;
+
+ void applyConfiguration();
+ void setConfiguration();
+
+private:
+ void PopulateUserList();
+ void UpdateCurrentUser();
+
+ void SelectUser(const QModelIndex& index);
+ void AddUser();
+ void RenameUser();
+ void DeleteUser();
+ void SetUserImage();
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+ QGraphicsScene* scene;
+
+ std::vector<QList<QStandardItem*>> list_items;
+
+ std::unique_ptr<Ui::ConfigureProfileManager> ui;
+ bool enabled = false;
+
+ std::unique_ptr<Service::Account::ProfileManager> profile_manager;
+};
diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui
new file mode 100644
index 000000000..dedba4998
--- /dev/null
+++ b/src/yuzu/configuration/configure_profile_manager.ui
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureProfileManager</class>
+ <widget class="QWidget" name="ConfigureProfileManager">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>366</width>
+ <height>483</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox">
+ <property name="title">
+ <string>Profile Manager</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetNoConstraint</enum>
+ </property>
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Current User</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGraphicsView" name="current_user_icon">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="interactive">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="current_user_username">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="pm_set_image">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Set Image</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_add">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_rename">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_remove">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_disable_info">
+ <property name="text">
+ <string>Profile management is available only when game is not running.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index ab5d46492..94e27349d 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -15,7 +15,6 @@
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/core.h"
-#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configure_system.h"
@@ -36,64 +35,9 @@ constexpr std::array<int, 12> days_in_month = {{
30,
31,
}};
-
-// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
-constexpr std::array<u8, 107> backup_jpeg{
- 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
- 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
- 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
- 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
- 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
- 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
- 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
-};
-
-QString GetImagePath(Service::Account::UUID uuid) {
- const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
- "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
- return QString::fromStdString(path);
-}
-
-QString GetAccountUsername(const Service::Account::ProfileManager& manager,
- Service::Account::UUID uuid) {
- Service::Account::ProfileBase profile;
- if (!manager.GetProfileBase(uuid, profile)) {
- return {};
- }
-
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
- return QString::fromStdString(text);
-}
-
-QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
- return ConfigureSystem::tr("%1\n%2",
- "%1 is the profile username, %2 is the formatted UUID (e.g. "
- "00112233-4455-6677-8899-AABBCCDDEEFF))")
- .arg(username, QString::fromStdString(uuid.FormatSwitch()));
-}
-
-QPixmap GetIcon(Service::Account::UUID uuid) {
- QPixmap icon{GetImagePath(uuid)};
-
- if (!icon) {
- icon.fill(Qt::black);
- icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
- }
-
- return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-}
-
-QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
- return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"),
- description_text, 1,
- static_cast<int>(Service::Account::profile_username_size));
-}
} // Anonymous namespace
-ConfigureSystem::ConfigureSystem(QWidget* parent)
- : QWidget(parent), ui(new Ui::ConfigureSystem),
- profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
+ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@@ -101,50 +45,17 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
- layout = new QVBoxLayout;
- tree_view = new QTreeView;
- item_model = new QStandardItemModel(tree_view);
- tree_view->setModel(item_model);
-
- tree_view->setAlternatingRowColors(true);
- tree_view->setSelectionMode(QHeaderView::SingleSelection);
- tree_view->setSelectionBehavior(QHeaderView::SelectRows);
- tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
- tree_view->setSortingEnabled(true);
- tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
- tree_view->setUniformRowHeights(true);
- tree_view->setIconSize({64, 64});
- tree_view->setContextMenuPolicy(Qt::NoContextMenu);
-
- item_model->insertColumns(0, 1);
- item_model->setHeaderData(0, Qt::Horizontal, "Users");
-
- // We must register all custom types with the Qt Automoc system so that we are able to use it
- // with signals/slots. In this case, QList falls under the umbrells of custom types.
- qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
-
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(0);
- layout->addWidget(tree_view);
-
- ui->scrollArea->setLayout(layout);
-
- connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser);
-
- connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
- connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
- connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
- connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
-
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
ui->rng_seed_edit->setEnabled(checked);
if (!checked)
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
});
- scene = new QGraphicsScene;
- ui->current_user_icon->setScene(scene);
+ connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {
+ ui->custom_rtc_edit->setEnabled(checked);
+ if (!checked)
+ ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
+ });
this->setConfiguration();
}
@@ -156,49 +67,19 @@ void ConfigureSystem::setConfiguration() {
ui->combo_language->setCurrentIndex(Settings::values.language_index);
- item_model->removeRows(0, item_model->rowCount());
- list_items.clear();
-
- PopulateUserList();
- UpdateCurrentUser();
-
ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value());
const auto rng_seed =
QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper();
ui->rng_seed_edit->setText(rng_seed);
-}
-
-void ConfigureSystem::PopulateUserList() {
- const auto& profiles = profile_manager->GetAllUsers();
- for (const auto& user : profiles) {
- Service::Account::ProfileBase profile;
- if (!profile_manager->GetProfileBase(user, profile))
- continue;
-
- const auto username = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
-
- list_items.push_back(QList<QStandardItem*>{new QStandardItem{
- GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
- }
- for (const auto& item : list_items)
- item_model->appendRow(item);
-}
-
-void ConfigureSystem::UpdateCurrentUser() {
- ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
-
- const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
- ASSERT(current_user);
- const auto username = GetAccountUsername(*profile_manager, *current_user);
+ ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
+ ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
- scene->clear();
- scene->addPixmap(
- GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
- ui->current_user_username->setText(username);
+ const auto rtc_time = Settings::values.custom_rtc.value_or(
+ std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
+ ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
}
void ConfigureSystem::ReadSystemSettings() {}
@@ -214,6 +95,12 @@ void ConfigureSystem::applyConfiguration() {
else
Settings::values.rng_seed = std::nullopt;
+ if (ui->custom_rtc_checkbox->isChecked())
+ Settings::values.custom_rtc =
+ std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch());
+ else
+ Settings::values.custom_rtc = std::nullopt;
+
Settings::Apply();
}
@@ -256,130 +143,3 @@ void ConfigureSystem::RefreshConsoleID() {
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
-
-void ConfigureSystem::SelectUser(const QModelIndex& index) {
- Settings::values.current_user =
- std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
-
- UpdateCurrentUser();
-
- ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
- ui->pm_rename->setEnabled(true);
- ui->pm_set_image->setEnabled(true);
-}
-
-void ConfigureSystem::AddUser() {
- const auto username =
- GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
- if (username.isEmpty()) {
- return;
- }
-
- const auto uuid = Service::Account::UUID::Generate();
- profile_manager->CreateNewUser(uuid, username.toStdString());
-
- item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
-}
-
-void ConfigureSystem::RenameUser() {
- const auto user = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(user);
- ASSERT(uuid);
-
- Service::Account::ProfileBase profile;
- if (!profile_manager->GetProfileBase(*uuid, profile))
- return;
-
- const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
- if (new_username.isEmpty()) {
- return;
- }
-
- const auto username_std = new_username.toStdString();
- std::fill(profile.username.begin(), profile.username.end(), '\0');
- std::copy(username_std.begin(), username_std.end(), profile.username.begin());
-
- profile_manager->SetProfileBase(*uuid, profile);
-
- item_model->setItem(
- user, 0,
- new QStandardItem{GetIcon(*uuid),
- FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
- UpdateCurrentUser();
-}
-
-void ConfigureSystem::DeleteUser() {
- const auto index = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(index);
- ASSERT(uuid);
- const auto username = GetAccountUsername(*profile_manager, *uuid);
-
- const auto confirm = QMessageBox::question(
- this, tr("Confirm Delete"),
- tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
-
- if (confirm == QMessageBox::No)
- return;
-
- if (Settings::values.current_user == tree_view->currentIndex().row())
- Settings::values.current_user = 0;
- UpdateCurrentUser();
-
- if (!profile_manager->RemoveUser(*uuid))
- return;
-
- item_model->removeRows(tree_view->currentIndex().row(), 1);
- tree_view->clearSelection();
-
- ui->pm_remove->setEnabled(false);
- ui->pm_rename->setEnabled(false);
-}
-
-void ConfigureSystem::SetUserImage() {
- const auto index = tree_view->currentIndex().row();
- const auto uuid = profile_manager->GetUser(index);
- ASSERT(uuid);
-
- const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
- tr("JPEG Images (*.jpg *.jpeg)"));
-
- if (file.isEmpty()) {
- return;
- }
-
- const auto image_path = GetImagePath(*uuid);
- if (QFile::exists(image_path) && !QFile::remove(image_path)) {
- QMessageBox::warning(
- this, tr("Error deleting image"),
- tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
- return;
- }
-
- const auto raw_path = QString::fromStdString(
- FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
- const QFileInfo raw_info{raw_path};
- if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
- QMessageBox::warning(this, tr("Error deleting file"),
- tr("Unable to delete existing file: %1.").arg(raw_path));
- return;
- }
-
- const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
- if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
- QMessageBox::warning(
- this, tr("Error creating user image directory"),
- tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
- return;
- }
-
- if (!QFile::copy(file, image_path)) {
- QMessageBox::warning(this, tr("Error copying user image"),
- tr("Unable to copy image from %1 to %2").arg(file, image_path));
- return;
- }
-
- const auto username = GetAccountUsername(*profile_manager, *uuid);
- item_model->setItem(index, 0,
- new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
- UpdateCurrentUser();
-}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index 07764e1f7..cf1e54de5 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -9,16 +9,6 @@
#include <QList>
#include <QWidget>
-class QGraphicsScene;
-class QStandardItem;
-class QStandardItemModel;
-class QTreeView;
-class QVBoxLayout;
-
-namespace Service::Account {
-class ProfileManager;
-}
-
namespace Ui {
class ConfigureSystem;
}
@@ -39,21 +29,6 @@ private:
void UpdateBirthdayComboBox(int birthmonth_index);
void RefreshConsoleID();
- void PopulateUserList();
- void UpdateCurrentUser();
- void SelectUser(const QModelIndex& index);
- void AddUser();
- void RenameUser();
- void DeleteUser();
- void SetUserImage();
-
- QVBoxLayout* layout;
- QTreeView* tree_view;
- QStandardItemModel* item_model;
- QGraphicsScene* scene;
-
- std::vector<QList<QStandardItem*>> list_items;
-
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
@@ -61,6 +36,4 @@ private:
int birthday = 0;
int language_index = 0;
int sound_index = 0;
-
- std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index a91580893..073327298 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -22,6 +22,13 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_sound">
+ <property name="text">
+ <string>Sound output mode</string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
@@ -114,27 +121,6 @@
</item>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_console_id">
- <property name="text">
- <string>Console ID:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_sound">
- <property name="text">
- <string>Sound output mode</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_birthday">
- <property name="text">
- <string>Birthday</string>
- </property>
- </widget>
- </item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item>
@@ -206,6 +192,20 @@
</item>
</layout>
</item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_console_id">
+ <property name="text">
+ <string>Console ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_birthday">
+ <property name="text">
+ <string>Birthday</string>
+ </property>
+ </widget>
+ </item>
<item row="3" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
@@ -241,21 +241,21 @@
</item>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_language">
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
- <string>Language</string>
+ <string>RNG Seed</string>
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_language">
<property name="text">
- <string>RNG Seed</string>
+ <string>Language</string>
</property>
</widget>
</item>
- <item row="4" column="1">
+ <item row="5" column="1">
<widget class="QLineEdit" name="rng_seed_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@@ -276,147 +276,44 @@
</property>
</widget>
</item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="gridGroupBox">
- <property name="title">
- <string>Profile Manager</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <property name="sizeConstraint">
- <enum>QLayout::SetNoConstraint</enum>
- </property>
- <item row="0" column="0">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Current User</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QGraphicsView" name="current_user_icon">
- <property name="minimumSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="interactive">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="current_user_username">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Username</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QScrollArea" name="scrollArea">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="custom_rtc_checkbox">
+ <property name="text">
+ <string>Custom RTC</string>
</property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QDateTimeEdit" name="custom_rtc_edit">
+ <property name="minimumDate">
+ <date>
+ <year>1970</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
</property>
- <property name="widgetResizable">
- <bool>false</bool>
+ <property name="displayFormat">
+ <string>d MMM yyyy h:mm:ss AP</string>
</property>
</widget>
</item>
- <item row="2" column="0">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QPushButton" name="pm_set_image">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Set Image</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="pm_add">
- <property name="text">
- <string>Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="pm_rename">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Rename</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="pm_remove">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Remove</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
</item>
<item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>System settings are available only when game is not running.</string>
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 3c2ccb76f..18566d028 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -89,12 +89,11 @@ void ConfigureWeb::OnLoginChanged() {
void ConfigureWeb::VerifyLogin() {
ui->button_verify_login->setDisabled(true);
- ui->button_verify_login->setText(tr("Verifying"));
- verify_watcher.setFuture(
- QtConcurrent::run([this, username = ui->edit_username->text().toStdString(),
- token = ui->edit_token->text().toStdString()]() {
- return Core::VerifyLogin(username, token);
- }));
+ ui->button_verify_login->setText(tr("Verifying..."));
+ verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(),
+ token = ui->edit_token->text().toStdString()] {
+ return Core::VerifyLogin(username, token);
+ }));
}
void ConfigureWeb::OnLoginVerified() {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 8e9524fd6..c0e3c5fa9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -376,7 +376,7 @@ void GameList::LoadCompatibilityList() {
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
QJsonArray arr = json.array();
- for (const QJsonValueRef& value : arr) {
+ for (const QJsonValueRef value : arr) {
QJsonObject game = value.toObject();
if (game.contains("compatibility") && game["compatibility"].isDouble()) {
@@ -384,7 +384,7 @@ void GameList::LoadCompatibilityList() {
QString directory = game["directory"].toString();
QJsonArray ids = game["releases"].toArray();
- for (const QJsonValueRef& id_ref : ids) {
+ for (const QJsonValueRef id_ref : ids) {
QJsonObject id_object = id_ref.toObject();
QString id = id_object["id"].toString();
compatibility_list.emplace(
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1d5a2b51a..f564de994 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,11 +10,14 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
+#include "applets/web_browser.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -96,6 +99,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/discord_impl.h"
#endif
+#ifdef YUZU_USE_QT_WEB_ENGINE
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+#endif
+
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -252,6 +263,144 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog();
}
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ NXInputWebEngineView web_browser_view(this);
+
+ // Scope to contain the QProgressDialog for initalization
+ {
+ QProgressDialog progress(this);
+ progress.setMinimumDuration(200);
+ progress.setLabelText(tr("Loading Web Applet..."));
+ progress.setRange(0, 4);
+ progress.setValue(0);
+ progress.show();
+
+ auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); });
+
+ while (!future.isFinished())
+ QApplication::processEvents();
+
+ progress.setValue(1);
+
+ // Load the special shim script to handle input and exit.
+ QWebEngineScript nx_shim;
+ nx_shim.setSourceCode(GetNXShimInjectionScript());
+ nx_shim.setWorldId(QWebEngineScript::MainWorld);
+ nx_shim.setName("nx_inject.js");
+ nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
+ nx_shim.setRunsOnSubFrames(true);
+ web_browser_view.page()->profile()->scripts()->insert(nx_shim);
+
+ web_browser_view.load(
+ QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() +
+ QString::fromStdString(std::string(additional_args))));
+
+ progress.setValue(2);
+
+ render_window->hide();
+ web_browser_view.setFocus();
+
+ const auto& layout = render_window->GetFramebufferLayout();
+ web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
+ web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
+ web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
+ Layout::ScreenUndocked::Width);
+ web_browser_view.settings()->setAttribute(
+ QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+
+ web_browser_view.show();
+
+ progress.setValue(3);
+
+ QApplication::processEvents();
+
+ progress.setValue(4);
+ }
+
+ bool finished = false;
+ QAction* exit_action = new QAction(tr("Exit Web Applet"), this);
+ connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; });
+ ui.menubar->addAction(exit_action);
+
+ auto& npad =
+ Core::System::GetInstance()
+ .ServiceManager()
+ .GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
+ .arg(QString::fromStdString(std::to_string(key_code))));
+ };
+
+ bool running_exit_check = false;
+ while (!finished) {
+ QApplication::processEvents();
+
+ if (!running_exit_check) {
+ web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"),
+ [&](const QVariant& res) {
+ running_exit_check = false;
+ if (res.toBool())
+ finished = true;
+ });
+ running_exit_check = true;
+ }
+
+ const auto input = npad.GetAndResetPressState();
+ for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ if ((input & (1 << i)) != 0) {
+ LOG_DEBUG(Frontend, "firing input for button id={:02X}", i);
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("yuzu_key_callbacks[%1]();").arg(i));
+ }
+ }
+
+ if (input & 0x00888000) // RStick Down | LStick Down | DPad Down
+ fire_js_keypress(40); // Down Arrow Key
+ else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right
+ fire_js_keypress(39); // Right Arrow Key
+ else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up
+ fire_js_keypress(38); // Up Arrow Key
+ else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left
+ fire_js_keypress(37); // Left Arrow Key
+ else if (input & 0x00000001) // A Button
+ fire_js_keypress(13); // Enter Key
+ }
+
+ web_browser_view.hide();
+ render_window->show();
+ render_window->setFocus();
+ ui.menubar->removeAction(exit_action);
+
+ // Needed to update render window focus/show and remove menubar action
+ QApplication::processEvents();
+ emit WebBrowserFinishedBrowsing();
+}
+
+#else
+
+void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
+ QMessageBox::warning(
+ this, tr("Web Applet"),
+ tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot "
+ "properly display the game manual or web page requested."),
+ QMessageBox::Ok, QMessageBox::Ok);
+
+ LOG_INFO(Frontend,
+ "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at "
+ "'{}' with arguments '{}'!",
+ filename, additional_args);
+
+ emit WebBrowserFinishedBrowsing();
+}
+
+#endif
+
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui.action_Report_Compatibility->setVisible(true);
@@ -612,6 +761,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));
system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));
+ system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this));
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -1325,6 +1475,7 @@ void GMainWindow::OnStartGame() {
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
+ qRegisterMetaType<std::string_view>("std::string_view");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index d560bf75b..2d705ad54 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -13,6 +13,7 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "ui_main.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -26,6 +27,7 @@ class GraphicsSurfaceWidget;
class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
+class QLabel;
class WaitTreeWidget;
enum class GameListOpenTarget;
@@ -103,11 +105,16 @@ signals:
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
+ void WebBrowserUnpackRomFS();
+ void WebBrowserFinishedBrowsing();
+
public slots:
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
+ void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
+
private:
void InitializeWidgets();
void InitializeDebugWidgets();
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index fe0d1eebf..7a77f76e8 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -325,13 +325,21 @@ void Config::ReadValues() {
Settings::values.current_user = std::clamp<int>(
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
- const auto enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
- if (enabled) {
+ const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
+ if (rng_seed_enabled) {
Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0);
} else {
Settings::values.rng_seed = std::nullopt;
}
+ const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
+ if (custom_rtc_enabled) {
+ Settings::values.custom_rtc =
+ std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0));
+ } else {
+ Settings::values.custom_rtc = std::nullopt;
+ }
+
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 0f3f8da50..ba51a4a51 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -183,6 +183,12 @@ enable_nfc =
rng_seed_enabled =
rng_seed =
+# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
+# This will auto-increment, with the time set being the time the game is started
+# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
+custom_rtc_enabled =
+custom_rtc =
+
# Sets the account username, max length is 32 characters
# yuzu (default)
username = yuzu