Compare commits

..

14 Commits

Author SHA1 Message Date
David Marcec
49c58b67cf Added super late fixes 2018-05-20 20:45:02 -07:00
David Marcec
031ba986d0 IocGetWaitbase -> IocChannelGetWaitbaseCommand 2018-05-11 11:41:09 -07:00
David Marcec
8bd8170d0b Implemented NVHOST_IOCTL_CHANNEL_GET_WAITBASE
struct + 4 seems to be hard coded at 0 and struct + 0 seems to be ignored?
2018-05-10 23:35:42 -07:00
Hexagon12
5e9c547952 Stubs for QLaunch (#428)
* Stubs for QLaunch

* Wiped unrelated stuff

* Addressed comment

* Dropped GetPopFromGeneralChannelEvent
2018-05-07 11:27:30 -04:00
Max Thomas
266703b50e hid: Tweaks, Analog Sticks (#435)
* hid: Update mouse/keyboard state

* hid: Working analog sticks

* hid: Nits

* hid: Nits

* hid: Update mystery sections

* hid: Tweaks
2018-05-07 11:06:02 -04:00
bunnei
9eccb5de9d Merge pull request #434 from lioncash/vdtor
memory_hook: Default virtual destructor in the cpp file
2018-05-03 15:09:14 -04:00
bunnei
8c665d6752 Merge pull request #433 from lioncash/logging
core_timing: Don't include the log header in core timing's header
2018-05-03 15:08:43 -04:00
Lioncash
732a77d0e8 memory_hook: Default virtual destructor in the cpp file
Prevents creating multiple copies of the vtable in every translation unit that uses the class.
Also silences a -Wweak-vtables warning
2018-05-03 08:12:16 -04:00
Lioncash
9f3641755e core_timing: Don't include the log header in core timing's header
Avoids propagating logging macros and facilities to files that may not need them.
This also allows hiding an internal constant.
2018-05-03 08:00:15 -04:00
bunnei
1147db9dd1 Merge pull request #431 from lioncash/fmt
general: Make formatting of logged hex values more straightforward
2018-05-02 15:24:41 -04:00
bunnei
b1a8e5914b Merge pull request #430 from lioncash/vec
vector_math: Ensure members are always initialized
2018-05-02 09:55:32 -04:00
bunnei
902182f80c Merge pull request #427 from bunnei/domain-inputs
ipc: Add support for PopIpcInterface() method.
2018-05-02 09:55:14 -04:00
bunnei
fadab1d5f3 ipc: Add support for PopIpcInterface() method.
- This can be used for domain objects as inputs to service functions.
2018-05-01 21:57:44 -04:00
Lioncash
acc10c7ee2 vector_math: Ensure members are always initialized
Ensures that values are always in a well-defined state.
2018-05-01 21:25:25 -04:00
19 changed files with 558 additions and 125 deletions

View File

@@ -52,8 +52,8 @@ static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
template <typename T>
class Vec2 {
public:
T x;
T y;
T x{};
T y{};
Vec2() = default;
Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
@@ -192,9 +192,9 @@ inline float Vec2<float>::Normalize() {
template <typename T>
class Vec3 {
public:
T x;
T y;
T z;
T x{};
T y{};
T z{};
Vec3() = default;
Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
@@ -392,10 +392,10 @@ typedef Vec3<float> Vec3f;
template <typename T>
class Vec4 {
public:
T x;
T y;
T z;
T w;
T x{};
T y{};
T z{};
T w{};
Vec4() = default;
Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}

View File

@@ -253,6 +253,7 @@ add_library(core STATIC
loader/nso.h
memory.cpp
memory.h
memory_hook.cpp
memory_hook.h
memory_setup.h
perf_stats.cpp

View File

@@ -6,6 +6,7 @@
#include <algorithm>
#include <cinttypes>
#include <limits>
#include <mutex>
#include <string>
#include <tuple>
@@ -57,7 +58,8 @@ static u64 event_fifo_id;
// to the event_queue by the emu thread
static Common::MPSCQueue<Event, false> ts_queue;
static constexpr int MAX_SLICE_LENGTH = 20000;
constexpr int MAX_SLICE_LENGTH = 20000;
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
static s64 idled_cycles;
@@ -70,6 +72,54 @@ static EventType* ev_lost = nullptr;
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {}
s64 usToCycles(s64 us) {
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (us > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (us / 1000000);
}
return (BASE_CLOCK_RATE * us) / 1000000;
}
s64 usToCycles(u64 us) {
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (us > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
}
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
}
s64 nsToCycles(s64 ns) {
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (ns > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (ns / 1000000000);
}
return (BASE_CLOCK_RATE * ns) / 1000000000;
}
s64 nsToCycles(u64 ns) {
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (ns > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
}
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
}
EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.

View File

@@ -18,17 +18,14 @@
*/
#include <functional>
#include <limits>
#include <string>
#include "common/common_types.h"
#include "common/logging/log.h"
namespace CoreTiming {
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
// The exact value used is of course unverified.
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
inline s64 msToCycles(int ms) {
// since ms is int there is no way to overflow
@@ -51,29 +48,9 @@ inline s64 usToCycles(int us) {
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
}
inline s64 usToCycles(s64 us) {
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (us > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (us / 1000000);
}
return (BASE_CLOCK_RATE * us) / 1000000;
}
s64 usToCycles(s64 us);
inline s64 usToCycles(u64 us) {
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (us > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
}
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
}
s64 usToCycles(u64 us);
inline s64 nsToCycles(float ns) {
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
@@ -83,29 +60,9 @@ inline s64 nsToCycles(int ns) {
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
}
inline s64 nsToCycles(s64 ns) {
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (ns > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (ns / 1000000000);
}
return (BASE_CLOCK_RATE * ns) / 1000000000;
}
s64 nsToCycles(s64 ns);
inline s64 nsToCycles(u64 ns) {
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
NGLOG_ERROR(Core_Timing, "Integer overflow, use max value");
return std::numeric_limits<s64>::max();
}
if (ns > MAX_VALUE_TO_MULTIPLY) {
NGLOG_DEBUG(Core_Timing, "Time very big, do rounding");
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
}
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
}
s64 nsToCycles(u64 ns);
inline u64 cyclesToNs(s64 cycles) {
return cycles * 1000000000 / BASE_CLOCK_RATE;

View File

@@ -167,6 +167,7 @@ struct DomainMessageHeader {
struct {
union {
BitField<0, 8, CommandType> command;
BitField<8, 8, u32_le> input_object_count;
BitField<16, 16, u32_le> size;
};
u32_le object_id;

View File

@@ -298,6 +298,13 @@ public:
template <typename T>
Kernel::SharedPtr<T> GetCopyObject(size_t index);
template <class T>
std::shared_ptr<T> PopIpcInterface() {
ASSERT(context->Session()->IsDomain());
ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
}
};
/// Pop ///

View File

@@ -202,6 +202,16 @@ public:
domain_objects.emplace_back(std::move(object));
}
template <typename T>
std::shared_ptr<T> GetDomainRequestHandler(size_t index) const {
return std::static_pointer_cast<T>(domain_request_handlers[index]);
}
void SetDomainRequestHandlers(
const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) {
domain_request_handlers = handlers;
}
/// Clears the list of objects so that no lingering objects are written accidentally to the
/// response buffer.
void ClearIncomingObjects() {
@@ -245,6 +255,8 @@ private:
unsigned data_payload_offset{};
unsigned buffer_c_offset{};
u32_le command{};
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
};
} // namespace Kernel

View File

@@ -61,6 +61,9 @@ void ServerSession::Acquire(Thread* thread) {
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
auto& domain_message_header = context.GetDomainMessageHeader();
if (domain_message_header) {
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
context.SetDomainRequestHandlers(domain_request_handlers);
// If there is a DomainMessageHeader, then this is CommandType "Request"
const u32 object_id{context.GetDomainMessageHeader()->object_id};
switch (domain_message_header->command) {

View File

@@ -103,7 +103,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
{41, nullptr, "IsSystemBufferSharingEnabled"},
{42, nullptr, "GetSystemSharedLayerHandle"},
{50, nullptr, "SetHandlesRequestToDisplay"},
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
{51, nullptr, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
{61, nullptr, "SetMediaPlaybackState"},
@@ -229,6 +229,13 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx)
NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -445,7 +452,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
{10, nullptr, "CreateApplicationAndPushAndRequestToStart"},
{11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"},
{12, nullptr, "CreateApplicationAndRequestToStart"},
{13, nullptr, "CreateApplicationAndRequestToStartForQuest"},
{13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest,
"CreateApplicationAndRequestToStartForQuest"},
{20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
{21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
{22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
@@ -502,6 +510,13 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
NGLOG_DEBUG(Service_AM, "called");
}
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
u128 uid = rp.PopRaw<u128>();
@@ -572,4 +587,64 @@ void InstallInterfaces(SM::ServiceManager& service_manager,
std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager);
}
IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") {
static const FunctionInfo functions[] = {
{10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
{11, nullptr, "LockForeground"},
{12, nullptr, "UnlockForeground"},
{20, nullptr, "PopFromGeneralChannel"},
{21, nullptr, "GetPopFromGeneralChannelEvent"},
{30, nullptr, "GetHomeButtonWriterLockAccessor"},
{31, nullptr, "GetWriterLockAccessorEx"},
};
RegisterHandlers(functions);
}
void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
NGLOG_WARNING(Service_AM, "(STUBBED) called");
}
IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
static const FunctionInfo functions[] = {
{0, nullptr, "RequestToEnterSleep"},
{1, nullptr, "EnterSleep"},
{2, nullptr, "StartSleepSequence"},
{3, nullptr, "StartShutdownSequence"},
{4, nullptr, "StartRebootSequence"},
{10, nullptr, "LoadAndApplyIdlePolicySettings"},
{11, nullptr, "NotifyCecSettingsChanged"},
{12, nullptr, "SetDefaultHomeButtonLongPressTime"},
{13, nullptr, "UpdateDefaultDisplayResolution"},
{14, nullptr, "ShouldSleepOnBoot"},
{15, nullptr, "GetHdcpAuthenticationFailedEvent"},
};
RegisterHandlers(functions);
}
IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
static const FunctionInfo functions[] = {
{0, nullptr, "CreateApplication"},
{1, nullptr, "PopLaunchRequestedApplication"},
{10, nullptr, "CreateSystemApplication"},
{100, nullptr, "PopFloatingApplicationForDevelopment"},
};
RegisterHandlers(functions);
}
IProcessWindingController::IProcessWindingController()
: ServiceFramework("IProcessWindingController") {
static const FunctionInfo functions[] = {
{0, nullptr, "GetLaunchReason"},
{11, nullptr, "OpenCallingLibraryApplet"},
{21, nullptr, "PushContext"},
{22, nullptr, "PopContext"},
{23, nullptr, "CancelWindingReservation"},
{30, nullptr, "WindAndDoReserved"},
{40, nullptr, "ReserveToStartAndWaitAndUnwindThis"},
{41, nullptr, "ReserveToStartAndWait"},
};
RegisterHandlers(functions);
}
} // namespace Service::AM

View File

@@ -70,6 +70,7 @@ private:
void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::SharedPtr<Kernel::Event> launchable_event;
@@ -113,6 +114,7 @@ public:
private:
void PopLaunchParameter(Kernel::HLERequestContext& ctx);
void CreateApplicationAndRequestToStartForQuest(Kernel::HLERequestContext& ctx);
void EnsureSaveData(Kernel::HLERequestContext& ctx);
void SetTerminateResult(Kernel::HLERequestContext& ctx);
void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
@@ -121,6 +123,29 @@ private:
void NotifyRunning(Kernel::HLERequestContext& ctx);
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
public:
IHomeMenuFunctions();
private:
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
};
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
public:
IGlobalStateController();
};
class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
public:
IApplicationCreator();
};
class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
public:
IProcessWindingController();
};
/// Registers all AM services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nvflinger);

View File

@@ -20,7 +20,7 @@ public:
{2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"},
{3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"},
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
{10, nullptr, "GetProcessWindingController"},
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
@@ -28,6 +28,93 @@ public:
RegisterHandlers(functions);
}
private:
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ICommonStateGetter>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetSelfController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISelfController>(nvflinger);
NGLOG_DEBUG(Service_AM, "called");
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IWindowController>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetAudioController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IAudioController>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDisplayController>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetProcessWindingController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IProcessWindingController>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IDebugFunctions>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletCreator>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>();
NGLOG_DEBUG(Service_AM, "called");
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
};
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &ISystemAppletProxy::GetSelfController, "GetSelfController"},
{2, &ISystemAppletProxy::GetWindowController, "GetWindowController"},
{3, &ISystemAppletProxy::GetAudioController, "GetAudioController"},
{4, &ISystemAppletProxy::GetDisplayController, "GetDisplayController"},
{10, nullptr, "GetProcessWindingController"},
{11, &ISystemAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
RegisterHandlers(functions);
}
private:
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -78,16 +165,43 @@ private:
NGLOG_DEBUG(Service_AM, "called");
}
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationFunctions>();
rb.PushIpcInterface<IHomeMenuFunctions>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IGlobalStateController>();
NGLOG_DEBUG(Service_AM, "called");
}
void GetApplicationCreator(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IApplicationCreator>();
NGLOG_DEBUG(Service_AM, "called");
}
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
};
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger);
NGLOG_DEBUG(Service_AM, "called");
}
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger);
NGLOG_DEBUG(Service_AM, "called");
}
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -98,9 +212,9 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) {
static const FunctionInfo functions[] = {
{100, nullptr, "OpenSystemAppletProxy"},
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
{200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
{201, nullptr, "OpenLibraryAppletProxy"},
{201, &AppletAE::OpenLibraryAppletProxy, "OpenLibraryAppletProxy"},
{300, nullptr, "OpenOverlayAppletProxy"},
{350, nullptr, "OpenSystemApplicationProxy"},
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},

View File

@@ -21,6 +21,8 @@ public:
~AppletAE() = default;
private:
void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;

View File

@@ -60,7 +60,10 @@ private:
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
// TODO(shinyquagsire23): sticks, gyro, touch, mouse, keyboard
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
// TODO(shinyquagsire23): gyro, touch, mouse, keyboard
}
void UpdatePadCallback(u64 userdata, int cycles_late) {
@@ -79,61 +82,70 @@ private:
controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
ControllerLayout& layout = mem.controllers[Controller_Handheld].layouts[index];
layout.header.num_entries = HID_NUM_ENTRIES;
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
for (size_t controller = 0; controller < mem.controllers.size(); controller++) {
for (int index = 0; index < HID_NUM_LAYOUTS; index++) {
ControllerLayout& layout = mem.controllers[controller].layouts[index];
layout.header.num_entries = HID_NUM_ENTRIES;
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
// each with a timestamp in number of samples since boot.
layout.header.timestamp_ticks = CoreTiming::GetTicks();
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
// each with a timestamp in number of samples since boot.
layout.header.timestamp_ticks = CoreTiming::GetTicks();
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
entry.timestamp++;
entry.timestamp_2++; // TODO(shinyquagsire23): Is this always identical to timestamp?
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
entry.timestamp++;
// TODO(shinyquagsire23): Is this always identical to timestamp?
entry.timestamp_2++;
// TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
// For now everything is just the default handheld layout, but split Joy-Con will
// rotate the face buttons and directions for certain layouts.
ControllerPadState& state = entry.buttons;
using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
// TODO(shinyquagsire23): More than just handheld input
if (controller != Controller_Handheld)
continue;
state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
// TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
// For now everything is just the default handheld layout, but split Joy-Con will
// rotate the face buttons and directions for certain layouts.
ControllerPadState& state = entry.buttons;
using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
// TODO(shinyquagsire23): Analog stick vals
state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
// TODO(shinyquagsire23): Update pad info proper, (circular buffers, timestamps,
// layouts)
const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
}
}
// TODO(bunnei): Properly implement the touch screen, the below will just write empty data
@@ -151,6 +163,71 @@ private:
touchscreen.entries[curr_entry].header.timestamp = sample_counter;
touchscreen.entries[curr_entry].header.num_touches = 0;
// TODO(shinyquagsire23): Properly implement mouse
Mouse& mouse = mem.mouse;
const u64 last_mouse_entry = mouse.header.latest_entry;
const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
mouse.header.timestamp_ticks = timestamp;
mouse.header.num_entries = mouse.entries.size();
mouse.header.max_entry_index = mouse.entries.size();
mouse.header.latest_entry = curr_mouse_entry;
mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
// TODO(shinyquagsire23): Properly implement keyboard
Keyboard& keyboard = mem.keyboard;
const u64 last_keyboard_entry = keyboard.header.latest_entry;
const u64 curr_keyboard_entry =
(keyboard.header.latest_entry + 1) % keyboard.entries.size();
const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
keyboard.header.timestamp_ticks = timestamp;
keyboard.header.num_entries = keyboard.entries.size();
keyboard.header.latest_entry = last_keyboard_entry;
keyboard.header.max_entry_index = keyboard.entries.size();
keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
// TODO(shinyquagsire23): Figure out what any of these are
for (size_t i = 0; i < mem.unk_input_1.size(); i++) {
UnkInput1& input = mem.unk_input_1[i];
const u64 last_input_entry = input.header.latest_entry;
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
input.header.timestamp_ticks = timestamp;
input.header.num_entries = input.entries.size();
input.header.latest_entry = last_input_entry;
input.header.max_entry_index = input.entries.size();
input.entries[curr_input_entry].timestamp = input_sample_counter;
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
}
for (size_t i = 0; i < mem.unk_input_2.size(); i++) {
UnkInput2& input = mem.unk_input_2[i];
input.header.timestamp_ticks = timestamp;
input.header.num_entries = 17;
input.header.latest_entry = 0;
input.header.max_entry_index = 0;
}
UnkInput3& input = mem.unk_input_3;
const u64 last_input_entry = input.header.latest_entry;
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
input.header.timestamp_ticks = timestamp;
input.header.num_entries = input.entries.size();
input.header.latest_entry = last_input_entry;
input.header.max_entry_index = input.entries.size();
input.entries[curr_input_entry].timestamp = input_sample_counter;
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
// TODO(shinyquagsire23): Signal events
std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
@@ -169,6 +246,7 @@ private:
std::atomic<bool> is_device_reload_pending{true};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {

View File

@@ -48,6 +48,11 @@ enum ControllerConnectionState {
ConnectionState_Wired = 1 << 1,
};
enum ControllerJoystick {
Joystick_Left = 0,
Joystick_Right = 1,
};
enum ControllerID {
Controller_Player1 = 0,
Controller_Player2 = 1,
@@ -63,6 +68,34 @@ enum ControllerID {
// End enums and output structs
// Begin UnkInput3
struct UnkInput3Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
struct UnkInput3Entry {
u64 timestamp;
u64 timestamp_2;
u64 unk_8;
u64 unk_10;
u64 unk_18;
};
static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
struct UnkInput3 {
UnkInput3Header header;
std::array<UnkInput3Entry, 17> entries;
std::array<u8, 0x138> padding;
};
static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
// End UnkInput3
// Begin TouchScreen
struct TouchScreenHeader {
@@ -204,6 +237,52 @@ static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect s
// End Keyboard
// Begin UnkInput1
struct UnkInput1Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
struct UnkInput1Entry {
u64 timestamp;
u64 timestamp_2;
u64 unk_8;
u64 unk_10;
u64 unk_18;
};
static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
struct UnkInput1 {
UnkInput1Header header;
std::array<UnkInput1Entry, 17> entries;
std::array<u8, 0x138> padding;
};
static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
// End UnkInput1
// Begin UnkInput2
struct UnkInput2Header {
u64 timestamp_ticks;
u64 num_entries;
u64 latest_entry;
u64 max_entry_index;
};
static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
struct UnkInput2 {
UnkInput2Header header;
std::array<u8, 0x1E0> padding;
};
static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
// End UnkInput2
// Begin Controller
struct ControllerMAC {
@@ -283,10 +362,10 @@ struct ControllerInputEntry {
u64 timestamp;
u64 timestamp_2;
ControllerPadState buttons;
u32 joystick_left_x;
u32 joystick_left_y;
u32 joystick_right_x;
u32 joystick_right_y;
s32 joystick_left_x;
s32 joystick_left_y;
s32 joystick_right_x;
s32 joystick_right_y;
u64 connection_state;
};
static_assert(sizeof(ControllerInputEntry) == 0x30,
@@ -312,17 +391,12 @@ static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorr
// End Controller
struct SharedMemory {
std::array<u8, 0x400> header;
UnkInput3 unk_input_3;
TouchScreen touchscreen;
Mouse mouse;
Keyboard keyboard;
std::array<u8, 0x400> unk_section_1;
std::array<u8, 0x400> unk_section_2;
std::array<u8, 0x400> unk_section_3;
std::array<u8, 0x400> unk_section_4;
std::array<u8, 0x200> unk_section_5;
std::array<u8, 0x200> unk_section_6;
std::array<u8, 0x200> unk_section_7;
std::array<UnkInput1, 4> unk_input_1;
std::array<UnkInput2, 3> unk_input_2;
std::array<u8, 0x800> unk_section_8;
std::array<u8, 0x4000> controller_serials;
std::array<Controller, 10> controllers;

View File

@@ -32,6 +32,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u
return AllocGPFIFOEx2(input, output);
case IoctlCommand::IocAllocObjCtxCommand:
return AllocateObjectContext(input, output);
case IoctlCommand::IocChannelGetWaitbaseCommand:
return GetWaitbase(input, output);
}
if (command.group == NVGPU_IOCTL_MAGIC) {
@@ -138,4 +140,13 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
return 0;
}
u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetWaitbase params{};
std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
NGLOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
params.value = 0; // Seems to be hard coded at 0
std::memcpy(output.data(), &params, output.size());
return 0;
}
} // namespace Service::Nvidia::Devices

View File

@@ -33,6 +33,7 @@ private:
IocChannelSetPriorityCommand = 0x4004480D,
IocAllocGPFIFOEx2Command = 0xC020481A,
IocAllocObjCtxCommand = 0xC0104809,
IocChannelGetWaitbaseCommand = 0xC0080003,
};
enum class CtxObjects : u32_le {
@@ -117,7 +118,13 @@ private:
IoctlFence fence_out; // returned new fence object for others to wait on
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence),
"submit_gpfifo is incorrect size");
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
u32 unknown; // seems to be ignored? Nintendo added this
u32 value;
};
static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size");
u32_le nvmap_fd{};
u64_le user_data{};
@@ -133,6 +140,7 @@ private:
u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
};

11
src/core/memory_hook.cpp Normal file
View File

@@ -0,0 +1,11 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/memory_hook.h"
namespace Memory {
MemoryHook::~MemoryHook() = default;
} // namespace Memory

View File

@@ -23,7 +23,7 @@ namespace Memory {
*/
class MemoryHook {
public:
virtual ~MemoryHook() = default;
virtual ~MemoryHook();
virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;

View File

@@ -99,6 +99,10 @@ enum Values {
NumAnalogs,
};
constexpr int STICK_HID_BEGIN = LStick;
constexpr int STICK_HID_END = NumAnalogs;
constexpr int NUM_STICKS_HID = NumAnalogs;
static const std::array<const char*, NumAnalogs> mapping = {{
"lstick",
"rstick",