Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ namespace platf {
set_motion_event_state, ///< Set motion event state
set_rgb_led, ///< Set RGB LED
set_adaptive_triggers, ///< Set adaptive triggers
set_player_led, ///< Set player-indicator LEDs (DualSense)
set_mic_led, ///< Set mic-mute LED (DualSense)
};

/**
Expand Down Expand Up @@ -208,6 +210,36 @@ namespace platf {
return msg;
}

/**
* @brief Create a player-LED (player-indicator) feedback command (DualSense).
*
* @param id Identifier for the controller.
* @param ledValue Raw 5-bit player-indicator bitmask (forwarded verbatim from the game's report).
* @return Constructed player-LED object.
*/
static gamepad_feedback_msg_t make_player_led(std::uint16_t id, std::uint8_t ledValue) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_player_led;
msg.id = id;
msg.data.player_led = {ledValue};
return msg;
}

/**
* @brief Create a mic-mute-LED feedback command (DualSense).
*
* @param id Identifier for the controller.
* @param ledState Mic-LED state (0 = off, 1 = on, 2 = pulse).
* @return Constructed mic-LED object.
*/
static gamepad_feedback_msg_t make_mic_led(std::uint16_t id, std::uint8_t ledState) {
gamepad_feedback_msg_t msg;
msg.type = gamepad_feedback_e::set_mic_led;
msg.id = id;
msg.data.mic_led = {ledState};
return msg;
}

gamepad_feedback_e type; ///< Feedback command type stored in the union payload.
std::uint16_t id; ///< Controller identifier associated with this message.

Expand All @@ -233,6 +265,14 @@ namespace platf {
std::uint8_t b;
} rgb_led;

struct {
std::uint8_t value; ///< Raw 5-bit player-indicator bitmask.
} player_led;

struct {
std::uint8_t state; ///< 0 = off, 1 = on, 2 = pulse.
} mic_led;

struct {
uint16_t controllerNumber;
uint8_t event_flags;
Expand Down
20 changes: 20 additions & 0 deletions src/platform/virtualhid_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ namespace platf::virtualhid {
std::uint8_t last_red = 0; ///< Last red LED value.
std::uint8_t last_green = 0; ///< Last green LED value.
std::uint8_t last_blue = 0; ///< Last blue LED value.
bool has_last_player_led = false; ///< Whether the last player-LED value is valid.
std::uint8_t last_player_led = 0; ///< Last player-indicator LED bitmask.
bool has_last_mic_led = false; ///< Whether the last mic-LED state is valid.
std::uint8_t last_mic_led = 0; ///< Last mic-mute LED state.
};

namespace {
Expand Down Expand Up @@ -390,6 +394,22 @@ namespace platf::virtualhid {
gamepad->last_blue = output.blue;
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_rgb_led(gamepad->client_relative_index, output.red, output.green, output.blue));
break;
case lvh::GamepadOutputKind::player_led:
if (gamepad->has_last_player_led && gamepad->last_player_led == output.player_led) {
return;
}
gamepad->has_last_player_led = true;
gamepad->last_player_led = output.player_led;
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_player_led(gamepad->client_relative_index, output.player_led));
break;
case lvh::GamepadOutputKind::mic_led:
if (gamepad->has_last_mic_led && gamepad->last_mic_led == output.mic_led) {
return;
}
gamepad->has_last_mic_led = true;
gamepad->last_mic_led = output.mic_led;
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_mic_led(gamepad->client_relative_index, output.mic_led));
break;
case lvh::GamepadOutputKind::adaptive_triggers:
raise_feedback_unlocked(gamepad, gamepad_feedback_msg_t::make_adaptive_triggers(gamepad->client_relative_index, output.adaptive_trigger_flags, output.left_trigger_effect_type, output.right_trigger_effect_type, output.left_trigger_effect, output.right_trigger_effect));
break;
Expand Down
50 changes: 50 additions & 0 deletions src/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ constexpr int IDX_RUMBLE_TRIGGER_DATA = 12; ///< Control-stream message index f
constexpr int IDX_SET_MOTION_EVENT = 13; ///< Control-stream message index for set motion event.
constexpr int IDX_SET_RGB_LED = 14; ///< Control-stream message index for set rgb led.
constexpr int IDX_SET_ADAPTIVE_TRIGGERS = 15; ///< Control-stream message index for set adaptive triggers.
constexpr int IDX_SET_PLAYER_LED = 16; ///< Control-stream message index for set player LED (DualSense).
constexpr int IDX_SET_MIC_LED = 17; ///< Control-stream message index for set mic LED (DualSense).

static const short packetTypes[] = {
0x0305, // Start A
Expand All @@ -67,6 +69,8 @@ static const short packetTypes[] = {
0x5501, // Set motion event (Sunshine protocol extension)
0x5502, // Set RGB LED (Sunshine protocol extension)
0x5503, // Set Adaptive triggers (Sunshine protocol extension)
0x5504, // Set Player LED (Sunshine protocol extension, DualSense)
0x5505, // Set Mic LED (Sunshine protocol extension, DualSense)
};

namespace asio = boost::asio;
Expand Down Expand Up @@ -239,6 +243,26 @@ namespace stream {
std::uint8_t b; ///< Blue LED channel.
};

/**
* @brief Control payload that sets the DualSense player-indicator LEDs.
*/
struct control_set_player_led_t {
control_header_v2 header; ///< Control message header preceding this payload.

std::uint16_t id; ///< Controller identifier associated with this message.
std::uint8_t value; ///< Raw 5-bit player-indicator bitmask.
};

/**
* @brief Control payload that sets the DualSense mic-mute LED.
*/
struct control_set_mic_led_t {
control_header_v2 header; ///< Control message header preceding this payload.

std::uint16_t id; ///< Controller identifier associated with this message.
std::uint8_t state; ///< Mic-LED state (0 = off, 1 = on, 2 = pulse).
};

/**
* @brief Control payload that configures DualSense adaptive triggers.
*/
Expand Down Expand Up @@ -1047,6 +1071,32 @@ namespace stream {
plaintext.type_right = msg.data.adaptive_triggers.type_right;
std::ranges::copy(msg.data.adaptive_triggers.right, plaintext.right);

std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
encrypted_payload;

payload = encode_control(session, util::view(plaintext), encrypted_payload);
} else if (msg.type == platf::gamepad_feedback_e::set_player_led) {
control_set_player_led_t plaintext;
plaintext.header.type = packetTypes[IDX_SET_PLAYER_LED];
plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2);

plaintext.id = util::endian::little(msg.id);
plaintext.value = msg.data.player_led.value;

BOOST_LOG(verbose) << "Player LED: "sv << msg.id << " :: "sv << util::hex(msg.data.player_led.value).to_string_view();
std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
encrypted_payload;

payload = encode_control(session, util::view(plaintext), encrypted_payload);
} else if (msg.type == platf::gamepad_feedback_e::set_mic_led) {
control_set_mic_led_t plaintext;
plaintext.header.type = packetTypes[IDX_SET_MIC_LED];
plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2);

plaintext.id = util::endian::little(msg.id);
plaintext.state = msg.data.mic_led.state;

BOOST_LOG(verbose) << "Mic LED: "sv << msg.id << " :: "sv << util::hex(msg.data.mic_led.state).to_string_view();
std::array<std::uint8_t, sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
encrypted_payload;

Expand Down