From 0219eda2db4668c77e2e1d45674e5fb53475eb64 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sun, 26 Apr 2020 05:04:35 +0530 Subject: [PATCH] Initial C++ Input Implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit contains the C++ side of the initial Input implementation, this is based on the work done in the `hid` branch in `bylaws/skyline`. Co-authored-by: ◱ PixelyIon --- .idea/jarRepositories.xml | 30 ++ app/CMakeLists.txt | 2 + app/src/main/cpp/emu_jni.cpp | 22 +- app/src/main/cpp/skyline/audio.h | 4 +- app/src/main/cpp/skyline/common.cpp | 6 +- app/src/main/cpp/skyline/common.h | 4 + app/src/main/cpp/skyline/input.cpp | 14 + app/src/main/cpp/skyline/input.h | 27 ++ app/src/main/cpp/skyline/input/common.h | 29 ++ app/src/main/cpp/skyline/input/npad.cpp | 177 ++++++++ app/src/main/cpp/skyline/input/npad.h | 397 ++++++++++++++++++ app/src/main/cpp/skyline/input/shared_mem.h | 216 ++++++++++ app/src/main/cpp/skyline/jvm.h | 2 +- app/src/main/cpp/skyline/os.h | 4 +- .../cpp/skyline/services/hid/IHidServer.cpp | 40 +- .../cpp/skyline/services/hid/IHidServer.h | 82 ---- .../java/emu/skyline/EmulationActivity.kt | 4 + 17 files changed, 949 insertions(+), 111 deletions(-) create mode 100644 .idea/jarRepositories.xml create mode 100644 app/src/main/cpp/skyline/input.cpp create mode 100644 app/src/main/cpp/skyline/input.h create mode 100644 app/src/main/cpp/skyline/input/common.h create mode 100644 app/src/main/cpp/skyline/input/npad.cpp create mode 100644 app/src/main/cpp/skyline/input/npad.h create mode 100644 app/src/main/cpp/skyline/input/shared_mem.h diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..e34606cc --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 8b1e4852..02edf8ee 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -45,6 +45,8 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu/syncpoint.cpp ${source_DIR}/skyline/gpu/texture.cpp ${source_DIR}/skyline/gpu/engines/maxwell_3d.cpp + ${source_DIR}/skyline/input.cpp + ${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/nro.cpp diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 84b9b07c..7fa83958 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -7,6 +7,7 @@ #include "skyline/common.h" #include "skyline/os.h" #include "skyline/jvm.h" +#include "skyline/input.h" bool Halt; jobject Surface; @@ -14,6 +15,7 @@ uint FaultCount; skyline::GroupMutex JniMtx; skyline::u16 fps; skyline::u32 frametime; +skyline::input::Input *input; void signalHandler(int signal) { syslog(LOG_ERR, "Halting program due to signal: %s", strsignal(signal)); @@ -50,30 +52,33 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( try { skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath)); + input = os.state.input.get(); env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath); auto romUri = env->GetStringUTFChars(romUriJstring, nullptr); logger->Info("Launching ROM {}", romUri); env->ReleaseStringUTFChars(romUriJstring, romUri); + os.Execute(romFd, static_cast(romType)); } catch (std::exception &e) { logger->Error(e.what()); } catch (...) { logger->Error("An unknown exception has occurred"); } + logger->Info("Emulation has ended"); auto end = std::chrono::steady_clock::now(); logger->Info("Done in: {} ms", (std::chrono::duration_cast(end - start).count())); } -extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setHalt(JNIEnv *env, jobject instance, jboolean halt) { +extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setHalt(JNIEnv *, jobject, jboolean halt) { JniMtx.lock(skyline::GroupMutex::Group::Group2); Halt = halt; JniMtx.unlock(); } -extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *env, jobject instance, jobject surface) { +extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *env, jobject, jobject surface) { JniMtx.lock(skyline::GroupMutex::Group::Group2); if (!env->IsSameObject(Surface, nullptr)) env->DeleteGlobalRef(Surface); @@ -84,10 +89,19 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv * JniMtx.unlock(); } -extern "C" JNIEXPORT jint Java_emu_skyline_EmulationActivity_getFps(JNIEnv *env, jobject thiz) { +extern "C" JNIEXPORT jint Java_emu_skyline_EmulationActivity_getFps(JNIEnv *, jobject ) { return fps; } -extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIEnv *env, jobject thiz) { +extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIEnv *, jobject ) { return static_cast(frametime) / 100; } + +extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jlong id, jint state) { + skyline::input::npad::NpadButton button{.raw = static_cast(id)}; + input->npad[0]->SetButtonState(button, static_cast(state)); +} + +extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint id, jint value) { + input->npad[0]->SetAxisValue(static_cast(id), value); +} diff --git a/app/src/main/cpp/skyline/audio.h b/app/src/main/cpp/skyline/audio.h index 1dfa6dce..711302e7 100644 --- a/app/src/main/cpp/skyline/audio.h +++ b/app/src/main/cpp/skyline/audio.h @@ -31,13 +31,13 @@ namespace skyline::audio { * @param releaseCallback The callback to call when a buffer has been released * @return A shared pointer to a new AudioTrack object */ - std::shared_ptr OpenTrack(u8 channelCount, u32 sampleRate, const std::function &releaseCallback); + std::shared_ptr OpenTrack(u8 channelCount, u32 sampleRate, const std::function &releaseCallback); /** * @brief Closes a track and frees its data * @param track The track to close */ - void CloseTrack(std::shared_ptr &track); + void CloseTrack(std::shared_ptr &track); /** * @brief The callback oboe uses to get audio sample data diff --git a/app/src/main/cpp/skyline/common.cpp b/app/src/main/cpp/skyline/common.cpp index 461cf417..299a31fd 100644 --- a/app/src/main/cpp/skyline/common.cpp +++ b/app/src/main/cpp/skyline/common.cpp @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include "common.h" #include "nce.h" #include "gpu.h" #include "audio.h" -#include -#include +#include "input.h" +#include "kernel/types/KThread.h" namespace skyline { void Mutex::lock() { @@ -154,6 +155,7 @@ namespace skyline { nce = std::make_shared(*this); gpu = std::make_shared(*this); audio = std::make_shared(*this); + input = std::make_shared(*this); } thread_local std::shared_ptr DeviceState::thread = nullptr; diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index b4b7e809..e93881e2 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -364,6 +364,9 @@ namespace skyline { namespace audio { class Audio; } + namespace input { + class Input; + } namespace loader { class Loader; } @@ -381,6 +384,7 @@ namespace skyline { std::shared_ptr nce; //!< This holds a reference to the NCE class std::shared_ptr gpu; //!< This holds a reference to the GPU class std::shared_ptr audio; //!< This holds a reference to the Audio class + std::shared_ptr input; //!< This holds a reference to the Input class std::shared_ptr loader; //!< This holds a reference to the Loader class std::shared_ptr jvm; //!< This holds a reference to the JvmManager class std::shared_ptr settings; //!< This holds a reference to the Settings class diff --git a/app/src/main/cpp/skyline/input.cpp b/app/src/main/cpp/skyline/input.cpp new file mode 100644 index 00000000..d0d36d21 --- /dev/null +++ b/app/src/main/cpp/skyline/input.cpp @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "input.h" + +namespace skyline::input { + Input::Input(const DeviceState &state) : state(state), commonNpad(std::make_shared(state)), hidKMem(std::make_shared(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))) { + hidMem = reinterpret_cast(hidKMem->kernel.address); + + for (uint i = 0; i < constant::NpadCount; i++) { + npad.at(i) = std::make_shared(hidMem->npad.at(i), npad::IndexToNpadId(i)); + } + } +} diff --git a/app/src/main/cpp/skyline/input.h b/app/src/main/cpp/skyline/input.h new file mode 100644 index 00000000..349c709d --- /dev/null +++ b/app/src/main/cpp/skyline/input.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "common.h" +#include "kernel/types/KSharedMemory.h" +#include "input/common.h" + +namespace skyline::input { + /** + * @brief The Input class manages input devices + */ + class Input { + private: + const DeviceState &state; //!< The state of the device + + public: + Input(const DeviceState &state); + + std::shared_ptr commonNpad; //!< The common npad device + std::array, constant::NpadCount> npad; //!< Array of npad devices + + std::shared_ptr hidKMem; //!< The shared memory reserved for HID input + HidSharedMemory *hidMem; //!< A pointer to the root of HID shared memory + }; +} diff --git a/app/src/main/cpp/skyline/input/common.h b/app/src/main/cpp/skyline/input/common.h new file mode 100644 index 00000000..acc91620 --- /dev/null +++ b/app/src/main/cpp/skyline/input/common.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "npad.h" + +namespace skyline::input { + /** + * @brief Encapsulates hid shared memory + */ + struct HidSharedMemory { + DebugPadSection debugPad; //!< The debug pad section + TouchScreenSection touchScreen; //!< The touch screen section + MouseSection mouse; //!< The mouse section + KeyboardSection keyboard; //!< The keyboard section + std::array xpad; //!< The xpads section + HomeButtonSection homeButton; //!< The home button section + SleepButtonSection sleepButton; //!< The sleep button section + CaptureButtonSection captureButton; //!< The capture button section + std::array inputDetector; //!< The input detectors section + u64 _pad0_[0x80 * 0x10]; //!< Unique pad (<5.0.0) + std::array npad; //!< The npads section + GestureSection gesture; //!< The gesture section + ConsoleSixAxisSensorSection consoleSixAxisSensor; //!< The gyro/accel section + u64 _pad1_[0x7BC]; + }; + static_assert(sizeof(HidSharedMemory) == 0x40000); +} diff --git a/app/src/main/cpp/skyline/input/npad.cpp b/app/src/main/cpp/skyline/input/npad.cpp new file mode 100644 index 00000000..2d69cd74 --- /dev/null +++ b/app/src/main/cpp/skyline/input/npad.cpp @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "npad.h" + +namespace skyline::input::npad { + u32 NpadIdToIndex(NpadId id) { + switch (id) { + case NpadId::Unknown: + return 8; + case NpadId::Handheld: + return 9; + default: + return static_cast(id); + } + } + + NpadId IndexToNpadId(u32 index) { + switch (index) { + case 8: + return NpadId::Unknown; + case 9: + return NpadId::Handheld; + default: + return static_cast(index); + } + } + + NpadDevice::NpadDevice(NpadSection &shmemSection, NpadId id) : shmemSection(shmemSection), id(id) {} + + void NpadDevice::Disconnect() { + connectionState.connected = false; + } + + void NpadDevice::Connect(NpadControllerType type) { + shmemSection.header.styles.raw = 0; + shmemSection.deviceType.raw = 0; + shmemSection.properties.raw = 0; + + connectionState.raw = 0; + connectionState.connected = true; + + switch (type) { + case NpadControllerType::Handheld: + shmemSection.header.styles.joyconHandheld = true; + shmemSection.deviceType.handheld = true; + shmemSection.deviceType.handheldLeft = true; + shmemSection.deviceType.handheldRight = true; + shmemSection.header.assignment = NpadJoyAssignment::Dual; + shmemSection.properties.ABXYButtonOriented = true; + shmemSection.properties.plusButtonCapability = true; + shmemSection.properties.minusButtonCapability = true; + + connectionState.handheld = true; + connectionState.leftJoyconConnected = true; + connectionState.rightJoyconConnected = true; + connectionState.leftJoyconHandheld = true; + connectionState.rightJoyconHandheld = true; + break; + case NpadControllerType::ProController: + shmemSection.header.styles.proController = true; + shmemSection.deviceType.fullKey = true; + shmemSection.deviceType.joyconRight = true; + shmemSection.header.assignment = NpadJoyAssignment::Single; + shmemSection.properties.ABXYButtonOriented = true; + shmemSection.properties.plusButtonCapability = true; + shmemSection.properties.minusButtonCapability = true; + break; + default: + throw exception("Unsupported controller type: {}", type); + } + + controllerType = type; + + shmemSection.header.singleColourStatus = NpadColourReadStatus::Success; + shmemSection.header.singleColour = {0, 0}; + + shmemSection.header.dualColourStatus = NpadColourReadStatus::Success; + shmemSection.header.leftColour = {0, 0}; //TODO: make these configurable + shmemSection.header.rightColour = {0, 0}; + + shmemSection.batteryLevel[0] = constant::NpadBatteryFull; + shmemSection.batteryLevel[1] = constant::NpadBatteryFull; + shmemSection.batteryLevel[2] = constant::NpadBatteryFull; + + // Set controllers initial state + SetButtonState(NpadButton{}, NpadButtonState::Released); + + //TODO: signal npad event + } + + NpadControllerInfo &NpadDevice::GetControllerDeviceInfo() { + switch (controllerType) { + case NpadControllerType::Handheld: + return shmemSection.handheldController; + case NpadControllerType::ProController: + default: + return shmemSection.fullKeyController; + } + } + + void NpadDevice::UpdateHeaders(NpadControllerInfo &controller, uint lastStateEntryIndex) { + controller.header.entryCount = constant::HidEntryCount; + controller.header.maxEntry = constant::HidEntryCount - 1; + controller.header.currentEntry = stateEntryIndex; + controller.header.timestamp = util::GetTimeNs(); + + memcpy(reinterpret_cast(&controller.state.at(stateEntryIndex)), reinterpret_cast(&controller.state.at(lastStateEntryIndex)), sizeof(NpadControllerState)); + + controller.state.at(stateEntryIndex).globalTimestamp++; + controller.state.at(stateEntryIndex).localTimestamp++; + controller.state.at(stateEntryIndex).status.raw = connectionState.raw; + } + + void NpadDevice::SetButtonState(NpadButton button, NpadButtonState state) { + NpadControllerInfo &controllerDeviceInfo = GetControllerDeviceInfo(); + uint lastStateEntryIndex = stateEntryIndex; + stateEntryIndex = (stateEntryIndex + 1) % constant::HidEntryCount; + + for (NpadControllerInfo &controllerInfo : {std::ref(controllerDeviceInfo), std::ref(shmemSection.systemExtController)}) { + UpdateHeaders(controllerInfo, lastStateEntryIndex); + + if (state == NpadButtonState::Pressed) + controllerInfo.state.at(stateEntryIndex).controller.buttons.raw |= button.raw; + else + controllerInfo.state.at(stateEntryIndex).controller.buttons.raw &= ~(button.raw); + } + } + + void NpadDevice::SetAxisValue(NpadAxisId axis, int value) { + NpadControllerInfo controllerDeviceInfo = GetControllerDeviceInfo(); + uint lastStateEntryIndex = stateEntryIndex; + stateEntryIndex = (stateEntryIndex + 1) % constant::HidEntryCount; + + for (NpadControllerInfo &controllerInfo : {std::ref(controllerDeviceInfo), std::ref(shmemSection.systemExtController)}) { + UpdateHeaders(controllerInfo, lastStateEntryIndex); + + switch (axis) { + case NpadAxisId::LX: + controllerInfo.state.at(stateEntryIndex).controller.leftX = value; + break; + case NpadAxisId::LY: + controllerInfo.state.at(stateEntryIndex).controller.leftY = -value; // Invert Y axis + break; + case NpadAxisId::RX: + controllerInfo.state.at(stateEntryIndex).controller.rightX = value; + break; + case NpadAxisId::RY: + controllerInfo.state.at(stateEntryIndex).controller.rightY = -value; // Invert Y axis + break; + } + } + } + + CommonNpad::CommonNpad(const DeviceState &state) : state(state) {} + + void CommonNpad::Activate() { + // Always mark controllers we support as supported + if (supportedStyles.raw == 0) { + supportedStyles.proController = true; + supportedStyles.joyconHandheld = true; + } + + for (uint i = 0; i < constant::NpadCount; i++) { + bool shouldConnect = (i == 0); // P1 + + //TODO: Read this as well as controller type from settings based off of NpadID + if (shouldConnect) { + if (state.settings->GetBool("operation_mode")) + state.input->npad.at(i)->Connect(NpadControllerType::Handheld); + else + state.input->npad.at(i)->Connect(NpadControllerType::ProController); + } + } + } +} diff --git a/app/src/main/cpp/skyline/input/npad.h b/app/src/main/cpp/skyline/input/npad.h new file mode 100644 index 00000000..d98f01e7 --- /dev/null +++ b/app/src/main/cpp/skyline/input/npad.h @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "shared_mem.h" + +namespace skyline { + namespace constant { + constexpr u32 NpadBatteryFull = 2; //!< The full battery state of an npad + constexpr u8 NpadCount = 10; //!< The number of npads in shared memory + } + + namespace input::npad { + union NpadButton { + struct { + bool a : 1; //!< The A button + bool b : 1; //!< The B button + bool x : 1; //!< The X button + bool y : 1; //!< The Y button + bool l3 : 1; //!< The L3 button + bool r3 : 1; //!< The R3 button + bool l : 1; //!< The L trigger + bool r : 1; //!< The R button + bool zl : 1; //!< The ZL trigger + bool zr : 1; //!< The ZR trigger + bool plus : 1; //!< The + button + bool minus : 1; //!< The - button + bool dpadLeft : 1; //!< D-Pad left + bool dpadUp : 1; //!< D-Pad up + bool dpadRight : 1; //!< D-Pad right + bool dpadDown : 1; //!< D-Pad down + bool leftStickLeft : 1; //!< Left stick left + bool leftStickUp : 1; //!< Left stick up + bool leftStickRight : 1; //!< Left stick right + bool leftStickDown : 1; //!< Left stick down + bool rightStickLeft : 1; //!< Right stick left + bool rightStickUp : 1; //!< Right stick up + bool rightStickRight : 1; //!< Right stick right + bool rightStickDown : 1; //!< Right stick down + bool leftSL : 1; //!< Left Joy-Con SL button + bool leftSr : 1; //!< Left Joy-Con SR button + bool rightSl : 1; //!< Right Joy-Con SL button + bool rightSr : 1; //!< Right Joy-Con SR button + bool touch : 1; //!< The touch button + }; + u64 raw; + }; + static_assert(sizeof(NpadButton) == 0x8); + + enum class NpadAxisId { + RX, //!< Right stick X + RY, //!< Right stick Y + LX, //!< Left stick X + LY //!< Left stick Y + }; + + enum class NpadButtonState : bool { + Released = false, //!< Released button + Pressed = true //!< Pressed button + }; + + /** + * @brief This holds the controller styles supported + */ + union NpadStyleSet { + struct { + bool proController : 1; //!< The Pro Controller + bool joyconHandheld : 1; //!< Joy-Cons in handheld mode + bool joyconDual : 1; //!< Joy-Cons in a pair + bool joyconLeft : 1; //!< Left Joy-Con only + bool joyconRight : 1; //!< Right Joy-Con only + bool gamecube : 1; //!< GameCube controller + bool palma : 1; //!< Poké Ball Plus controller + bool nes : 1; //!< NES controller + bool nesHandheld : 1; //!< NES controller in handheld mode + bool snes : 1; //!< SNES controller + }; + u32 raw; + }; + static_assert(sizeof(NpadStyleSet) == 0x4); + + /** + * @brief This holds a Controller's ID (https://switchbrew.org/wiki/HID_services#NpadIdType) + */ + enum class NpadId : u32 { + Player1 = 0x0, //!< 1st Player + Player2 = 0x1, //!< 2nd Player + Player3 = 0x2, //!< 3rd Player + Player4 = 0x3, //!< 4th Player + Player5 = 0x4, //!< 5th Player + Player6 = 0x5, //!< 6th Player + Player7 = 0x6, //!< 7th Player + Player8 = 0x7, //!< 8th Player + Unknown = 0x10, //!< Unknown + Handheld = 0x20 //!< Handheld mode + }; + + /** + * @brief This denotes the orientation of the Joy-Con(s) + */ + enum class NpadJoyOrientation : u64 { + Vertical = 0, //!< The Joy-Con is held vertically + Horizontal = 1, //!< The Joy-Con is held horizontally + Unset = 2 //!< Not set + }; + + /** + * @brief This denotes the assignment of the Joy-Con(s) + */ + enum class NpadJoyAssignment : u32 { + Dual = 0, //!< Dual Joy-Cons + Single = 1, //!< Single Joy-Con + Unset = 2 //!< Not set + }; + + /** + * @brief This denotes the colour read status of an npad + */ + enum class NpadColourReadStatus : u32 { + Success = 0, //!< Success + Invalid = 1, //!< Invalid color + Disconnected = 2 //!< Controller not connected + }; + + /** + * @brief This denotes the type of an npad + */ + enum class NpadControllerType { + None, //!< Nothing + ProController, //!< A Pro Controller + Handheld, //!< Handheld mode + JoyconDual, //!< Dual Joy-Cons + JoyconLeft, //!< Left Joy-Con + JoyconRight, //!< Right Joy-Con + Palma, //!< Poké Ball Plus + }; + + /** + * @brief This denotes the connection state of an npad + */ + union NpadConnectionState { + struct { + bool connected : 1; //!< Is connected + bool handheld : 1; //!< Is in handheld mode + bool leftJoyconConnected : 1; //!< Is the left Joy-Con connected + bool leftJoyconHandheld : 1; //!< Is the left Joy-Con handheld + bool rightJoyconConnected : 1; //!< Is the right Joy-Con connected + bool rightJoyconHandheld : 1; //!< Is the right Joy-Con handheld + }; + u64 raw; + }; + static_assert(sizeof(NpadConnectionState) == 0x8); + + /** + * @brief This denotes the device type of an npad + */ + union NpadDeviceType { + struct { + bool fullKey : 1; //!< Pro/GC controller + bool handheld : 1; //!< Handheld mode + bool handheldLeft : 1; //!< Joy-Con/Famicom/NES left controller + bool handheldRight : 1; //!< Joy-Con/Famicom/NES right controller + bool joyconLeft : 1; //!< Left Joy-Con + bool joyconRight : 1; //!< Right Joy-Con + bool palma : 1; //!< Pokeball Plus controller + bool larkLeftFamicom : 1; //!< Famicom left Joy-Con + bool larkRightFamicom : 1;//!< Famicom right Joy-Con + bool larkLeftNES : 1; //!< NES left Joy-Con + bool larkRightNES : 1; //!< NES right Joy-Con + u32 _unk1_ : 4; + bool systemExt : 1; //!< Generic external controller + u32 _unk2_ : 14; + bool system : 1; //!< Generic controller + }; + u32 raw; + }; + static_assert(sizeof(NpadDeviceType) == 0x4); + + /** + * @brief This denotes the system properties of the npad + */ + union NpadSystemProperties { + struct { + bool powerInfo0Charging : 1; //!< Info 0 Charging + bool powerInfo1Charging : 1; //!< Info 1 Charging + bool powerInfo2Charging : 1; //!< Info 2 Charging + bool powerInfo0PowerConnected : 1; //!< Info 0 Connected + bool powerInfo1PowerConnected : 1; //!< Info 1 Connected + bool powerInfo2PowerConnected : 1; //!< Info 2 Connected + u64 _unk_ : 3; + bool unsupportedButtonPressedSystem : 1; //!< Unsupported buttons are pressed on system controller + bool unsupportedButtonPressedSystemExt : 1; //!< Unsupported buttons are pressed on system external controller + bool ABXYButtonOriented : 1; //!< Are the ABXY Buttons oriented + bool SLSRuttonOriented : 1; //!< Are the SLSR Buttons oriented + bool plusButtonCapability : 1; //!< Does the + button exist + bool minusButtonCapability : 1; //!< Does the - button exist + bool directionalButtonsSupported : 1; //!< Does the controller have a D-Pad + }; + u64 raw; + }; + static_assert(sizeof(NpadSystemProperties) == 0x8); + + /** + * @brief This denotes the system button properties of the npad + */ + union NpadSystemButtonProperties { + struct { + bool unintendedHomeButtonInputProtectionEnabled : 1; //!< Is unintended home button input protection enabled + }; + u32 raw; + }; + static_assert(sizeof(NpadSystemButtonProperties) == 0x4); + + /** + * @brief This denotes the colour of an npad + */ + struct NpadColour { + u32 bodyColour; //!< The body colour + u32 buttonColour; //!< The button colour + }; + static_assert(sizeof(NpadColour) == 0x8); + + /** + * @brief This is the header of an npad entry + */ + struct NpadHeader { + NpadStyleSet styles; //!< The style set + NpadJoyAssignment assignment; //!< The pad assignment + + NpadColourReadStatus singleColourStatus; //!< The single colour status + NpadColour singleColour; //!< The colours + + NpadColourReadStatus dualColourStatus; //!< The dual colour status + NpadColour rightColour; //!< The right colours + NpadColour leftColour; //!< The left colours + }; + static_assert(sizeof(NpadHeader) == 0x28); + + /** + * @brief This contains controller input data + */ + struct NpadController { + NpadButton buttons; //!< The pressed buttons + + u32 leftX; //!< The left stick X + u32 leftY; //!< The left stick Y + + u32 rightX; //!< The right stick X + u32 rightY; //!< The right stick Y + }; + static_assert(sizeof(NpadController) == 0x18); + + /** + * @brief This contains info about controller input data + */ + struct NpadControllerState { + u64 globalTimestamp; //!< The global timestamp + u64 localTimestamp; //!< The local timestamp + NpadController controller; //!< The npad controller + NpadConnectionState status; //!< The npad connection status + }; + static_assert(sizeof(NpadControllerState) == 0x30); + + /** + * @brief This contains all the input states + */ + struct NpadControllerInfo { + CommonHeader header; //!< The common data header + std::array state; //!< The npad state + }; + static_assert(sizeof(NpadControllerInfo) == 0x350); + + /** + * @brief An npad section in shared memory + */ + struct NpadSection { + NpadHeader header; //!< The npad header + + NpadControllerInfo fullKeyController; //!< The full key controller + NpadControllerInfo handheldController; //!< The handheld controller + NpadControllerInfo dualController; //!< The dual Joy-Con controller + NpadControllerInfo leftController; //!< The left Joy-Con controller + NpadControllerInfo rightController; //!< The right Joy-Con controller + NpadControllerInfo pokeballController; //!< The Pokeball Plus controller + NpadControllerInfo systemExtController; //!< The system external controller + + u64 _unk_[0xE1 * 6]; //!< Unused sixaxis data + + NpadDeviceType deviceType; //!< The device type + + u32 _pad0_; + + NpadSystemProperties properties; //!< The system properties + NpadSystemButtonProperties buttonProperties; //!< The button properties + + u32 batteryLevel[3]; //!< The battery level reported + + u32 _pad1_[0x395]; + }; + static_assert(sizeof(NpadSection) == 0x5000); + + /** + * @brief Converts the ID of an npad to the index in shared memory + * @param id The ID of an npad + * @return The index in shared memory + */ + u32 NpadIdToIndex(NpadId id); + + /** + * @brief Converts the index in shared memory to the ID of an npad + * @param id The index in shared memory + * @return The ID of the npad + */ + NpadId IndexToNpadId(u32 index); + + class NpadDevice { + private: + NpadId id; //!< The ID of the npad + NpadControllerType controllerType{NpadControllerType::None}; //!< The type of controller + uint stateEntryIndex{}; //!< The index of the current state entry + + NpadConnectionState connectionState{}; //!< The state of the connection + NpadSection &shmemSection; //!< The section in HID shared memory for this controller + + /** + * @brief Updates headers for a new shared memory entry + * @param controller The controller to operate on + * @param lastStateEntryIndex The index of the previous state entry + */ + void UpdateHeaders(NpadControllerInfo &controller, uint lastStateEntryIndex); + + /** + * @return The controller device info appropriate for the controllers type + */ + NpadControllerInfo &GetControllerDeviceInfo(); + + public: + bool supported{false}; //!< If the npad marked as supported + + /** + * @param shmemSection A reference to the controllers shared memory region + * @param id The ID of the npad + */ + NpadDevice(NpadSection &shmemSection, NpadId id); + + /** + * @brief Sets the joycon assignment in shared memory + * @param assignment The assignment to set + */ + inline void SetAssignment(NpadJoyAssignment assignment) { + shmemSection.header.assignment = assignment; + } + + /** + * @brief Connects a controller to the guest + * @param type The type of controller to connect + */ + void Connect(NpadControllerType type); + + /** + * @brief Disconnects the controller from the guest + */ + void Disconnect(); + + /** + * @brief Changes a button's state on the virtual controller + * @param button The button work on + * @param state Whether to release or press the button + */ + void SetButtonState(NpadButton button, NpadButtonState state); + + /** + * @brief Sets an axis to a value on the virtual controller + * @param axis The axis to change + * @param value The value to use + */ + void SetAxisValue(NpadAxisId axis, int value); + }; + + class CommonNpad { + private: + const DeviceState &state; //!< The state of the device + + public: + NpadStyleSet supportedStyles{}; //!< The supported style sets + NpadJoyOrientation orientation{NpadJoyOrientation::Unset}; //!< The Joy-Con orientation to use + + CommonNpad(const DeviceState &state); + + /** + * @brief Activates npad support + */ + void Activate(); + }; + } +} diff --git a/app/src/main/cpp/skyline/input/shared_mem.h b/app/src/main/cpp/skyline/input/shared_mem.h new file mode 100644 index 00000000..ceab690f --- /dev/null +++ b/app/src/main/cpp/skyline/input/shared_mem.h @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include + +namespace skyline { + namespace constant { + constexpr u8 HidEntryCount = 17; //!< The amount of state entries in each hid device + } + + namespace input { + struct CommonHeader { + u64 timestamp; + u64 entryCount; + u64 currentEntry; + u64 maxEntry; + }; + static_assert(sizeof(CommonHeader) == 0x20); + + + + struct DebugPadState { + u64 timestamp; + u8 _unk_[0x20]; + }; + static_assert(sizeof(DebugPadState) == 0x28); + + struct DebugPadSection { + CommonHeader header; + std::array state; + u64 _pad_[0x27]; + }; + static_assert(sizeof(DebugPadSection) == 0x400); + + + + struct TouchScreenStateData { + u64 timestamp; + u32 _pad0_; + + u32 index; + + u32 positionX; + u32 positionY; + + u32 diameterX; + u32 diameterY; + + u32 angle; + u32 _pad1_; + }; + static_assert(sizeof(TouchScreenStateData) == 0x28); + + struct TouchScreenState { + u64 globalTimestamp; + u64 localTimestamp; + + u64 touchCount; + std::array data; + }; + static_assert(sizeof(TouchScreenState) == 0x298); + + struct TouchScreenSection { + CommonHeader header; + std::array state; + u64 _pad_[0x79]; + + }; + static_assert(sizeof(TouchScreenSection) == 0x3000); + + + + struct MouseState { + u64 globalTimestamp; + u64 localTimestamp; + + u32 positionX; + u32 positionY; + + u32 changeX; + u32 changeY; + + u32 scrollChangeY; + u32 scrollChangeX; + + u64 buttons; + }; + static_assert(sizeof(MouseState) == 0x30); + + struct MouseSection { + CommonHeader header; + std::array state; + u64 _pad_[22]; + }; + static_assert(sizeof(MouseSection) == 0x400); + + + + struct KeyboardState { + u64 globalTimestamp; + u64 localTimestamp; + + u64 modifers; + u64 keysDown[4]; + }; + static_assert(sizeof(KeyboardState) == 0x38); + + struct KeyboardSection { + CommonHeader header; + std::array state; + u64 _pad_[5]; + }; + static_assert(sizeof(KeyboardSection) == 0x400); + + + + struct BasicXpadState { + u64 globalTimestamp; + u64 _unk_[4]; + }; + static_assert(sizeof(BasicXpadState) == 0x28); + + struct BasicXpadSection { + CommonHeader header; + std::array state; + u64 _pad_[39]; + }; + static_assert(sizeof(BasicXpadSection) == 0x400); + + + + struct HomeButtonState { + u64 globalTimestamp; + u64 _unk_[2]; + }; + static_assert(sizeof(HomeButtonState) == 0x18); + + struct HomeButtonSection { + CommonHeader header; + std::array state; + u64 _pad_[9]; + }; + static_assert(sizeof(HomeButtonSection) == 0x200); + + + + struct SleepButtonState { + u64 globalTimestamp; + u64 _unk_[2]; + }; + static_assert(sizeof(SleepButtonState) == 0x18); + + struct SleepButtonSection { + CommonHeader header; + std::array state; + u64 _pad_[9]; + }; + static_assert(sizeof(SleepButtonSection) == 0x200); + + + + struct CaptureButtonState { + u64 globalTimestamp; + u64 _unk_[2]; + }; + static_assert(sizeof(CaptureButtonState) == 0x18); + + struct CaptureButtonSection { + CommonHeader header; + std::array state; + u64 _pad_[9]; + }; + static_assert(sizeof(CaptureButtonSection) == 0x200); + + + struct InputDetectorState { + u64 globalTimestamp; + u64 _unk_[2]; + }; + static_assert(sizeof(InputDetectorState) == 0x18); + + struct InputDetectorSection { + CommonHeader header; + std::array state; + u64 _pad_[6]; + }; + static_assert(sizeof(InputDetectorSection) == 0x80); + + + struct GestureState { + u64 globalTimestamp; + u64 _unk_[12]; + }; + static_assert(sizeof(GestureState) == 0x68); + + struct GestureSection { + CommonHeader header; + std::array state; + u64 _pad_[31]; + }; + static_assert(sizeof(GestureSection) == 0x800); + + struct ConsoleSixAxisSensorSection { + u64 timestamp; + bool resting : 1; + u32 _pad0_ : 3; + u32 verticalizationError; + u32 gyroBias[3]; + u32 _pad1_; + }; + static_assert(sizeof(ConsoleSixAxisSensorSection) == 0x20); + } +} diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index e4c06ef8..52dcaae1 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -3,7 +3,7 @@ #pragma once -#include +#include "common.h" #include namespace skyline { diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 9f937864..6a7ff8d6 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -18,10 +18,8 @@ namespace skyline::kernel { * @brief The OS class manages the interaction between Skyline components and the underlying OS in NCE */ class OS { - private: - DeviceState state; //!< The state of the device - public: + DeviceState state; //!< The state of the device std::shared_ptr process; //!< The KProcess object for the emulator, representing the guest process service::ServiceManager serviceManager; //!< This manages all of the service functions MemoryManager memory; //!< The MemoryManager object for this process diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp index fcb4d0b9..26b47198 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include "IHidServer.h" +using namespace skyline::input; + namespace skyline::service::hid { IHidServer::IHidServer(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::hid_IHidServer, "hid:IHidServer", { {0x0, SFUNC(IHidServer::CreateAppletResource)}, @@ -13,50 +16,53 @@ namespace skyline::service::hid { {0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)}, {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)} - }) {} + }) { + state.input->commonNpad->Activate(); + } void IHidServer::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - resource = std::make_shared(state, manager); - manager.RegisterService(resource, session, response); + manager.RegisterService(SRVREG(IAppletResource), session, response); } void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto styleSet = request.Pop(); + auto styleSet = request.Pop(); + state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast(styleSet.proController), static_cast(styleSet.joyconHandheld), static_cast(styleSet.joyconDual), static_cast(styleSet.joyconLeft), static_cast - (styleSet.joyconRight), static_cast(styleSet.gamecube), static_cast(styleSet.pokeball), static_cast(styleSet.nes), static_cast(styleSet.nesHandheld), static_cast(styleSet.snes)); + (styleSet.joyconRight), static_cast(styleSet.gamecube), static_cast(styleSet.palma), static_cast(styleSet.nes), static_cast(styleSet.nesHandheld), static_cast(styleSet.snes)); } void IHidServer::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { const auto &buffer = request.inputBuf.at(0); - size_t numId = buffer.size / sizeof(NpadId); + size_t numId = buffer.size / sizeof(npad::NpadId); u64 address = buffer.address; for (size_t i = 0; i < numId; i++) { - auto id = state.process->GetObject(address); - deviceMap[id] = JoyConDevice(id); - address += sizeof(NpadId); + auto id = state.process->GetObject(address); + state.input->npad.at(NpadIdToIndex(id))->supported = true; + + address += sizeof(npad::NpadId); } } void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - deviceMap[request.Pop()].assignment = JoyConAssignment::Single; + auto appletResourceUID = request.Pop(); + state.input->commonNpad->orientation = request.Pop(); } void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - orientation = request.Pop(); + auto id = request.Pop(); + state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Single); } void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto controllerId = request.Pop(); - auto appletUserId = request.Pop(); - - deviceMap[controllerId].assignment = JoyConAssignment::Single; - deviceMap[controllerId].side = request.Pop(); + auto id = request.Pop(); + state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Single); } void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - deviceMap[request.Pop()].assignment = JoyConAssignment::Dual; + auto id = request.Pop(); + state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Dual); } } diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.h b/app/src/main/cpp/skyline/services/hid/IHidServer.h index 24529cfc..ae326bc8 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.h +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.h @@ -12,88 +12,6 @@ namespace skyline::service::hid { * @brief IHidServer or hid service is used to access input devices (https://switchbrew.org/wiki/HID_services#hid) */ class IHidServer : public BaseService { - private: - /** - * @brief This holds the controller styles supported by an application - */ - struct StyleSet { - bool proController : 1; //!< The Pro Controller - bool joyconHandheld : 1; //!< Joy-Cons in handheld mode - bool joyconDual : 1; //!< Joy-Cons in a pair - bool joyconLeft : 1; //!< Left Joy-Con only - bool joyconRight : 1; //!< Right Joy-Con only - bool gamecube : 1; //!< GameCube controller - bool pokeball : 1; //!< Poké Ball Plus controller - bool nes : 1; //!< NES controller - bool nesHandheld : 1; //!< NES controller in handheld mode - bool snes : 1; //!< SNES controller - u32 _pad0_ : 22; - }; - static_assert(sizeof(StyleSet) == 4); - - /** - * @brief This holds a Controller's ID (https://switchbrew.org/wiki/HID_services#NpadIdType) - */ - enum class NpadId : u32 { - Player1 = 0x0, //!< 1st Player - Player2 = 0x1, //!< 2nd Player - Player3 = 0x2, //!< 3rd Player - Player4 = 0x3, //!< 4th Player - Player5 = 0x4, //!< 5th Player - Player6 = 0x5, //!< 6th Player - Player7 = 0x6, //!< 7th Player - Player8 = 0x7, //!< 8th Player - Unknown = 0x10, //!< Unknown - Handheld = 0x20 //!< Handheld mode - }; - - /** - * @brief This holds a Controller's Assignment mode - */ - enum class JoyConAssignment { - Dual, //!< Dual Joy-Cons - Single, //!< Single Joy-Con - Unset //!< Not set - }; - - /** - * @brief This holds which Joy-Con to use Single mode (Not if SetNpadJoyAssignmentModeSingleByDefault is used) - */ - enum class JoyConSide : i64 { - Left, //!< Left Joy-Con - Right, //!< Right Joy-Con - Unset //!< Not set - }; - - /** - * @brief This denotes the orientation of the Joy-Con(s) - */ - enum class JoyConOrientation : u64 { - Vertical, //!< The Joy-Con is held vertically - Horizontal, //!< The Joy-Con is held horizontally - Unset //!< Not set - }; - - // TODO: Replace JoyConDevice with base NpadDevice class - - /** - * @brief This holds the state of a single Npad device - */ - struct JoyConDevice { - NpadId id; //!< The ID of this device - JoyConAssignment assignment{JoyConAssignment::Unset}; //!< The assignment mode of this device - JoyConSide side{JoyConSide::Unset}; //!< The type of the device - - JoyConDevice() : id(NpadId::Unknown) {} - - JoyConDevice(const NpadId &id) : id(id) {} - }; - - std::shared_ptr resource{}; //!< A shared pointer to the applet resource - std::optional styleSet; //!< The controller styles supported by the application - std::unordered_map deviceMap; //!< Mapping from a controller's ID to it's corresponding JoyConDevice - JoyConOrientation orientation{JoyConOrientation::Unset}; //!< The Orientation of the Joy-Con(s) - public: IHidServer(const DeviceState &state, ServiceManager &manager); diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index d4c03f47..bfb133a0 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -84,6 +84,10 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { */ private external fun getFrametime() : Float + private external fun setButtonState(id : Long, state : Int) + + private external fun setAxisValue(id : Int, value : Int) + /** * This executes the specified ROM, [preferenceFd] is assumed to be valid beforehand *