New upstream version 0-1317+ds

This commit is contained in:
Andrea Pappacoda 2023-01-23 19:21:14 +01:00
parent 16d8bd32a1
commit 560091b3f4
108 changed files with 12786 additions and 570 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)
@ -242,7 +244,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 +608,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,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,7 @@ endif()
if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
add_subdirectory(Vulkan-Headers EXCLUDE_FROM_ALL)
endif()
add_library(demangle STATIC)
target_include_directories(demangle PUBLIC ./demangle)
target_sources(demangle PRIVATE demangle/ItaniumDemangle.cpp)

104
externals/demangle/Demangle.h vendored Normal file
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

93
externals/demangle/DemangleConfig.h vendored Normal file
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

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 "Demangle.h"
#include "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();
}

5582
externals/demangle/ItaniumDemangle.h vendored Normal file

File diff suppressed because it is too large Load Diff

127
externals/demangle/StringView.h vendored Normal file
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

192
externals/demangle/Utility.h vendored Normal file
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

@ -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 demangle)
if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(common PRIVATE precompiled_headers.h)

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

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/demangle.h"
#include "common/scope_exit.h"
namespace llvm {
char* itaniumDemangle(const char* mangled_name, char* buf, size_t* n, int* status);
}
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
@ -316,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

@ -483,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

@ -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,7 +1568,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
}
AnalogSticks EmulatedController::GetSticks() const {
std::unique_lock lock{mutex};
std::scoped_lock lock{mutex};
if (is_configuring) {
return {};
@ -1478,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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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,6 +23,7 @@
#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
@ -81,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",
@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
UnregisterEngine(sdl);
UnregisterEngine(joycon);
#endif
Common::Input::UnregisterInputFactory("touch_from_button");
@ -133,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
@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl;
}
if (engine == joycon->GetEngineName()) {
return joycon;
}
#endif
return nullptr;
}
@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return true;
}
if (engine == joycon->GetEngineName()) {
return true;
}
#endif
return false;
}
@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
udp_client->BeginConfiguration();
#ifdef HAVE_SDL2
sdl->BeginConfiguration();
joycon->BeginConfiguration();
#endif
}
@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
udp_client->EndConfiguration();
#ifdef HAVE_SDL2
sdl->EndConfiguration();
joycon->EndConfiguration();
#endif
}
@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon;
#endif
};

View File

@ -436,6 +436,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
if (info.type == TextureType::Buffer) {
lod = Id{};
}
if (Sirit::ValidId(ms)) {
// This image is multisampled, lod must be implicit
lod = Id{};
}
const ImageOperands operands(offset, lod, ms);
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());

View File

@ -35,6 +35,7 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
const spv::ImageFormat format{spv::ImageFormat::Unknown};
const Id type{ctx.F32[1]};
const bool depth{desc.is_depth};
const bool ms{desc.is_multisample};
switch (desc.type) {
case TextureType::Color1D:
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format);
@ -42,9 +43,9 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
case TextureType::Color2D:
case TextureType::Color2DRect:
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format);
case TextureType::ColorArray2D:
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format);
case TextureType::Color3D:
return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format);
case TextureType::ColorCube:

View File

@ -121,8 +121,6 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
}
if (tld.lod != 0) {
lod = v.X(meta_reg++);
} else {
lod = v.ir.Imm32(0U);
}
if (tld.aoffi != 0) {
offset = MakeOffset(v, meta_reg, tld.type);

View File

@ -73,7 +73,7 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
const IR::Reg reg_a{tlds.src_reg_a};
const IR::Reg reg_b{tlds.src_reg_b};
IR::Value coords;
IR::U32 lod{v.ir.Imm32(0U)};
IR::U32 lod;
IR::Value offsets;
IR::U32 multisample;
Shader::TextureType texture_type{};

View File

@ -524,6 +524,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
const auto& cbuf{texture_inst.cbuf};
auto flags{inst->Flags<IR::TextureInstInfo>()};
bool is_multisample{false};
switch (inst->GetOpcode()) {
case IR::Opcode::ImageQueryDimensions:
flags.type.Assign(ReadTextureType(env, cbuf));
@ -538,6 +539,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
}
break;
case IR::Opcode::ImageFetch:
if (flags.type == TextureType::Color2D || flags.type == TextureType::Color2DRect ||
flags.type == TextureType::ColorArray2D) {
is_multisample = !inst->Arg(4).IsEmpty();
} else {
inst->SetArg(4, IR::U32{});
}
if (flags.type != TextureType::Color1D) {
break;
}
@ -613,6 +620,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
.is_depth = flags.is_depth != 0,
.is_multisample = is_multisample,
.has_secondary = cbuf.has_secondary,
.cbuf_index = cbuf.index,
.cbuf_offset = cbuf.offset,

View File

@ -109,6 +109,7 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip
struct TextureDescriptor {
TextureType type;
bool is_depth;
bool is_multisample;
bool has_secondary;
u32 cbuf_index;
u32 cbuf_offset;

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"

View File

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

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include <array>
#include <bitset>

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 "core/internal_network/network.h"
#include "core/internal_network/sockets.h"

View File

@ -6,7 +6,7 @@
#include <thread>
#include <boost/asio.hpp>
#include <boost/crc.hpp>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "input_common/drivers/udp_client.h"
#include "input_common/helpers/udp_protocol.h"

View File

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
// Catch provides the main function since we've given it the
// CATCH_CONFIG_MAIN preprocessor directive.

View File

@ -4,7 +4,7 @@
#include <stdexcept>
#include <unordered_map>
#include <catch2/catch.hpp>
#include <catch2/catch_test_macros.hpp>
#include "common/alignment.h"
#include "common/common_types.h"

View File

@ -468,7 +468,7 @@ void Maxwell3D::ProcessMacroBind(u32 data) {
}
void Maxwell3D::ProcessFirmwareCall4() {
LOG_WARNING(HW_GPU, "(STUBBED) called");
LOG_DEBUG(HW_GPU, "(STUBBED) called");
// Firmware call 4 is a blob that changes some registers depending on its parameters.
// These registers don't affect emulation and so are stubbed by setting 0xd00 to 1.

View File

@ -57,7 +57,7 @@ NsightAftermathTracker::NsightAftermathTracker() {
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps(
GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback,
ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) {
ShaderDebugInfoCallback, CrashDumpDescriptionCallback, nullptr, this))) {
LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
return;
}
@ -83,7 +83,7 @@ void NsightAftermathTracker::SaveShader(std::span<const u32> spirv) const {
std::scoped_lock lock{mutex};
GFSDK_Aftermath_ShaderHash hash;
GFSDK_Aftermath_ShaderBinaryHash hash;
if (!GFSDK_Aftermath_SUCCEED(
GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) {
LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module");
@ -121,8 +121,8 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
u32 json_size = 0;
if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr,
this, &json_size))) {
GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, this,
&json_size))) {
LOG_ERROR(Render_Vulkan, "Failed to generate JSON");
return;
}

View File

@ -70,28 +70,28 @@ const std::array<int, 2> Config::default_ringcon_analogs{{
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}},
}};
// clang-format on
@ -440,6 +440,7 @@ void Config::ReadControlValues() {
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
Settings::values.mouse_panning = false;
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
ReadBasicSetting(Settings::values.enable_joycon_driver);
ReadBasicSetting(Settings::values.tas_enable);
ReadBasicSetting(Settings::values.tas_loop);
@ -747,7 +748,7 @@ void Config::ReadShortcutValues() {
for (const auto& [name, group, shortcut] : default_hotkeys) {
qt_config->beginGroup(group);
qt_config->beginGroup(name);
// No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
// No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
// for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
// a file dialog in windowed mode
UISettings::values.shortcuts.push_back(
@ -756,7 +757,7 @@ void Config::ReadShortcutValues() {
{ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(),
ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq)
.toString(),
shortcut.context}});
shortcut.context, ReadSetting(QStringLiteral("Repeat"), shortcut.repeat).toBool()}});
qt_config->endGroup();
qt_config->endGroup();
}
@ -1139,6 +1140,7 @@ void Config::SaveControlValues() {
WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
WriteGlobalSetting(Settings::values.motion_enabled);
WriteBasicSetting(Settings::values.enable_raw_input);
WriteBasicSetting(Settings::values.enable_joycon_driver);
WriteBasicSetting(Settings::values.keyboard_enabled);
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
@ -1393,6 +1395,7 @@ void Config::SaveShortcutValues() {
WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq,
default_hotkey.controller_keyseq);
WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context);
WriteSetting(QStringLiteral("Repeat"), shortcut.repeat, default_hotkey.repeat);
qt_config->endGroup();
qt_config->endGroup();
}

View File

@ -92,3 +92,13 @@ void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index
combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
}
int ConfigurationShared::GetComboboxIndex(int global_setting_index, const QComboBox* combobox) {
if (Settings::IsConfiguringGlobal()) {
return combobox->currentIndex();
}
if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
return global_setting_index;
}
return combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET;
}

View File

@ -69,4 +69,7 @@ void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox
void InsertGlobalItem(QComboBox* combobox, int global_index);
// Returns the correct index of a QComboBox taking into account global configuration
int GetComboboxIndex(int global_setting_index, const QComboBox* combobox);
} // namespace ConfigurationShared

View File

@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());
UpdateUIEnabled();
}

View File

@ -2696,6 +2696,22 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="enable_joycon_driver">
<property name="toolTip">
<string>Requires restarting yuzu</string>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Enable direct JoyCon driver</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="mouse_panning">
<property name="minimumSize">
<size>
@ -2708,7 +2724,7 @@
</property>
</widget>
</item>
<item row="5" column="2">
<item row="6" column="2">
<widget class="QSpinBox" name="mouse_panning_sensitivity">
<property name="toolTip">
<string>Mouse sensitivity</string>
@ -2730,14 +2746,14 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="motion_touch">
<property name="text">
<string>Motion / Touch</string>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="7" column="2">
<widget class="QPushButton" name="buttonMotionTouch">
<property name="text">
<string>Configure</string>

View File

@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("R");
case Common::Input::ButtonNames::TriggerL:
return QObject::tr("L");
case Common::Input::ButtonNames::TriggerZR:
return QObject::tr("ZR");
case Common::Input::ButtonNames::TriggerZL:
return QObject::tr("ZL");
case Common::Input::ButtonNames::TriggerSR:
return QObject::tr("SR");
case Common::Input::ButtonNames::TriggerSL:
return QObject::tr("SL");
case Common::Input::ButtonNames::ButtonStickL:
return QObject::tr("Stick L");
case Common::Input::ButtonNames::ButtonStickR:
return QObject::tr("Stick R");
case Common::Input::ButtonNames::ButtonA:
return QObject::tr("A");
case Common::Input::ButtonNames::ButtonB:
@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("Y");
case Common::Input::ButtonNames::ButtonStart:
return QObject::tr("Start");
case Common::Input::ButtonNames::ButtonPlus:
return QObject::tr("Plus");
case Common::Input::ButtonNames::ButtonMinus:
return QObject::tr("Minus");
case Common::Input::ButtonNames::ButtonHome:
return QObject::tr("Home");
case Common::Input::ButtonNames::ButtonCapture:
return QObject::tr("Capture");
case Common::Input::ButtonNames::L1:
return QObject::tr("L1");
case Common::Input::ButtonNames::L2:

View File

@ -103,9 +103,13 @@ void PlayerControlPreview::UpdateColors() {
colors.left = colors.primary;
colors.right = colors.primary;
// Possible alternative to set colors from settings
// colors.left = QColor(controller->GetColors().left.body);
// colors.right = QColor(controller->GetColors().right.body);
const auto color_left = controller->GetColorsValues()[0].body;
const auto color_right = controller->GetColorsValues()[1].body;
if (color_left != 0 && color_right != 0) {
colors.left = QColor(color_left);
colors.right = QColor(color_right);
}
}
void PlayerControlPreview::ResetInputs() {

View File

@ -4,9 +4,11 @@
#include <memory>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include <fmt/format.h>
#include "core/hid/emulated_devices.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
@ -126,9 +128,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
ui->buttonRingAnalogPush,
};
emulated_device = hid_core_.GetEmulatedDevices();
emulated_device->SaveCurrentConfig();
emulated_device->EnableConfiguration();
emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
emulated_controller->SaveCurrentConfig();
emulated_controller->EnableConfiguration();
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
.is_npad_service = false,
};
callback_key = emulated_controller->SetCallback(engine_callback);
is_controller_set = true;
LoadConfiguration();
@ -143,9 +152,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
HandleClick(
analog_map_buttons[sub_button_id],
[=, this](const Common::ParamPackage& params) {
Common::ParamPackage param = emulated_device->GetRingParam();
Common::ParamPackage param = emulated_controller->GetRingParam();
SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
emulated_device->SetRingParam(param);
emulated_controller->SetRingParam(param);
},
InputCommon::Polling::InputType::Stick);
});
@ -155,16 +164,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
connect(analog_button, &QPushButton::customContextMenuRequested,
[=, this](const QPoint& menu_location) {
QMenu context_menu;
Common::ParamPackage param = emulated_device->GetRingParam();
Common::ParamPackage param = emulated_controller->GetRingParam();
context_menu.addAction(tr("Clear"), [&] {
emulated_device->SetRingParam({});
emulated_controller->SetRingParam(param);
analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Invert axis"), [&] {
const bool invert_value = param.Get("invert_x", "+") == "-";
const std::string invert_str = invert_value ? "+" : "-";
param.Set("invert_x", invert_str);
emulated_device->SetRingParam(param);
emulated_controller->SetRingParam(param);
for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
++sub_button_id2) {
analog_map_buttons[sub_button_id2]->setText(
@ -177,16 +186,19 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
}
connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
Common::ParamPackage param = emulated_device->GetRingParam();
Common::ParamPackage param = emulated_controller->GetRingParam();
const auto slider_value = ui->sliderRingAnalogDeadzone->value();
ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
param.Set("deadzone", slider_value / 100.0f);
emulated_device->SetRingParam(param);
emulated_controller->SetRingParam(param);
});
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
&ConfigureRingController::RestoreDefaults);
connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
&ConfigureRingController::EnableRingController);
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
@ -202,7 +214,14 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
}
ConfigureRingController::~ConfigureRingController() {
emulated_device->DisableConfiguration();
emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
emulated_controller->DisableConfiguration();
if (is_controller_set) {
emulated_controller->DeleteCallback(callback_key);
is_controller_set = false;
}
};
void ConfigureRingController::changeEvent(QEvent* event) {
@ -219,7 +238,7 @@ void ConfigureRingController::RetranslateUI() {
void ConfigureRingController::UpdateUI() {
RetranslateUI();
const Common::ParamPackage param = emulated_device->GetRingParam();
const Common::ParamPackage param = emulated_controller->GetRingParam();
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
auto* const analog_button = analog_map_buttons[sub_button_id];
@ -240,9 +259,9 @@ void ConfigureRingController::UpdateUI() {
}
void ConfigureRingController::ApplyConfiguration() {
emulated_device->DisableConfiguration();
emulated_device->SaveCurrentConfig();
emulated_device->EnableConfiguration();
emulated_controller->DisableConfiguration();
emulated_controller->SaveCurrentConfig();
emulated_controller->EnableConfiguration();
}
void ConfigureRingController::LoadConfiguration() {
@ -252,10 +271,62 @@ void ConfigureRingController::LoadConfiguration() {
void ConfigureRingController::RestoreDefaults() {
const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
emulated_device->SetRingParam(Common::ParamPackage(default_ring_string));
emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
UpdateUI();
}
void ConfigureRingController::EnableRingController() {
const auto dialog_title = tr("Error enabling ring input");
is_ring_enabled = false;
ui->ring_controller_sensor_value->setText(tr("Not connected"));
if (!Settings::values.enable_joycon_driver) {
QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
return;
}
ui->enable_ring_controller_button->setEnabled(false);
ui->enable_ring_controller_button->setText(tr("Configuring"));
// SetPollingMode is blocking. Allow to update the button status before calling the command
repaint();
const auto result = emulated_controller->SetPollingMode(
Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
switch (result) {
case Common::Input::DriverResult::Success:
is_ring_enabled = true;
break;
case Common::Input::DriverResult::NotSupported:
QMessageBox::warning(this, dialog_title,
tr("The current mapped device doesn't support the ring controller"));
break;
case Common::Input::DriverResult::NoDeviceDetected:
QMessageBox::warning(this, dialog_title,
tr("The current mapped device doesn't have a ring attached"));
break;
default:
QMessageBox::warning(this, dialog_title,
tr("Unexpected driver result %1").arg(static_cast<int>(result)));
break;
}
ui->enable_ring_controller_button->setEnabled(true);
ui->enable_ring_controller_button->setText(tr("Enable"));
}
void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
if (!is_ring_enabled) {
return;
}
if (type != Core::HID::ControllerTriggerType::RingController) {
return;
}
const auto value = emulated_controller->GetRingSensorValues();
const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
ui->ring_controller_sensor_value->setText(tex_value);
}
void ConfigureRingController::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::InputType type) {

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