Compare commits

..

107 Commits

Author SHA1 Message Date
Morph
4cdf69c378 default_ini: Split and concatenate the config string literal
We are dangerously close to MSVC's 16384 character limit for string literals. Breaking this string up and concatenating will allow for more settings to be added in the future.
2023-01-25 14:06:06 -05:00
bunnei
44b981fd3e Merge pull request #9662 from abouvier/cmake-llvm
cmake: prefer system llvm library
2023-01-24 11:03:14 -08:00
liamwhite
a68af583ea Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2
2023-01-24 09:29:37 -05:00
Alexandre Bouvier
34b1ea9c19 cmake: prefer system llvm library 2023-01-23 06:23:00 +01:00
liamwhite
f99f618d45 Merge pull request #9555 from abouvier/catch2-update
tests: update catch2 to 3.0.1
2023-01-22 18:22:47 -05:00
liamwhite
78df1ddce8 Merge pull request #9660 from german77/koreaToTaiwan
yuzu: Fix language comobox crash
2023-01-22 13:14:38 -05:00
liamwhite
9184b6ed2b Merge pull request #9656 from liamwhite/nsight
nsight_aftermath_tracker: update for latest Aftermath SDK
2023-01-22 13:14:28 -05:00
liamwhite
ff2bbc8205 Merge pull request #9637 from SaiKai/repeat_shortcuts
allow volume up/down hotkeys to be repeated
2023-01-22 13:14:19 -05:00
liamwhite
02ac593257 Merge pull request #9617 from german77/off_by_one
core: hid: Fix stick minimum range
2023-01-22 13:14:10 -05:00
liamwhite
9705094a57 Merge pull request #9613 from Kelebek1/demangle
Add stacktrace symbol demangling
2023-01-22 13:13:58 -05:00
german77
a436467152 yuzu: Fix language comobox crash 2023-01-22 10:42:59 -06:00
Liam
c6eab71d5c nsight_aftermath_tracker: update for latest Aftermath SDK 2023-01-21 13:01:19 -05:00
Kelebek1
31229dd245 Change licenses 2023-01-21 06:19:43 +00:00
bunnei
380dcde154 Merge pull request #9642 from Tachi107/appstream-metadata-fix
fix(dist): wrap screenshots in <image> tags
2023-01-20 21:02:38 -08:00
bunnei
f78068d7bf Merge pull request #9611 from liamwhite/patch-1
debugger: add host fastmem pointer fetch command
2023-01-20 10:00:33 -08:00
Narr the Reg
d9ee7c3297 core: hid: Make use of SCOPE_EXIT and SCOPE_GUARD where applicable 2023-01-20 00:51:46 -06:00
german77
fafa92cfb8 input_common: Fix joycon mappings 2023-01-20 00:51:46 -06:00
german77
340f15d1fa input_common: Address byte review 2023-01-20 00:51:45 -06:00
bunnei
7d77798f0e Merge pull request #9640 from german77/why_sdl
input_common: reset sdl motion if data is invalid
2023-01-19 18:47:21 -08:00
Narr the Reg
4a307a7b3a core: hid: Only set the polling mode to the correct side 2023-01-19 18:05:23 -06:00
german77
b40aefb39e input_common: Drop Pro controller support from custom driver 2023-01-19 18:05:22 -06:00
german77
d05ea2f3eb input_common: Fix issue where ring and irs are enabled at the same time 2023-01-19 18:05:22 -06:00
Narr the Reg
459fb2b213 input_common: Implement joycon ir camera 2023-01-19 18:05:22 -06:00
german77
5cb437703f yuzu: Add ring controller test button 2023-01-19 18:05:22 -06:00
german77
527dad7097 input_common: Use DriverResult on all engines 2023-01-19 18:05:22 -06:00
german77
e1a3bda4d9 Address review comments 2023-01-19 18:05:22 -06:00
Narr the Reg
1c08d532e0 core: hid: Fix input regressions 2023-01-19 18:05:22 -06:00
german77
6d6b7bdbc3 input_common: Implement joycon nfc 2023-01-19 18:05:21 -06:00
Narr the Reg
6e33731f29 input_common: Add dual joycon support 2023-01-19 18:05:21 -06:00
Narr the Reg
751d36e739 input_common: Add support for joycon ring controller 2023-01-19 18:05:21 -06:00
Narr the Reg
f09a023292 input_common: Add support for joycon input reports 2023-01-19 18:05:21 -06:00
Narr the Reg
5676c2e17f input_common: Use calibration from joycon 2023-01-19 18:05:21 -06:00
Narr the Reg
594b2ade6d input_common: Add support for joycon generic functions 2023-01-19 18:05:21 -06:00
Narr the Reg
6aa6301acd input_common: Add joycon low level functions 2023-01-19 18:05:21 -06:00
Narr the Reg
36d5e0a411 service: hid: Set led pattern and fix color detection 2023-01-19 18:05:20 -06:00
Narr the Reg
ed5fa10e97 core: hid: Enable pulling color data from controllers 2023-01-19 18:05:20 -06:00
Narr the Reg
a4074001fe core: hid: Migrate ring from emulated devices to emulated controller 2023-01-19 18:05:20 -06:00
Narr the Reg
18c9f8eeed yuzu: Update controller colors and button names 2023-01-19 18:05:20 -06:00
Narr the Reg
2d802893e7 input_common: Disable SDL driver with switch controllers 2023-01-19 18:05:20 -06:00
Narr the Reg
d80e6c399b input_common: Initial skeleton for custom joycon driver 2023-01-19 18:05:20 -06:00
liamwhite
475370c8f8 Merge pull request #9556 from vonchenplus/draw_texture
video_core: Implement maxwell3d draw texture method
2023-01-19 14:58:53 -05:00
bunnei
9ca3a4758a Merge pull request #9623 from liamwhite/wp-oops
memory: fix watchpoint use when fastmem is enabled
2023-01-19 10:50:21 -08:00
Andrea Pappacoda
9f08b3c3c9 fix(dist): wrap screenshots in <image> tags
The [appstream] spec says that <screenshot/> tags must be wrapped in
either <image/> or <video/> tags, so this patch does just that.

[appstream]: https://freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-screenshots
2023-01-19 10:13:30 +01:00
Narr the Reg
67c3c65f7b Merge pull request #9638 from Kelebek1/firmware4
Demote maxwell3d Firmware4 call log to debug
2023-01-18 23:15:22 -06:00
bunnei
413df0811d Merge pull request #9619 from liamwhite/timing-spaghetti
timing: wait for completion on unregister
2023-01-18 15:13:38 -08:00
german77
db6cb9cc0a input_common: reset sdl motion if data is invalid 2023-01-18 10:05:55 -06:00
Jonas Gutenschwager
887ed5c0e2 fix format 2023-01-18 15:49:47 +01:00
bunnei
82e2ac6026 Merge pull request #9615 from merryhime/upsample-ob1
audio_core: Corrective fixes to upsampler
2023-01-17 23:34:12 -08:00
bunnei
1551f97950 Merge pull request #9608 from liamwhite/fps
nvnflinger: correct swap interval handling
2023-01-17 23:13:47 -08:00
Kelebek1
5a106cf11e Demote maxwell3d Firmware4 call log to debug 2023-01-18 01:59:11 +00:00
Jonas Gutenschwager
83754e4789 allow volume up/down hotkeys to be repeated 2023-01-18 00:39:31 +01:00
bunnei
28fde29924 Merge pull request #9612 from goldenx86/1.5xScaler
1.5X, 7X, 8X resolution scaler options
2023-01-16 17:49:06 -08:00
bunnei
58a10ed5d0 Merge pull request #9622 from merryhime/dynarmic
externals: Update dynarmic to 6.4.4
2023-01-15 23:22:54 -08:00
Feng Chen
9fc7ca1731 Address feedback 2023-01-16 10:27:57 +08:00
Liam
f1a0ce0e70 memory: fix watchpoint use when fastmem is enabled 2023-01-15 10:24:31 -05:00
Merry
2817af0961 externals: Update dynarmic to 6.4.4 2023-01-15 02:31:00 +00:00
Liam
0953cdd271 timing: wait for completion on unregister 2023-01-14 15:48:01 -05:00
Kelebek1
42b16bb33a Be careful of mangled out of bounds read 2023-01-14 19:53:55 +00:00
german77
50c86b3c2a core: hid: Fix stick minimum range 2023-01-14 11:53:43 -06:00
Merry
a0e8e5b22e upsample: Fix coefficient format 2023-01-14 17:09:03 +00:00
Merry
122a8faa38 audio_core: Fix off-by-one error in upsampler 2023-01-14 15:19:11 +00:00
Kelebek1
ce0b8d618d Move demangle impl to cpp 2023-01-14 05:12:41 +00:00
Kelebek1
80a55c1663 Add stacktrace symbol demangling 2023-01-14 04:43:21 +00:00
Matías Locatti
d7dbd8e774 Update settings.h 2023-01-13 04:56:25 -03:00
Matías Locatti
4294429a07 CPP 2023-01-13 04:55:26 -03:00
Matías Locatti
bb2aec00a2 UI change 2023-01-13 04:54:29 -03:00
Matías Locatti
3ba53f2511 1.5X resolution scaler option 2023-01-13 02:34:24 -03:00
Liam
e9c3d16f6f debugger: add host fastmem pointer fetch command 2023-01-12 18:35:14 -05:00
Liam
2f2ef5b147 nvnflinger: correct swap interval handling 2023-01-11 22:05:08 -05:00
bunnei
0e8f98a441 Merge pull request #9605 from german77/mouse_mapping
yuzu: Read mouse scroll
2023-01-10 17:32:58 -08:00
Narr the Reg
87b02f78e1 yuzu: Read mouse wheel input 2023-01-10 18:20:58 -06:00
Morph
c277dad25f Merge pull request #9596 from liamwhite/mvk
MoltenVK: restrict number of vertex attributes/bindings to 16
2023-01-10 18:11:44 -05:00
liamwhite
cbcf210c19 Merge pull request #9582 from yuzu-emu/revert-9518-revert-9504-pg2
Revert "Revert "k_page_group: synchronize""
2023-01-10 15:27:33 -05:00
liamwhite
0eae0b6803 Merge pull request #9601 from liamwhite/it-never-ends
qt: unlock during signal emission
2023-01-10 15:27:12 -05:00
liamwhite
74404261d2 Merge pull request #9598 from liamwhite/indirect
vulkan_common: fix indirect draw with count
2023-01-10 15:27:02 -05:00
liamwhite
e2c68edd35 Merge pull request #9595 from liamwhite/per-game
qt: fix configuration weirdness on turbo
2023-01-10 15:26:49 -05:00
liamwhite
9f974ea818 Merge pull request #9565 from MonsterDruide1/tas-multiplayer-lengths
TAS: Show all script lengths for multiplayer
2023-01-10 15:26:36 -05:00
Liam
385ddef8c3 qt: unlock during signal emission 2023-01-10 12:46:01 -05:00
Liam
fa8581e900 vulkan_common: fix indirect draw with count 2023-01-10 09:43:36 -05:00
TellowKrinkle
eaf425bd32 MoltenVK: restrict number of vertex attributes/bindings to 16 2023-01-09 19:01:09 -05:00
Liam
1caa84e652 qt: fix configuration weirdness on turbo 2023-01-09 17:54:49 -05:00
UltraHDR
fda0e7e989 macOS: Make Yuzu show up in the Launchpad Games folder (#9594)
https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype
This makes it show up in the Launchpad Games folder
2023-01-09 16:43:45 -05:00
Morph
6bf1436f5c Merge pull request #9589 from liamwhite/default
renderer_vulkan: disable turbo by default
2023-01-09 16:23:24 -05:00
Morph
ffb12f4f9f Merge pull request #9581 from liamwhite/turbo2
renderer_vulkan: pause turbo submissions on inactive queue
2023-01-09 16:23:16 -05:00
Morph
a4cd747122 Merge pull request #9530 from liamwhite/vk-feature-init
vulkan_device: refactor feature testing
2023-01-09 16:23:04 -05:00
Morph
3be8312120 Merge pull request #9569 from liamwhite/shutdown-wars
qt: additional fixes for reentrant shutdown
2023-01-09 16:21:27 -05:00
Liam
279005448a vulkan_device: refactor feature testing 2023-01-09 16:12:16 -05:00
Fernando S
948ac976d1 Merge pull request #9583 from FernandoS27/fuck-ogl-already
VideoCore: Fix OGL cache invalidation.
2023-01-08 14:05:29 -05:00
Liam
0df43e4e07 renderer_vulkan: disable turbo by default 2023-01-08 11:38:30 -05:00
Fernando Sahmkow
efbb6fe288 VideoCore: Fix OGL cache invalidation. 2023-01-07 21:56:17 -05:00
bunnei
a5693afa03 Revert "Revert "k_page_group: synchronize"" 2023-01-07 15:32:10 -08:00
bunnei
66e4a48b75 Merge pull request #9563 from german77/crash_not_allowed
input_common: Create an update engine
2023-01-07 13:41:27 -08:00
MonsterDruide1
115bf20448 TAS: Show all script lengths for multiplayer 2023-01-07 21:15:37 +01:00
liamwhite
926438b68d Merge pull request #9578 from bylaws/patch-2
Avoid OOB array access reading passthrough attr mask
2023-01-07 15:14:08 -05:00
Liam
c19c8ac92c renderer_vulkan: pause turbo submissions on inactive queue 2023-01-07 14:35:11 -05:00
Billy Laws
d34275a260 Avoid OOB array access reading passthrough attr mask
YFC 1.5 extended the size of the varying mask used to hold passthrough attrs without considering this
2023-01-07 18:38:21 +00:00
Narr the Reg
432d48d9c8 Merge pull request #9570 from liamwhite/less-clock-boost
renderer_vulkan: disable clock boost on unvalidated devices
2023-01-07 10:41:37 -06:00
Narr the Reg
cc92b7fd94 Merge pull request #9573 from liamwhite/optional
vulkan_device: avoid attempt to access empty optional
2023-01-07 10:40:21 -06:00
Liam
444b25bae1 vulkan_device: avoid attempt to access empty optional 2023-01-06 21:23:21 -05:00
Liam
2e4dde12c7 renderer_vulkan: disable clock boost on unvalidated devices 2023-01-06 19:07:47 -05:00
liamwhite
c0f17e1b27 Merge pull request #9567 from german77/antialias
opengl: Sanitize antialiasing config
2023-01-06 15:20:23 -05:00
Narr the Reg
4bda2b475f opengl: Sanitize antialiasing config 2023-01-06 13:42:20 -06:00
Narr the Reg
8042ce7e19 input_common: Create an update engine 2023-01-05 19:24:29 -06:00
Feng Chen
013b689153 video_core: Implement opengl/vulkan draw_texture 2023-01-05 12:41:33 +08:00
Feng Chen
1e8cee2ddf video_core: Implement maxwell3d draw texture method 2023-01-05 12:41:28 +08:00
Alexandre Bouvier
7a2bd13f5b cmake: support the standard cmake testing option 2023-01-05 05:03:34 +01:00
Alexandre Bouvier
d0fe27708e tests: update catch2 to 3.0.1 2023-01-05 04:58:31 +01:00
167 changed files with 14586 additions and 2168 deletions

View File

@@ -3,12 +3,14 @@
cmake_minimum_required(VERSION 3.22)
project(yuzu)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
include(CMakeDependentOption)
project(yuzu)
include(CTest)
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@@ -42,7 +44,7 @@ option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(YUZU_TESTS "Compile tests" ON)
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
@@ -206,6 +208,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED)
find_package(inih MODULE)
find_package(LLVM MODULE)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
@@ -242,7 +245,7 @@ if (ENABLE_WEB_SERVICE)
endif()
if (YUZU_TESTS)
find_package(Catch2 2.13.7 REQUIRED)
find_package(Catch2 3.0.1 REQUIRED)
endif()
find_package(Boost 1.73.0 COMPONENTS context)
@@ -606,7 +609,6 @@ if (YUZU_USE_FASTER_LD AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
endif()
endif()
enable_testing()
add_subdirectory(externals)
add_subdirectory(src)

View File

@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_package(LLVM QUIET CONFIG)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LLVM CONFIG_MODE)
if (LLVM_FOUND AND NOT TARGET LLVM::Demangle)
add_library(LLVM::Demangle INTERFACE IMPORTED)
llvm_map_components_to_libnames(LLVM_LIBRARIES demangle)
target_compile_definitions(LLVM::Demangle INTERFACE ${LLVM_DEFINITIONS})
target_include_directories(LLVM::Demangle INTERFACE ${LLVM_INCLUDE_DIRS})
target_link_libraries(LLVM::Demangle INTERFACE ${LLVM_LIBRARIES})
endif()

View File

@@ -0,0 +1,15 @@
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -53,10 +53,10 @@ SPDX-License-Identifier: CC0-1.0
<developer_name>yuzu Emulator Team</developer_name>
<content_rating type="oars-1.0"/>
<screenshots>
<screenshot type="default">https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/001-Super%20Mario%20Odyssey%20.png</screenshot>
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/004-The%20Legend%20of%20Zelda%20Skyward%20Sword%20HD.png</screenshot>
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/007-Pokemon%20Sword.png</screenshot>
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/010-Hyrule%20Warriors%20Age%20of%20Calamity.png</screenshot>
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/039-Pok%C3%A9mon%20Mystery%20Dungeon%20Rescue%20Team%20DX.png.png.png</screenshot>
<screenshot type="default"><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/001-Super%20Mario%20Odyssey%20.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/004-The%20Legend%20of%20Zelda%20Skyward%20Sword%20HD.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/007-Pokemon%20Sword.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/010-Hyrule%20Warriors%20Age%20of%20Calamity.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/039-Pok%C3%A9mon%20Mystery%20Dungeon%20Rescue%20Team%20DX.png.png.png</image></screenshot>
</screenshots>
</component>

View File

@@ -5,6 +5,9 @@
# some of its variables, which is only possible in 3.13+
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
# Disable tests in all externals supporting the standard option name
set(BUILD_TESTING OFF)
# xbyak
if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
add_subdirectory(xbyak EXCLUDE_FROM_ALL)
@@ -154,3 +157,10 @@ endif()
if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
add_subdirectory(Vulkan-Headers EXCLUDE_FROM_ALL)
endif()
if (NOT TARGET LLVM::Demangle)
add_library(demangle STATIC)
target_include_directories(demangle PUBLIC ./demangle)
target_sources(demangle PRIVATE demangle/ItaniumDemangle.cpp)
add_library(LLVM::Demangle ALIAS demangle)
endif()

588
externals/demangle/ItaniumDemangle.cpp vendored Normal file
View File

@@ -0,0 +1,588 @@
//===------------------------- ItaniumDemangle.cpp ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-FileCopyrightText: Part of the LLVM Project
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// FIXME: (possibly) incomplete list of features that clang mangles that this
// file does not yet support:
// - C++ modules TS
#include "llvm/Demangle/Demangle.h"
#include "llvm/Demangle/ItaniumDemangle.h"
#include <cassert>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <numeric>
#include <utility>
#include <vector>
using namespace llvm;
using namespace llvm::itanium_demangle;
constexpr const char *itanium_demangle::FloatData<float>::spec;
constexpr const char *itanium_demangle::FloatData<double>::spec;
constexpr const char *itanium_demangle::FloatData<long double>::spec;
// <discriminator> := _ <non-negative number> # when number < 10
// := __ <non-negative number> _ # when number >= 10
// extension := decimal-digit+ # at the end of string
const char *itanium_demangle::parse_discriminator(const char *first,
const char *last) {
// parse but ignore discriminator
if (first != last) {
if (*first == '_') {
const char *t1 = first + 1;
if (t1 != last) {
if (std::isdigit(*t1))
first = t1 + 1;
else if (*t1 == '_') {
for (++t1; t1 != last && std::isdigit(*t1); ++t1)
;
if (t1 != last && *t1 == '_')
first = t1 + 1;
}
}
} else if (std::isdigit(*first)) {
const char *t1 = first + 1;
for (; t1 != last && std::isdigit(*t1); ++t1)
;
if (t1 == last)
first = last;
}
}
return first;
}
#ifndef NDEBUG
namespace {
struct DumpVisitor {
unsigned Depth = 0;
bool PendingNewline = false;
template<typename NodeT> static constexpr bool wantsNewline(const NodeT *) {
return true;
}
static bool wantsNewline(NodeArray A) { return !A.empty(); }
static constexpr bool wantsNewline(...) { return false; }
template<typename ...Ts> static bool anyWantNewline(Ts ...Vs) {
for (bool B : {wantsNewline(Vs)...})
if (B)
return true;
return false;
}
void printStr(const char *S) { fprintf(stderr, "%s", S); }
void print(StringView SV) {
fprintf(stderr, "\"%.*s\"", (int)SV.size(), SV.begin());
}
void print(const Node *N) {
if (N)
N->visit(std::ref(*this));
else
printStr("<null>");
}
void print(NodeOrString NS) {
if (NS.isNode())
print(NS.asNode());
else if (NS.isString())
print(NS.asString());
else
printStr("NodeOrString()");
}
void print(NodeArray A) {
++Depth;
printStr("{");
bool First = true;
for (const Node *N : A) {
if (First)
print(N);
else
printWithComma(N);
First = false;
}
printStr("}");
--Depth;
}
// Overload used when T is exactly 'bool', not merely convertible to 'bool'.
void print(bool B) { printStr(B ? "true" : "false"); }
template <class T>
typename std::enable_if<std::is_unsigned<T>::value>::type print(T N) {
fprintf(stderr, "%llu", (unsigned long long)N);
}
template <class T>
typename std::enable_if<std::is_signed<T>::value>::type print(T N) {
fprintf(stderr, "%lld", (long long)N);
}
void print(ReferenceKind RK) {
switch (RK) {
case ReferenceKind::LValue:
return printStr("ReferenceKind::LValue");
case ReferenceKind::RValue:
return printStr("ReferenceKind::RValue");
}
}
void print(FunctionRefQual RQ) {
switch (RQ) {
case FunctionRefQual::FrefQualNone:
return printStr("FunctionRefQual::FrefQualNone");
case FunctionRefQual::FrefQualLValue:
return printStr("FunctionRefQual::FrefQualLValue");
case FunctionRefQual::FrefQualRValue:
return printStr("FunctionRefQual::FrefQualRValue");
}
}
void print(Qualifiers Qs) {
if (!Qs) return printStr("QualNone");
struct QualName { Qualifiers Q; const char *Name; } Names[] = {
{QualConst, "QualConst"},
{QualVolatile, "QualVolatile"},
{QualRestrict, "QualRestrict"},
};
for (QualName Name : Names) {
if (Qs & Name.Q) {
printStr(Name.Name);
Qs = Qualifiers(Qs & ~Name.Q);
if (Qs) printStr(" | ");
}
}
}
void print(SpecialSubKind SSK) {
switch (SSK) {
case SpecialSubKind::allocator:
return printStr("SpecialSubKind::allocator");
case SpecialSubKind::basic_string:
return printStr("SpecialSubKind::basic_string");
case SpecialSubKind::string:
return printStr("SpecialSubKind::string");
case SpecialSubKind::istream:
return printStr("SpecialSubKind::istream");
case SpecialSubKind::ostream:
return printStr("SpecialSubKind::ostream");
case SpecialSubKind::iostream:
return printStr("SpecialSubKind::iostream");
}
}
void print(TemplateParamKind TPK) {
switch (TPK) {
case TemplateParamKind::Type:
return printStr("TemplateParamKind::Type");
case TemplateParamKind::NonType:
return printStr("TemplateParamKind::NonType");
case TemplateParamKind::Template:
return printStr("TemplateParamKind::Template");
}
}
void newLine() {
printStr("\n");
for (unsigned I = 0; I != Depth; ++I)
printStr(" ");
PendingNewline = false;
}
template<typename T> void printWithPendingNewline(T V) {
print(V);
if (wantsNewline(V))
PendingNewline = true;
}
template<typename T> void printWithComma(T V) {
if (PendingNewline || wantsNewline(V)) {
printStr(",");
newLine();
} else {
printStr(", ");
}
printWithPendingNewline(V);
}
struct CtorArgPrinter {
DumpVisitor &Visitor;
template<typename T, typename ...Rest> void operator()(T V, Rest ...Vs) {
if (Visitor.anyWantNewline(V, Vs...))
Visitor.newLine();
Visitor.printWithPendingNewline(V);
int PrintInOrder[] = { (Visitor.printWithComma(Vs), 0)..., 0 };
(void)PrintInOrder;
}
};
template<typename NodeT> void operator()(const NodeT *Node) {
Depth += 2;
fprintf(stderr, "%s(", itanium_demangle::NodeKind<NodeT>::name());
Node->match(CtorArgPrinter{*this});
fprintf(stderr, ")");
Depth -= 2;
}
void operator()(const ForwardTemplateReference *Node) {
Depth += 2;
fprintf(stderr, "ForwardTemplateReference(");
if (Node->Ref && !Node->Printing) {
Node->Printing = true;
CtorArgPrinter{*this}(Node->Ref);
Node->Printing = false;
} else {
CtorArgPrinter{*this}(Node->Index);
}
fprintf(stderr, ")");
Depth -= 2;
}
};
}
void itanium_demangle::Node::dump() const {
DumpVisitor V;
visit(std::ref(V));
V.newLine();
}
#endif
namespace {
class BumpPointerAllocator {
struct BlockMeta {
BlockMeta* Next;
size_t Current;
};
static constexpr size_t AllocSize = 4096;
static constexpr size_t UsableAllocSize = AllocSize - sizeof(BlockMeta);
alignas(long double) char InitialBuffer[AllocSize];
BlockMeta* BlockList = nullptr;
void grow() {
char* NewMeta = static_cast<char *>(std::malloc(AllocSize));
if (NewMeta == nullptr)
std::terminate();
BlockList = new (NewMeta) BlockMeta{BlockList, 0};
}
void* allocateMassive(size_t NBytes) {
NBytes += sizeof(BlockMeta);
BlockMeta* NewMeta = reinterpret_cast<BlockMeta*>(std::malloc(NBytes));
if (NewMeta == nullptr)
std::terminate();
BlockList->Next = new (NewMeta) BlockMeta{BlockList->Next, 0};
return static_cast<void*>(NewMeta + 1);
}
public:
BumpPointerAllocator()
: BlockList(new (InitialBuffer) BlockMeta{nullptr, 0}) {}
void* allocate(size_t N) {
N = (N + 15u) & ~15u;
if (N + BlockList->Current >= UsableAllocSize) {
if (N > UsableAllocSize)
return allocateMassive(N);
grow();
}
BlockList->Current += N;
return static_cast<void*>(reinterpret_cast<char*>(BlockList + 1) +
BlockList->Current - N);
}
void reset() {
while (BlockList) {
BlockMeta* Tmp = BlockList;
BlockList = BlockList->Next;
if (reinterpret_cast<char*>(Tmp) != InitialBuffer)
std::free(Tmp);
}
BlockList = new (InitialBuffer) BlockMeta{nullptr, 0};
}
~BumpPointerAllocator() { reset(); }
};
class DefaultAllocator {
BumpPointerAllocator Alloc;
public:
void reset() { Alloc.reset(); }
template<typename T, typename ...Args> T *makeNode(Args &&...args) {
return new (Alloc.allocate(sizeof(T)))
T(std::forward<Args>(args)...);
}
void *allocateNodeArray(size_t sz) {
return Alloc.allocate(sizeof(Node *) * sz);
}
};
} // unnamed namespace
//===----------------------------------------------------------------------===//
// Code beyond this point should not be synchronized with libc++abi.
//===----------------------------------------------------------------------===//
using Demangler = itanium_demangle::ManglingParser<DefaultAllocator>;
char *llvm::itaniumDemangle(const char *MangledName, char *Buf,
size_t *N, int *Status) {
if (MangledName == nullptr || (Buf != nullptr && N == nullptr)) {
if (Status)
*Status = demangle_invalid_args;
return nullptr;
}
int InternalStatus = demangle_success;
Demangler Parser(MangledName, MangledName + std::strlen(MangledName));
OutputStream S;
Node *AST = Parser.parse();
if (AST == nullptr)
InternalStatus = demangle_invalid_mangled_name;
else if (!initializeOutputStream(Buf, N, S, 1024))
InternalStatus = demangle_memory_alloc_failure;
else {
assert(Parser.ForwardTemplateRefs.empty());
AST->print(S);
S += '\0';
if (N != nullptr)
*N = S.getCurrentPosition();
Buf = S.getBuffer();
}
if (Status)
*Status = InternalStatus;
return InternalStatus == demangle_success ? Buf : nullptr;
}
ItaniumPartialDemangler::ItaniumPartialDemangler()
: RootNode(nullptr), Context(new Demangler{nullptr, nullptr}) {}
ItaniumPartialDemangler::~ItaniumPartialDemangler() {
delete static_cast<Demangler *>(Context);
}
ItaniumPartialDemangler::ItaniumPartialDemangler(
ItaniumPartialDemangler &&Other)
: RootNode(Other.RootNode), Context(Other.Context) {
Other.Context = Other.RootNode = nullptr;
}
ItaniumPartialDemangler &ItaniumPartialDemangler::
operator=(ItaniumPartialDemangler &&Other) {
std::swap(RootNode, Other.RootNode);
std::swap(Context, Other.Context);
return *this;
}
// Demangle MangledName into an AST, storing it into this->RootNode.
bool ItaniumPartialDemangler::partialDemangle(const char *MangledName) {
Demangler *Parser = static_cast<Demangler *>(Context);
size_t Len = std::strlen(MangledName);
Parser->reset(MangledName, MangledName + Len);
RootNode = Parser->parse();
return RootNode == nullptr;
}
static char *printNode(const Node *RootNode, char *Buf, size_t *N) {
OutputStream S;
if (!initializeOutputStream(Buf, N, S, 128))
return nullptr;
RootNode->print(S);
S += '\0';
if (N != nullptr)
*N = S.getCurrentPosition();
return S.getBuffer();
}
char *ItaniumPartialDemangler::getFunctionBaseName(char *Buf, size_t *N) const {
if (!isFunction())
return nullptr;
const Node *Name = static_cast<const FunctionEncoding *>(RootNode)->getName();
while (true) {
switch (Name->getKind()) {
case Node::KAbiTagAttr:
Name = static_cast<const AbiTagAttr *>(Name)->Base;
continue;
case Node::KStdQualifiedName:
Name = static_cast<const StdQualifiedName *>(Name)->Child;
continue;
case Node::KNestedName:
Name = static_cast<const NestedName *>(Name)->Name;
continue;
case Node::KLocalName:
Name = static_cast<const LocalName *>(Name)->Entity;
continue;
case Node::KNameWithTemplateArgs:
Name = static_cast<const NameWithTemplateArgs *>(Name)->Name;
continue;
default:
return printNode(Name, Buf, N);
}
}
}
char *ItaniumPartialDemangler::getFunctionDeclContextName(char *Buf,
size_t *N) const {
if (!isFunction())
return nullptr;
const Node *Name = static_cast<const FunctionEncoding *>(RootNode)->getName();
OutputStream S;
if (!initializeOutputStream(Buf, N, S, 128))
return nullptr;
KeepGoingLocalFunction:
while (true) {
if (Name->getKind() == Node::KAbiTagAttr) {
Name = static_cast<const AbiTagAttr *>(Name)->Base;
continue;
}
if (Name->getKind() == Node::KNameWithTemplateArgs) {
Name = static_cast<const NameWithTemplateArgs *>(Name)->Name;
continue;
}
break;
}
switch (Name->getKind()) {
case Node::KStdQualifiedName:
S += "std";
break;
case Node::KNestedName:
static_cast<const NestedName *>(Name)->Qual->print(S);
break;
case Node::KLocalName: {
auto *LN = static_cast<const LocalName *>(Name);
LN->Encoding->print(S);
S += "::";
Name = LN->Entity;
goto KeepGoingLocalFunction;
}
default:
break;
}
S += '\0';
if (N != nullptr)
*N = S.getCurrentPosition();
return S.getBuffer();
}
char *ItaniumPartialDemangler::getFunctionName(char *Buf, size_t *N) const {
if (!isFunction())
return nullptr;
auto *Name = static_cast<FunctionEncoding *>(RootNode)->getName();
return printNode(Name, Buf, N);
}
char *ItaniumPartialDemangler::getFunctionParameters(char *Buf,
size_t *N) const {
if (!isFunction())
return nullptr;
NodeArray Params = static_cast<FunctionEncoding *>(RootNode)->getParams();
OutputStream S;
if (!initializeOutputStream(Buf, N, S, 128))
return nullptr;
S += '(';
Params.printWithComma(S);
S += ')';
S += '\0';
if (N != nullptr)
*N = S.getCurrentPosition();
return S.getBuffer();
}
char *ItaniumPartialDemangler::getFunctionReturnType(
char *Buf, size_t *N) const {
if (!isFunction())
return nullptr;
OutputStream S;
if (!initializeOutputStream(Buf, N, S, 128))
return nullptr;
if (const Node *Ret =
static_cast<const FunctionEncoding *>(RootNode)->getReturnType())
Ret->print(S);
S += '\0';
if (N != nullptr)
*N = S.getCurrentPosition();
return S.getBuffer();
}
char *ItaniumPartialDemangler::finishDemangle(char *Buf, size_t *N) const {
assert(RootNode != nullptr && "must call partialDemangle()");
return printNode(static_cast<Node *>(RootNode), Buf, N);
}
bool ItaniumPartialDemangler::hasFunctionQualifiers() const {
assert(RootNode != nullptr && "must call partialDemangle()");
if (!isFunction())
return false;
auto *E = static_cast<const FunctionEncoding *>(RootNode);
return E->getCVQuals() != QualNone || E->getRefQual() != FrefQualNone;
}
bool ItaniumPartialDemangler::isCtorOrDtor() const {
const Node *N = static_cast<const Node *>(RootNode);
while (N) {
switch (N->getKind()) {
default:
return false;
case Node::KCtorDtorName:
return true;
case Node::KAbiTagAttr:
N = static_cast<const AbiTagAttr *>(N)->Base;
break;
case Node::KFunctionEncoding:
N = static_cast<const FunctionEncoding *>(N)->getName();
break;
case Node::KLocalName:
N = static_cast<const LocalName *>(N)->Entity;
break;
case Node::KNameWithTemplateArgs:
N = static_cast<const NameWithTemplateArgs *>(N)->Name;
break;
case Node::KNestedName:
N = static_cast<const NestedName *>(N)->Name;
break;
case Node::KStdQualifiedName:
N = static_cast<const StdQualifiedName *>(N)->Child;
break;
}
}
return false;
}
bool ItaniumPartialDemangler::isFunction() const {
assert(RootNode != nullptr && "must call partialDemangle()");
return static_cast<const Node *>(RootNode)->getKind() ==
Node::KFunctionEncoding;
}
bool ItaniumPartialDemangler::isSpecialName() const {
assert(RootNode != nullptr && "must call partialDemangle()");
auto K = static_cast<const Node *>(RootNode)->getKind();
return K == Node::KSpecialName || K == Node::KCtorVtableSpecialName;
}
bool ItaniumPartialDemangler::isData() const {
return !isFunction() && !isSpecialName();
}

View File

@@ -0,0 +1,104 @@
//===--- Demangle.h ---------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-FileCopyrightText: Part of the LLVM Project
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_DEMANGLE_DEMANGLE_H
#define LLVM_DEMANGLE_DEMANGLE_H
#include <cstddef>
#include <string>
namespace llvm {
/// This is a llvm local version of __cxa_demangle. Other than the name and
/// being in the llvm namespace it is identical.
///
/// The mangled_name is demangled into buf and returned. If the buffer is not
/// large enough, realloc is used to expand it.
///
/// The *status will be set to a value from the following enumeration
enum : int {
demangle_unknown_error = -4,
demangle_invalid_args = -3,
demangle_invalid_mangled_name = -2,
demangle_memory_alloc_failure = -1,
demangle_success = 0,
};
char *itaniumDemangle(const char *mangled_name, char *buf, size_t *n,
int *status);
enum MSDemangleFlags {
MSDF_None = 0,
MSDF_DumpBackrefs = 1 << 0,
MSDF_NoAccessSpecifier = 1 << 1,
MSDF_NoCallingConvention = 1 << 2,
MSDF_NoReturnType = 1 << 3,
MSDF_NoMemberType = 1 << 4,
};
char *microsoftDemangle(const char *mangled_name, char *buf, size_t *n,
int *status, MSDemangleFlags Flags = MSDF_None);
/// "Partial" demangler. This supports demangling a string into an AST
/// (typically an intermediate stage in itaniumDemangle) and querying certain
/// properties or partially printing the demangled name.
struct ItaniumPartialDemangler {
ItaniumPartialDemangler();
ItaniumPartialDemangler(ItaniumPartialDemangler &&Other);
ItaniumPartialDemangler &operator=(ItaniumPartialDemangler &&Other);
/// Demangle into an AST. Subsequent calls to the rest of the member functions
/// implicitly operate on the AST this produces.
/// \return true on error, false otherwise
bool partialDemangle(const char *MangledName);
/// Just print the entire mangled name into Buf. Buf and N behave like the
/// second and third parameters to itaniumDemangle.
char *finishDemangle(char *Buf, size_t *N) const;
/// Get the base name of a function. This doesn't include trailing template
/// arguments, ie for "a::b<int>" this function returns "b".
char *getFunctionBaseName(char *Buf, size_t *N) const;
/// Get the context name for a function. For "a::b::c", this function returns
/// "a::b".
char *getFunctionDeclContextName(char *Buf, size_t *N) const;
/// Get the entire name of this function.
char *getFunctionName(char *Buf, size_t *N) const;
/// Get the parameters for this function.
char *getFunctionParameters(char *Buf, size_t *N) const;
char *getFunctionReturnType(char *Buf, size_t *N) const;
/// If this function has any any cv or reference qualifiers. These imply that
/// the function is a non-static member function.
bool hasFunctionQualifiers() const;
/// If this symbol describes a constructor or destructor.
bool isCtorOrDtor() const;
/// If this symbol describes a function.
bool isFunction() const;
/// If this symbol describes a variable.
bool isData() const;
/// If this symbol is a <special-name>. These are generally implicitly
/// generated by the implementation, such as vtables and typeinfo names.
bool isSpecialName() const;
~ItaniumPartialDemangler();
private:
void *RootNode;
void *Context;
};
} // namespace llvm
#endif

View File

@@ -0,0 +1,93 @@
//===--- DemangleConfig.h ---------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-FileCopyrightText: Part of the LLVM Project
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains a variety of feature test macros copied from
// include/llvm/Support/Compiler.h so that LLVMDemangle does not need to take
// a dependency on LLVMSupport.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_DEMANGLE_COMPILER_H
#define LLVM_DEMANGLE_COMPILER_H
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#ifndef __has_cpp_attribute
#define __has_cpp_attribute(x) 0
#endif
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#ifndef DEMANGLE_GNUC_PREREQ
#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) \
((__GNUC__ << 20) + (__GNUC_MINOR__ << 10) + __GNUC_PATCHLEVEL__ >= \
((maj) << 20) + ((min) << 10) + (patch))
#elif defined(__GNUC__) && defined(__GNUC_MINOR__)
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) \
((__GNUC__ << 20) + (__GNUC_MINOR__ << 10) >= ((maj) << 20) + ((min) << 10))
#else
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) 0
#endif
#endif
#if __has_attribute(used) || DEMANGLE_GNUC_PREREQ(3, 1, 0)
#define DEMANGLE_ATTRIBUTE_USED __attribute__((__used__))
#else
#define DEMANGLE_ATTRIBUTE_USED
#endif
#if __has_builtin(__builtin_unreachable) || DEMANGLE_GNUC_PREREQ(4, 5, 0)
#define DEMANGLE_UNREACHABLE __builtin_unreachable()
#elif defined(_MSC_VER)
#define DEMANGLE_UNREACHABLE __assume(false)
#else
#define DEMANGLE_UNREACHABLE
#endif
#if __has_attribute(noinline) || DEMANGLE_GNUC_PREREQ(3, 4, 0)
#define DEMANGLE_ATTRIBUTE_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define DEMANGLE_ATTRIBUTE_NOINLINE __declspec(noinline)
#else
#define DEMANGLE_ATTRIBUTE_NOINLINE
#endif
#if !defined(NDEBUG)
#define DEMANGLE_DUMP_METHOD DEMANGLE_ATTRIBUTE_NOINLINE DEMANGLE_ATTRIBUTE_USED
#else
#define DEMANGLE_DUMP_METHOD DEMANGLE_ATTRIBUTE_NOINLINE
#endif
#if __cplusplus > 201402L && __has_cpp_attribute(fallthrough)
#define DEMANGLE_FALLTHROUGH [[fallthrough]]
#elif __has_cpp_attribute(gnu::fallthrough)
#define DEMANGLE_FALLTHROUGH [[gnu::fallthrough]]
#elif !__cplusplus
// Workaround for llvm.org/PR23435, since clang 3.6 and below emit a spurious
// error when __has_cpp_attribute is given a scoped attribute in C mode.
#define DEMANGLE_FALLTHROUGH
#elif __has_cpp_attribute(clang::fallthrough)
#define DEMANGLE_FALLTHROUGH [[clang::fallthrough]]
#else
#define DEMANGLE_FALLTHROUGH
#endif
#define DEMANGLE_NAMESPACE_BEGIN namespace llvm { namespace itanium_demangle {
#define DEMANGLE_NAMESPACE_END } }
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
//===--- StringView.h -------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-FileCopyrightText: Part of the LLVM Project
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// FIXME: Use std::string_view instead when we support C++17.
//
//===----------------------------------------------------------------------===//
#ifndef DEMANGLE_STRINGVIEW_H
#define DEMANGLE_STRINGVIEW_H
#include "DemangleConfig.h"
#include <algorithm>
#include <cassert>
#include <cstring>
DEMANGLE_NAMESPACE_BEGIN
class StringView {
const char *First;
const char *Last;
public:
static const size_t npos = ~size_t(0);
template <size_t N>
StringView(const char (&Str)[N]) : First(Str), Last(Str + N - 1) {}
StringView(const char *First_, const char *Last_)
: First(First_), Last(Last_) {}
StringView(const char *First_, size_t Len)
: First(First_), Last(First_ + Len) {}
StringView(const char *Str) : First(Str), Last(Str + std::strlen(Str)) {}
StringView() : First(nullptr), Last(nullptr) {}
StringView substr(size_t From) const {
return StringView(begin() + From, size() - From);
}
size_t find(char C, size_t From = 0) const {
size_t FindBegin = std::min(From, size());
// Avoid calling memchr with nullptr.
if (FindBegin < size()) {
// Just forward to memchr, which is faster than a hand-rolled loop.
if (const void *P = ::memchr(First + FindBegin, C, size() - FindBegin))
return size_t(static_cast<const char *>(P) - First);
}
return npos;
}
StringView substr(size_t From, size_t To) const {
if (To >= size())
To = size() - 1;
if (From >= size())
From = size() - 1;
return StringView(First + From, First + To);
}
StringView dropFront(size_t N = 1) const {
if (N >= size())
N = size();
return StringView(First + N, Last);
}
StringView dropBack(size_t N = 1) const {
if (N >= size())
N = size();
return StringView(First, Last - N);
}
char front() const {
assert(!empty());
return *begin();
}
char back() const {
assert(!empty());
return *(end() - 1);
}
char popFront() {
assert(!empty());
return *First++;
}
bool consumeFront(char C) {
if (!startsWith(C))
return false;
*this = dropFront(1);
return true;
}
bool consumeFront(StringView S) {
if (!startsWith(S))
return false;
*this = dropFront(S.size());
return true;
}
bool startsWith(char C) const { return !empty() && *begin() == C; }
bool startsWith(StringView Str) const {
if (Str.size() > size())
return false;
return std::equal(Str.begin(), Str.end(), begin());
}
const char &operator[](size_t Idx) const { return *(begin() + Idx); }
const char *begin() const { return First; }
const char *end() const { return Last; }
size_t size() const { return static_cast<size_t>(Last - First); }
bool empty() const { return First == Last; }
};
inline bool operator==(const StringView &LHS, const StringView &RHS) {
return LHS.size() == RHS.size() &&
std::equal(LHS.begin(), LHS.end(), RHS.begin());
}
DEMANGLE_NAMESPACE_END
#endif

View File

@@ -0,0 +1,192 @@
//===--- Utility.h ----------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-FileCopyrightText: Part of the LLVM Project
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Provide some utility classes for use in the demangler(s).
//
//===----------------------------------------------------------------------===//
#ifndef DEMANGLE_UTILITY_H
#define DEMANGLE_UTILITY_H
#include "StringView.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <limits>
DEMANGLE_NAMESPACE_BEGIN
// Stream that AST nodes write their string representation into after the AST
// has been parsed.
class OutputStream {
char *Buffer;
size_t CurrentPosition;
size_t BufferCapacity;
// Ensure there is at least n more positions in buffer.
void grow(size_t N) {
if (N + CurrentPosition >= BufferCapacity) {
BufferCapacity *= 2;
if (BufferCapacity < N + CurrentPosition)
BufferCapacity = N + CurrentPosition;
Buffer = static_cast<char *>(std::realloc(Buffer, BufferCapacity));
if (Buffer == nullptr)
std::terminate();
}
}
void writeUnsigned(uint64_t N, bool isNeg = false) {
// Handle special case...
if (N == 0) {
*this << '0';
return;
}
char Temp[21];
char *TempPtr = std::end(Temp);
while (N) {
*--TempPtr = '0' + char(N % 10);
N /= 10;
}
// Add negative sign...
if (isNeg)
*--TempPtr = '-';
this->operator<<(StringView(TempPtr, std::end(Temp)));
}
public:
OutputStream(char *StartBuf, size_t Size)
: Buffer(StartBuf), CurrentPosition(0), BufferCapacity(Size) {}
OutputStream() = default;
void reset(char *Buffer_, size_t BufferCapacity_) {
CurrentPosition = 0;
Buffer = Buffer_;
BufferCapacity = BufferCapacity_;
}
/// If a ParameterPackExpansion (or similar type) is encountered, the offset
/// into the pack that we're currently printing.
unsigned CurrentPackIndex = std::numeric_limits<unsigned>::max();
unsigned CurrentPackMax = std::numeric_limits<unsigned>::max();
OutputStream &operator+=(StringView R) {
size_t Size = R.size();
if (Size == 0)
return *this;
grow(Size);
std::memmove(Buffer + CurrentPosition, R.begin(), Size);
CurrentPosition += Size;
return *this;
}
OutputStream &operator+=(char C) {
grow(1);
Buffer[CurrentPosition++] = C;
return *this;
}
OutputStream &operator<<(StringView R) { return (*this += R); }
OutputStream &operator<<(char C) { return (*this += C); }
OutputStream &operator<<(long long N) {
if (N < 0)
writeUnsigned(static_cast<unsigned long long>(-N), true);
else
writeUnsigned(static_cast<unsigned long long>(N));
return *this;
}
OutputStream &operator<<(unsigned long long N) {
writeUnsigned(N, false);
return *this;
}
OutputStream &operator<<(long N) {
return this->operator<<(static_cast<long long>(N));
}
OutputStream &operator<<(unsigned long N) {
return this->operator<<(static_cast<unsigned long long>(N));
}
OutputStream &operator<<(int N) {
return this->operator<<(static_cast<long long>(N));
}
OutputStream &operator<<(unsigned int N) {
return this->operator<<(static_cast<unsigned long long>(N));
}
size_t getCurrentPosition() const { return CurrentPosition; }
void setCurrentPosition(size_t NewPos) { CurrentPosition = NewPos; }
char back() const {
return CurrentPosition ? Buffer[CurrentPosition - 1] : '\0';
}
bool empty() const { return CurrentPosition == 0; }
char *getBuffer() { return Buffer; }
char *getBufferEnd() { return Buffer + CurrentPosition - 1; }
size_t getBufferCapacity() { return BufferCapacity; }
};
template <class T> class SwapAndRestore {
T &Restore;
T OriginalValue;
bool ShouldRestore = true;
public:
SwapAndRestore(T &Restore_) : SwapAndRestore(Restore_, Restore_) {}
SwapAndRestore(T &Restore_, T NewVal)
: Restore(Restore_), OriginalValue(Restore) {
Restore = std::move(NewVal);
}
~SwapAndRestore() {
if (ShouldRestore)
Restore = std::move(OriginalValue);
}
void shouldRestore(bool ShouldRestore_) { ShouldRestore = ShouldRestore_; }
void restoreNow(bool Force) {
if (!Force && !ShouldRestore)
return;
Restore = std::move(OriginalValue);
ShouldRestore = false;
}
SwapAndRestore(const SwapAndRestore &) = delete;
SwapAndRestore &operator=(const SwapAndRestore &) = delete;
};
inline bool initializeOutputStream(char *Buf, size_t *N, OutputStream &S,
size_t InitSize) {
size_t BufferSize;
if (Buf == nullptr) {
Buf = static_cast<char *>(std::malloc(InitSize));
if (Buf == nullptr)
return false;
BufferSize = InitSize;
} else
BufferSize = *N;
S.reset(Buf, BufferSize);
return true;
}
DEMANGLE_NAMESPACE_END
#endif

View File

@@ -20,25 +20,25 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
const u32 target_sample_count, const u32 source_sample_count,
UpsamplerState* state) {
constexpr u32 WindowSize = 10;
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
-1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc1{
0.95376587f, -0.12872314f, 0.060028076f, -0.032470703f, 0.017669678f,
-0.009124756f, 0.004272461f, -0.001739502f, 0.000579834f, -0.000091552734f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
-1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc2{
0.8230896f, -0.19161987f, 0.093444824f, -0.05090332f, 0.027557373f,
-0.014038086f, 0.0064697266f, -0.002532959f, 0.00079345703f, -0.00012207031f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
-1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc3{
0.6298828f, -0.19274902f, 0.09725952f, -0.05319214f, 0.028625488f,
-0.014373779f, 0.006500244f, -0.0024719238f, 0.0007324219f, -0.000091552734f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
-0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc4{
0.4057312f, -0.1468811f, 0.07601929f, -0.041656494f, 0.022216797f,
-0.011016846f, 0.004852295f, -0.0017700195f, 0.00048828125f, -0.000030517578f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
-1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc5{
0.1854248f, -0.075164795f, 0.03967285f, -0.021728516f, 0.011474609f,
-0.005584717f, 0.0024108887f, -0.0008239746f, 0.00021362305f, 0.0f,
};
if (!state->initialized) {
@@ -91,52 +91,31 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
};
auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
auto calculate_sample = [&state](std::span<const Common::FixedPoint<17, 15>> coeffs1,
std::span<const Common::FixedPoint<17, 15>> coeffs2) -> s32 {
auto output_index{state->history_output_index};
auto start_pos{output_index - state->history_start_index + 1U};
auto end_pos{10U};
u64 result{0};
if (start_pos < 10) {
end_pos = start_pos;
}
for (u32 coeff_index = 0; coeff_index < 10; coeff_index++) {
result += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs1[coeff_index].to_raw();
u64 prev_contrib{0};
u32 coeff_index{0};
for (; coeff_index < end_pos; coeff_index++, output_index--) {
prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs1[coeff_index].to_raw();
}
auto end_index{state->history_end_index};
for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
coeffs1[coeff_index].to_raw();
output_index = output_index == state->history_start_index ? state->history_end_index
: output_index - 1;
}
output_index =
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
start_pos = state->history_end_index - output_index + 1U;
end_pos = 10U;
if (start_pos < 10) {
end_pos = start_pos;
for (u32 coeff_index = 0; coeff_index < 10; coeff_index++) {
result += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs2[coeff_index].to_raw();
output_index = output_index == state->history_end_index ? state->history_start_index
: output_index + 1;
}
u64 next_contrib{0};
coeff_index = 0;
for (; coeff_index < end_pos; coeff_index++, output_index++) {
next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs2[coeff_index].to_raw();
}
auto start_index{state->history_start_index};
for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
coeffs2[coeff_index].to_raw();
}
return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
return static_cast<s32>(result >> (8 + 15));
};
switch (state->ratio.to_int_floor()) {
@@ -150,23 +129,23 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
break;
case 1:
output[write_index] = calculate_sample(SincWindow3, SincWindow4);
output[write_index] = calculate_sample(WindowedSinc1, WindowedSinc5);
break;
case 2:
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
break;
case 3:
output[write_index] = calculate_sample(SincWindow5, SincWindow5);
output[write_index] = calculate_sample(WindowedSinc3, WindowedSinc3);
break;
case 4:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
break;
case 5:
output[write_index] = calculate_sample(SincWindow4, SincWindow3);
output[write_index] = calculate_sample(WindowedSinc5, WindowedSinc1);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
@@ -183,11 +162,11 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
break;
case 1:
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
break;
case 2:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
@@ -204,12 +183,12 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
break;
case 1:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
break;
case 2:
increment();
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);

View File

@@ -38,6 +38,8 @@ add_library(common STATIC
common_precompiled_headers.h
common_types.h
concepts.h
demangle.cpp
demangle.h
div_ceil.h
dynamic_library.cpp
dynamic_library.h
@@ -175,7 +177,7 @@ endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle)
if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(common PRIVATE precompiled_headers.h)

35
src/common/demangle.cpp Normal file
View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <llvm/Demangle/Demangle.h>
#include "common/demangle.h"
#include "common/scope_exit.h"
namespace Common {
std::string DemangleSymbol(const std::string& mangled) {
auto is_itanium = [](const std::string& name) -> bool {
// A valid Itanium encoding requires 1-4 leading underscores, followed by 'Z'.
auto pos = name.find_first_not_of('_');
return pos > 0 && pos <= 4 && pos < name.size() && name[pos] == 'Z';
};
if (mangled.empty()) {
return mangled;
}
char* demangled = nullptr;
SCOPE_EXIT({ std::free(demangled); });
if (is_itanium(mangled)) {
demangled = llvm::itaniumDemangle(mangled.c_str(), nullptr, nullptr, nullptr);
}
if (!demangled) {
return mangled;
}
return demangled;
}
} // namespace Common

12
src/common/demangle.h Normal file
View File

@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common {
std::string DemangleSymbol(const std::string& mangled);
} // namespace Common

View File

@@ -51,6 +51,8 @@ enum class PollingMode {
NFC,
// Enable infrared camera polling
IR,
// Enable ring controller polling
Ring,
};
enum class CameraFormat {
@@ -62,21 +64,22 @@ enum class CameraFormat {
None,
};
// Vibration reply from the controller
enum class VibrationError {
None,
// Different results that can happen from a device request
enum class DriverResult {
Success,
WrongReply,
Timeout,
UnsupportedControllerType,
HandleInUse,
ErrorReadingData,
ErrorWritingData,
NoDeviceDetected,
InvalidHandle,
NotSupported,
Disabled,
Unknown,
};
// Polling mode reply from the controller
enum class PollingError {
None,
NotSupported,
Unknown,
};
// Nfc reply from the controller
enum class NfcState {
Success,
@@ -90,13 +93,6 @@ enum class NfcState {
Unknown,
};
// Ir camera reply from the controller
enum class CameraError {
None,
NotSupported,
Unknown,
};
// Hint for amplification curve to be used
enum class VibrationAmplificationType {
Linear,
@@ -190,6 +186,8 @@ struct TouchStatus {
struct BodyColorStatus {
u32 body{};
u32 buttons{};
u32 left_grip{};
u32 right_grip{};
};
// HD rumble data
@@ -228,17 +226,31 @@ enum class ButtonNames {
Engine,
// This will display the button by value instead of the button name
Value,
// Joycon button names
ButtonLeft,
ButtonRight,
ButtonDown,
ButtonUp,
TriggerZ,
TriggerR,
TriggerL,
ButtonA,
ButtonB,
ButtonX,
ButtonY,
ButtonPlus,
ButtonMinus,
ButtonHome,
ButtonCapture,
ButtonStickL,
ButtonStickR,
TriggerL,
TriggerZL,
TriggerSL,
TriggerR,
TriggerZR,
TriggerSR,
// GC button names
TriggerZ,
ButtonStart,
// DS4 button names
@@ -292,9 +304,6 @@ class InputDevice {
public:
virtual ~InputDevice() = default;
// Request input device to update if necessary
virtual void SoftUpdate() {}
// Force input device to update data regardless of the current state
virtual void ForceUpdate() {}
@@ -319,22 +328,24 @@ class OutputDevice {
public:
virtual ~OutputDevice() = default;
virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) {
return DriverResult::NotSupported;
}
virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
return VibrationError::NotSupported;
virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
return DriverResult::NotSupported;
}
virtual bool IsVibrationEnabled() {
return false;
}
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
return PollingError::NotSupported;
virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
return DriverResult::NotSupported;
}
virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
return CameraError::NotSupported;
virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
return DriverResult::NotSupported;
}
virtual NfcState SupportsNfc() const {

View File

@@ -129,6 +129,10 @@ void UpdateRescalingInfo() {
info.up_scale = 1;
info.down_shift = 0;
break;
case ResolutionSetup::Res3_2X:
info.up_scale = 3;
info.down_shift = 1;
break;
case ResolutionSetup::Res2X:
info.up_scale = 2;
info.down_shift = 0;
@@ -149,6 +153,14 @@ void UpdateRescalingInfo() {
info.up_scale = 6;
info.down_shift = 0;
break;
case ResolutionSetup::Res7X:
info.up_scale = 7;
info.down_shift = 0;
break;
case ResolutionSetup::Res8X:
info.up_scale = 8;
info.down_shift = 0;
break;
default:
ASSERT(false);
info.up_scale = 1;

View File

@@ -56,11 +56,14 @@ enum class ResolutionSetup : u32 {
Res1_2X = 0,
Res3_4X = 1,
Res1X = 2,
Res2X = 3,
Res3X = 4,
Res4X = 5,
Res5X = 6,
Res6X = 7,
Res3_2X = 3,
Res2X = 4,
Res3X = 5,
Res4X = 6,
Res5X = 7,
Res6X = 8,
Res7X = 9,
Res8X = 10,
};
enum class ScalingFilter : u32 {
@@ -415,7 +418,7 @@ struct Values {
// Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{
RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
SwitchableSetting<bool> renderer_force_max_clock{true, "force_max_clock"};
SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
Setting<bool> renderer_debug{false, "debug"};
Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
Setting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
@@ -480,6 +483,7 @@ struct Values {
Setting<bool> enable_raw_input{false, "enable_raw_input"};
Setting<bool> controller_navigation{true, "controller_navigation"};
Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};

View File

@@ -226,6 +226,7 @@ add_library(core STATIC
hle/kernel/k_page_buffer.h
hle/kernel/k_page_heap.cpp
hle/kernel/k_page_heap.h
hle/kernel/k_page_group.cpp
hle/kernel/k_page_group.h
hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h

View File

@@ -1,14 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef _MSC_VER
#include <cxxabi.h>
#endif
#include <map>
#include <optional>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/demangle.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/arm/symbols.h"
@@ -71,20 +69,8 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
const auto symbol_set = symbols.find(entry.module);
if (symbol_set != symbols.end()) {
const auto symbol = Symbols::GetSymbolName(symbol_set->second, entry.offset);
if (symbol.has_value()) {
#ifdef _MSC_VER
// TODO(DarkLordZach): Add demangling of symbol names.
entry.name = *symbol;
#else
int status{-1};
char* demangled{abi::__cxa_demangle(symbol->c_str(), nullptr, nullptr, &status)};
if (status == 0 && demangled != nullptr) {
entry.name = demangled;
std::free(demangled);
} else {
entry.name = *symbol;
}
#endif
if (symbol) {
entry.name = Common::DemangleSymbol(*symbol);
}
}
}

View File

@@ -142,16 +142,24 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
}
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
std::scoped_lock scope{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
});
std::uintptr_t user_data, bool wait) {
{
std::scoped_lock lk{basic_lock};
const auto itr =
std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
});
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
// Force any in-progress events to finish
if (wait) {
std::scoped_lock lk{advance_lock};
}
}
@@ -190,20 +198,6 @@ u64 CoreTiming::GetClockTicks() const {
return CpuCyclesToClockCycles(ticks);
}
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
std::scoped_lock lock{basic_lock};
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type.lock().get() == event_type.get();
});
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
std::optional<s64> CoreTiming::Advance() {
std::scoped_lock lock{advance_lock, basic_lock};
global_timer = GetGlobalTimeNs().count();

View File

@@ -98,10 +98,13 @@ public:
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data = 0, bool absolute_time = false);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data,
bool wait = true);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const std::shared_ptr<EventType>& event_type);
void UnscheduleEventWithoutWait(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
UnscheduleEvent(event_type, user_data, false);
}
void AddTicks(u64 ticks_to_add);

View File

@@ -11,6 +11,7 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/debugger/gdbstub.h"
@@ -731,7 +732,25 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
auto* process = system.CurrentProcess();
auto& page_table = process->PageTable();
if (command_str == "get info") {
const char* commands = "Commands:\n"
" get fastmem\n"
" get info\n"
" get mappings\n";
if (command_str == "get fastmem") {
if (Settings::IsFastmemEnabled()) {
const auto& impl = page_table.PageTableImpl();
const auto region = reinterpret_cast<uintptr_t>(impl.fastmem_arena);
const auto region_bits = impl.current_address_space_width_in_bits;
const auto region_size = 1ULL << region_bits;
reply = fmt::format("Region bits: {}\n"
"Host address: {:#x} - {:#x}\n",
region_bits, region, region + region_size - 1);
} else {
reply = "Fastmem is not enabled.\n";
}
} else if (command_str == "get info") {
Loader::AppLoader::Modules modules;
system.GetAppLoader().ReadNSOModules(modules);
@@ -787,9 +806,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
cur_addr = next_address;
}
} else if (command_str == "help") {
reply = "Commands:\n get info\n get mappings\n";
reply = commands;
} else {
reply = "Unknown command.\nCommands:\n get info\n get mappings\n";
reply = "Unknown command.\n";
reply += commands;
}
std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()};

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <common/scope_exit.h>
#include "common/polyfill_ranges.h"
#include "common/thread.h"
@@ -10,6 +11,7 @@
namespace Core::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
constexpr s32 HID_JOYSTICK_MIN = 0x7ffe;
constexpr s32 HID_TRIGGER_MAX = 0x7fff;
// Use a common UUID for TAS and Virtual Gamepad
constexpr Common::UUID TAS_UUID =
@@ -93,6 +95,7 @@ void EmulatedController::ReloadFromSettings() {
motion_params[index] = Common::ParamPackage(player.motions[index]);
}
controller.color_values = {};
controller.colors_state.fullkey = {
.body = GetNpadColor(player.body_color_left),
.button = GetNpadColor(player.button_color_left),
@@ -106,6 +109,8 @@ void EmulatedController::ReloadFromSettings() {
.button = GetNpadColor(player.button_color_right),
};
ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
// Other or debug controller should always be a pro controller
if (npad_id_type != NpadIdType::Other) {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
@@ -132,18 +137,28 @@ void EmulatedController::LoadDevices() {
trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
color_params[LeftIndex] = left_joycon;
color_params[RightIndex] = right_joycon;
color_params[LeftIndex].Set("color", true);
color_params[RightIndex].Set("color", true);
battery_params[LeftIndex] = left_joycon;
battery_params[RightIndex] = right_joycon;
battery_params[LeftIndex].Set("battery", true);
battery_params[RightIndex].Set("battery", true);
camera_params = Common::ParamPackage{"engine:camera,camera:1"};
nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
camera_params[0] = right_joycon;
camera_params[0].Set("camera", true);
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
nfc_params[1] = right_joycon;
nfc_params[1].Set("nfc", true);
output_params[LeftIndex] = left_joycon;
output_params[RightIndex] = right_joycon;
output_params[2] = camera_params;
output_params[3] = nfc_params;
output_params[2] = camera_params[1];
output_params[3] = nfc_params[0];
output_params[LeftIndex].Set("output", true);
output_params[RightIndex].Set("output", true);
output_params[2].Set("output", true);
@@ -159,8 +174,11 @@ void EmulatedController::LoadDevices() {
Common::Input::CreateInputDevice);
std::ranges::transform(battery_params, battery_devices.begin(),
Common::Input::CreateInputDevice);
camera_devices = Common::Input::CreateInputDevice(camera_params);
nfc_devices = Common::Input::CreateInputDevice(nfc_params);
std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(ring_params, ring_analog_devices.begin(),
Common::Input::CreateInputDevice);
std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(output_params, output_devices.begin(),
Common::Input::CreateOutputDevice);
@@ -322,6 +340,19 @@ void EmulatedController::ReloadInput() {
battery_devices[index]->ForceUpdate();
}
for (std::size_t index = 0; index < color_devices.size(); ++index) {
if (!color_devices[index]) {
continue;
}
color_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetColors(callback, index);
},
});
color_devices[index]->ForceUpdate();
}
for (std::size_t index = 0; index < motion_devices.size(); ++index) {
if (!motion_devices[index]) {
continue;
@@ -335,22 +366,37 @@ void EmulatedController::ReloadInput() {
motion_devices[index]->ForceUpdate();
}
if (camera_devices) {
camera_devices->SetCallback({
for (std::size_t index = 0; index < camera_devices.size(); ++index) {
if (!camera_devices[index]) {
continue;
}
camera_devices[index]->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
});
camera_devices->ForceUpdate();
camera_devices[index]->ForceUpdate();
}
if (nfc_devices) {
if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
nfc_devices->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
});
nfc_devices->ForceUpdate();
for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
if (!ring_analog_devices[index]) {
continue;
}
ring_analog_devices[index]->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
});
ring_analog_devices[index]->ForceUpdate();
}
for (std::size_t index = 0; index < nfc_devices.size(); ++index) {
if (!nfc_devices[index]) {
continue;
}
nfc_devices[index]->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
});
nfc_devices[index]->ForceUpdate();
}
// Register TAS devices. No need to force update
@@ -420,6 +466,9 @@ void EmulatedController::UnloadInput() {
for (auto& battery : battery_devices) {
battery.reset();
}
for (auto& color : color_devices) {
color.reset();
}
for (auto& output : output_devices) {
output.reset();
}
@@ -435,8 +484,15 @@ void EmulatedController::UnloadInput() {
for (auto& stick : virtual_stick_devices) {
stick.reset();
}
camera_devices.reset();
nfc_devices.reset();
for (auto& camera : camera_devices) {
camera.reset();
}
for (auto& ring : ring_analog_devices) {
ring.reset();
}
for (auto& nfc : nfc_devices) {
nfc.reset();
}
}
void EmulatedController::EnableConfiguration() {
@@ -448,6 +504,11 @@ void EmulatedController::EnableConfiguration() {
void EmulatedController::DisableConfiguration() {
is_configuring = false;
// Get Joycon colors before turning on the controller
for (const auto& color_device : color_devices) {
color_device->ForceUpdate();
}
// Apply temporary npad type to the real controller
if (tmp_npad_type != npad_type) {
if (is_connected) {
@@ -501,6 +562,9 @@ void EmulatedController::SaveCurrentConfig() {
for (std::size_t index = 0; index < player.motions.size(); ++index) {
player.motions[index] = motion_params[index].Serialize();
}
if (npad_id_type == NpadIdType::Player1) {
Settings::values.ringcon_analogs = ring_params[0].Serialize();
}
}
void EmulatedController::RestoreConfig() {
@@ -772,17 +836,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
if (index >= controller.stick_values.size()) {
return;
}
std::unique_lock lock{mutex};
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
std::scoped_lock lock{mutex};
const auto stick_value = TransformToStick(callback);
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
if (controller.stick_values[index].uuid != uuid) {
const bool is_tas = uuid == TAS_UUID;
if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) {
trigger_guard.Cancel();
return;
}
if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&
!stick_value.right) {
trigger_guard.Cancel();
return;
}
}
@@ -793,14 +861,19 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
if (is_configuring) {
controller.analog_stick_state.left = {};
controller.analog_stick_state.right = {};
lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, false);
return;
}
const auto FloatToShort = [](float a) {
if (a > 0) {
return static_cast<s32>(a * HID_JOYSTICK_MAX);
}
return static_cast<s32>(a * HID_JOYSTICK_MIN);
};
const AnalogStickState stick{
.x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX),
.y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX),
.x = FloatToShort(controller.stick_values[index].x.value),
.y = FloatToShort(controller.stick_values[index].y.value),
};
switch (index) {
@@ -819,9 +892,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
break;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Stick, true);
}
void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
@@ -829,7 +899,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
if (index >= controller.trigger_values.size()) {
return;
}
std::unique_lock lock{mutex};
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
std::scoped_lock lock{mutex};
const auto trigger_value = TransformToTrigger(callback);
// Only read trigger values that have the same uuid or are pressed once
@@ -845,13 +917,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
if (is_configuring) {
controller.gc_trigger_state.left = 0;
controller.gc_trigger_state.right = 0;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Trigger, false);
return;
}
// Only GC controllers have analog triggers
if (npad_type != NpadStyleIndex::GameCube) {
trigger_guard.Cancel();
return;
}
@@ -868,9 +939,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
controller.npad_button_state.zr.Assign(trigger.pressed.value);
break;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Trigger, true);
}
void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
@@ -878,7 +946,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
if (index >= controller.motion_values.size()) {
return;
}
std::unique_lock lock{mutex};
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
std::scoped_lock lock{mutex};
auto& raw_status = controller.motion_values[index].raw_status;
auto& emulated = controller.motion_values[index].emulated;
@@ -899,8 +968,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
force_update_motion = raw_status.force_update;
if (is_configuring) {
lock.unlock();
TriggerOnChange(ControllerTriggerType::Motion, false);
return;
}
@@ -910,9 +977,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
motion.rotation = emulated.GetRotations();
motion.orientation = emulated.GetOrientation();
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Motion, true);
void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= controller.color_values.size()) {
return;
}
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
std::scoped_lock lock{mutex};
controller.color_values[index] = TransformToColor(callback);
if (is_configuring) {
return;
}
if (controller.color_values[index].body == 0) {
trigger_guard.Cancel();
return;
}
controller.colors_state.fullkey = {
.body = GetNpadColor(controller.color_values[index].body),
.button = GetNpadColor(controller.color_values[index].buttons),
};
if (npad_type == NpadStyleIndex::ProController) {
controller.colors_state.left = {
.body = GetNpadColor(controller.color_values[index].left_grip),
.button = GetNpadColor(controller.color_values[index].buttons),
};
controller.colors_state.right = {
.body = GetNpadColor(controller.color_values[index].right_grip),
.button = GetNpadColor(controller.color_values[index].buttons),
};
} else {
switch (index) {
case LeftIndex:
controller.colors_state.left = {
.body = GetNpadColor(controller.color_values[index].body),
.button = GetNpadColor(controller.color_values[index].buttons),
};
break;
case RightIndex:
controller.colors_state.right = {
.body = GetNpadColor(controller.color_values[index].body),
.button = GetNpadColor(controller.color_values[index].buttons),
};
break;
}
}
}
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
@@ -920,12 +1034,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
if (index >= controller.battery_values.size()) {
return;
}
std::unique_lock lock{mutex};
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
std::scoped_lock lock{mutex};
controller.battery_values[index] = TransformToBattery(callback);
if (is_configuring) {
lock.unlock();
TriggerOnChange(ControllerTriggerType::Battery, false);
return;
}
@@ -981,18 +1094,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
};
break;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::Battery, true);
}
void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
std::unique_lock lock{mutex};
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
std::scoped_lock lock{mutex};
controller.camera_values = TransformToCamera(callback);
if (is_configuring) {
lock.unlock();
TriggerOnChange(ControllerTriggerType::IrSensor, false);
return;
}
@@ -1000,18 +1109,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
controller.camera_state.format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
controller.camera_state.data = controller.camera_values.data;
}
lock.unlock();
TriggerOnChange(ControllerTriggerType::IrSensor, true);
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
std::scoped_lock lock{mutex};
const auto force_value = TransformToStick(callback);
controller.ring_analog_value = force_value.x;
if (is_configuring) {
return;
}
controller.ring_analog_state.force = force_value.x.value;
}
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
std::unique_lock lock{mutex};
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
std::scoped_lock lock{mutex};
controller.nfc_values = TransformToNfc(callback);
if (is_configuring) {
lock.unlock();
TriggerOnChange(ControllerTriggerType::Nfc, false);
return;
}
@@ -1019,9 +1138,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
controller.nfc_values.state,
controller.nfc_values.data,
};
lock.unlock();
TriggerOnChange(ControllerTriggerType::Nfc, true);
}
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
@@ -1053,7 +1169,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
.type = type,
};
return output_devices[device_index]->SetVibration(status) ==
Common::Input::VibrationError::None;
Common::Input::DriverResult::Success;
}
bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
@@ -1075,16 +1191,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
return output_devices[device_index]->IsVibrationEnabled();
}
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
Common::Input::DriverResult EmulatedController::SetPollingMode(
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_output_device = output_devices[3];
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
if (device_index == EmulatedDeviceIndex::LeftIndex) {
return left_output_device->SetPollingMode(polling_mode);
}
return virtual_nfc_result == Common::Input::PollingError::None ||
mapped_nfc_result == Common::Input::PollingError::None;
if (device_index == EmulatedDeviceIndex::RightIndex) {
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
if (virtual_nfc_result == Common::Input::DriverResult::Success) {
return virtual_nfc_result;
}
return mapped_nfc_result;
}
left_output_device->SetPollingMode(polling_mode);
right_output_device->SetPollingMode(polling_mode);
nfc_output_device->SetPollingMode(polling_mode);
return Common::Input::DriverResult::Success;
}
bool EmulatedController::SetCameraFormat(
@@ -1095,13 +1227,22 @@ bool EmulatedController::SetCameraFormat(
auto& camera_output_device = output_devices[2];
if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
camera_format)) == Common::Input::CameraError::None) {
camera_format)) == Common::Input::DriverResult::Success) {
return true;
}
// Fallback to Qt camera if native device doesn't have support
return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
camera_format)) == Common::Input::CameraError::None;
camera_format)) == Common::Input::DriverResult::Success;
}
Common::ParamPackage EmulatedController::GetRingParam() const {
return ring_params[0];
}
void EmulatedController::SetRingParam(Common::ParamPackage param) {
ring_params[0] = std::move(param);
ReloadInput();
}
bool EmulatedController::HasNfc() const {
@@ -1255,39 +1396,35 @@ void EmulatedController::Connect(bool use_temporary_value) {
return;
}
std::unique_lock lock{mutex};
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
std::scoped_lock lock{mutex};
if (is_configuring) {
tmp_is_connected = true;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Connected, false);
return;
}
if (is_connected) {
trigger_guard.Cancel();
return;
}
is_connected = true;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Connected, true);
}
void EmulatedController::Disconnect() {
std::unique_lock lock{mutex};
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
std::scoped_lock lock{mutex};
if (is_configuring) {
tmp_is_connected = false;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Disconnected, false);
return;
}
if (!is_connected) {
trigger_guard.Cancel();
return;
}
is_connected = false;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Disconnected, true);
}
bool EmulatedController::IsConnected(bool get_temporary_value) const {
@@ -1312,19 +1449,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
}
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
std::unique_lock lock{mutex};
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
std::scoped_lock lock{mutex};
if (is_configuring) {
if (tmp_npad_type == npad_type_) {
trigger_guard.Cancel();
return;
}
tmp_npad_type = npad_type_;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Type, false);
return;
}
if (npad_type == npad_type_) {
trigger_guard.Cancel();
return;
}
if (is_connected) {
@@ -1332,9 +1471,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
NpadIdTypeToIndex(npad_id_type));
}
npad_type = npad_type_;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Type, true);
}
LedPattern EmulatedController::GetLedPattern() const {
@@ -1395,6 +1531,10 @@ CameraValues EmulatedController::GetCameraValues() const {
return controller.camera_values;
}
RingAnalogValue EmulatedController::GetRingSensorValues() const {
return controller.ring_analog_value;
}
HomeButtonState EmulatedController::GetHomeButtons() const {
std::scoped_lock lock{mutex};
if (is_configuring) {
@@ -1428,22 +1568,12 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
}
AnalogSticks EmulatedController::GetSticks() const {
std::unique_lock lock{mutex};
std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
}
// Some drivers like stick from buttons need constant refreshing
for (auto& device : stick_devices) {
if (!device) {
continue;
}
lock.unlock();
device->SoftUpdate();
lock.lock();
}
return controller.analog_stick_state;
}
@@ -1488,6 +1618,10 @@ const CameraState& EmulatedController::GetCamera() const {
return controller.camera_state;
}
RingSensorForce EmulatedController::GetRingSensorForce() const {
return controller.ring_analog_state;
}
const NfcState& EmulatedController::GetNfc() const {
std::scoped_lock lock{mutex};
return controller.nfc_state;

View File

@@ -35,19 +35,27 @@ using ControllerMotionDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
using TriggerDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using ColorDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
using CameraDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using RingAnalogDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using NfcDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using CameraParams = Common::ParamPackage;
using NfcParams = Common::ParamPackage;
using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
@@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
using CameraValues = Common::Input::CameraStatus;
using RingAnalogValue = Common::Input::AnalogStatus;
using NfcValues = Common::Input::NfcStatus;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
@@ -84,6 +93,10 @@ struct CameraState {
std::size_t sample{};
};
struct RingSensorForce {
f32 force;
};
struct NfcState {
Common::Input::NfcState state{};
std::vector<u8> data{};
@@ -116,6 +129,7 @@ struct ControllerStatus {
BatteryValues battery_values{};
VibrationValues vibration_values{};
CameraValues camera_values{};
RingAnalogValue ring_analog_value{};
NfcValues nfc_values{};
// Data for HID serices
@@ -129,6 +143,7 @@ struct ControllerStatus {
ControllerColors colors_state{};
BatteryLevelState battery_state{};
CameraState camera_state{};
RingSensorForce ring_analog_state{};
NfcState nfc_state{};
};
@@ -141,6 +156,7 @@ enum class ControllerTriggerType {
Battery,
Vibration,
IrSensor,
RingController,
Nfc,
Connected,
Disconnected,
@@ -294,6 +310,9 @@ public:
/// Returns the latest camera status from the controller with parameters
CameraValues GetCameraValues() const;
/// Returns the latest status of analog input from the ring sensor with parameters
RingAnalogValue GetRingSensorValues() const;
/// Returns the latest status of button input for the hid::HomeButton service
HomeButtonState GetHomeButtons() const;
@@ -324,6 +343,9 @@ public:
/// Returns the latest camera status from the controller
const CameraState& GetCamera() const;
/// Returns the latest ringcon force sensor value
RingSensorForce GetRingSensorForce() const;
/// Returns the latest ntag status from the controller
const NfcState& GetNfc() const;
@@ -341,10 +363,12 @@ public:
/**
* Sets the desired data to be polled from a controller
* @param device_index index of the controller to set the polling mode
* @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
* @return true if SetPollingMode was successfull
* @return driver result from this command
*/
bool SetPollingMode(Common::Input::PollingMode polling_mode);
Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
Common::Input::PollingMode polling_mode);
/**
* Sets the desired camera format to be polled from a controller
@@ -353,6 +377,15 @@ public:
*/
bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
// Returns the current mapped ring device
Common::ParamPackage GetRingParam() const;
/**
* Updates the current mapped ring device
* @param param ParamPackage with ring sensor data to be mapped
*/
void SetRingParam(Common::ParamPackage param);
/// Returns true if the device has nfc support
bool HasNfc() const;
@@ -432,10 +465,17 @@ private:
*/
void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the color status of the controller
* @param callback A CallbackStatus containing the color status
* @param index color ID of the to be updated
*/
void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the battery status of the controller
* @param callback A CallbackStatus containing the battery status
* @param index Button ID of the to be updated
* @param index battery ID of the to be updated
*/
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
@@ -445,6 +485,12 @@ private:
*/
void SetCamera(const Common::Input::CallbackStatus& callback);
/**
* Updates the ring analog sensor status of the ring controller
* @param callback A CallbackStatus containing the force status
*/
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
/**
* Updates the nfc status of the controller
* @param callback A CallbackStatus containing the nfc status
@@ -484,7 +530,9 @@ private:
ControllerMotionParams motion_params;
TriggerParams trigger_params;
BatteryParams battery_params;
ColorParams color_params;
CameraParams camera_params;
RingAnalogParams ring_params;
NfcParams nfc_params;
OutputParams output_params;
@@ -493,7 +541,9 @@ private:
ControllerMotionDevices motion_devices;
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
ColorDevices color_devices;
CameraDevices camera_devices;
RingAnalogDevices ring_analog_devices;
NfcDevices nfc_devices;
OutputDevices output_devices;

View File

@@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
ReloadInput();
}
@@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {
key_index++;
}
ring_analog_device = Common::Input::CreateInputDevice(ring_params);
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
@@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {
},
});
}
if (ring_analog_device) {
ring_analog_device->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
});
}
}
void EmulatedDevices::UnloadInput() {
@@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {
for (auto& button : keyboard_modifier_devices) {
button.reset();
}
ring_analog_device.reset();
}
void EmulatedDevices::EnableConfiguration() {
@@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
Settings::values.ringcon_analogs = ring_params.Serialize();
}
void EmulatedDevices::RestoreConfig() {
@@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {
ReloadFromSettings();
}
Common::ParamPackage EmulatedDevices::GetRingParam() const {
return ring_params;
}
void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
ring_params = std::move(param);
ReloadInput();
}
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.keyboard_values.size()) {
@@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
std::lock_guard lock{mutex};
const auto force_value = TransformToStick(callback);
device_status.ring_analog_value = force_value.x;
if (is_configuring) {
device_status.ring_analog_value = {};
TriggerOnChange(DeviceTriggerType::RingController);
return;
}
device_status.ring_analog_state.force = force_value.x.value;
TriggerOnChange(DeviceTriggerType::RingController);
}
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_values;
@@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
return device_status.mouse_button_values;
}
RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
return device_status.ring_analog_value;
}
KeyboardKey EmulatedDevices::GetKeyboard() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_state;
@@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
return device_status.mouse_wheel_state;
}
RingSensorForce EmulatedDevices::GetRingSensorForce() const {
return device_status.ring_analog_state;
}
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {

View File

@@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
using RingAnalogParams = Common::ParamPackage;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -41,17 +39,12 @@ using MouseButtonValues =
using MouseAnalogValues =
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickValue = Common::Input::TouchStatus;
using RingAnalogValue = Common::Input::AnalogStatus;
struct MousePosition {
f32 x;
f32 y;
};
struct RingSensorForce {
f32 force;
};
struct DeviceStatus {
// Data from input_common
KeyboardValues keyboard_values{};
@@ -59,7 +52,6 @@ struct DeviceStatus {
MouseButtonValues mouse_button_values{};
MouseAnalogValues mouse_analog_values{};
MouseStickValue mouse_stick_value{};
RingAnalogValue ring_analog_value{};
// Data for HID serices
KeyboardKey keyboard_state{};
@@ -67,7 +59,6 @@ struct DeviceStatus {
MouseButton mouse_button_state{};
MousePosition mouse_position_state{};
AnalogStickState mouse_wheel_state{};
RingSensorForce ring_analog_state{};
};
enum class DeviceTriggerType {
@@ -138,9 +129,6 @@ public:
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
/// Returns the latest status of analog input from the ring sensor with parameters
RingAnalogValue GetRingSensorValues() const;
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
@@ -156,9 +144,6 @@ public:
/// Returns the latest mouse wheel change
AnalogStickState GetMouseWheel() const;
/// Returns the latest ringcon force sensor value
RingSensorForce GetRingSensorForce() const;
/**
* Adds a callback to the list of events
* @param update_callback InterfaceUpdateCallback that will be triggered
@@ -224,14 +209,11 @@ private:
bool is_configuring{false};
RingAnalogParams ring_params;
KeyboardDevices keyboard_devices;
KeyboardModifierDevices keyboard_modifier_devices;
MouseButtonDevices mouse_button_devices;
MouseAnalogDevices mouse_analog_devices;
MouseStickDevice mouse_stick_device;
RingAnalogDevice ring_analog_device;
mutable std::mutex mutex;
mutable std::mutex callback_mutex;

View File

@@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
return nfc;
}
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
switch (callback.type) {
case Common::Input::InputType::Color:
return callback.color_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
return {};
break;
}
}
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;

View File

@@ -88,10 +88,18 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
* Converts raw input data into a valid nfc status.
*
* @param callback Supported callbacks: Nfc.
* @return A valid CameraObject object.
* @return A valid data tag vector.
*/
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid color status.
*
* @param callback Supported callbacks: Color.
* @return A valid Color object.
*/
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
/**
* Converts raw analog data into a valid analog value
* @param analog An analog object containing raw data and properties

View File

@@ -27,13 +27,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
auto& page_table = m_owner->PageTable();
// Construct the page group.
m_page_group = {};
m_page_group.emplace(kernel, page_table.GetBlockInfoManager());
// Lock the memory.
R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size))
R_TRY(page_table.LockForCodeMemory(std::addressof(*m_page_group), addr, size))
// Clear the memory.
for (const auto& block : m_page_group.Nodes()) {
for (const auto& block : *m_page_group) {
std::memset(device_memory.GetPointer<void>(block.GetAddress()), 0xFF, block.GetSize());
}
@@ -51,12 +51,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
void KCodeMemory::Finalize() {
// Unlock.
if (!m_is_mapped && !m_is_owner_mapped) {
const size_t size = m_page_group.GetNumPages() * PageSize;
m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group);
const size_t size = m_page_group->GetNumPages() * PageSize;
m_owner->PageTable().UnlockForCodeMemory(m_address, size, *m_page_group);
}
// Close the page group.
m_page_group = {};
m_page_group->Close();
m_page_group->Finalize();
// Close our reference to our owner.
m_owner->Close();
@@ -64,7 +65,7 @@ void KCodeMemory::Finalize() {
Result KCodeMemory::Map(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
@@ -74,7 +75,7 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
// Map the memory.
R_TRY(kernel.CurrentProcess()->PageTable().MapPages(
address, m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
// Mark ourselves as mapped.
m_is_mapped = true;
@@ -84,13 +85,13 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
Result KCodeMemory::Unmap(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
// Unmap the memory.
R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, m_page_group,
R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, *m_page_group,
KMemoryState::CodeOut));
// Mark ourselves as unmapped.
@@ -101,7 +102,7 @@ Result KCodeMemory::Unmap(VAddr address, size_t size) {
Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
@@ -125,7 +126,7 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
// Map the memory.
R_TRY(
m_owner->PageTable().MapPages(address, m_page_group, KMemoryState::GeneratedCode, k_perm));
m_owner->PageTable().MapPages(address, *m_page_group, KMemoryState::GeneratedCode, k_perm));
// Mark ourselves as mapped.
m_is_owner_mapped = true;
@@ -135,13 +136,13 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
// Validate the size.
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
// Unmap the memory.
R_TRY(m_owner->PageTable().UnmapPages(address, m_page_group, KMemoryState::GeneratedCode));
R_TRY(m_owner->PageTable().UnmapPages(address, *m_page_group, KMemoryState::GeneratedCode));
// Mark ourselves as unmapped.
m_is_owner_mapped = false;

View File

@@ -3,6 +3,8 @@
#pragma once
#include <optional>
#include "common/common_types.h"
#include "core/device_memory.h"
#include "core/hle/kernel/k_auto_object.h"
@@ -49,11 +51,11 @@ public:
return m_address;
}
size_t GetSize() const {
return m_is_initialized ? m_page_group.GetNumPages() * PageSize : 0;
return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
}
private:
KPageGroup m_page_group{};
std::optional<KPageGroup> m_page_group{};
KProcess* m_owner{};
VAddr m_address{};
KLightLock m_lock;

View File

@@ -18,7 +18,8 @@ void KHardwareTimer::Initialize() {
}
void KHardwareTimer::Finalize() {
this->DisableInterrupt();
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
m_wakeup_time = std::numeric_limits<s64>::max();
m_event_type.reset();
}
@@ -59,7 +60,8 @@ void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
}
void KHardwareTimer::DisableInterrupt() {
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
m_kernel.System().CoreTiming().UnscheduleEventWithoutWait(m_event_type,
reinterpret_cast<uintptr_t>(this));
m_wakeup_time = std::numeric_limits<s64>::max();
}

View File

@@ -223,7 +223,7 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
// Ensure that we don't leave anything un-freed.
ON_RESULT_FAILURE {
for (const auto& it : out->Nodes()) {
for (const auto& it : *out) {
auto& manager = this->GetManager(it.GetAddress());
const size_t node_num_pages = std::min<u64>(
it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
@@ -285,7 +285,7 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
m_has_optimized_process[static_cast<size_t>(pool)], true));
// Open the first reference to the pages.
for (const auto& block : out->Nodes()) {
for (const auto& block : *out) {
PAddr cur_address = block.GetAddress();
size_t remaining_pages = block.GetNumPages();
while (remaining_pages > 0) {
@@ -335,7 +335,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Perform optimized memory tracking, if we should.
if (optimized) {
// Iterate over the allocated blocks.
for (const auto& block : out->Nodes()) {
for (const auto& block : *out) {
// Get the block extents.
const PAddr block_address = block.GetAddress();
const size_t block_pages = block.GetNumPages();
@@ -391,7 +391,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
}
} else {
// Set all the allocated memory.
for (const auto& block : out->Nodes()) {
for (const auto& block : *out) {
std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
block.GetSize());
}

View File

@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_dynamic_resource_manager.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
namespace Kernel {
void KPageGroup::Finalize() {
KBlockInfo* cur = m_first_block;
while (cur != nullptr) {
KBlockInfo* next = cur->GetNext();
m_manager->Free(cur);
cur = next;
}
m_first_block = nullptr;
m_last_block = nullptr;
}
void KPageGroup::CloseAndReset() {
auto& mm = m_kernel.MemoryManager();
KBlockInfo* cur = m_first_block;
while (cur != nullptr) {
KBlockInfo* next = cur->GetNext();
mm.Close(cur->GetAddress(), cur->GetNumPages());
m_manager->Free(cur);
cur = next;
}
m_first_block = nullptr;
m_last_block = nullptr;
}
size_t KPageGroup::GetNumPages() const {
size_t num_pages = 0;
for (const auto& it : *this) {
num_pages += it.GetNumPages();
}
return num_pages;
}
Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) {
// Succeed immediately if we're adding no pages.
R_SUCCEED_IF(num_pages == 0);
// Check for overflow.
ASSERT(addr < addr + num_pages * PageSize);
// Try to just append to the last block.
if (m_last_block != nullptr) {
R_SUCCEED_IF(m_last_block->TryConcatenate(addr, num_pages));
}
// Allocate a new block.
KBlockInfo* new_block = m_manager->Allocate();
R_UNLESS(new_block != nullptr, ResultOutOfResource);
// Initialize the block.
new_block->Initialize(addr, num_pages);
// Add the block to our list.
if (m_last_block != nullptr) {
m_last_block->SetNext(new_block);
} else {
m_first_block = new_block;
}
m_last_block = new_block;
R_SUCCEED();
}
void KPageGroup::Open() const {
auto& mm = m_kernel.MemoryManager();
for (const auto& it : *this) {
mm.Open(it.GetAddress(), it.GetNumPages());
}
}
void KPageGroup::OpenFirst() const {
auto& mm = m_kernel.MemoryManager();
for (const auto& it : *this) {
mm.OpenFirst(it.GetAddress(), it.GetNumPages());
}
}
void KPageGroup::Close() const {
auto& mm = m_kernel.MemoryManager();
for (const auto& it : *this) {
mm.Close(it.GetAddress(), it.GetNumPages());
}
}
bool KPageGroup::IsEquivalentTo(const KPageGroup& rhs) const {
auto lit = this->begin();
auto rit = rhs.begin();
auto lend = this->end();
auto rend = rhs.end();
while (lit != lend && rit != rend) {
if (*lit != *rit) {
return false;
}
++lit;
++rit;
}
return lit == lend && rit == rend;
}
} // namespace Kernel

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,24 +13,23 @@
namespace Kernel {
class KBlockInfoManager;
class KernelCore;
class KPageGroup;
class KBlockInfo {
private:
friend class KPageGroup;
public:
constexpr KBlockInfo() = default;
constexpr explicit KBlockInfo() : m_next(nullptr) {}
constexpr void Initialize(PAddr addr, size_t np) {
constexpr void Initialize(KPhysicalAddress addr, size_t np) {
ASSERT(Common::IsAligned(addr, PageSize));
ASSERT(static_cast<u32>(np) == np);
m_page_index = static_cast<u32>(addr) / PageSize;
m_page_index = static_cast<u32>(addr / PageSize);
m_num_pages = static_cast<u32>(np);
}
constexpr PAddr GetAddress() const {
constexpr KPhysicalAddress GetAddress() const {
return m_page_index * PageSize;
}
constexpr size_t GetNumPages() const {
@@ -39,10 +38,10 @@ public:
constexpr size_t GetSize() const {
return this->GetNumPages() * PageSize;
}
constexpr PAddr GetEndAddress() const {
constexpr KPhysicalAddress GetEndAddress() const {
return (m_page_index + m_num_pages) * PageSize;
}
constexpr PAddr GetLastAddress() const {
constexpr KPhysicalAddress GetLastAddress() const {
return this->GetEndAddress() - 1;
}
@@ -62,8 +61,8 @@ public:
return !(*this == rhs);
}
constexpr bool IsStrictlyBefore(PAddr addr) const {
const PAddr end = this->GetEndAddress();
constexpr bool IsStrictlyBefore(KPhysicalAddress addr) const {
const KPhysicalAddress end = this->GetEndAddress();
if (m_page_index != 0 && end == 0) {
return false;
@@ -72,11 +71,11 @@ public:
return end < addr;
}
constexpr bool operator<(PAddr addr) const {
constexpr bool operator<(KPhysicalAddress addr) const {
return this->IsStrictlyBefore(addr);
}
constexpr bool TryConcatenate(PAddr addr, size_t np) {
constexpr bool TryConcatenate(KPhysicalAddress addr, size_t np) {
if (addr != 0 && addr == this->GetEndAddress()) {
m_num_pages += static_cast<u32>(np);
return true;
@@ -90,96 +89,118 @@ private:
}
private:
friend class KPageGroup;
KBlockInfo* m_next{};
u32 m_page_index{};
u32 m_num_pages{};
};
static_assert(sizeof(KBlockInfo) <= 0x10);
class KPageGroup final {
class KPageGroup {
public:
class Node final {
class Iterator {
public:
constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
using iterator_category = std::forward_iterator_tag;
using value_type = const KBlockInfo;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
constexpr u64 GetAddress() const {
return addr;
constexpr explicit Iterator(pointer n) : m_node(n) {}
constexpr bool operator==(const Iterator& rhs) const {
return m_node == rhs.m_node;
}
constexpr bool operator!=(const Iterator& rhs) const {
return !(*this == rhs);
}
constexpr std::size_t GetNumPages() const {
return num_pages;
constexpr pointer operator->() const {
return m_node;
}
constexpr reference operator*() const {
return *m_node;
}
constexpr std::size_t GetSize() const {
return GetNumPages() * PageSize;
constexpr Iterator& operator++() {
m_node = m_node->GetNext();
return *this;
}
constexpr Iterator operator++(int) {
const Iterator it{*this};
++(*this);
return it;
}
private:
u64 addr{};
std::size_t num_pages{};
pointer m_node{};
};
public:
KPageGroup() = default;
KPageGroup(u64 address, u64 num_pages) {
ASSERT(AddBlock(address, num_pages).IsSuccess());
explicit KPageGroup(KernelCore& kernel, KBlockInfoManager* m)
: m_kernel{kernel}, m_manager{m} {}
~KPageGroup() {
this->Finalize();
}
constexpr std::list<Node>& Nodes() {
return nodes;
void CloseAndReset();
void Finalize();
Iterator begin() const {
return Iterator{m_first_block};
}
Iterator end() const {
return Iterator{nullptr};
}
bool empty() const {
return m_first_block == nullptr;
}
constexpr const std::list<Node>& Nodes() const {
return nodes;
Result AddBlock(KPhysicalAddress addr, size_t num_pages);
void Open() const;
void OpenFirst() const;
void Close() const;
size_t GetNumPages() const;
bool IsEquivalentTo(const KPageGroup& rhs) const;
bool operator==(const KPageGroup& rhs) const {
return this->IsEquivalentTo(rhs);
}
std::size_t GetNumPages() const {
std::size_t num_pages = 0;
for (const Node& node : nodes) {
num_pages += node.GetNumPages();
}
return num_pages;
bool operator!=(const KPageGroup& rhs) const {
return !(*this == rhs);
}
bool IsEqual(KPageGroup& other) const {
auto this_node = nodes.begin();
auto other_node = other.nodes.begin();
while (this_node != nodes.end() && other_node != other.nodes.end()) {
if (this_node->GetAddress() != other_node->GetAddress() ||
this_node->GetNumPages() != other_node->GetNumPages()) {
return false;
}
this_node = std::next(this_node);
other_node = std::next(other_node);
}
return this_node == nodes.end() && other_node == other.nodes.end();
}
Result AddBlock(u64 address, u64 num_pages) {
if (!num_pages) {
return ResultSuccess;
}
if (!nodes.empty()) {
const auto node = nodes.back();
if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
address = node.GetAddress();
num_pages += node.GetNumPages();
nodes.pop_back();
}
}
nodes.push_back({address, num_pages});
return ResultSuccess;
}
bool Empty() const {
return nodes.empty();
}
void Finalize() {}
private:
std::list<Node> nodes;
KernelCore& m_kernel;
KBlockInfo* m_first_block{};
KBlockInfo* m_last_block{};
KBlockInfoManager* m_manager{};
};
class KScopedPageGroup {
public:
explicit KScopedPageGroup(const KPageGroup* gp) : m_pg(gp) {
if (m_pg) {
m_pg->Open();
}
}
explicit KScopedPageGroup(const KPageGroup& gp) : KScopedPageGroup(std::addressof(gp)) {}
~KScopedPageGroup() {
if (m_pg) {
m_pg->Close();
}
}
void CancelClose() {
m_pg = nullptr;
}
private:
const KPageGroup* m_pg{};
};
} // namespace Kernel

View File

@@ -100,7 +100,7 @@ constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType a
KPageTable::KPageTable(Core::System& system_)
: m_general_lock{system_.Kernel()},
m_map_physical_memory_lock{system_.Kernel()}, m_system{system_} {}
m_map_physical_memory_lock{system_.Kernel()}, m_system{system_}, m_kernel{system_.Kernel()} {}
KPageTable::~KPageTable() = default;
@@ -373,7 +373,7 @@ Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState sta
m_memory_block_slab_manager);
// Allocate and open.
KPageGroup pg;
KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
&pg, num_pages,
KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, m_allocation_option)));
@@ -432,7 +432,7 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si
const size_t num_pages = size / PageSize;
// Create page groups for the memory being mapped.
KPageGroup pg;
KPageGroup pg{m_kernel, m_block_info_manager};
AddRegionToPages(src_address, num_pages, pg);
// Reprotect the source as kernel-read/not mapped.
@@ -593,7 +593,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
const size_t size = num_pages * PageSize;
// We're making a new group, not adding to an existing one.
R_UNLESS(pg.Empty(), ResultInvalidCurrentMemory);
R_UNLESS(pg.empty(), ResultInvalidCurrentMemory);
// Begin traversal.
Common::PageTable::TraversalContext context;
@@ -640,11 +640,10 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
R_SUCCEED();
}
bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t num_pages) {
bool KPageTable::IsValidPageGroup(const KPageGroup& pg, VAddr addr, size_t num_pages) {
ASSERT(this->IsLockedByCurrentThread());
const size_t size = num_pages * PageSize;
const auto& pg = pg_ll.Nodes();
const auto& memory_layout = m_system.Kernel().MemoryLayout();
// Empty groups are necessarily invalid.
@@ -942,9 +941,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
ON_RESULT_FAILURE {
if (cur_mapped_addr != dst_addr) {
// HACK: Manually close the pages.
HACK_ClosePages(dst_addr, (cur_mapped_addr - dst_addr) / PageSize);
ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
KMemoryPermission::None, OperationType::Unmap)
.IsSuccess());
@@ -1020,9 +1016,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
// Map the page.
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
// HACK: Manually open the pages.
HACK_OpenPages(start_partial_page, 1);
// Update tracking extents.
cur_mapped_addr += PageSize;
cur_block_addr += PageSize;
@@ -1051,9 +1044,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
cur_block_addr));
// HACK: Manually open the pages.
HACK_OpenPages(cur_block_addr, cur_block_size / PageSize);
// Update tracking extents.
cur_mapped_addr += cur_block_size;
cur_block_addr = next_entry.phys_addr;
@@ -1073,9 +1063,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
cur_block_addr));
// HACK: Manually open the pages.
HACK_OpenPages(cur_block_addr, last_block_size / PageSize);
// Update tracking extents.
cur_mapped_addr += last_block_size;
cur_block_addr += last_block_size;
@@ -1107,9 +1094,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
// Map the page.
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
// HACK: Manually open the pages.
HACK_OpenPages(end_partial_page, 1);
}
// Update memory blocks to reflect our changes
@@ -1211,9 +1195,6 @@ Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState
const size_t aligned_size = aligned_end - aligned_start;
const size_t aligned_num_pages = aligned_size / PageSize;
// HACK: Manually close the pages.
HACK_ClosePages(aligned_start, aligned_num_pages);
// Unmap the pages.
R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
@@ -1501,17 +1482,6 @@ void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLi
}
}
void KPageTable::HACK_OpenPages(PAddr phys_addr, size_t num_pages) {
m_system.Kernel().MemoryManager().OpenFirst(phys_addr, num_pages);
}
void KPageTable::HACK_ClosePages(VAddr virt_addr, size_t num_pages) {
for (size_t index = 0; index < num_pages; ++index) {
const auto paddr = GetPhysicalAddr(virt_addr + (index * PageSize));
m_system.Kernel().MemoryManager().Close(paddr, 1);
}
}
Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
KScopedLightLock phys_lk(m_map_physical_memory_lock);
@@ -1572,7 +1542,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the new memory.
KPageGroup pg;
KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
&pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
@@ -1650,7 +1620,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
KScopedPageTableUpdater updater(this);
// Prepare to iterate over the memory.
auto pg_it = pg.Nodes().begin();
auto pg_it = pg.begin();
PAddr pg_phys_addr = pg_it->GetAddress();
size_t pg_pages = pg_it->GetNumPages();
@@ -1680,9 +1650,6 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
last_unmap_address + 1 - cur_address) /
PageSize;
// HACK: Manually close the pages.
HACK_ClosePages(cur_address, cur_pages);
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
OperationType::Unmap)
@@ -1703,7 +1670,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Release any remaining unmapped memory.
m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
for (++pg_it; pg_it != pg.Nodes().end(); ++pg_it) {
for (++pg_it; pg_it != pg.end(); ++pg_it) {
m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
pg_it->GetNumPages());
m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
@@ -1731,7 +1698,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Check if we're at the end of the physical block.
if (pg_pages == 0) {
// Ensure there are more pages to map.
ASSERT(pg_it != pg.Nodes().end());
ASSERT(pg_it != pg.end());
// Advance our physical block.
++pg_it;
@@ -1742,10 +1709,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Map whatever we can.
const size_t cur_pages = std::min(pg_pages, map_pages);
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
OperationType::Map, pg_phys_addr));
// HACK: Manually open the pages.
HACK_OpenPages(pg_phys_addr, cur_pages);
OperationType::MapFirst, pg_phys_addr));
// Advance.
cur_address += cur_pages * PageSize;
@@ -1888,9 +1852,6 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
last_address + 1 - cur_address) /
PageSize;
// HACK: Manually close the pages.
HACK_ClosePages(cur_address, cur_pages);
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
.IsSuccess());
@@ -1955,7 +1916,7 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size)
R_TRY(dst_allocator_result);
// Map the memory.
KPageGroup page_linked_list;
KPageGroup page_linked_list{m_kernel, m_block_info_manager};
const size_t num_pages{size / PageSize};
const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
@@ -2022,14 +1983,14 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size
num_dst_allocator_blocks);
R_TRY(dst_allocator_result);
KPageGroup src_pages;
KPageGroup dst_pages;
KPageGroup src_pages{m_kernel, m_block_info_manager};
KPageGroup dst_pages{m_kernel, m_block_info_manager};
const size_t num_pages{size / PageSize};
AddRegionToPages(src_address, num_pages, src_pages);
AddRegionToPages(dst_address, num_pages, dst_pages);
R_UNLESS(dst_pages.IsEqual(src_pages), ResultInvalidMemoryRegion);
R_UNLESS(dst_pages.IsEquivalentTo(src_pages), ResultInvalidMemoryRegion);
{
auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); });
@@ -2060,7 +2021,7 @@ Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list,
VAddr cur_addr{addr};
for (const auto& node : page_linked_list.Nodes()) {
for (const auto& node : page_linked_list) {
if (const auto result{
Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())};
result.IsError()) {
@@ -2160,7 +2121,7 @@ Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) {
VAddr cur_addr{addr};
for (const auto& node : page_linked_list.Nodes()) {
for (const auto& node : page_linked_list) {
if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None,
OperationType::Unmap)};
result.IsError()) {
@@ -2527,13 +2488,13 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the heap extension.
KPageGroup pg;
KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
&pg, allocation_size / PageSize,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
// Clear all the newly allocated pages.
for (const auto& it : pg.Nodes()) {
for (const auto& it : pg) {
std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
it.GetSize());
}
@@ -2610,11 +2571,23 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
if (is_map_only) {
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
} else {
KPageGroup page_group;
R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
&page_group, needed_num_pages,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
// Create a page group tohold the pages we allocate.
KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
&pg, needed_num_pages,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
// Ensure that the page group is closed when we're done working with it.
SCOPE_EXIT({ pg.Close(); });
// Clear all pages.
for (const auto& it : pg) {
std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()),
m_heap_fill_value, it.GetSize());
}
R_TRY(Operate(addr, needed_num_pages, pg, OperationType::MapGroup));
}
// Update the blocks.
@@ -2795,19 +2768,28 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_
ASSERT(num_pages > 0);
ASSERT(num_pages == page_group.GetNumPages());
for (const auto& node : page_group.Nodes()) {
const size_t size{node.GetNumPages() * PageSize};
switch (operation) {
case OperationType::MapGroup: {
// We want to maintain a new reference to every page in the group.
KScopedPageGroup spg(page_group);
switch (operation) {
case OperationType::MapGroup:
for (const auto& node : page_group) {
const size_t size{node.GetNumPages() * PageSize};
// Map the pages.
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, size, node.GetAddress());
break;
default:
ASSERT(false);
break;
addr += size;
}
addr += size;
// We succeeded! We want to persist the reference to the pages.
spg.CancelClose();
break;
}
default:
ASSERT(false);
break;
}
R_SUCCEED();
@@ -2822,13 +2804,29 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
ASSERT(ContainsPages(addr, num_pages));
switch (operation) {
case OperationType::Unmap:
case OperationType::Unmap: {
// Ensure that any pages we track close on exit.
KPageGroup pages_to_close{m_kernel, this->GetBlockInfoManager()};
SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
this->AddRegionToPages(addr, num_pages, pages_to_close);
m_system.Memory().UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize);
break;
}
case OperationType::MapFirst:
case OperationType::Map: {
ASSERT(map_addr);
ASSERT(Common::IsAligned(map_addr, PageSize));
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
// Open references to pages, if we should.
if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) {
if (operation == OperationType::MapFirst) {
m_kernel.MemoryManager().OpenFirst(map_addr, num_pages);
} else {
m_kernel.MemoryManager().Open(map_addr, num_pages);
}
}
break;
}
case OperationType::Separate: {

View File

@@ -107,6 +107,10 @@ public:
return *m_page_table_impl;
}
KBlockInfoManager* GetBlockInfoManager() {
return m_block_info_manager;
}
bool CanContain(VAddr addr, size_t size, KMemoryState state) const;
protected:
@@ -261,10 +265,6 @@ private:
void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address,
size_t size, KMemoryPermission prot_perm);
// HACK: These will be removed once we automatically manage page reference counts.
void HACK_OpenPages(PAddr phys_addr, size_t num_pages);
void HACK_ClosePages(VAddr virt_addr, size_t num_pages);
mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock;
@@ -488,6 +488,7 @@ private:
std::unique_ptr<Common::PageTable> m_page_table_impl;
Core::System& m_system;
KernelCore& m_kernel;
};
} // namespace Kernel

View File

@@ -13,10 +13,7 @@
namespace Kernel {
KSharedMemory::KSharedMemory(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
KSharedMemory::~KSharedMemory() {
kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemoryMax, size);
}
KSharedMemory::~KSharedMemory() = default;
Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
Svc::MemoryPermission owner_permission_,
@@ -49,7 +46,8 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
R_UNLESS(physical_address != 0, ResultOutOfMemory);
//! Insert the result into our page group.
page_group.emplace(physical_address, num_pages);
page_group.emplace(kernel, &kernel.GetSystemSystemResource().GetBlockInfoManager());
page_group->AddBlock(physical_address, num_pages);
// Commit our reservation.
memory_reservation.Commit();
@@ -62,7 +60,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
is_initialized = true;
// Clear all pages in the memory.
for (const auto& block : page_group->Nodes()) {
for (const auto& block : *page_group) {
std::memset(device_memory_.GetPointer<void>(block.GetAddress()), 0, block.GetSize());
}
@@ -71,13 +69,8 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
void KSharedMemory::Finalize() {
// Close and finalize the page group.
// page_group->Close();
// page_group->Finalize();
//! HACK: Manually close.
for (const auto& block : page_group->Nodes()) {
kernel.MemoryManager().Close(block.GetAddress(), block.GetNumPages());
}
page_group->Close();
page_group->Finalize();
// Release the memory reservation.
resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);

View File

@@ -14,4 +14,7 @@ constexpr std::size_t PageSize{1 << PageBits};
using Page = std::array<u8, PageSize>;
using KPhysicalAddress = PAddr;
using KProcessAddress = VAddr;
} // namespace Kernel

View File

@@ -1485,7 +1485,7 @@ static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle p
ResultInvalidMemoryRegion);
// Create a new page group.
KPageGroup pg;
KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()};
R_TRY(src_pt.MakeAndOpenPageGroup(
std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess,
KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None,

View File

@@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
}
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
shared_memory->fullkey_color.fullkey = body_colors.left;
shared_memory->joycon_color.attribute = ColorAttribute::Ok;
shared_memory->joycon_color.left = body_colors.left;
shared_memory->battery_level_dual = battery_level.left.battery_level;
@@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
shared_memory->fullkey_color.fullkey = body_colors.right;
shared_memory->joycon_color.attribute = ColorAttribute::Ok;
shared_memory->joycon_color.right = body_colors.right;
shared_memory->battery_level_right = battery_level.right.battery_level;
@@ -332,6 +336,20 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
controller.is_connected = true;
controller.device->Connect();
controller.device->SetLedPattern();
if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
if (controller.is_dual_left_connected) {
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex,
Common::Input::PollingMode::Active);
}
if (controller.is_dual_right_connected) {
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
}
} else {
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
Common::Input::PollingMode::Active);
}
SignalStyleSetChangedEvent(npad_id);
WriteEmptyEntry(controller.shared_memory);
}

View File

@@ -297,13 +297,13 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
LOG_INFO(Service_HID,
"called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
"player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
parameters.enable, parameters.bus_handle.abstracted_pad_id,
parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
parameters.applet_resource_user_id);
LOG_DEBUG(Service_HID,
"called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
"player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
parameters.enable, parameters.bus_handle.abstracted_pad_id,
parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
parameters.applet_resource_user_id);
const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
@@ -326,11 +326,11 @@ void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto bus_handle_{rp.PopRaw<BusHandle>()};
LOG_INFO(Service_HID,
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
"is_valid={}",
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
bus_handle_.player_number, bus_handle_.is_valid);
LOG_DEBUG(Service_HID,
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
"is_valid={}",
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
bus_handle_.player_number, bus_handle_.is_valid);
const auto device_index = GetDeviceIndexFromHandle(bus_handle_);

View File

@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hid/emulated_devices.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
@@ -12,16 +12,20 @@ namespace Service::HID {
RingController::RingController(Core::HID::HIDCore& hid_core_,
KernelHelpers::ServiceContext& service_context_)
: HidbusBase(service_context_) {
input = hid_core_.GetEmulatedDevices();
input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
}
RingController::~RingController() = default;
void RingController::OnInit() {
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Ring);
return;
}
void RingController::OnRelease() {
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
return;
};

View File

@@ -9,7 +9,7 @@
#include "core/hle/service/hid/hidbus/hidbus_base.h"
namespace Core::HID {
class EmulatedDevices;
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
@@ -248,6 +248,6 @@ private:
.zero = {.value = idle_value, .crc = 225},
};
Core::HID::EmulatedDevices* input;
Core::HID::EmulatedController* input;
};
} // namespace Service::HID

View File

@@ -108,6 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
auto result = IsIrCameraHandleValid(parameters.camera_handle);
if (result.IsSuccess()) {
// TODO: Stop Image processor
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
result = ResultSuccess;
}
@@ -139,6 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -170,6 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
auto& image_transfer_processor =
GetProcessor<ClusteringProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -219,6 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -294,6 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
auto& image_transfer_processor =
GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -343,6 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
MakeProcessor<PointingProcessor>(camera_handle, device);
auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
image_transfer_processor.SetConfig(processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -453,6 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -479,6 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
MakeProcessor<IrLedProcessor>(camera_handle, device);
auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
image_transfer_processor.SetConfig(processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
}
IPC::ResponseBuilder rb{ctx, 2};
@@ -504,6 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
auto result = IsIrCameraHandleValid(parameters.camera_handle);
if (result.IsSuccess()) {
// TODO: Stop image processor async
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
result = ResultSuccess;
}

View File

@@ -130,7 +130,9 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
return WrongDeviceState;
}
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::NFC) !=
Common::Input::DriverResult::Success) {
LOG_ERROR(Service_NFC, "Nfc not supported");
return NfcDisabled;
}
@@ -141,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
}
Result NfcDevice::StopDetection() {
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
if (device_state == NFP::DeviceState::Initialized) {
return ResultSuccess;

View File

@@ -152,7 +152,9 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
return WrongDeviceState;
}
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::NFC) !=
Common::Input::DriverResult::Success) {
LOG_ERROR(Service_NFP, "Nfc not supported");
return NfcDisabled;
}
@@ -163,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
}
Result NfpDevice::StopDetection() {
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
if (device_state == DeviceState::Initialized) {
return ResultSuccess;

View File

@@ -312,8 +312,6 @@ void NVFlinger::Compose() {
}
s64 NVFlinger::GetNextTicks() const {
static constexpr s64 max_hertz = 120LL;
const auto& settings = Settings::values;
auto speed_scale = 1.f;
if (settings.use_multi_core.GetValue()) {
@@ -327,9 +325,11 @@ s64 NVFlinger::GetNextTicks() const {
}
}
const auto next_ticks = ((1000000000 * (1LL << swap_interval)) / max_hertz);
// As an extension, treat nonpositive swap interval as framerate multiplier.
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
: 60.f / static_cast<f32>(swap_interval);
return static_cast<s64>(speed_scale * static_cast<float>(next_ticks));
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
} // namespace Service::NVFlinger

View File

@@ -133,7 +133,7 @@ private:
/// layers.
u32 next_buffer_queue_id = 1;
u32 swap_interval = 1;
s32 swap_interval = 1;
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> multi_composition_event;

View File

@@ -383,6 +383,10 @@ struct Memory::Impl {
return;
}
if (Settings::IsFastmemEnabled()) {
system.DeviceMemory().buffer.Protect(vaddr, size, !debug, !debug);
}
// Iterate over a contiguous CPU address space, marking/unmarking the region.
// The region is at a granularity of CPU pages.

View File

@@ -51,8 +51,29 @@ endif()
if (ENABLE_SDL2)
target_sources(input_common PRIVATE
drivers/joycon.cpp
drivers/joycon.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
helpers/joycon_driver.cpp
helpers/joycon_driver.h
helpers/joycon_protocol/calibration.cpp
helpers/joycon_protocol/calibration.h
helpers/joycon_protocol/common_protocol.cpp
helpers/joycon_protocol/common_protocol.h
helpers/joycon_protocol/generic_functions.cpp
helpers/joycon_protocol/generic_functions.h
helpers/joycon_protocol/joycon_types.h
helpers/joycon_protocol/irs.cpp
helpers/joycon_protocol/irs.h
helpers/joycon_protocol/nfc.cpp
helpers/joycon_protocol/nfc.h
helpers/joycon_protocol/poller.cpp
helpers/joycon_protocol/poller.h
helpers/joycon_protocol/ringcon.cpp
helpers/joycon_protocol/ringcon.h
helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h
)
target_link_libraries(input_common PRIVATE SDL2::SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)

View File

@@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
}
}
Common::Input::CameraError Camera::SetCameraFormat(
Common::Input::DriverResult Camera::SetCameraFormat(
[[maybe_unused]] const PadIdentifier& identifier_,
const Common::Input::CameraFormat camera_format) {
status.format = camera_format;
return Common::Input::CameraError::None;
return Common::Input::DriverResult::Success;
}
} // namespace InputCommon

View File

@@ -22,8 +22,8 @@ public:
std::size_t getImageWidth() const;
std::size_t getImageHeight() const;
Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
Common::Input::CameraFormat camera_format) override;
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
Common::Input::CameraFormat camera_format) override;
private:
Common::Input::CameraStatus status{};

View File

@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
return true;
}
Common::Input::VibrationError GCAdapter::SetVibration(
Common::Input::DriverResult GCAdapter::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
const auto processed_amplitude =
@@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
pads[identifier.port].rumble_amplitude = processed_amplitude;
if (!rumble_enabled) {
return Common::Input::VibrationError::Disabled;
return Common::Input::DriverResult::Disabled;
}
return Common::Input::VibrationError::None;
return Common::Input::DriverResult::Success;
}
bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {

View File

@@ -25,7 +25,7 @@ public:
explicit GCAdapter(std::string input_engine_);
~GCAdapter() override;
Common::Input::VibrationError SetVibration(
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
bool IsVibrationEnabled(const PadIdentifier& identifier) override;

View File

@@ -0,0 +1,677 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/format.h>
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/joycon.h"
#include "input_common/helpers/joycon_driver.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon {
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
// Avoid conflicting with SDL driver
if (!Settings::values.enable_joycon_driver) {
return;
}
LOG_INFO(Input, "Joycon driver Initialization started");
const int init_res = SDL_hid_init();
if (init_res == 0) {
Setup();
} else {
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
}
}
Joycons::~Joycons() {
Reset();
}
void Joycons::Reset() {
scan_thread = {};
for (const auto& device : left_joycons) {
if (!device) {
continue;
}
device->Stop();
}
for (const auto& device : right_joycons) {
if (!device) {
continue;
}
device->Stop();
}
SDL_hid_exit();
}
void Joycons::Setup() {
u32 port = 0;
PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
for (auto& device : left_joycons) {
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
device = std::make_shared<Joycon::JoyconDriver>(port++);
}
port = 0;
for (auto& device : right_joycons) {
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
device = std::make_shared<Joycon::JoyconDriver>(port++);
}
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
}
void Joycons::ScanThread(std::stop_token stop_token) {
constexpr u16 nintendo_vendor_id = 0x057e;
Common::SetCurrentThreadName("JoyconScanThread");
while (!stop_token.stop_requested()) {
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
SDL_hid_device_info* cur_dev = devs;
while (cur_dev) {
if (IsDeviceNew(cur_dev)) {
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
cur_dev->product_id);
RegisterNewDevice(cur_dev);
}
cur_dev = cur_dev->next;
}
SDL_hid_free_enumeration(devs);
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
Joycon::ControllerType type{};
Joycon::SerialNumber serial_number{};
const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
if (result != Joycon::DriverResult::Success) {
return false;
}
const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
if (result2 != Joycon::DriverResult::Success) {
return false;
}
auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return false;
}
if (!device->IsConnected()) {
return false;
}
if (device->GetHandleSerialNumber() != serial_number) {
return false;
}
return true;
};
// Check if device already exist
switch (type) {
case Joycon::ControllerType::Left:
for (const auto& device : left_joycons) {
if (is_handle_identical(device)) {
return false;
}
}
break;
case Joycon::ControllerType::Right:
for (const auto& device : right_joycons) {
if (is_handle_identical(device)) {
return false;
}
}
break;
default:
return false;
}
return true;
}
void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
Joycon::ControllerType type{};
auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
auto handle = GetNextFreeHandle(type);
if (handle == nullptr) {
LOG_WARNING(Input, "No free handles available");
return;
}
if (result == Joycon::DriverResult::Success) {
result = handle->RequestDeviceAccess(device_info);
}
if (result == Joycon::DriverResult::Success) {
LOG_WARNING(Input, "Initialize device");
const std::size_t port = handle->GetDevicePort();
const Joycon::JoyconCallbacks callbacks{
.on_battery_data = {[this, port, type](Joycon::Battery value) {
OnBatteryUpdate(port, type, value);
}},
.on_color_data = {[this, port, type](Joycon::Color value) {
OnColorUpdate(port, type, value);
}},
.on_button_data = {[this, port, type](int id, bool value) {
OnButtonUpdate(port, type, id, value);
}},
.on_stick_data = {[this, port, type](int id, f32 value) {
OnStickUpdate(port, type, id, value);
}},
.on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
OnMotionUpdate(port, type, id, value);
}},
.on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
.on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
OnAmiiboUpdate(port, amiibo_data);
}},
.on_camera_data = {[this, port](const std::vector<u8>& camera_data,
Joycon::IrsResolution format) {
OnCameraUpdate(port, camera_data, format);
}},
};
handle->InitializeDevice();
handle->SetCallbacks(callbacks);
}
}
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
Joycon::ControllerType type) const {
if (type == Joycon::ControllerType::Left) {
const auto unconnected_device =
std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
if (unconnected_device != left_joycons.end()) {
return *unconnected_device;
}
}
if (type == Joycon::ControllerType::Right) {
const auto unconnected_device = std::ranges::find_if(
right_joycons, [](auto& device) { return !device->IsConnected(); });
if (unconnected_device != right_joycons.end()) {
return *unconnected_device;
}
}
return nullptr;
}
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
const auto handle = GetHandle(identifier);
if (handle == nullptr) {
return false;
}
return handle->IsVibrationEnabled();
}
Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
const Common::Input::VibrationStatus& vibration) {
const Joycon::VibrationValue native_vibration{
.low_amplitude = vibration.low_amplitude,
.low_frequency = vibration.low_frequency,
.high_amplitude = vibration.high_amplitude,
.high_frequency = vibration.high_frequency,
};
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
handle->SetVibration(native_vibration);
return Common::Input::DriverResult::Success;
}
Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
const Common::Input::LedStatus& led_status) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
int led_config = led_status.led_1 ? 1 : 0;
led_config += led_status.led_2 ? 2 : 0;
led_config += led_status.led_3 ? 4 : 0;
led_config += led_status.led_4 ? 8 : 0;
return static_cast<Common::Input::DriverResult>(
handle->SetLedConfig(static_cast<u8>(led_config)));
}
Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
Common::Input::CameraFormat camera_format) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::DriverResult::InvalidHandle;
}
return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
};
Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
return Common::Input::NfcState::Success;
};
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
const std::vector<u8>& data) {
return Common::Input::NfcState::NotSupported;
};
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
const Common::Input::PollingMode polling_mode) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
LOG_ERROR(Input, "Invalid handle {}", identifier.port);
return Common::Input::DriverResult::InvalidHandle;
}
switch (polling_mode) {
case Common::Input::PollingMode::Active:
return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
case Common::Input::PollingMode::Pasive:
return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
case Common::Input::PollingMode::IR:
return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
case Common::Input::PollingMode::NFC:
return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
case Common::Input::PollingMode::Ring:
return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
default:
return Common::Input::DriverResult::NotSupported;
}
}
void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
Joycon::Battery value) {
const auto identifier = GetIdentifier(port, type);
if (value.charging != 0) {
SetBattery(identifier, Common::Input::BatteryLevel::Charging);
return;
}
Common::Input::BatteryLevel battery{};
switch (value.status) {
case 0:
battery = Common::Input::BatteryLevel::Empty;
break;
case 1:
battery = Common::Input::BatteryLevel::Critical;
break;
case 2:
battery = Common::Input::BatteryLevel::Low;
break;
case 3:
battery = Common::Input::BatteryLevel::Medium;
break;
case 4:
default:
battery = Common::Input::BatteryLevel::Full;
break;
}
SetBattery(identifier, battery);
}
void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
const Joycon::Color& value) {
const auto identifier = GetIdentifier(port, type);
Common::Input::BodyColorStatus color{
.body = value.body,
.buttons = value.buttons,
.left_grip = value.left_grip,
.right_grip = value.right_grip,
};
SetColor(identifier, color);
}
void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
const auto identifier = GetIdentifier(port, type);
SetButton(identifier, id, value);
}
void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
const auto identifier = GetIdentifier(port, type);
SetAxis(identifier, id, value);
}
void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
const Joycon::MotionData& value) {
const auto identifier = GetIdentifier(port, type);
BasicMotion motion_data{
.gyro_x = value.gyro_x,
.gyro_y = value.gyro_y,
.gyro_z = value.gyro_z,
.accel_x = value.accel_x,
.accel_y = value.accel_y,
.accel_z = value.accel_z,
.delta_timestamp = 15000,
};
SetMotion(identifier, id, motion_data);
}
void Joycons::OnRingConUpdate(f32 ring_data) {
// To simplify ring detection it will always be mapped to an empty identifier for all
// controllers
constexpr PadIdentifier identifier = {
.guid = Common::UUID{},
.port = 0,
.pad = 0,
};
SetAxis(identifier, 100, ring_data);
}
void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
: Common::Input::NfcState::NewAmiibo;
SetNfc(identifier, {nfc_state, amiibo_data});
}
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
Joycon::IrsResolution format) {
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
}
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return false;
}
if (!device->IsConnected()) {
return false;
}
if (device->GetDevicePort() == identifier.port) {
return true;
}
return false;
};
const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
if (type == Joycon::ControllerType::Left) {
const auto matching_device = std::ranges::find_if(
left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
if (matching_device != left_joycons.end()) {
return *matching_device;
}
}
if (type == Joycon::ControllerType::Right) {
const auto matching_device = std::ranges::find_if(
right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
if (matching_device != right_joycons.end()) {
return *matching_device;
}
}
return nullptr;
}
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
return {
.guid = Common::UUID{guid},
.port = port,
.pad = static_cast<std::size_t>(type),
};
}
Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
const auto identifier = GetIdentifier(port, type);
return {
{"engine", GetEngineName()},
{"guid", identifier.guid.RawString()},
{"port", std::to_string(identifier.port)},
{"pad", std::to_string(identifier.pad)},
};
}
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
std::vector<Common::ParamPackage> devices{};
auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
if (!device) {
return;
}
if (!device->IsConnected()) {
return;
}
auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
device->GetDevicePort() + 1);
param.Set("display", std::move(name));
devices.emplace_back(param);
};
for (const auto& controller : left_joycons) {
add_entry(controller);
}
for (const auto& controller : right_joycons) {
add_entry(controller);
}
// List dual joycon pairs
for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
if (!left_joycons[i] || !right_joycons[i]) {
continue;
}
if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
continue;
}
auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
const auto type = Joycon::ControllerType::Dual;
std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
main_param.Set("display", std::move(name));
main_param.Set("guid2", second_param.Get("guid", ""));
main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
devices.emplace_back(main_param);
}
return devices;
}
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
18>
switch_to_joycon_button = {
std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
{Settings::NativeButton::B, Joycon::PadButton::B, true},
{Settings::NativeButton::X, Joycon::PadButton::X, true},
{Settings::NativeButton::Y, Joycon::PadButton::Y, true},
{Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
{Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
{Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
{Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
{Settings::NativeButton::L, Joycon::PadButton::L, false},
{Settings::NativeButton::R, Joycon::PadButton::R, true},
{Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
{Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
{Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
{Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
{Settings::NativeButton::Home, Joycon::PadButton::Home, true},
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
{Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
{Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
};
if (!params.Has("port")) {
return {};
}
ButtonMapping mapping{};
for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
if (pad == Joycon::ControllerType::Dual) {
pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
}
Common::ParamPackage button_params = GetParamPackage(port, pad);
button_params.Set("button", static_cast<int>(joycon_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
// Map SL and SR buttons for left joycons
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
Common::ParamPackage sl_button_params = button_params;
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
}
// Map SL and SR buttons for right joycons
if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
Common::ParamPackage sl_button_params = button_params;
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
}
return mapping;
}
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
auto pad_right = pad_left;
if (pad_left == Joycon::ControllerType::Dual) {
pad_left = Joycon::ControllerType::Left;
pad_right = Joycon::ControllerType::Right;
}
AnalogMapping mapping = {};
Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
return mapping;
}
MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
if (!params.Has("port")) {
return {};
}
const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
auto pad_right = pad_left;
if (pad_left == Joycon::ControllerType::Dual) {
pad_left = Joycon::ControllerType::Left;
pad_right = Joycon::ControllerType::Right;
}
MotionMapping mapping = {};
Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
left_motion_params.Set("motion", 0);
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
right_Motion_params.Set("motion", 1);
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
return mapping;
}
Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
switch (button) {
case Joycon::PadButton::Left:
return Common::Input::ButtonNames::ButtonLeft;
case Joycon::PadButton::Right:
return Common::Input::ButtonNames::ButtonRight;
case Joycon::PadButton::Down:
return Common::Input::ButtonNames::ButtonDown;
case Joycon::PadButton::Up:
return Common::Input::ButtonNames::ButtonUp;
case Joycon::PadButton::LeftSL:
case Joycon::PadButton::RightSL:
return Common::Input::ButtonNames::TriggerSL;
case Joycon::PadButton::LeftSR:
case Joycon::PadButton::RightSR:
return Common::Input::ButtonNames::TriggerSR;
case Joycon::PadButton::L:
return Common::Input::ButtonNames::TriggerL;
case Joycon::PadButton::R:
return Common::Input::ButtonNames::TriggerR;
case Joycon::PadButton::ZL:
return Common::Input::ButtonNames::TriggerZL;
case Joycon::PadButton::ZR:
return Common::Input::ButtonNames::TriggerZR;
case Joycon::PadButton::A:
return Common::Input::ButtonNames::ButtonA;
case Joycon::PadButton::B:
return Common::Input::ButtonNames::ButtonB;
case Joycon::PadButton::X:
return Common::Input::ButtonNames::ButtonX;
case Joycon::PadButton::Y:
return Common::Input::ButtonNames::ButtonY;
case Joycon::PadButton::Plus:
return Common::Input::ButtonNames::ButtonPlus;
case Joycon::PadButton::Minus:
return Common::Input::ButtonNames::ButtonMinus;
case Joycon::PadButton::Home:
return Common::Input::ButtonNames::ButtonHome;
case Joycon::PadButton::Capture:
return Common::Input::ButtonNames::ButtonCapture;
case Joycon::PadButton::StickL:
return Common::Input::ButtonNames::ButtonStickL;
case Joycon::PadButton::StickR:
return Common::Input::ButtonNames::ButtonStickR;
default:
return Common::Input::ButtonNames::Undefined;
}
}
Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
if (params.Has("button")) {
return GetUIButtonName(params);
}
if (params.Has("axis")) {
return Common::Input::ButtonNames::Value;
}
if (params.Has("motion")) {
return Common::Input::ButtonNames::Engine;
}
return Common::Input::ButtonNames::Invalid;
}
std::string Joycons::JoyconName(Joycon::ControllerType type) const {
switch (type) {
case Joycon::ControllerType::Left:
return "Left Joycon";
case Joycon::ControllerType::Right:
return "Right Joycon";
case Joycon::ControllerType::Pro:
return "Pro Controller";
case Joycon::ControllerType::Grip:
return "Grip Controller";
case Joycon::ControllerType::Dual:
return "Dual Joycon";
default:
return "Unknown Joycon";
}
}
} // namespace InputCommon

View File

@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <thread>
#include <SDL_hidapi.h>
#include "input_common/input_engine.h"
namespace InputCommon::Joycon {
using SerialNumber = std::array<u8, 15>;
struct Battery;
struct Color;
struct MotionData;
enum class ControllerType;
enum class DriverResult;
enum class IrsResolution;
class JoyconDriver;
} // namespace InputCommon::Joycon
namespace InputCommon {
class Joycons final : public InputCommon::InputEngine {
public:
explicit Joycons(const std::string& input_engine_);
~Joycons();
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
const Common::Input::LedStatus& led_status) override;
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
Common::Input::CameraFormat camera_format) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
const std::vector<u8>& data) override;
Common::Input::DriverResult SetPollingMode(
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
private:
static constexpr std::size_t MaxSupportedControllers = 8;
/// For shutting down, clear all data, join all threads, release usb devices
void Reset();
/// Registers controllers, clears all data and starts the scan thread
void Setup();
/// Actively searchs for new devices
void ScanThread(std::stop_token stop_token);
/// Returns true if device is valid and not registered
bool IsDeviceNew(SDL_hid_device_info* device_info) const;
/// Tries to connect to the new device
void RegisterNewDevice(SDL_hid_device_info* device_info);
/// Returns the next free handle
std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
const Joycon::MotionData& value);
void OnRingConUpdate(f32 ring_data);
void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
Joycon::IrsResolution format);
/// Returns a JoyconHandle corresponding to a PadIdentifier
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
/// Returns a PadIdentifier corresponding to the port number and joycon type
PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
/// Returns a ParamPackage corresponding to the port number and joycon type
Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
std::string JoyconName(std::size_t port) const;
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
/// Returns the name of the device in text format
std::string JoyconName(Joycon::ControllerType type) const;
std::jthread scan_thread;
// Joycon types are split by type to ease supporting dualjoycon configurations
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
};
} // namespace InputCommon

View File

@@ -40,25 +40,26 @@ public:
}
void EnableMotion() {
if (sdl_controller) {
SDL_GameController* controller = sdl_controller.get();
has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
if (has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
}
if (has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
if (!sdl_controller) {
return;
}
SDL_GameController* controller = sdl_controller.get();
if (HasMotion()) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE);
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE);
}
has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
if (has_accel) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
}
if (has_gyro) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
}
bool HasGyro() const {
return has_gyro;
}
bool HasAccel() const {
return has_accel;
bool HasMotion() const {
return has_gyro || has_accel;
}
bool UpdateMotion(SDL_ControllerSensorEvent event) {
@@ -85,6 +86,20 @@ public:
if (time_difference == 0) {
return false;
}
// Motion data is invalid
if (motion.accel_x == 0 && motion.gyro_x == 0 && motion.accel_y == 0 &&
motion.gyro_y == 0 && motion.accel_z == 0 && motion.gyro_z == 0) {
if (motion_error_count++ < 200) {
return false;
}
// Try restarting the sensor
motion_error_count = 0;
EnableMotion();
return false;
}
motion_error_count = 0;
motion.delta_timestamp = time_difference * 1000;
return true;
}
@@ -250,6 +265,7 @@ private:
mutable std::mutex mutex;
u64 last_motion_update{};
std::size_t motion_error_count{};
bool has_gyro{false};
bool has_accel{false};
bool has_vibration{false};
@@ -318,6 +334,15 @@ void SDLDriver::InitJoystick(int joystick_index) {
const auto guid = GetGUID(sdl_joystick);
if (Settings::values.enable_joycon_driver) {
if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
(guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
SDL_JoystickClose(sdl_joystick);
return;
}
}
std::scoped_lock lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
@@ -440,9 +465,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
// not a generic one
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
// Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
if (Settings::values.enable_joycon_driver) {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
} else {
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
}
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
// driver on Linux.
@@ -532,7 +561,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
return devices;
}
Common::Input::VibrationError SDLDriver::SetVibration(
Common::Input::DriverResult SDLDriver::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto joystick =
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@@ -566,7 +595,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
.vibration = new_vibration,
});
return Common::Input::VibrationError::None;
return Common::Input::DriverResult::Success;
}
bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
@@ -942,18 +971,18 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p
MotionMapping mapping = {};
joystick->EnableMotion();
if (joystick->HasGyro() || joystick->HasAccel()) {
if (joystick->HasMotion()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
}
if (params.Has("guid2")) {
joystick2->EnableMotion();
if (joystick2->HasGyro() || joystick2->HasAccel()) {
if (joystick2->HasMotion()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
}
} else {
if (joystick->HasGyro() || joystick->HasAccel()) {
if (joystick->HasMotion()) {
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
}

View File

@@ -63,7 +63,7 @@ public:
bool IsStickInverted(const Common::ParamPackage& params) override;
Common::Input::VibrationError SetVibration(
Common::Input::DriverResult SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
bool IsVibrationEnabled(const PadIdentifier& identifier) override;

View File

@@ -156,10 +156,12 @@ void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
};
}
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
std::tuple<TasState, size_t, std::array<size_t, PLAYER_NUMBER>> Tas::GetStatus() const {
TasState state;
std::array<size_t, PLAYER_NUMBER> lengths{0};
if (is_recording) {
return {TasState::Recording, 0, record_commands.size()};
lengths[0] = record_commands.size();
return {TasState::Recording, record_commands.size(), lengths};
}
if (is_running) {
@@ -168,7 +170,11 @@ std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
state = TasState::Stopped;
}
return {state, current_command, script_length};
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
lengths[i] = commands[i].size();
}
return {state, current_command, lengths};
}
void Tas::UpdateThread() {

View File

@@ -124,7 +124,7 @@ public:
* Current playback progress ;
* Total length of script file currently loaded or being recorded
*/
std::tuple<TasState, size_t, size_t> GetStatus() const;
std::tuple<TasState, size_t, std::array<size_t, PLAYER_NUMBER>> GetStatus() const;
private:
enum class TasAxis : u8;

View File

@@ -22,22 +22,23 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
VirtualAmiibo::~VirtualAmiibo() = default;
Common::Input::PollingError VirtualAmiibo::SetPollingMode(
Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier_,
const Common::Input::PollingMode polling_mode_) {
polling_mode = polling_mode_;
if (polling_mode == Common::Input::PollingMode::NFC) {
switch (polling_mode) {
case Common::Input::PollingMode::NFC:
if (state == State::Initialized) {
state = State::WaitingForAmiibo;
}
} else {
return Common::Input::DriverResult::Success;
default:
if (state == State::AmiiboIsOpen) {
CloseAmiibo();
}
return Common::Input::DriverResult::NotSupported;
}
return Common::Input::PollingError::None;
}
Common::Input::NfcState VirtualAmiibo::SupportsNfc(

View File

@@ -36,7 +36,7 @@ public:
~VirtualAmiibo() override;
// Sets polling mode to a controller
Common::Input::PollingError SetPollingMode(
Common::Input::DriverResult SetPollingMode(
const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;

View File

@@ -0,0 +1,572 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/swap.h"
#include "common/thread.h"
#include "input_common/helpers/joycon_driver.h"
#include "input_common/helpers/joycon_protocol/calibration.h"
#include "input_common/helpers/joycon_protocol/generic_functions.h"
#include "input_common/helpers/joycon_protocol/irs.h"
#include "input_common/helpers/joycon_protocol/nfc.h"
#include "input_common/helpers/joycon_protocol/poller.h"
#include "input_common/helpers/joycon_protocol/ringcon.h"
#include "input_common/helpers/joycon_protocol/rumble.h"
namespace InputCommon::Joycon {
JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
hidapi_handle = std::make_shared<JoyconHandle>();
}
JoyconDriver::~JoyconDriver() {
Stop();
}
void JoyconDriver::Stop() {
is_connected = false;
input_thread = {};
}
DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
std::scoped_lock lock{mutex};
handle_device_type = ControllerType::None;
GetDeviceType(device_info, handle_device_type);
if (handle_device_type == ControllerType::None) {
return DriverResult::UnsupportedControllerType;
}
hidapi_handle->handle =
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
std::memcpy(&handle_serial_number, device_info->serial_number, 15);
if (!hidapi_handle->handle) {
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
device_info->vendor_id, device_info->product_id);
return DriverResult::HandleInUse;
}
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
return DriverResult::Success;
}
DriverResult JoyconDriver::InitializeDevice() {
if (!hidapi_handle->handle) {
return DriverResult::InvalidHandle;
}
std::scoped_lock lock{mutex};
disable_input_thread = true;
// Reset Counters
error_counter = 0;
hidapi_handle->packet_counter = 0;
// Reset external device status
starlink_connected = false;
ring_connected = false;
amiibo_detected = false;
// Set HW default configuration
vibration_enabled = true;
motion_enabled = true;
hidbus_enabled = false;
nfc_enabled = false;
passive_enabled = false;
irs_enabled = false;
gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
gyro_performance = Joycon::GyroPerformance::HZ833;
accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
// Initialize HW Protocols
calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
// Get fixed joycon info
generic_protocol->GetVersionNumber(version);
generic_protocol->GetColor(color);
if (handle_device_type == ControllerType::Pro) {
// Some 3rd party controllers aren't pro controllers
generic_protocol->GetControllerType(device_type);
} else {
device_type = handle_device_type;
}
generic_protocol->GetSerialNumber(serial_number);
supported_features = GetSupportedFeatures();
// Get Calibration data
calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
calibration_protocol->GetImuCalibration(motion_calibration);
// Set led status
generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
// Apply HW configuration
SetPollingMode();
// Initialize joycon poller
joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
right_stick_calibration, motion_calibration);
// Start pooling for data
is_connected = true;
if (!input_thread_running) {
input_thread =
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
}
disable_input_thread = false;
return DriverResult::Success;
}
void JoyconDriver::InputThread(std::stop_token stop_token) {
LOG_INFO(Input, "Joycon Adapter input thread started");
Common::SetCurrentThreadName("JoyconInput");
input_thread_running = true;
// Max update rate is 5ms, ensure we are always able to read a bit faster
constexpr int ThreadDelay = 2;
std::vector<u8> buffer(MaxBufferSize);
while (!stop_token.stop_requested()) {
int status = 0;
if (!IsInputThreadValid()) {
input_thread.request_stop();
continue;
}
// By disabling the input thread we can ensure custom commands will succeed as no package is
// skipped
if (!disable_input_thread) {
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
ThreadDelay);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
}
if (IsPayloadCorrect(status, buffer)) {
OnNewData(buffer);
}
std::this_thread::yield();
}
is_connected = false;
input_thread_running = false;
LOG_INFO(Input, "Joycon Adapter input thread stopped");
}
void JoyconDriver::OnNewData(std::span<u8> buffer) {
const auto report_mode = static_cast<InputReport>(buffer[0]);
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
// experience
switch (report_mode) {
case InputReport::STANDARD_FULL_60HZ:
case InputReport::NFC_IR_MODE_60HZ:
case InputReport::SIMPLE_HID_MODE: {
const auto now = std::chrono::steady_clock::now();
const auto new_delta_time = static_cast<u64>(
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
last_update = now;
joycon_poller->UpdateColor(color);
break;
}
default:
break;
}
const MotionStatus motion_status{
.is_enabled = motion_enabled,
.delta_time = delta_time,
.gyro_sensitivity = gyro_sensitivity,
.accelerometer_sensitivity = accelerometer_sensitivity,
};
// TODO: Remove this when calibration is properly loaded and not calculated
if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive));
calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
}
const RingStatus ring_status{
.is_enabled = ring_connected,
.default_value = ring_calibration.default_value,
.max_value = ring_calibration.max_value,
.min_value = ring_calibration.min_value,
};
if (irs_protocol->IsEnabled()) {
irs_protocol->RequestImage(buffer);
joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
}
if (nfc_protocol->IsEnabled()) {
if (amiibo_detected) {
if (!nfc_protocol->HasAmiibo()) {
joycon_poller->UpdateAmiibo({});
amiibo_detected = false;
return;
}
}
if (!amiibo_detected) {
std::vector<u8> data(0x21C);
const auto result = nfc_protocol->ScanAmiibo(data);
if (result == DriverResult::Success) {
joycon_poller->UpdateAmiibo(data);
amiibo_detected = true;
}
}
}
switch (report_mode) {
case InputReport::STANDARD_FULL_60HZ:
joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
break;
case InputReport::NFC_IR_MODE_60HZ:
joycon_poller->ReadNfcIRMode(buffer, motion_status);
break;
case InputReport::SIMPLE_HID_MODE:
joycon_poller->ReadPassiveMode(buffer);
break;
case InputReport::SUBCMD_REPLY:
LOG_DEBUG(Input, "Unhandled command reply");
break;
default:
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
break;
}
}
DriverResult JoyconDriver::SetPollingMode() {
disable_input_thread = true;
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
if (motion_enabled && supported_features.motion) {
generic_protocol->EnableImu(true);
generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
accelerometer_sensitivity, accelerometer_performance);
} else {
generic_protocol->EnableImu(false);
}
if (irs_protocol->IsEnabled()) {
irs_protocol->DisableIrs();
}
if (nfc_protocol->IsEnabled()) {
amiibo_detected = false;
nfc_protocol->DisableNfc();
}
if (ring_protocol->IsEnabled()) {
ring_connected = false;
ring_protocol->DisableRingCon();
}
if (irs_enabled && supported_features.irs) {
auto result = irs_protocol->EnableIrs();
if (result == DriverResult::Success) {
disable_input_thread = false;
return result;
}
irs_protocol->DisableIrs();
LOG_ERROR(Input, "Error enabling IRS");
}
if (nfc_enabled && supported_features.nfc) {
auto result = nfc_protocol->EnableNfc();
if (result == DriverResult::Success) {
result = nfc_protocol->StartNFCPollingMode();
}
if (result == DriverResult::Success) {
disable_input_thread = false;
return result;
}
nfc_protocol->DisableNfc();
LOG_ERROR(Input, "Error enabling NFC");
}
if (hidbus_enabled && supported_features.hidbus) {
auto result = ring_protocol->EnableRingCon();
if (result == DriverResult::Success) {
result = ring_protocol->StartRingconPolling();
}
if (result == DriverResult::Success) {
ring_connected = true;
disable_input_thread = false;
return result;
}
ring_connected = false;
ring_protocol->DisableRingCon();
LOG_ERROR(Input, "Error enabling Ringcon");
}
if (passive_enabled && supported_features.passive) {
const auto result = generic_protocol->EnablePassiveMode();
if (result == DriverResult::Success) {
disable_input_thread = false;
return result;
}
LOG_ERROR(Input, "Error enabling passive mode");
}
// Default Mode
const auto result = generic_protocol->EnableActiveMode();
if (result != DriverResult::Success) {
LOG_ERROR(Input, "Error enabling active mode");
}
disable_input_thread = false;
return result;
}
JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
SupportedFeatures features{
.passive = true,
.motion = true,
.vibration = true,
};
if (device_type == ControllerType::Right) {
features.nfc = true;
features.irs = true;
features.hidbus = true;
}
if (device_type == ControllerType::Pro) {
features.nfc = true;
}
return features;
}
bool JoyconDriver::IsInputThreadValid() const {
if (!is_connected.load()) {
return false;
}
if (hidapi_handle->handle == nullptr) {
return false;
}
// Controller is not responding. Terminate connection
if (error_counter > MaxErrorCount) {
return false;
}
return true;
}
bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
if (status <= -1) {
error_counter++;
return false;
}
// There's no new data
if (status == 0) {
return false;
}
// No reply ever starts with zero
if (buffer[0] == 0x00) {
error_counter++;
return false;
}
error_counter = 0;
return true;
}
DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
std::scoped_lock lock{mutex};
if (disable_input_thread) {
return DriverResult::HandleInUse;
}
return rumble_protocol->SendVibration(vibration);
}
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
std::scoped_lock lock{mutex};
if (disable_input_thread) {
return DriverResult::HandleInUse;
}
return generic_protocol->SetLedPattern(led_pattern);
}
DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
std::scoped_lock lock{mutex};
if (disable_input_thread) {
return DriverResult::HandleInUse;
}
disable_input_thread = true;
const auto result = irs_protocol->SetIrsConfig(mode_, format_);
disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::SetPasiveMode() {
std::scoped_lock lock{mutex};
motion_enabled = false;
hidbus_enabled = false;
nfc_enabled = false;
passive_enabled = true;
irs_enabled = false;
return SetPollingMode();
}
DriverResult JoyconDriver::SetActiveMode() {
if (is_ring_disabled_by_irs) {
is_ring_disabled_by_irs = false;
SetActiveMode();
return SetRingConMode();
}
std::scoped_lock lock{mutex};
motion_enabled = true;
hidbus_enabled = false;
nfc_enabled = false;
passive_enabled = false;
irs_enabled = false;
return SetPollingMode();
}
DriverResult JoyconDriver::SetIrMode() {
std::scoped_lock lock{mutex};
if (!supported_features.irs) {
return DriverResult::NotSupported;
}
if (ring_connected) {
is_ring_disabled_by_irs = true;
}
motion_enabled = false;
hidbus_enabled = false;
nfc_enabled = false;
passive_enabled = false;
irs_enabled = true;
return SetPollingMode();
}
DriverResult JoyconDriver::SetNfcMode() {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
motion_enabled = true;
hidbus_enabled = false;
nfc_enabled = true;
passive_enabled = false;
irs_enabled = false;
return SetPollingMode();
}
DriverResult JoyconDriver::SetRingConMode() {
std::scoped_lock lock{mutex};
if (!supported_features.hidbus) {
return DriverResult::NotSupported;
}
motion_enabled = true;
hidbus_enabled = true;
nfc_enabled = false;
passive_enabled = false;
irs_enabled = false;
const auto result = SetPollingMode();
if (!ring_connected) {
return DriverResult::NoDeviceDetected;
}
return result;
}
bool JoyconDriver::IsConnected() const {
std::scoped_lock lock{mutex};
return is_connected.load();
}
bool JoyconDriver::IsVibrationEnabled() const {
std::scoped_lock lock{mutex};
return vibration_enabled;
}
FirmwareVersion JoyconDriver::GetDeviceVersion() const {
std::scoped_lock lock{mutex};
return version;
}
Color JoyconDriver::GetDeviceColor() const {
std::scoped_lock lock{mutex};
return color;
}
std::size_t JoyconDriver::GetDevicePort() const {
std::scoped_lock lock{mutex};
return port;
}
ControllerType JoyconDriver::GetDeviceType() const {
std::scoped_lock lock{mutex};
return device_type;
}
ControllerType JoyconDriver::GetHandleDeviceType() const {
std::scoped_lock lock{mutex};
return handle_device_type;
}
SerialNumber JoyconDriver::GetSerialNumber() const {
std::scoped_lock lock{mutex};
return serial_number;
}
SerialNumber JoyconDriver::GetHandleSerialNumber() const {
std::scoped_lock lock{mutex};
return handle_serial_number;
}
void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
joycon_poller->SetCallbacks(callbacks);
}
DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
ControllerType& controller_type) {
static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
{0x2007, ControllerType::Right},
};
constexpr u16 nintendo_vendor_id = 0x057e;
controller_type = ControllerType::None;
if (device_info->vendor_id != nintendo_vendor_id) {
return DriverResult::UnsupportedControllerType;
}
for (const auto& [product_id, type] : supported_devices) {
if (device_info->product_id == static_cast<u16>(product_id)) {
controller_type = type;
return Joycon::DriverResult::Success;
}
}
return Joycon::DriverResult::UnsupportedControllerType;
}
DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
SerialNumber& serial_number) {
if (device_info->serial_number == nullptr) {
return DriverResult::Unknown;
}
std::memcpy(&serial_number, device_info->serial_number, 15);
return Joycon::DriverResult::Success;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <span>
#include <thread>
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
class CalibrationProtocol;
class GenericProtocol;
class IrsProtocol;
class NfcProtocol;
class JoyconPoller;
class RingConProtocol;
class RumbleProtocol;
class JoyconDriver final {
public:
explicit JoyconDriver(std::size_t port_);
~JoyconDriver();
DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
DriverResult InitializeDevice();
void Stop();
bool IsConnected() const;
bool IsVibrationEnabled() const;
FirmwareVersion GetDeviceVersion() const;
Color GetDeviceColor() const;
std::size_t GetDevicePort() const;
ControllerType GetDeviceType() const;
ControllerType GetHandleDeviceType() const;
SerialNumber GetSerialNumber() const;
SerialNumber GetHandleSerialNumber() const;
DriverResult SetVibration(const VibrationValue& vibration);
DriverResult SetLedConfig(u8 led_pattern);
DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
DriverResult SetPasiveMode();
DriverResult SetActiveMode();
DriverResult SetIrMode();
DriverResult SetNfcMode();
DriverResult SetRingConMode();
void SetCallbacks(const JoyconCallbacks& callbacks);
// Returns device type from hidapi handle
static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
ControllerType& controller_type);
// Returns serial number from hidapi handle
static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
SerialNumber& serial_number);
private:
struct SupportedFeatures {
bool passive{};
bool hidbus{};
bool irs{};
bool motion{};
bool nfc{};
bool vibration{};
};
/// Main thread, actively request new data from the handle
void InputThread(std::stop_token stop_token);
/// Called everytime a valid package arrives
void OnNewData(std::span<u8> buffer);
/// Updates device configuration to enable or disable features
DriverResult SetPollingMode();
/// Returns true if input thread is valid and doesn't need to be stopped
bool IsInputThreadValid() const;
/// Returns true if the data should be interpreted. Otherwise the error counter is incremented
bool IsPayloadCorrect(int status, std::span<const u8> buffer);
/// Returns a list of supported features that can be enabled on this device
SupportedFeatures GetSupportedFeatures();
// Protocol Features
std::unique_ptr<CalibrationProtocol> calibration_protocol;
std::unique_ptr<GenericProtocol> generic_protocol;
std::unique_ptr<IrsProtocol> irs_protocol;
std::unique_ptr<NfcProtocol> nfc_protocol;
std::unique_ptr<JoyconPoller> joycon_poller;
std::unique_ptr<RingConProtocol> ring_protocol;
std::unique_ptr<RumbleProtocol> rumble_protocol;
// Connection status
std::atomic<bool> is_connected{};
u64 delta_time;
std::size_t error_counter{};
std::shared_ptr<JoyconHandle> hidapi_handle;
std::chrono::time_point<std::chrono::steady_clock> last_update;
// External device status
bool starlink_connected{};
bool ring_connected{};
bool amiibo_detected{};
bool is_ring_disabled_by_irs{};
// Harware configuration
u8 leds{};
ReportMode mode{};
bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
bool hidbus_enabled{}; // External device support
bool irs_enabled{}; // Infrared camera input
bool motion_enabled{}; // Enables motion input
bool nfc_enabled{}; // Enables Amiibo detection
bool vibration_enabled{}; // Allows vibrations
// Calibration data
GyroSensitivity gyro_sensitivity{};
GyroPerformance gyro_performance{};
AccelerometerSensitivity accelerometer_sensitivity{};
AccelerometerPerformance accelerometer_performance{};
JoyStickCalibration left_stick_calibration{};
JoyStickCalibration right_stick_calibration{};
MotionCalibration motion_calibration{};
RingCalibration ring_calibration{};
// Fixed joycon info
FirmwareVersion version{};
Color color{};
std::size_t port{};
ControllerType device_type{}; // Device type reported by controller
ControllerType handle_device_type{}; // Device type reported by hidapi
SerialNumber serial_number{}; // Serial number reported by controller
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
SupportedFeatures supported_features{};
// Thread related
mutable std::mutex mutex;
std::jthread input_thread;
bool input_thread_running{};
bool disable_input_thread{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,184 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "input_common/helpers/joycon_protocol/calibration.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
ScopedSetBlocking sb(this);
std::vector<u8> buffer;
DriverResult result{DriverResult::Success};
calibration = {};
result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
if (result == DriverResult::Success) {
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
if (has_user_calibration) {
result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
} else {
result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
}
}
if (result == DriverResult::Success) {
calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
}
// Nintendo fix for drifting stick
// result = ReadSPI(0x60, 0x86 ,buffer, 16);
// calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
// Set a valid default calibration if data is missing
ValidateCalibration(calibration);
return result;
}
DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
ScopedSetBlocking sb(this);
std::vector<u8> buffer;
DriverResult result{DriverResult::Success};
calibration = {};
result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
if (result == DriverResult::Success) {
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
if (has_user_calibration) {
result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
} else {
result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
}
}
if (result == DriverResult::Success) {
calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
}
// Nintendo fix for drifting stick
// buffer = ReadSPI(0x60, 0x98 , 16);
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
// Set a valid default calibration if data is missing
ValidateCalibration(calibration);
return result;
}
DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
ScopedSetBlocking sb(this);
std::vector<u8> buffer;
DriverResult result{DriverResult::Success};
calibration = {};
result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
if (result == DriverResult::Success) {
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
if (has_user_calibration) {
result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
} else {
result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
}
}
if (result == DriverResult::Success) {
IMUCalibration device_calibration{};
memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
}
ValidateCalibration(calibration);
return result;
}
DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
s16 current_value) {
// TODO: Get default calibration form ring itself
if (ring_data_max == 0 && ring_data_min == 0) {
ring_data_max = current_value + 800;
ring_data_min = current_value - 800;
ring_data_default = current_value;
}
ring_data_max = std::max(ring_data_max, current_value);
ring_data_min = std::min(ring_data_min, current_value);
calibration = {
.default_value = ring_data_default,
.max_value = ring_data_max,
.min_value = ring_data_min,
};
return DriverResult::Success;
}
void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
constexpr u16 DefaultStickCenter{2048};
constexpr u16 DefaultStickRange{1740};
if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
calibration.x.center = DefaultStickCenter;
}
if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
calibration.x.max = DefaultStickRange;
}
if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
calibration.x.min = DefaultStickRange;
}
if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
calibration.y.center = DefaultStickCenter;
}
if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
calibration.y.max = DefaultStickRange;
}
if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
calibration.y.min = DefaultStickRange;
}
}
void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
for (auto& sensor : calibration.accelerometer) {
if (sensor.scale == 0) {
sensor.scale = 0x4000;
}
}
for (auto& sensor : calibration.gyro) {
if (sensor.scale == 0) {
sensor.scale = 0x3be7;
}
}
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <vector>
#include "input_common/helpers/joycon_protocol/common_protocol.h"
namespace InputCommon::Joycon {
enum class DriverResult;
struct JoyStickCalibration;
struct IMUCalibration;
struct JoyconHandle;
} // namespace InputCommon::Joycon
namespace InputCommon::Joycon {
/// Driver functions related to retrieving calibration data from the device
class CalibrationProtocol final : private JoyconCommonProtocol {
public:
explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
/**
* Sends a request to obtain the left stick calibration from memory
* @param is_factory_calibration if true factory values will be returned
* @returns JoyStickCalibration of the left joystick
*/
DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
/**
* Sends a request to obtain the right stick calibration from memory
* @param is_factory_calibration if true factory values will be returned
* @returns JoyStickCalibration of the right joystick
*/
DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
/**
* Sends a request to obtain the motion calibration from memory
* @returns ImuCalibration of the motion sensor
*/
DriverResult GetImuCalibration(MotionCalibration& calibration);
/**
* Calculates on run time the proper calibration of the ring controller
* @returns RingCalibration of the ring sensor
*/
DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
private:
void ValidateCalibration(JoyStickCalibration& calibration);
void ValidateCalibration(MotionCalibration& calibration);
s16 ring_data_max = 0;
s16 ring_data_default = 0;
s16 ring_data_min = 0;
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,299 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/common_protocol.h"
namespace InputCommon::Joycon {
JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
: hidapi_handle{std::move(hidapi_handle_)} {}
u8 JoyconCommonProtocol::GetCounter() {
hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
return hidapi_handle->packet_counter;
}
void JoyconCommonProtocol::SetBlocking() {
SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
}
void JoyconCommonProtocol::SetNonBlocking() {
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
}
DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
std::vector<u8> buffer;
const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
controller_type = ControllerType::None;
if (result == DriverResult::Success) {
controller_type = static_cast<ControllerType>(buffer[0]);
// Fallback to 3rd party pro controllers
if (controller_type == ControllerType::None) {
controller_type = ControllerType::Pro;
}
}
return result;
}
DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
ControllerType controller_type{ControllerType::None};
const auto result = GetDeviceType(controller_type);
if (result != DriverResult::Success || controller_type == ControllerType::None) {
return DriverResult::UnsupportedControllerType;
}
hidapi_handle->handle =
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
if (!hidapi_handle->handle) {
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
device_info->vendor_id, device_info->product_id);
return DriverResult::HandleInUse;
}
SetNonBlocking();
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
}
DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
if (result == -1) {
return DriverResult::ErrorWritingData;
}
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
constexpr int timeout_mili = 66;
constexpr int MaxTries = 15;
int tries = 0;
output.resize(MaxSubCommandResponseSize);
do {
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
MaxSubCommandResponseSize, timeout_mili);
if (result < 1) {
LOG_ERROR(Input, "No response from joycon");
}
if (tries++ > MaxTries) {
return DriverResult::Timeout;
}
} while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
return DriverResult::WrongReply;
}
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
std::vector<u8>& output) {
std::vector<u8> local_buffer(MaxResponseSize);
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
local_buffer[1] = GetCounter();
local_buffer[10] = static_cast<u8>(sc);
for (std::size_t i = 0; i < buffer.size(); ++i) {
local_buffer[11 + i] = buffer[i];
}
auto result = SendData(local_buffer);
if (result != DriverResult::Success) {
return result;
}
result = GetSubCommandResponse(sc, output);
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
std::vector<u8> output;
return SendSubCommand(sc, buffer, output);
}
DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
std::vector<u8> local_buffer(MaxResponseSize);
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
local_buffer[1] = GetCounter();
local_buffer[10] = static_cast<u8>(sc);
for (std::size_t i = 0; i < buffer.size(); ++i) {
local_buffer[11 + i] = buffer[i];
}
return SendData(local_buffer);
}
DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
std::vector<u8> local_buffer(MaxResponseSize);
local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
local_buffer[1] = GetCounter();
memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
return SendData(local_buffer);
}
DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
constexpr std::size_t MaxTries = 10;
std::size_t tries = 0;
std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
std::vector<u8> local_buffer(size + 20);
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
do {
const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
if (result != DriverResult::Success) {
return result;
}
if (tries++ > MaxTries) {
return DriverResult::Timeout;
}
} while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
// Remove header from output
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
if (result != DriverResult::Success) {
LOG_ERROR(Input, "SendMCUData failed with error {}", result);
}
return result;
}
DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
LOG_DEBUG(Input, "ConfigureMCU");
std::array<u8, sizeof(MCUConfig)> config_buffer;
memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
if (result != DriverResult::Success) {
LOG_ERROR(Input, "Set MCU config failed with error {}", result);
}
return result;
}
DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
std::vector<u8>& output) {
const int report_mode = static_cast<u8>(report_mode_);
constexpr int TimeoutMili = 200;
constexpr int MaxTries = 9;
int tries = 0;
output.resize(0x170);
do {
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
if (result < 1) {
LOG_ERROR(Input, "No response from joycon attempt {}", tries);
}
if (tries++ > MaxTries) {
return DriverResult::Timeout;
}
} while (output[0] != report_mode || output[49] == 0xFF);
if (output[0] != report_mode || output[49] == 0xFF) {
return DriverResult::WrongReply;
}
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
std::span<const u8> buffer,
std::vector<u8>& output) {
std::vector<u8> local_buffer(MaxResponseSize);
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
local_buffer[1] = GetCounter();
local_buffer[9] = static_cast<u8>(sc);
for (std::size_t i = 0; i < buffer.size(); ++i) {
local_buffer[10 + i] = buffer[i];
}
auto result = SendData(local_buffer);
if (result != DriverResult::Success) {
return result;
}
result = GetMCUDataResponse(report_mode, output);
return DriverResult::Success;
}
DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
std::vector<u8> output;
constexpr std::size_t MaxTries{8};
std::size_t tries{};
do {
const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ > MaxTries) {
return DriverResult::WrongReply;
}
} while (output[49] != 1 || output[56] != static_cast<u8>(mode));
return DriverResult::Success;
}
// crc-8-ccitt / polynomial 0x07 look up table
constexpr std::array<u8, 256> mcu_crc8_table = {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
u8 crc8 = 0x0;
for (int i = 0; i < size; ++i) {
crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
}
return crc8;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <memory>
#include <span>
#include <vector>
#include "common/common_types.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
/// Joycon driver functions that handle low level communication
class JoyconCommonProtocol {
public:
explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
/**
* Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
* data to read before returning.
*/
void SetBlocking();
/**
* Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
* immediately with a value of 0 if there is no data to be read
*/
void SetNonBlocking();
/**
* Sends a request to obtain the joycon type from device
* @returns controller type of the joycon
*/
DriverResult GetDeviceType(ControllerType& controller_type);
/**
* Verifies and sets the joycon_handle if device is valid
* @param device info from the driver
* @returns success if the device is valid
*/
DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
/**
* Sends a request to set the polling mode of the joycon
* @param report_mode polling mode to be set
*/
DriverResult SetReportMode(Joycon::ReportMode report_mode);
/**
* Sends data to the joycon device
* @param buffer data to be send
*/
DriverResult SendData(std::span<const u8> buffer);
/**
* Waits for incoming data of the joycon device that matchs the subcommand
* @param sub_command type of data to be returned
* @returns a buffer containing the responce
*/
DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
/**
* Sends a sub command to the device and waits for it's reply
* @param sc sub command to be send
* @param buffer data to be send
* @returns output buffer containing the responce
*/
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
/**
* Sends a sub command to the device and waits for it's reply and ignores the output
* @param sc sub command to be send
* @param buffer data to be send
*/
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
/**
* Sends a mcu command to the device
* @param sc sub command to be send
* @param buffer data to be send
*/
DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
/**
* Sends vibration data to the joycon
* @param buffer data to be send
*/
DriverResult SendVibrationReport(std::span<const u8> buffer);
/**
* Reads the SPI memory stored on the joycon
* @param Initial address location
* @param size in bytes to be read
* @returns output buffer containing the responce
*/
DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
/**
* Enables MCU chip on the joycon
* @param enable if true the chip will be enabled
*/
DriverResult EnableMCU(bool enable);
/**
* Configures the MCU to the correspoinding mode
* @param MCUConfig configuration
*/
DriverResult ConfigureMCU(const MCUConfig& config);
/**
* Waits until there's MCU data available. On timeout returns error
* @param report mode of the expected reply
* @returns a buffer containing the responce
*/
DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
/**
* Sends data to the MCU chip and waits for it's reply
* @param report mode of the expected reply
* @param sub command to be send
* @param buffer data to be send
* @returns output buffer containing the responce
*/
DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
std::vector<u8>& output);
/**
* Wait's until the MCU chip is on the specified mode
* @param report mode of the expected reply
* @param MCUMode configuration
*/
DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
/**
* Calculates the checksum from the MCU data
* @param buffer containing the data to be send
* @param size of the buffer in bytes
* @returns byte with the correct checksum
*/
u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
private:
/**
* Increments and returns the packet counter of the handle
* @param joycon_handle device to send the data
* @returns packet counter value
*/
u8 GetCounter();
std::shared_ptr<JoyconHandle> hidapi_handle;
};
class ScopedSetBlocking {
public:
explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
m_self->SetBlocking();
}
~ScopedSetBlocking() {
m_self->SetNonBlocking();
}
private:
JoyconCommonProtocol* m_self{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,125 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/generic_functions.h"
namespace InputCommon::Joycon {
GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult GenericProtocol::EnablePassiveMode() {
ScopedSetBlocking sb(this);
return SetReportMode(ReportMode::SIMPLE_HID_MODE);
}
DriverResult GenericProtocol::EnableActiveMode() {
ScopedSetBlocking sb(this);
return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
}
DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
ScopedSetBlocking sb(this);
std::vector<u8> output;
const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
device_info = {};
if (result == DriverResult::Success) {
memcpy(&device_info, output.data(), sizeof(DeviceInfo));
}
return result;
}
DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
return GetDeviceType(controller_type);
}
DriverResult GenericProtocol::EnableImu(bool enable) {
ScopedSetBlocking sb(this);
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
}
DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
AccelerometerSensitivity asen,
AccelerometerPerformance afrec) {
ScopedSetBlocking sb(this);
const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
}
DriverResult GenericProtocol::GetBattery(u32& battery_level) {
// This function is meant to request the high resolution battery status
battery_level = 0;
return DriverResult::NotSupported;
}
DriverResult GenericProtocol::GetColor(Color& color) {
ScopedSetBlocking sb(this);
std::vector<u8> buffer;
const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
color = {};
if (result == DriverResult::Success) {
color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
}
return result;
}
DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
ScopedSetBlocking sb(this);
std::vector<u8> buffer;
const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
serial_number = {};
if (result == DriverResult::Success) {
memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
}
return result;
}
DriverResult GenericProtocol::GetTemperature(u32& temperature) {
// Not all devices have temperature sensor
temperature = 25;
return DriverResult::NotSupported;
}
DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
DeviceInfo device_info{};
const auto result = GetDeviceInfo(device_info);
version = device_info.firmware;
return result;
}
DriverResult GenericProtocol::SetHomeLight() {
ScopedSetBlocking sb(this);
static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
}
DriverResult GenericProtocol::SetLedBusy() {
return DriverResult::NotSupported;
}
DriverResult GenericProtocol::SetLedPattern(u8 leds) {
ScopedSetBlocking sb(this);
const std::array<u8, 1> buffer{leds};
return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
}
DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
return SetLedPattern(static_cast<u8>(leds << 4));
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include "input_common/helpers/joycon_protocol/common_protocol.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
/// Joycon driver functions that easily implemented
class GenericProtocol final : private JoyconCommonProtocol {
public:
explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
/// Enables passive mode. This mode only sends button data on change. Sticks will return digital
/// data instead of analog. Motion will be disabled
DriverResult EnablePassiveMode();
/// Enables active mode. This mode will return the current status every 5-15ms
DriverResult EnableActiveMode();
/**
* Sends a request to obtain the joycon firmware and mac from handle
* @returns controller device info
*/
DriverResult GetDeviceInfo(DeviceInfo& controller_type);
/**
* Sends a request to obtain the joycon type from handle
* @returns controller type of the joycon
*/
DriverResult GetControllerType(ControllerType& controller_type);
/**
* Enables motion input
* @param enable if true motion data will be enabled
*/
DriverResult EnableImu(bool enable);
/**
* Configures the motion sensor with the specified parameters
* @param gsen gyroscope sensor sensitvity in degrees per second
* @param gfrec gyroscope sensor frequency in hertz
* @param asen accelerometer sensitivity in G force
* @param afrec accelerometer frequency in hertz
*/
DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
/**
* Request battery level from the device
* @returns battery level
*/
DriverResult GetBattery(u32& battery_level);
/**
* Request joycon colors from the device
* @returns colors of the body and buttons
*/
DriverResult GetColor(Color& color);
/**
* Request joycon serial number from the device
* @returns 16 byte serial number
*/
DriverResult GetSerialNumber(SerialNumber& serial_number);
/**
* Request joycon serial number from the device
* @returns 16 byte serial number
*/
DriverResult GetTemperature(u32& temperature);
/**
* Request joycon serial number from the device
* @returns 16 byte serial number
*/
DriverResult GetVersionNumber(FirmwareVersion& version);
/**
* Sets home led behaviour
*/
DriverResult SetHomeLight();
/**
* Sets home led into a slow breathing state
*/
DriverResult SetLedBusy();
/**
* Sets the 4 player leds on the joycon on a solid state
* @params bit flag containing the led state
*/
DriverResult SetLedPattern(u8 leds);
/**
* Sets the 4 player leds on the joycon on a blinking state
* @returns bit flag containing the led state
*/
DriverResult SetLedBlinkPattern(u8 leds);
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,298 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/irs.h"
namespace InputCommon::Joycon {
IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult IrsProtocol::EnableIrs() {
LOG_INFO(Input, "Enable IRS");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
}
if (result == DriverResult::Success) {
result = EnableMCU(true);
}
if (result == DriverResult::Success) {
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
}
if (result == DriverResult::Success) {
const MCUConfig config{
.command = MCUCommand::ConfigureMCU,
.sub_command = MCUSubCommand::SetMCUMode,
.mode = MCUMode::IR,
.crc = {},
};
result = ConfigureMCU(config);
}
if (result == DriverResult::Success) {
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
}
if (result == DriverResult::Success) {
result = ConfigureIrs();
}
if (result == DriverResult::Success) {
result = WriteRegistersStep1();
}
if (result == DriverResult::Success) {
result = WriteRegistersStep2();
}
is_enabled = true;
return result;
}
DriverResult IrsProtocol::DisableIrs() {
LOG_DEBUG(Input, "Disable IRS");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = EnableMCU(false);
}
is_enabled = false;
return result;
}
DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
irs_mode = mode;
switch (format) {
case IrsResolution::Size320x240:
resolution_code = IrsResolutionCode::Size320x240;
fragments = IrsFragments::Size320x240;
resolution = IrsResolution::Size320x240;
break;
case IrsResolution::Size160x120:
resolution_code = IrsResolutionCode::Size160x120;
fragments = IrsFragments::Size160x120;
resolution = IrsResolution::Size160x120;
break;
case IrsResolution::Size80x60:
resolution_code = IrsResolutionCode::Size80x60;
fragments = IrsFragments::Size80x60;
resolution = IrsResolution::Size80x60;
break;
case IrsResolution::Size20x15:
resolution_code = IrsResolutionCode::Size20x15;
fragments = IrsFragments::Size20x15;
resolution = IrsResolution::Size20x15;
break;
case IrsResolution::Size40x30:
default:
resolution_code = IrsResolutionCode::Size40x30;
fragments = IrsFragments::Size40x30;
resolution = IrsResolution::Size40x30;
break;
}
// Restart feature
if (is_enabled) {
DisableIrs();
return EnableIrs();
}
return DriverResult::Success;
}
DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
const u8 next_packet_fragment =
static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
if (buffer[0] == 0x31 && buffer[49] == 0x03) {
u8 new_packet_fragment = buffer[52];
if (new_packet_fragment == next_packet_fragment) {
packet_fragment = next_packet_fragment;
memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
return RequestFrame(packet_fragment);
}
if (new_packet_fragment == packet_fragment) {
return RequestFrame(packet_fragment);
}
return ResendFrame(next_packet_fragment);
}
return RequestFrame(packet_fragment);
}
DriverResult IrsProtocol::ConfigureIrs() {
LOG_DEBUG(Input, "Configure IRS");
constexpr std::size_t max_tries = 28;
std::vector<u8> output;
std::size_t tries = 0;
const IrsConfigure irs_configuration{
.command = MCUCommand::ConfigureIR,
.sub_command = MCUSubCommand::SetDeviceMode,
.irs_mode = IrsMode::ImageTransfer,
.number_of_fragments = fragments,
.mcu_major_version = 0x0500,
.mcu_minor_version = 0x1800,
.crc = {},
};
buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
std::array<u8, sizeof(IrsConfigure)> request_data{};
memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
do {
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ >= max_tries) {
return DriverResult::WrongReply;
}
} while (output[15] != 0x0b);
return DriverResult::Success;
}
DriverResult IrsProtocol::WriteRegistersStep1() {
LOG_DEBUG(Input, "WriteRegistersStep1");
DriverResult result{DriverResult::Success};
constexpr std::size_t max_tries = 28;
std::vector<u8> output;
std::size_t tries = 0;
const IrsWriteRegisters irs_registers{
.command = MCUCommand::ConfigureIR,
.sub_command = MCUSubCommand::WriteDeviceRegisters,
.number_of_registers = 0x9,
.registers =
{
IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
{IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
{IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
{IrRegistersAddress::ExposureTime, 0x00},
{IrRegistersAddress::Leds, static_cast<u8>(leds)},
{IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
{IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
{IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
{IrRegistersAddress::WhitePixelThreshold, 0xc8},
},
.crc = {},
};
std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
std::array<u8, 38> mcu_request{0x02};
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
mcu_request[37] = 0xFF;
if (result != DriverResult::Success) {
return result;
}
do {
result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
// First time we need to set the report mode
if (result == DriverResult::Success && tries == 0) {
result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
}
if (result == DriverResult::Success && tries == 0) {
GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
}
if (result != DriverResult::Success) {
return result;
}
if (tries++ >= max_tries) {
return DriverResult::WrongReply;
}
} while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
return DriverResult::Success;
}
DriverResult IrsProtocol::WriteRegistersStep2() {
LOG_DEBUG(Input, "WriteRegistersStep2");
constexpr std::size_t max_tries = 28;
std::vector<u8> output;
std::size_t tries = 0;
const IrsWriteRegisters irs_registers{
.command = MCUCommand::ConfigureIR,
.sub_command = MCUSubCommand::WriteDeviceRegisters,
.number_of_registers = 0x8,
.registers =
{
IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
static_cast<u8>(led_intensity >> 8)},
{IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
{IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
{IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
{IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
{IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
{IrRegistersAddress::UpdateTime, 0x2d},
{IrRegistersAddress::FinalizeConfig, 0x01},
},
.crc = {},
};
std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
do {
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ >= max_tries) {
return DriverResult::WrongReply;
}
} while (output[15] != 0x13 && output[15] != 0x23);
return DriverResult::Success;
}
DriverResult IrsProtocol::RequestFrame(u8 frame) {
std::array<u8, 38> mcu_request{};
mcu_request[3] = frame;
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
mcu_request[37] = 0xFF;
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
}
DriverResult IrsProtocol::ResendFrame(u8 frame) {
std::array<u8, 38> mcu_request{};
mcu_request[1] = 0x1;
mcu_request[2] = frame;
mcu_request[3] = 0x0;
mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
mcu_request[37] = 0xFF;
return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
}
std::vector<u8> IrsProtocol::GetImage() const {
return buf_image;
}
IrsResolution IrsProtocol::GetIrsFormat() const {
return resolution;
}
bool IrsProtocol::IsEnabled() const {
return is_enabled;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <vector>
#include "input_common/helpers/joycon_protocol/common_protocol.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
class IrsProtocol final : private JoyconCommonProtocol {
public:
explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
DriverResult EnableIrs();
DriverResult DisableIrs();
DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
DriverResult RequestImage(std::span<u8> buffer);
std::vector<u8> GetImage() const;
IrsResolution GetIrsFormat() const;
bool IsEnabled() const;
private:
DriverResult ConfigureIrs();
DriverResult WriteRegistersStep1();
DriverResult WriteRegistersStep2();
DriverResult RequestFrame(u8 frame);
DriverResult ResendFrame(u8 frame);
IrsMode irs_mode{IrsMode::ImageTransfer};
IrsResolution resolution{IrsResolution::Size40x30};
IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
IrsFragments fragments{IrsFragments::Size40x30};
IrLeds leds{IrLeds::BrightAndDim};
IrExLedFilter led_filter{IrExLedFilter::Enabled};
IrImageFlip image_flip{IrImageFlip::Normal};
u8 digital_gain{0x01};
u16 exposure{0x2490};
u16 led_intensity{0x0f10};
u32 denoise{0x012344};
u8 packet_fragment{};
std::vector<u8> buf_image; // 8bpp greyscale image.
bool is_enabled{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,612 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <array>
#include <functional>
#include <SDL_hidapi.h>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace InputCommon::Joycon {
constexpr u32 MaxErrorCount = 50;
constexpr u32 MaxBufferSize = 368;
constexpr u32 MaxResponseSize = 49;
constexpr u32 MaxSubCommandResponseSize = 64;
constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
using MacAddress = std::array<u8, 6>;
using SerialNumber = std::array<u8, 15>;
enum class ControllerType {
None,
Left,
Right,
Pro,
Grip,
Dual,
};
enum class PadAxes {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
Undefined,
};
enum class PadMotion {
LeftMotion,
RightMotion,
Undefined,
};
enum class PadButton : u32 {
Down = 0x000001,
Up = 0x000002,
Right = 0x000004,
Left = 0x000008,
LeftSR = 0x000010,
LeftSL = 0x000020,
L = 0x000040,
ZL = 0x000080,
Y = 0x000100,
X = 0x000200,
B = 0x000400,
A = 0x000800,
RightSR = 0x001000,
RightSL = 0x002000,
R = 0x004000,
ZR = 0x008000,
Minus = 0x010000,
Plus = 0x020000,
StickR = 0x040000,
StickL = 0x080000,
Home = 0x100000,
Capture = 0x200000,
};
enum class PasivePadButton : u32 {
Down_A = 0x0001,
Right_X = 0x0002,
Left_B = 0x0004,
Up_Y = 0x0008,
SL = 0x0010,
SR = 0x0020,
Minus = 0x0100,
Plus = 0x0200,
StickL = 0x0400,
StickR = 0x0800,
Home = 0x1000,
Capture = 0x2000,
L_R = 0x4000,
ZL_ZR = 0x8000,
};
enum class OutputReport : u8 {
RUMBLE_AND_SUBCMD = 0x01,
FW_UPDATE_PKT = 0x03,
RUMBLE_ONLY = 0x10,
MCU_DATA = 0x11,
USB_CMD = 0x80,
};
enum class InputReport : u8 {
SUBCMD_REPLY = 0x21,
STANDARD_FULL_60HZ = 0x30,
NFC_IR_MODE_60HZ = 0x31,
SIMPLE_HID_MODE = 0x3F,
INPUT_USB_RESPONSE = 0x81,
};
enum class FeatureReport : u8 {
Last_SUBCMD = 0x02,
OTA_GW_UPGRADE = 0x70,
SETUP_MEM_READ = 0x71,
MEM_READ = 0x72,
ERASE_MEM_SECTOR = 0x73,
MEM_WRITE = 0x74,
LAUNCH = 0x75,
};
enum class SubCommand : u8 {
STATE = 0x00,
MANUAL_BT_PAIRING = 0x01,
REQ_DEV_INFO = 0x02,
SET_REPORT_MODE = 0x03,
TRIGGERS_ELAPSED = 0x04,
GET_PAGE_LIST_STATE = 0x05,
SET_HCI_STATE = 0x06,
RESET_PAIRING_INFO = 0x07,
LOW_POWER_MODE = 0x08,
SPI_FLASH_READ = 0x10,
SPI_FLASH_WRITE = 0x11,
RESET_MCU = 0x20,
SET_MCU_CONFIG = 0x21,
SET_MCU_STATE = 0x22,
SET_PLAYER_LIGHTS = 0x30,
GET_PLAYER_LIGHTS = 0x31,
SET_HOME_LIGHT = 0x38,
ENABLE_IMU = 0x40,
SET_IMU_SENSITIVITY = 0x41,
WRITE_IMU_REG = 0x42,
READ_IMU_REG = 0x43,
ENABLE_VIBRATION = 0x48,
GET_REGULATED_VOLTAGE = 0x50,
SET_EXTERNAL_CONFIG = 0x58,
UNKNOWN_RINGCON = 0x59,
UNKNOWN_RINGCON2 = 0x5A,
UNKNOWN_RINGCON3 = 0x5C,
};
enum class UsbSubCommand : u8 {
CONN_STATUS = 0x01,
HADSHAKE = 0x02,
BAUDRATE_3M = 0x03,
NO_TIMEOUT = 0x04,
EN_TIMEOUT = 0x05,
RESET = 0x06,
PRE_HANDSHAKE = 0x91,
SEND_UART = 0x92,
};
enum class CalMagic : u8 {
USR_MAGIC_0 = 0xB2,
USR_MAGIC_1 = 0xA1,
USRR_MAGI_SIZE = 2,
};
enum class CalAddr {
SERIAL_NUMBER = 0X6000,
DEVICE_TYPE = 0X6012,
COLOR_EXIST = 0X601B,
FACT_LEFT_DATA = 0X603d,
FACT_RIGHT_DATA = 0X6046,
COLOR_DATA = 0X6050,
FACT_IMU_DATA = 0X6020,
USER_LEFT_MAGIC = 0X8010,
USER_LEFT_DATA = 0X8012,
USER_RIGHT_MAGIC = 0X801B,
USER_RIGHT_DATA = 0X801D,
USER_IMU_MAGIC = 0X8026,
USER_IMU_DATA = 0X8028,
};
enum class ReportMode : u8 {
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
MCU_UPDATE_STATE = 0x23,
STANDARD_FULL_60HZ = 0x30,
NFC_IR_MODE_60HZ = 0x31,
SIMPLE_HID_MODE = 0x3F,
};
enum class GyroSensitivity : u8 {
DPS250,
DPS500,
DPS1000,
DPS2000, // Default
};
enum class AccelerometerSensitivity : u8 {
G8, // Default
G4,
G2,
G16,
};
enum class GyroPerformance : u8 {
HZ833,
HZ208, // Default
};
enum class AccelerometerPerformance : u8 {
HZ200,
HZ100, // Default
};
enum class MCUCommand : u8 {
ConfigureMCU = 0x21,
ConfigureIR = 0x23,
};
enum class MCUSubCommand : u8 {
SetMCUMode = 0x0,
SetDeviceMode = 0x1,
ReadDeviceMode = 0x02,
WriteDeviceRegisters = 0x4,
};
enum class MCUMode : u8 {
Suspend = 0,
Standby = 1,
Ringcon = 3,
NFC = 4,
IR = 5,
MaybeFWUpdate = 6,
};
enum class MCURequest : u8 {
GetMCUStatus = 1,
GetNFCData = 2,
GetIRData = 3,
};
enum class MCUReport : u8 {
Empty = 0x00,
StateReport = 0x01,
IRData = 0x03,
BusyInitializing = 0x0b,
IRStatus = 0x13,
IRRegisters = 0x1b,
NFCState = 0x2a,
NFCReadData = 0x3a,
EmptyAwaitingCmd = 0xff,
};
enum class MCUPacketFlag : u8 {
MorePacketsRemaining = 0x00,
LastCommandPacket = 0x08,
};
enum class NFCReadCommand : u8 {
CancelAll = 0x00,
StartPolling = 0x01,
StopPolling = 0x02,
StartWaitingRecieve = 0x04,
Ntag = 0x06,
Mifare = 0x0F,
};
enum class NFCTagType : u8 {
AllTags = 0x00,
Ntag215 = 0x01,
};
enum class NFCPages {
Block0 = 0,
Block45 = 45,
Block135 = 135,
Block231 = 231,
};
enum class NFCStatus : u8 {
LastPackage = 0x04,
TagLost = 0x07,
};
enum class IrsMode : u8 {
None = 0x02,
Moment = 0x03,
Dpd = 0x04,
Clustering = 0x06,
ImageTransfer = 0x07,
Silhouette = 0x08,
TeraImage = 0x09,
SilhouetteTeraImage = 0x0A,
};
enum class IrsResolution {
Size320x240,
Size160x120,
Size80x60,
Size40x30,
Size20x15,
None,
};
enum class IrsResolutionCode : u8 {
Size320x240 = 0x00, // Full pixel array
Size160x120 = 0x50, // Sensor Binning [2 X 2]
Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
};
// Size of image divided by 300
enum class IrsFragments : u8 {
Size20x15 = 0x00,
Size40x30 = 0x03,
Size80x60 = 0x0f,
Size160x120 = 0x3f,
Size320x240 = 0xFF,
};
enum class IrLeds : u8 {
BrightAndDim = 0x00,
Bright = 0x20,
Dim = 0x10,
None = 0x30,
};
enum class IrExLedFilter : u8 {
Disabled = 0x00,
Enabled = 0x03,
};
enum class IrImageFlip : u8 {
Normal = 0x00,
Inverted = 0x02,
};
enum class IrRegistersAddress : u16 {
UpdateTime = 0x0400,
FinalizeConfig = 0x0700,
LedFilter = 0x0e00,
Leds = 0x1000,
LedIntensitiyMSB = 0x1100,
LedIntensitiyLSB = 0x1200,
ImageFlip = 0x2d00,
Resolution = 0x2e00,
DigitalGainLSB = 0x2e01,
DigitalGainMSB = 0x2f01,
ExposureLSB = 0x3001,
ExposureMSB = 0x3101,
ExposureTime = 0x3201,
WhitePixelThreshold = 0x4301,
DenoiseSmoothing = 0x6701,
DenoiseEdge = 0x6801,
DenoiseColor = 0x6901,
};
enum class DriverResult {
Success,
WrongReply,
Timeout,
UnsupportedControllerType,
HandleInUse,
ErrorReadingData,
ErrorWritingData,
NoDeviceDetected,
InvalidHandle,
NotSupported,
Disabled,
Unknown,
};
struct MotionSensorCalibration {
s16 offset;
s16 scale;
};
struct MotionCalibration {
std::array<MotionSensorCalibration, 3> accelerometer;
std::array<MotionSensorCalibration, 3> gyro;
};
// Basic motion data containing data from the sensors and a timestamp in microseconds
struct MotionData {
float gyro_x{};
float gyro_y{};
float gyro_z{};
float accel_x{};
float accel_y{};
float accel_z{};
u64 delta_timestamp{};
};
struct JoyStickAxisCalibration {
u16 max{1};
u16 min{1};
u16 center{0};
};
struct JoyStickCalibration {
JoyStickAxisCalibration x;
JoyStickAxisCalibration y;
};
struct RingCalibration {
s16 default_value;
s16 max_value;
s16 min_value;
};
struct Color {
u32 body;
u32 buttons;
u32 left_grip;
u32 right_grip;
};
struct Battery {
union {
u8 raw{};
BitField<0, 4, u8> unknown;
BitField<4, 1, u8> charging;
BitField<5, 3, u8> status;
};
};
struct VibrationValue {
f32 low_amplitude;
f32 low_frequency;
f32 high_amplitude;
f32 high_frequency;
};
struct JoyconHandle {
SDL_hid_device* handle = nullptr;
u8 packet_counter{};
};
struct MCUConfig {
MCUCommand command;
MCUSubCommand sub_command;
MCUMode mode;
INSERT_PADDING_BYTES(0x22);
u8 crc;
};
static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
#pragma pack(push, 1)
struct InputReportPassive {
InputReport report_mode;
u16 button_input;
u8 stick_state;
std::array<u8, 10> unknown_data;
};
static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
struct InputReportActive {
InputReport report_mode;
u8 packet_id;
Battery battery_status;
std::array<u8, 3> button_input;
std::array<u8, 3> left_stick_state;
std::array<u8, 3> right_stick_state;
u8 vibration_code;
std::array<s16, 6 * 2> motion_input;
INSERT_PADDING_BYTES(0x2);
s16 ring_input;
};
static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
struct InputReportNfcIr {
InputReport report_mode;
u8 packet_id;
Battery battery_status;
std::array<u8, 3> button_input;
std::array<u8, 3> left_stick_state;
std::array<u8, 3> right_stick_state;
u8 vibration_code;
std::array<s16, 6 * 2> motion_input;
INSERT_PADDING_BYTES(0x4);
};
static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
#pragma pack(pop)
struct IMUCalibration {
std::array<s16, 3> accelerometer_offset;
std::array<s16, 3> accelerometer_scale;
std::array<s16, 3> gyroscope_offset;
std::array<s16, 3> gyroscope_scale;
};
static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
struct NFCReadBlock {
u8 start;
u8 end;
};
static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
struct NFCReadBlockCommand {
u8 block_count{};
std::array<NFCReadBlock, 4> blocks{};
};
static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
struct NFCReadCommandData {
u8 unknown;
u8 uuid_length;
u8 unknown_2;
std::array<u8, 6> uid;
NFCTagType tag_type;
NFCReadBlockCommand read_block;
};
static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
struct NFCPollingCommandData {
u8 enable_mifare;
u8 unknown_1;
u8 unknown_2;
u8 unknown_3;
u8 unknown_4;
};
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
struct NFCRequestState {
MCUSubCommand sub_command;
NFCReadCommand command_argument;
u8 packet_id;
INSERT_PADDING_BYTES(0x1);
MCUPacketFlag packet_flag;
u8 data_length;
union {
std::array<u8, 0x1F> raw_data;
NFCReadCommandData nfc_read;
NFCPollingCommandData nfc_polling;
};
u8 crc;
};
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
struct IrsConfigure {
MCUCommand command;
MCUSubCommand sub_command;
IrsMode irs_mode;
IrsFragments number_of_fragments;
u16 mcu_major_version;
u16 mcu_minor_version;
INSERT_PADDING_BYTES(0x1D);
u8 crc;
};
static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
#pragma pack(push, 1)
struct IrsRegister {
IrRegistersAddress address;
u8 value;
};
static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
struct IrsWriteRegisters {
MCUCommand command;
MCUSubCommand sub_command;
u8 number_of_registers;
std::array<IrsRegister, 9> registers;
INSERT_PADDING_BYTES(0x7);
u8 crc;
};
static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
#pragma pack(pop)
struct FirmwareVersion {
u8 major;
u8 minor;
};
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
struct DeviceInfo {
FirmwareVersion firmware;
MacAddress mac_address;
};
static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
struct MotionStatus {
bool is_enabled;
u64 delta_time;
GyroSensitivity gyro_sensitivity;
AccelerometerSensitivity accelerometer_sensitivity;
};
struct RingStatus {
bool is_enabled;
s16 default_value;
s16 max_value;
s16 min_value;
};
struct JoyconCallbacks {
std::function<void(Battery)> on_battery_data;
std::function<void(Color)> on_color_data;
std::function<void(int, bool)> on_button_data;
std::function<void(int, f32)> on_stick_data;
std::function<void(int, const MotionData&)> on_motion_data;
std::function<void(f32)> on_ring_data;
std::function<void(const std::vector<u8>&)> on_amiibo_data;
std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,400 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/nfc.h"
namespace InputCommon::Joycon {
NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult NfcProtocol::EnableNfc() {
LOG_INFO(Input, "Enable NFC");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
}
if (result == DriverResult::Success) {
result = EnableMCU(true);
}
if (result == DriverResult::Success) {
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
}
if (result == DriverResult::Success) {
const MCUConfig config{
.command = MCUCommand::ConfigureMCU,
.sub_command = MCUSubCommand::SetMCUMode,
.mode = MCUMode::NFC,
.crc = {},
};
result = ConfigureMCU(config);
}
return result;
}
DriverResult NfcProtocol::DisableNfc() {
LOG_DEBUG(Input, "Disable NFC");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = EnableMCU(false);
}
is_enabled = false;
return result;
}
DriverResult NfcProtocol::StartNFCPollingMode() {
LOG_DEBUG(Input, "Start NFC pooling Mode");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
if (result == DriverResult::Success) {
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIsReady();
}
if (result == DriverResult::Success) {
is_enabled = true;
}
return result;
}
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
LOG_DEBUG(Input, "Start NFC pooling Mode");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
if (result == DriverResult::Success) {
result = StartPolling(tag_data);
}
if (result == DriverResult::Success) {
result = ReadTag(tag_data);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIsReady();
}
if (result == DriverResult::Success) {
result = StartPolling(tag_data);
}
if (result == DriverResult::Success) {
result = GetAmiiboData(data);
}
return result;
}
bool NfcProtocol::HasAmiibo() {
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
if (result == DriverResult::Success) {
result = StartPolling(tag_data);
}
return result == DriverResult::Success;
}
DriverResult NfcProtocol::WaitUntilNfcIsReady() {
constexpr std::size_t timeout_limit = 10;
std::vector<u8> output;
std::size_t tries = 0;
do {
auto result = SendStartWaitingRecieveRequest(output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ > timeout_limit) {
return DriverResult::Timeout;
}
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
output[56] != 0x00);
return DriverResult::Success;
}
DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
LOG_DEBUG(Input, "Start Polling for tag");
constexpr std::size_t timeout_limit = 7;
std::vector<u8> output;
std::size_t tries = 0;
do {
const auto result = SendStartPollingRequest(output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ > timeout_limit) {
return DriverResult::Timeout;
}
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
data.type = output[62];
data.uuid.resize(output[64]);
memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
return DriverResult::Success;
}
DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
constexpr std::size_t timeout_limit = 10;
std::vector<u8> output;
std::size_t tries = 0;
std::string uuid_string;
for (auto& content : data.uuid) {
uuid_string += fmt::format(" {:02x}", content);
}
LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
tries = 0;
NFCPages ntag_pages = NFCPages::Block0;
// Read Tag data
while (true) {
auto result = SendReadAmiiboRequest(output, ntag_pages);
const auto mcu_report = static_cast<MCUReport>(output[49]);
const auto nfc_status = static_cast<NFCStatus>(output[56]);
if (result != DriverResult::Success) {
return result;
}
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
if (data.type != 2) {
continue;
}
switch (output[74]) {
case 0:
ntag_pages = NFCPages::Block135;
break;
case 3:
ntag_pages = NFCPages::Block45;
break;
case 4:
ntag_pages = NFCPages::Block231;
break;
default:
return DriverResult::ErrorReadingData;
}
continue;
}
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
// finished
SendStopPollingRequest(output);
return DriverResult::Success;
}
// Ignore other state reports
if (mcu_report == MCUReport::NFCState) {
continue;
}
if (tries++ > timeout_limit) {
return DriverResult::Timeout;
}
}
return DriverResult::Success;
}
DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
constexpr std::size_t timeout_limit = 10;
std::vector<u8> output;
std::size_t tries = 0;
NFCPages ntag_pages = NFCPages::Block135;
std::size_t ntag_buffer_pos = 0;
// Read Tag data
while (true) {
auto result = SendReadAmiiboRequest(output, ntag_pages);
const auto mcu_report = static_cast<MCUReport>(output[49]);
const auto nfc_status = static_cast<NFCStatus>(output[56]);
if (result != DriverResult::Success) {
return result;
}
if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
if (output[52] == 0x01) {
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
ntag_buffer_pos += payload_size - 60;
} else {
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
}
continue;
}
if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
LOG_INFO(Input, "Finished reading amiibo");
return DriverResult::Success;
}
// Ignore other state reports
if (mcu_report == MCUReport::NFCState) {
continue;
}
if (tries++ > timeout_limit) {
return DriverResult::Timeout;
}
}
return DriverResult::Success;
}
DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
NFCRequestState request{
.sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StartPolling,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
.data_length = sizeof(NFCPollingCommandData),
.nfc_polling =
{
.enable_mifare = 0x01,
.unknown_1 = 0x00,
.unknown_2 = 0x00,
.unknown_3 = 0x2c,
.unknown_4 = 0x01,
},
.crc = {},
};
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
}
DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
NFCRequestState request{
.sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StopPolling,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
.data_length = 0,
.raw_data = {},
.crc = {},
};
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
}
DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
NFCRequestState request{
.sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StartWaitingRecieve,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
.data_length = 0,
.raw_data = {},
.crc = {},
};
std::vector<u8> request_data(sizeof(NFCRequestState));
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
}
DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
NFCRequestState request{
.sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::Ntag,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
.data_length = sizeof(NFCReadCommandData),
.nfc_read =
{
.unknown = 0xd0,
.uuid_length = 0x07,
.unknown_2 = 0x00,
.uid = {},
.tag_type = NFCTagType::AllTags,
.read_block = GetReadBlockCommand(ntag_pages),
},
.crc = {},
};
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
}
NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
switch (pages) {
case NFCPages::Block0:
return {
.block_count = 1,
};
case NFCPages::Block45:
return {
.block_count = 1,
.blocks =
{
NFCReadBlock{0x00, 0x2C},
},
};
case NFCPages::Block135:
return {
.block_count = 3,
.blocks =
{
NFCReadBlock{0x00, 0x3b},
{0x3c, 0x77},
{0x78, 0x86},
},
};
case NFCPages::Block231:
return {
.block_count = 4,
.blocks =
{
NFCReadBlock{0x00, 0x3b},
{0x3c, 0x77},
{0x78, 0x83},
{0xb4, 0xe6},
},
};
default:
return {};
};
}
bool NfcProtocol::IsEnabled() const {
return is_enabled;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <vector>
#include "input_common/helpers/joycon_protocol/common_protocol.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
class NfcProtocol final : private JoyconCommonProtocol {
public:
explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
DriverResult EnableNfc();
DriverResult DisableNfc();
DriverResult StartNFCPollingMode();
DriverResult ScanAmiibo(std::vector<u8>& data);
bool HasAmiibo();
bool IsEnabled() const;
private:
struct TagFoundData {
u8 type;
std::vector<u8> uuid;
};
DriverResult WaitUntilNfcIsReady();
DriverResult StartPolling(TagFoundData& data);
DriverResult ReadTag(const TagFoundData& data);
DriverResult GetAmiiboData(std::vector<u8>& data);
DriverResult SendStartPollingRequest(std::vector<u8>& output);
DriverResult SendStopPollingRequest(std::vector<u8>& output);
DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
bool is_enabled{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,341 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/poller.h"
namespace InputCommon::Joycon {
JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
JoyStickCalibration right_stick_calibration_,
MotionCalibration motion_calibration_)
: device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
callbacks = std::move(callbacks_);
}
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
const RingStatus& ring_status) {
InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive));
switch (device_type) {
case Joycon::ControllerType::Left:
UpdateActiveLeftPadInput(data, motion_status);
break;
case Joycon::ControllerType::Right:
UpdateActiveRightPadInput(data, motion_status);
break;
case Joycon::ControllerType::Pro:
UpdateActiveProPadInput(data, motion_status);
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
if (ring_status.is_enabled) {
UpdateRing(data.ring_input, ring_status);
}
callbacks.on_battery_data(data.battery_status);
}
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
InputReportPassive data{};
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
switch (device_type) {
case Joycon::ControllerType::Left:
UpdatePasiveLeftPadInput(data);
break;
case Joycon::ControllerType::Right:
UpdatePasiveRightPadInput(data);
break;
case Joycon::ControllerType::Pro:
UpdatePasiveProPadInput(data);
break;
case Joycon::ControllerType::Grip:
case Joycon::ControllerType::Dual:
case Joycon::ControllerType::None:
break;
}
}
void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
// This mode is compatible with the active mode
ReadActiveMode(buffer, motion_status, {});
}
void JoyconPoller::UpdateColor(const Color& color) {
callbacks.on_color_data(color);
}
void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
callbacks.on_amiibo_data(amiibo_data);
}
void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
callbacks.on_camera_data(camera_data, format);
}
void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
float normalized_value = static_cast<float>(value - ring_status.default_value);
if (normalized_value > 0) {
normalized_value = normalized_value /
static_cast<float>(ring_status.max_value - ring_status.default_value);
}
if (normalized_value < 0) {
normalized_value = normalized_value /
static_cast<float>(ring_status.default_value - ring_status.min_value);
}
callbacks.on_ring_data(normalized_value);
}
void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 11> left_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
Joycon::PadButton::Capture, Joycon::PadButton::StickL,
};
const u32 raw_button =
static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
const int button = static_cast<int>(left_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_left_axis_x =
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
const u16 raw_left_axis_y =
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
if (motion_status.is_enabled) {
auto left_motion = GetMotionInput(input, motion_status);
// Rotate motion axis to the correct direction
left_motion.accel_y = -left_motion.accel_y;
left_motion.accel_z = -left_motion.accel_z;
left_motion.gyro_x = -left_motion.gyro_x;
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
}
}
void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 11> right_buttons{
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickR,
};
const u32 raw_button =
static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
const int button = static_cast<int>(right_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_right_axis_x =
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
const u16 raw_right_axis_y =
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (motion_status.is_enabled) {
auto right_motion = GetMotionInput(input, motion_status);
// Rotate motion axis to the correct direction
right_motion.accel_x = -right_motion.accel_x;
right_motion.accel_y = -right_motion.accel_y;
right_motion.gyro_z = -right_motion.gyro_z;
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
}
}
void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
const MotionStatus& motion_status) {
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
};
const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
(input.button_input[1] << 16));
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
const int button = static_cast<int>(pro_buttons[i]);
callbacks.on_button_data(button, button_status);
}
const u16 raw_left_axis_x =
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
const u16 raw_left_axis_y =
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
const u16 raw_right_axis_x =
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
const u16 raw_right_axis_y =
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (motion_status.is_enabled) {
auto pro_motion = GetMotionInput(input, motion_status);
pro_motion.gyro_x = -pro_motion.gyro_x;
pro_motion.accel_y = -pro_motion.accel_y;
pro_motion.accel_z = -pro_motion.accel_z;
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
}
}
void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
Joycon::PasivePadButton::StickL,
};
for (auto left_button : left_buttons) {
const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
const int button = static_cast<int>(left_button);
callbacks.on_button_data(button, button_status);
}
}
void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
Joycon::PasivePadButton::StickR,
};
for (auto right_button : right_buttons) {
const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
const int button = static_cast<int>(right_button);
callbacks.on_button_data(button, button_status);
}
}
void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
};
for (auto pro_button : pro_buttons) {
const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
const int button = static_cast<int>(pro_button);
callbacks.on_button_data(button, button_status);
}
}
f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
const f32 value = static_cast<f32>(raw_value - calibration.center);
if (value > 0.0f) {
return value / calibration.max;
}
return value / calibration.min;
}
f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
AccelerometerSensitivity sensitivity) const {
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
switch (sensitivity) {
case Joycon::AccelerometerSensitivity::G2:
return value / 4.0f;
case Joycon::AccelerometerSensitivity::G4:
return value / 2.0f;
case Joycon::AccelerometerSensitivity::G8:
return value;
case Joycon::AccelerometerSensitivity::G16:
return value * 2.0f;
}
return value;
}
f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
GyroSensitivity sensitivity) const {
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
switch (sensitivity) {
case Joycon::GyroSensitivity::DPS250:
return value / 8.0f;
case Joycon::GyroSensitivity::DPS500:
return value / 4.0f;
case Joycon::GyroSensitivity::DPS1000:
return value / 2.0f;
case Joycon::GyroSensitivity::DPS2000:
return value;
}
return value;
}
s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
const InputReportActive& input) const {
return input.motion_input[(sensor * 3) + axis];
}
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
const MotionStatus& motion_status) const {
MotionData motion{};
const auto& accel_cal = motion_calibration.accelerometer;
const auto& gyro_cal = motion_calibration.gyro;
const s16 raw_accel_x = input.motion_input[1];
const s16 raw_accel_y = input.motion_input[0];
const s16 raw_accel_z = input.motion_input[2];
const s16 raw_gyro_x = input.motion_input[4];
const s16 raw_gyro_y = input.motion_input[3];
const s16 raw_gyro_z = input.motion_input[5];
motion.delta_timestamp = motion_status.delta_time;
motion.accel_x =
GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
motion.accel_y =
GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
motion.accel_z =
GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
// TODO(German77): Return all three samples data
return motion;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <functional>
#include <span>
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
// Handles input packages and triggers the corresponding input events
class JoyconPoller {
public:
JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
JoyStickCalibration right_stick_calibration_,
MotionCalibration motion_calibration_);
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
/// Handles data from passive packages
void ReadPassiveMode(std::span<u8> buffer);
/// Handles data from active packages
void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
const RingStatus& ring_status);
/// Handles data from nfc or ir packages
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
void UpdateColor(const Color& color);
void UpdateRing(s16 value, const RingStatus& ring_status);
void UpdateAmiibo(const std::vector<u8>& amiibo_data);
void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
private:
void UpdateActiveLeftPadInput(const InputReportActive& input,
const MotionStatus& motion_status);
void UpdateActiveRightPadInput(const InputReportActive& input,
const MotionStatus& motion_status);
void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
void UpdatePasiveProPadInput(const InputReportPassive& buffer);
/// Returns a calibrated joystick axis from raw axis data
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
/// Returns a calibrated accelerometer axis from raw motion data
f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
AccelerometerSensitivity sensitivity) const;
/// Returns a calibrated gyro axis from raw motion data
f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
GyroSensitivity sensitivity) const;
/// Returns a raw motion value from a buffer
s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
/// Returns motion data from a buffer
MotionData GetMotionInput(const InputReportActive& input,
const MotionStatus& motion_status) const;
ControllerType device_type{};
// Device calibration
JoyStickCalibration left_stick_calibration{};
JoyStickCalibration right_stick_calibration{};
MotionCalibration motion_calibration{};
Joycon::JoyconCallbacks callbacks{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/ringcon.h"
namespace InputCommon::Joycon {
RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult RingConProtocol::EnableRingCon() {
LOG_DEBUG(Input, "Enable Ringcon");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
}
if (result == DriverResult::Success) {
result = EnableMCU(true);
}
if (result == DriverResult::Success) {
const MCUConfig config{
.command = MCUCommand::ConfigureMCU,
.sub_command = MCUSubCommand::SetDeviceMode,
.mode = MCUMode::Standby,
.crc = {},
};
result = ConfigureMCU(config);
}
return result;
}
DriverResult RingConProtocol::DisableRingCon() {
LOG_DEBUG(Input, "Disable RingCon");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
result = EnableMCU(false);
}
is_enabled = false;
return result;
}
DriverResult RingConProtocol::StartRingconPolling() {
LOG_DEBUG(Input, "Enable Ringcon");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
bool is_connected = false;
if (result == DriverResult::Success) {
result = IsRingConnected(is_connected);
}
if (result == DriverResult::Success && is_connected) {
LOG_INFO(Input, "Ringcon detected");
result = ConfigureRing();
}
if (result == DriverResult::Success) {
is_enabled = true;
}
return result;
}
DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
LOG_DEBUG(Input, "IsRingConnected");
constexpr std::size_t max_tries = 28;
constexpr u8 ring_controller_id = 0x20;
std::vector<u8> output;
std::size_t tries = 0;
is_connected = false;
do {
std::array<u8, 1> empty_data{};
const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
if (result != DriverResult::Success) {
return result;
}
if (tries++ >= max_tries) {
return DriverResult::NoDeviceDetected;
}
} while (output[16] != ring_controller_id);
is_connected = true;
return DriverResult::Success;
}
DriverResult RingConProtocol::ConfigureRing() {
LOG_DEBUG(Input, "ConfigureRing");
static constexpr std::array<u8, 37> ring_config{
0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
if (result != DriverResult::Success) {
return result;
}
static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
}
bool RingConProtocol::IsEnabled() const {
return is_enabled;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <vector>
#include "input_common/helpers/joycon_protocol/common_protocol.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
class RingConProtocol final : private JoyconCommonProtocol {
public:
explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
DriverResult EnableRingCon();
DriverResult DisableRingCon();
DriverResult StartRingconPolling();
bool IsEnabled() const;
private:
DriverResult IsRingConnected(bool& is_connected);
DriverResult ConfigureRing();
bool is_enabled{};
};
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,299 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cmath>
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/rumble.h"
namespace InputCommon::Joycon {
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
: JoyconCommonProtocol(std::move(handle)) {}
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
LOG_DEBUG(Input, "Enable Rumble");
ScopedSetBlocking sb(this);
const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
}
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
return SendVibrationReport(DefaultVibrationBuffer);
}
// Protect joycons from damage from strong vibrations
const f32 clamp_amplitude =
1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
const u8 encoded_high_amplitude =
EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
// Duplicate rumble for now
buffer[4] = buffer[0];
buffer[5] = buffer[1];
buffer[6] = buffer[2];
buffer[7] = buffer[3];
return SendVibrationReport(buffer);
}
u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
const u8 new_frequency =
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
return static_cast<u16>((new_frequency - 0x60) * 4);
}
u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
const u8 new_frequency =
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
return static_cast<u8>(new_frequency - 0x40);
}
u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
// More information about these values can be found here:
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
std::pair<f32, int>{0.0f, 0x0},
{0.01f, 0x2},
{0.012f, 0x4},
{0.014f, 0x6},
{0.017f, 0x8},
{0.02f, 0x0a},
{0.024f, 0x0c},
{0.028f, 0x0e},
{0.033f, 0x10},
{0.04f, 0x12},
{0.047f, 0x14},
{0.056f, 0x16},
{0.067f, 0x18},
{0.08f, 0x1a},
{0.095f, 0x1c},
{0.112f, 0x1e},
{0.117f, 0x20},
{0.123f, 0x22},
{0.128f, 0x24},
{0.134f, 0x26},
{0.14f, 0x28},
{0.146f, 0x2a},
{0.152f, 0x2c},
{0.159f, 0x2e},
{0.166f, 0x30},
{0.173f, 0x32},
{0.181f, 0x34},
{0.189f, 0x36},
{0.198f, 0x38},
{0.206f, 0x3a},
{0.215f, 0x3c},
{0.225f, 0x3e},
{0.23f, 0x40},
{0.235f, 0x42},
{0.24f, 0x44},
{0.245f, 0x46},
{0.251f, 0x48},
{0.256f, 0x4a},
{0.262f, 0x4c},
{0.268f, 0x4e},
{0.273f, 0x50},
{0.279f, 0x52},
{0.286f, 0x54},
{0.292f, 0x56},
{0.298f, 0x58},
{0.305f, 0x5a},
{0.311f, 0x5c},
{0.318f, 0x5e},
{0.325f, 0x60},
{0.332f, 0x62},
{0.34f, 0x64},
{0.347f, 0x66},
{0.355f, 0x68},
{0.362f, 0x6a},
{0.37f, 0x6c},
{0.378f, 0x6e},
{0.387f, 0x70},
{0.395f, 0x72},
{0.404f, 0x74},
{0.413f, 0x76},
{0.422f, 0x78},
{0.431f, 0x7a},
{0.44f, 0x7c},
{0.45f, 0x7e},
{0.46f, 0x80},
{0.47f, 0x82},
{0.48f, 0x84},
{0.491f, 0x86},
{0.501f, 0x88},
{0.512f, 0x8a},
{0.524f, 0x8c},
{0.535f, 0x8e},
{0.547f, 0x90},
{0.559f, 0x92},
{0.571f, 0x94},
{0.584f, 0x96},
{0.596f, 0x98},
{0.609f, 0x9a},
{0.623f, 0x9c},
{0.636f, 0x9e},
{0.65f, 0xa0},
{0.665f, 0xa2},
{0.679f, 0xa4},
{0.694f, 0xa6},
{0.709f, 0xa8},
{0.725f, 0xaa},
{0.741f, 0xac},
{0.757f, 0xae},
{0.773f, 0xb0},
{0.79f, 0xb2},
{0.808f, 0xb4},
{0.825f, 0xb6},
{0.843f, 0xb8},
{0.862f, 0xba},
{0.881f, 0xbc},
{0.9f, 0xbe},
{0.92f, 0xc0},
{0.94f, 0xc2},
{0.96f, 0xc4},
{0.981f, 0xc6},
{1.003f, 0xc8},
};
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
if (amplitude <= amplitude_value) {
return static_cast<u8>(code);
}
}
return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
}
u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
// More information about these values can be found here:
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
std::pair<f32, int>{0.0f, 0x0040},
{0.01f, 0x8040},
{0.012f, 0x0041},
{0.014f, 0x8041},
{0.017f, 0x0042},
{0.02f, 0x8042},
{0.024f, 0x0043},
{0.028f, 0x8043},
{0.033f, 0x0044},
{0.04f, 0x8044},
{0.047f, 0x0045},
{0.056f, 0x8045},
{0.067f, 0x0046},
{0.08f, 0x8046},
{0.095f, 0x0047},
{0.112f, 0x8047},
{0.117f, 0x0048},
{0.123f, 0x8048},
{0.128f, 0x0049},
{0.134f, 0x8049},
{0.14f, 0x004a},
{0.146f, 0x804a},
{0.152f, 0x004b},
{0.159f, 0x804b},
{0.166f, 0x004c},
{0.173f, 0x804c},
{0.181f, 0x004d},
{0.189f, 0x804d},
{0.198f, 0x004e},
{0.206f, 0x804e},
{0.215f, 0x004f},
{0.225f, 0x804f},
{0.23f, 0x0050},
{0.235f, 0x8050},
{0.24f, 0x0051},
{0.245f, 0x8051},
{0.251f, 0x0052},
{0.256f, 0x8052},
{0.262f, 0x0053},
{0.268f, 0x8053},
{0.273f, 0x0054},
{0.279f, 0x8054},
{0.286f, 0x0055},
{0.292f, 0x8055},
{0.298f, 0x0056},
{0.305f, 0x8056},
{0.311f, 0x0057},
{0.318f, 0x8057},
{0.325f, 0x0058},
{0.332f, 0x8058},
{0.34f, 0x0059},
{0.347f, 0x8059},
{0.355f, 0x005a},
{0.362f, 0x805a},
{0.37f, 0x005b},
{0.378f, 0x805b},
{0.387f, 0x005c},
{0.395f, 0x805c},
{0.404f, 0x005d},
{0.413f, 0x805d},
{0.422f, 0x005e},
{0.431f, 0x805e},
{0.44f, 0x005f},
{0.45f, 0x805f},
{0.46f, 0x0060},
{0.47f, 0x8060},
{0.48f, 0x0061},
{0.491f, 0x8061},
{0.501f, 0x0062},
{0.512f, 0x8062},
{0.524f, 0x0063},
{0.535f, 0x8063},
{0.547f, 0x0064},
{0.559f, 0x8064},
{0.571f, 0x0065},
{0.584f, 0x8065},
{0.596f, 0x0066},
{0.609f, 0x8066},
{0.623f, 0x0067},
{0.636f, 0x8067},
{0.65f, 0x0068},
{0.665f, 0x8068},
{0.679f, 0x0069},
{0.694f, 0x8069},
{0.709f, 0x006a},
{0.725f, 0x806a},
{0.741f, 0x006b},
{0.757f, 0x806b},
{0.773f, 0x006c},
{0.79f, 0x806c},
{0.808f, 0x006d},
{0.825f, 0x806d},
{0.843f, 0x006e},
{0.862f, 0x806e},
{0.881f, 0x006f},
{0.9f, 0x806f},
{0.92f, 0x0070},
{0.94f, 0x8070},
{0.96f, 0x0071},
{0.981f, 0x8071},
{1.003f, 0x0072},
};
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
if (amplitude <= amplitude_value) {
return static_cast<u16>(code);
}
}
return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <vector>
#include "input_common/helpers/joycon_protocol/common_protocol.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace InputCommon::Joycon {
class RumbleProtocol final : private JoyconCommonProtocol {
public:
explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
DriverResult EnableRumble(bool is_enabled);
DriverResult SendVibration(const VibrationValue& vibration);
private:
u16 EncodeHighFrequency(f32 frequency) const;
u8 EncodeLowFrequency(f32 frequency) const;
u8 EncodeHighAmplitude(f32 amplitude) const;
u16 EncodeLowAmplitude(f32 amplitude) const;
};
} // namespace InputCommon::Joycon

View File

@@ -13,11 +13,11 @@ class Stick final : public Common::Input::InputDevice {
public:
using Button = std::unique_ptr<Common::Input::InputDevice>;
Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_,
float modifier_scale_, float modifier_angle_)
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
modifier_angle(modifier_angle_) {
right(std::move(right_)), modifier(std::move(modifier_)), updater(std::move(updater_)),
modifier_scale(modifier_scale_), modifier_angle(modifier_angle_) {
up->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback_) {
@@ -48,6 +48,9 @@ public:
UpdateModButtonStatus(callback_);
},
});
updater->SetCallback({
.on_change = [this](const Common::Input::CallbackStatus& callback_) { SoftUpdate(); },
});
last_x_axis_value = 0.0f;
last_y_axis_value = 0.0f;
}
@@ -248,7 +251,7 @@ public:
modifier->ForceUpdate();
}
void SoftUpdate() override {
void SoftUpdate() {
Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Stick,
.stick_status = GetStatus(),
@@ -308,6 +311,7 @@ private:
Button left;
Button right;
Button modifier;
Button updater;
float modifier_scale{};
float modifier_angle{};
float angle{};
@@ -331,11 +335,12 @@ std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine));
auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine));
auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine));
auto updater = Common::Input::CreateInputDeviceFromString("engine:updater,button:0");
auto modifier_scale = params.Get("modifier_scale", 0.5f);
auto modifier_angle = params.Get("modifier_angle", 5.5f);
return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
std::move(right), std::move(modifier), modifier_scale,
modifier_angle);
std::move(right), std::move(modifier), std::move(updater),
modifier_scale, modifier_angle);
}
} // namespace InputCommon

View File

@@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
TriggerOnBatteryChange(identifier, value);
}
void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) {
{
std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.color = value;
}
}
TriggerOnColorChange(identifier, value);
}
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
{
std::scoped_lock lock{mutex};
@@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
return controller.battery;
}
Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const {
std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
identifier.pad, identifier.port);
return {};
}
const ControllerData& controller = controller_iter->second;
return controller.color;
}
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
@@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
}
}
void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier,
[[maybe_unused]] Common::Input::BodyColorStatus value) {
std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
}
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
const BasicMotion& value) {
std::scoped_lock lock{mutex_callback};

View File

@@ -40,6 +40,7 @@ enum class EngineInputType {
Battery,
Button,
Camera,
Color,
HatButton,
Motion,
Nfc,
@@ -104,14 +105,17 @@ public:
void EndConfiguration();
// Sets a led pattern for a controller
virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::LedStatus& led_status) {}
virtual Common::Input::DriverResult SetLeds(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::LedStatus& led_status) {
return Common::Input::DriverResult::NotSupported;
}
// Sets rumble to a controller
virtual Common::Input::VibrationError SetVibration(
virtual Common::Input::DriverResult SetVibration(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
return Common::Input::VibrationError::NotSupported;
return Common::Input::DriverResult::NotSupported;
}
// Returns true if device supports vibrations
@@ -120,17 +124,17 @@ public:
}
// Sets polling mode to a controller
virtual Common::Input::PollingError SetPollingMode(
virtual Common::Input::DriverResult SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::PollingMode polling_mode) {
return Common::Input::PollingError::NotSupported;
return Common::Input::DriverResult::NotSupported;
}
// Sets camera format to a controller
virtual Common::Input::CameraError SetCameraFormat(
virtual Common::Input::DriverResult SetCameraFormat(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] Common::Input::CameraFormat camera_format) {
return Common::Input::CameraError::NotSupported;
return Common::Input::DriverResult::NotSupported;
}
// Returns success if nfc is supported
@@ -199,6 +203,7 @@ public:
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
@@ -212,6 +217,7 @@ protected:
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
@@ -227,6 +233,7 @@ private:
std::unordered_map<int, float> axes;
std::unordered_map<int, BasicMotion> motions;
Common::Input::BatteryLevel battery{};
Common::Input::BodyColorStatus color{};
Common::Input::CameraStatus camera{};
Common::Input::NfcStatus nfc{};
};
@@ -235,6 +242,8 @@ private:
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
void TriggerOnColorChange(const PadIdentifier& identifier,
Common::Input::BodyColorStatus value);
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
const BasicMotion& value);
void TriggerOnCameraChange(const PadIdentifier& identifier,

View File

@@ -76,7 +76,7 @@ void MappingFactory::RegisterButton(const MappingData& data) {
break;
case EngineInputType::Analog:
// Ignore mouse axis when mapping buttons
if (data.engine == "mouse") {
if (data.engine == "mouse" && data.index != 4) {
return;
}
new_input.Set("axis", data.index);

View File

@@ -498,6 +498,58 @@ private:
InputEngine* input_engine;
};
class InputFromColor final : public Common::Input::InputDevice {
public:
explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_)
: identifier(identifier_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Color,
.index = 0,
.callback = engine_callback,
};
last_color_value = {};
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromColor() override {
input_engine->DeleteCallback(callback_key);
}
Common::Input::BodyColorStatus GetStatus() const {
return input_engine->GetColor(identifier);
}
void ForceUpdate() override {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Color,
.color_status = GetStatus(),
};
last_color_value = status.color_status;
TriggerOnChange(status);
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Color,
.color_status = GetStatus(),
};
if (status.color_status.body != last_color_value.body) {
last_color_value = status.color_status;
TriggerOnChange(status);
}
}
private:
const PadIdentifier identifier;
int callback_key;
Common::Input::BodyColorStatus last_color_value;
InputEngine* input_engine;
};
class InputFromMotion final : public Common::Input::InputDevice {
public:
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
@@ -754,11 +806,11 @@ public:
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
: identifier(identifier_), input_engine(input_engine_) {}
void SetLED(const Common::Input::LedStatus& led_status) override {
input_engine->SetLeds(identifier, led_status);
Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override {
return input_engine->SetLeds(identifier, led_status);
}
Common::Input::VibrationError SetVibration(
Common::Input::DriverResult SetVibration(
const Common::Input::VibrationStatus& vibration_status) override {
return input_engine->SetVibration(identifier, vibration_status);
}
@@ -767,11 +819,12 @@ public:
return input_engine->IsVibrationEnabled(identifier);
}
Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {
return input_engine->SetPollingMode(identifier, polling_mode);
}
Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
Common::Input::DriverResult SetCameraFormat(
Common::Input::CameraFormat camera_format) override {
return input_engine->SetCameraFormat(identifier, camera_format);
}
@@ -966,6 +1019,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
return std::make_unique<InputFromBattery>(identifier, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
input_engine->PreSetController(identifier);
return std::make_unique<InputFromColor>(identifier, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
Common::ParamPackage params) {
const PadIdentifier identifier = {
@@ -1053,6 +1118,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
if (params.Has("battery")) {
return CreateBatteryDevice(params);
}
if (params.Has("color")) {
return CreateColorDevice(params);
}
if (params.Has("camera")) {
return CreateCameraDevice(params);
}

View File

@@ -190,6 +190,17 @@ private:
std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
const Common::ParamPackage& params);
/**
* Creates a color device from the parameters given.
* @param params contains parameters for creating the device:
* - "guid": text string for identifying controllers
* - "port": port of the connected device
* - "pad": slot of the connected controller
* @returns a unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateColorDevice(
const Common::ParamPackage& params);
/**
* Creates a motion device from the parameters given.
* @param params contains parameters for creating the device:

View File

@@ -23,11 +23,34 @@
#include "input_common/drivers/gc_adapter.h"
#endif
#ifdef HAVE_SDL2
#include "input_common/drivers/joycon.h"
#include "input_common/drivers/sdl_driver.h"
#endif
namespace InputCommon {
/// Dummy engine to get periodic updates
class UpdateEngine final : public InputEngine {
public:
explicit UpdateEngine(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
PreSetController(identifier);
}
void PumpEvents() {
SetButton(identifier, 0, last_state);
last_state = !last_state;
}
private:
static constexpr PadIdentifier identifier = {
.guid = Common::UUID{},
.port = 0,
.pad = 0,
};
bool last_state{};
};
struct InputSubsystem::Impl {
template <typename Engine>
void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) {
@@ -45,6 +68,7 @@ struct InputSubsystem::Impl {
void Initialize() {
mapping_factory = std::make_shared<MappingFactory>();
RegisterEngine("updater", update_engine);
RegisterEngine("keyboard", keyboard);
RegisterEngine("mouse", mouse);
RegisterEngine("touch", touch_screen);
@@ -58,6 +82,7 @@ struct InputSubsystem::Impl {
RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
RegisterEngine("sdl", sdl);
RegisterEngine("joycon", joycon);
#endif
Common::Input::RegisterInputFactory("touch_from_button",
@@ -74,6 +99,7 @@ struct InputSubsystem::Impl {
}
void Shutdown() {
UnregisterEngine(update_engine);
UnregisterEngine(keyboard);
UnregisterEngine(mouse);
UnregisterEngine(touch_screen);
@@ -87,6 +113,7 @@ struct InputSubsystem::Impl {
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
UnregisterEngine(sdl);
UnregisterEngine(joycon);
#endif
Common::Input::UnregisterInputFactory("touch_from_button");
@@ -109,6 +136,8 @@ struct InputSubsystem::Impl {
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#ifdef HAVE_SDL2
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
auto sdl_devices = sdl->GetInputDevices();
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
#endif
@@ -140,6 +169,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl;
}
if (engine == joycon->GetEngineName()) {
return joycon;
}
#endif
return nullptr;
}
@@ -223,6 +255,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return true;
}
if (engine == joycon->GetEngineName()) {
return true;
}
#endif
return false;
}
@@ -236,6 +271,7 @@ struct InputSubsystem::Impl {
udp_client->BeginConfiguration();
#ifdef HAVE_SDL2
sdl->BeginConfiguration();
joycon->BeginConfiguration();
#endif
}
@@ -248,10 +284,12 @@ struct InputSubsystem::Impl {
udp_client->EndConfiguration();
#ifdef HAVE_SDL2
sdl->EndConfiguration();
joycon->EndConfiguration();
#endif
}
void PumpEvents() const {
update_engine->PumpEvents();
#ifdef HAVE_SDL2
sdl->PumpEvents();
#endif
@@ -263,6 +301,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<MappingFactory> mapping_factory;
std::shared_ptr<UpdateEngine> update_engine;
std::shared_ptr<Keyboard> keyboard;
std::shared_ptr<Mouse> mouse;
std::shared_ptr<TouchScreen> touch_screen;
@@ -278,6 +317,7 @@ struct InputSubsystem::Impl {
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon;
#endif
};

View File

@@ -259,7 +259,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
program.is_geometry_passthrough = sph.common0.geometry_passthrough != 0;
if (program.is_geometry_passthrough) {
const auto& mask{env.GpPassthroughMask()};
for (size_t i = 0; i < program.info.passthrough.mask.size(); ++i) {
for (size_t i = 0; i < mask.size() * 32; ++i) {
program.info.passthrough.mask[i] = ((mask[i / 32] >> (i % 32)) & 1) == 0;
}

View File

@@ -14,7 +14,6 @@ add_executable(tests
core/core_timing.cpp
core/internal_network/network.cpp
precompiled_headers.h
tests.cpp
video_core/buffer_base.cpp
input_common/calibration_configuration_job.cpp
)
@@ -22,7 +21,7 @@ add_executable(tests
create_target_directory_groups(tests)
target_link_libraries(tests PRIVATE common core input_common)
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads)
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2WithMain Threads::Threads)
add_test(NAME tests COMMAND tests)

View File

@@ -4,7 +4,7 @@
#include <array>
#include <cstring>
#include <type_traits>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/bit_field.h"
TEST_CASE("BitField", "[common]") {

View File

@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/cityhash.h"

View File

@@ -11,7 +11,7 @@
#include <unordered_map>
#include <vector>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/common_types.h"
#include "common/fiber.h"

View File

@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/host_memory.h"
#include "common/literals.h"

View File

@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include <math.h>
#include "common/logging/backend.h"
#include "common/param_package.h"

View File

@@ -3,7 +3,7 @@
#include <stdexcept>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/range_map.h"

View File

@@ -7,7 +7,7 @@
#include <numeric>
#include <thread>
#include <vector>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/ring_buffer.h"
namespace Common {

View File

@@ -5,7 +5,7 @@
#include <array>
#include <cstring>
#include <span>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/common_types.h"
#include "common/scratch_buffer.h"

Some files were not shown because too many files have changed in this diff Show More