mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-29 03:46:50 +01:00
Merge pull request #232 from Belcross-Dev/grind-exit-playstation-pads
Grind exits: fix triggers not registering on DualShock and DualSense controllers and improve curved grind exits
This commit is contained in:
1 file changed
+70
-19
@@ -77,6 +77,24 @@ BOOLEAN __stdcall captured_hid_attributes(HANDLE file, PHIDD_ATTRIBUTES attribut
|
||||
SetLastError(error);
|
||||
return result;
|
||||
}
|
||||
std::atomic<dingosdk::overlay::GamepadFilter>& gamepad_filter() {
|
||||
static std::atomic<dingosdk::overlay::GamepadFilter> value{};
|
||||
return value;
|
||||
}
|
||||
// Full Bluetooth reports have a CRC over the 0xA1 input prefix and payload.
|
||||
// A stale checksum makes the native reader discard a changed report.
|
||||
void refresh_bluetooth_crc(std::uint8_t* bytes, DWORD size) noexcept {
|
||||
if (size != 78 || (bytes[0] != 0x31 && bytes[0] != 0x11)) return;
|
||||
std::uint32_t crc = 0xffffffffu;
|
||||
const auto feed = [&](std::uint8_t byte) {
|
||||
crc ^= byte;
|
||||
for (unsigned bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u)));
|
||||
};
|
||||
feed(0xa1);
|
||||
for (DWORD i = 0; i < size - 4; ++i) feed(bytes[i]);
|
||||
crc = ~crc;
|
||||
std::memcpy(bytes + size - 4, &crc, sizeof(crc));
|
||||
}
|
||||
void neutral_hid(const InputCaptureHooks::HidRead& read, DWORD size) {
|
||||
if (!block_polled_input() || !read.buffer || size > read.capacity) return;
|
||||
auto* bytes = static_cast<std::uint8_t*>(read.buffer);
|
||||
@@ -100,25 +118,62 @@ void neutral_hid(const InputCaptureHooks::HidRead& read, DWORD size) {
|
||||
data[4] = 8; data[5] = 0; data[6] &= 0xfc; // retain DS4 sequence bits
|
||||
data[7] = data[8] = 0;
|
||||
}
|
||||
// Full Bluetooth reports have a CRC over the 0xA1 input prefix and payload.
|
||||
// A stale checksum makes the native reader discard the neutral report.
|
||||
if (size == 78 && (bytes[0] == 0x31 || bytes[0] == 0x11)) {
|
||||
std::uint32_t crc = 0xffffffffu;
|
||||
const auto feed = [&](std::uint8_t byte) {
|
||||
crc ^= byte;
|
||||
for (unsigned bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u)));
|
||||
};
|
||||
feed(0xa1);
|
||||
for (DWORD i = 0; i < size - 4; ++i) feed(bytes[i]);
|
||||
crc = ~crc;
|
||||
std::memcpy(bytes + size - 4, &crc, sizeof(crc));
|
||||
}
|
||||
refresh_bluetooth_crc(bytes, size);
|
||||
static std::atomic<bool> reported{};
|
||||
if (!reported.exchange(true)) logging::printf(logging::Level::info, logging::Channel::input,
|
||||
"Native PlayStation HID report neutralized; overlay retains its independent input stream. (%s over %s, report 0x%02x of %lu bytes, "
|
||||
"the game's buffer %lu.)", read.kind == PlayStationPad::dualsense ? "DualSense" : "DualShock 4",
|
||||
bluetooth ? "Bluetooth" : "USB", bytes[0], size, read.capacity);
|
||||
}
|
||||
// The gamepad filter (DingoSDKOverlaySetGamepadFilter) on the game's own native PlayStation
|
||||
// reads, as filter_pad applies it to XInput: a DualShock 4 or DualSense read without Steam Input
|
||||
// never goes through XInput. The report is parsed into XInput's layout, filtered, and what the
|
||||
// filter changed (triggers, left stick X) is written back in the report's own layout.
|
||||
inline constexpr unsigned hid_filter_slot = 3; // the filter's pad slot for native PlayStation pads
|
||||
void filter_hid(const InputCaptureHooks::HidRead& read, DWORD size) noexcept {
|
||||
const auto filter = gamepad_filter().load(std::memory_order_acquire);
|
||||
if (!filter || !read.buffer || !size || size > read.capacity) return;
|
||||
auto* bytes = static_cast<std::uint8_t*>(read.buffer);
|
||||
const bool bluetooth = playstation_bluetooth(size);
|
||||
const auto parsed = parse_playstation_report(read.kind, bluetooth, bytes, size);
|
||||
if (!parsed) return;
|
||||
const dingosdk::overlay::GamepadValues before{parsed->buttons, parsed->left_trigger, parsed->right_trigger,
|
||||
parsed->left_x, parsed->left_y, parsed->right_x, parsed->right_y};
|
||||
auto pad = before;
|
||||
filter(hid_filter_slot, pad);
|
||||
if (pad.left_trigger == before.left_trigger && pad.right_trigger == before.right_trigger && pad.left_x == before.left_x) return;
|
||||
// Where parse_playstation_report found them: sticks (left X first), the shoulder byte (L2 0x04,
|
||||
// R2 0x08 digital bits) and the two analog triggers.
|
||||
std::uint8_t *sticks{}, *shoulder{}, *left{}, *right{};
|
||||
const auto id = bytes[0];
|
||||
if (read.kind == PlayStationPad::dualsense && ((id == 0x01 && !bluetooth) || id == 0x31)) {
|
||||
auto* data = bytes + (id == 0x31 ? 2 : 1);
|
||||
sticks = data; left = data + 4; right = data + 5; shoulder = data + 8;
|
||||
} else if (id == 0x01) {
|
||||
sticks = bytes + 1; shoulder = bytes + 6; left = bytes + 8; right = bytes + 9;
|
||||
} else if (read.kind == PlayStationPad::dualshock4 && id == 0x11) {
|
||||
sticks = bytes + 3; shoulder = bytes + 8; left = bytes + 10; right = bytes + 11;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (pad.left_trigger != before.left_trigger) {
|
||||
*left = pad.left_trigger;
|
||||
if (!pad.left_trigger) *shoulder = static_cast<std::uint8_t>(*shoulder & ~0x04u);
|
||||
}
|
||||
if (pad.right_trigger != before.right_trigger) {
|
||||
*right = pad.right_trigger;
|
||||
if (!pad.right_trigger) *shoulder = static_cast<std::uint8_t>(*shoulder & ~0x08u);
|
||||
}
|
||||
if (pad.left_x != before.left_x)
|
||||
sticks[0] = static_cast<std::uint8_t>(std::clamp(static_cast<int>(pad.left_x) / 258 + 128, 0, 255));
|
||||
refresh_bluetooth_crc(bytes, size);
|
||||
}
|
||||
// A finished native PlayStation read of the game's: neutral while a menu holds the input, else
|
||||
// through the gamepad filter.
|
||||
void handle_hid(const InputCaptureHooks::HidRead& read, DWORD size) noexcept {
|
||||
if (block_polled_input()) neutral_hid(read, size);
|
||||
else filter_hid(read, size);
|
||||
}
|
||||
void complete_hid(OVERLAPPED* request, DWORD bytes, bool success, bool finished) {
|
||||
auto& h = input_hooks();
|
||||
InputCaptureHooks::HidRead read{};
|
||||
@@ -129,7 +184,7 @@ void complete_hid(OVERLAPPED* request, DWORD bytes, bool success, bool finished)
|
||||
read = found->second;
|
||||
if (finished) h.hid_reads.erase(found);
|
||||
}
|
||||
if (success) neutral_hid(read, bytes);
|
||||
if (success) handle_hid(read, bytes);
|
||||
}
|
||||
BOOL WINAPI captured_read_file(HANDLE file, LPVOID buffer, DWORD size, LPDWORD bytes, LPOVERLAPPED request) {
|
||||
auto& h = input_hooks();
|
||||
@@ -163,7 +218,7 @@ BOOL WINAPI captured_read_file(HANDLE file, LPVOID buffer, DWORD size, LPDWORD b
|
||||
const auto result = original<decltype(&ReadFile)>(h.read_file)(file, buffer, size, bytes, request);
|
||||
const auto error = GetLastError();
|
||||
if (read.kind != PlayStationPad::none) {
|
||||
if (result) neutral_hid(read, bytes ? *bytes : request ? static_cast<DWORD>(request->InternalHigh) : 0);
|
||||
if (result) handle_hid(read, bytes ? *bytes : request ? static_cast<DWORD>(request->InternalHigh) : 0);
|
||||
if (!result && error != ERROR_IO_PENDING && request) complete_hid(request, 0, false, true);
|
||||
}
|
||||
SetLastError(error);
|
||||
@@ -266,10 +321,6 @@ std::string xinput_entry_owners() {
|
||||
return text.empty() ? "none" : text;
|
||||
}
|
||||
|
||||
std::atomic<dingosdk::overlay::GamepadFilter>& gamepad_filter() {
|
||||
static std::atomic<dingosdk::overlay::GamepadFilter> value{};
|
||||
return value;
|
||||
}
|
||||
// The game's own read only: not ReSkate's (overlay_input_access) and not an inner layer of a
|
||||
// read already being answered (Steam's hook going on to XInput runs inside OverlayInputAccess).
|
||||
void filter_pad(bool outer, DWORD user, XINPUT_STATE* output) noexcept {
|
||||
|
||||
Reference in new issue
Block a user