From 8ce31c280a92a041474e95db1a6958c5b1158537 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Sun, 14 Jun 2026 12:01:36 +0100 Subject: [PATCH] add Skate 3 demo path input automation --- include/rex/kernel/xam/input_injection.h | 18 ++++ src/kernel/xam/apps/xgi_app.cpp | 13 +++ src/kernel/xam/xam_input.cpp | 132 ++++++++++++++++++++++- src/ui/window.cpp | 6 ++ src/ui/window_win.cpp | 3 + 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 include/rex/kernel/xam/input_injection.h diff --git a/include/rex/kernel/xam/input_injection.h b/include/rex/kernel/xam/input_injection.h new file mode 100644 index 0000000..5287e2d --- /dev/null +++ b/include/rex/kernel/xam/input_injection.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace rex::kernel::xam { + +struct SyntheticInputStep { + uint16_t buttons; + uint32_t poll_count; +}; + +void QueueSyntheticInput(uint16_t buttons, uint32_t poll_count); +void QueueSyntheticInputSequence(const SyntheticInputStep* steps, size_t step_count); +void SetSyntheticAutoTap(uint16_t buttons, bool enabled); +void ClearSyntheticInput(); + +} // namespace rex::kernel::xam diff --git a/src/kernel/xam/apps/xgi_app.cpp b/src/kernel/xam/apps/xgi_app.cpp index 8b019bd..a1ca1c1 100644 --- a/src/kernel/xam/apps/xgi_app.cpp +++ b/src/kernel/xam/apps/xgi_app.cpp @@ -10,7 +10,10 @@ */ #include +#include #include +#include +#include #include #include @@ -45,6 +48,16 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, kernel_state_->SetUserContext(user_index, context_id, context_value); rex::graphics::ultrawide_debug::NotifySkate3GameplayContext(user_index, context_id, context_value); + if (rex::cvar::Query("skate3_demo_path") && user_index == 0 && + context_id == 0x8001) { + if (context_value == 0) { + rex::kernel::xam::SetSyntheticAutoTap(rex::input::X_INPUT_GAMEPAD_A, true); + REXKRNL_INFO("Skate 3 demo path: gameplay context 0; enabling dialog A auto-tap"); + } else if (context_value == 1) { + rex::kernel::xam::ClearSyntheticInput(); + REXKRNL_INFO("Skate 3 demo path: gameplay context 1; stopping synthetic input"); + } + } if (!old_value || *old_value != context_value) { REXKRNL_INFO("XGIUserSetContextEx(user={}, context={:#x}, value={:#x})", user_index, context_id, context_value); diff --git a/src/kernel/xam/xam_input.cpp b/src/kernel/xam/xam_input.cpp index 80c3634..c493512 100644 --- a/src/kernel/xam/xam_input.cpp +++ b/src/kernel/xam/xam_input.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,11 @@ #include #include #include +#include +#include +#include +#include +#include #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -34,6 +40,122 @@ using rex::input::X_INPUT_VIBRATION; constexpr uint32_t XINPUT_FLAG_GAMEPAD = 0x01; constexpr uint32_t XINPUT_FLAG_ANY_USER = 1 << 30; +namespace { + +constexpr size_t kMaxSyntheticInputSteps = 16; + +std::atomic g_synthetic_input_active{false}; +std::mutex g_synthetic_input_mutex; +std::array g_synthetic_input_steps{}; +size_t g_synthetic_input_step_count = 0; +size_t g_synthetic_input_step_index = 0; +uint32_t g_synthetic_input_step_remaining = 0; +uint16_t g_synthetic_auto_tap_buttons = 0; +bool g_synthetic_auto_tap_enabled = false; + +void QueueSyntheticInputSequenceLocked(const SyntheticInputStep* steps, size_t step_count) { + g_synthetic_input_step_count = std::min(step_count, kMaxSyntheticInputSteps); + g_synthetic_input_step_index = 0; + for (size_t i = 0; i < g_synthetic_input_step_count; ++i) { + g_synthetic_input_steps[i] = steps[i]; + } + while (g_synthetic_input_step_count != 0 && + g_synthetic_input_steps[g_synthetic_input_step_count - 1].poll_count == 0) { + --g_synthetic_input_step_count; + } + g_synthetic_input_step_remaining = + g_synthetic_input_step_count ? g_synthetic_input_steps[0].poll_count : 0; + g_synthetic_input_active.store(g_synthetic_input_step_count != 0 && + g_synthetic_input_step_remaining != 0, + std::memory_order_relaxed); +} + +void EnsureSyntheticAutoTapLocked() { + if (!g_synthetic_auto_tap_enabled || g_synthetic_auto_tap_buttons == 0 || + g_synthetic_input_active.load(std::memory_order_relaxed)) { + return; + } + + const SyntheticInputStep auto_tap[] = { + {g_synthetic_auto_tap_buttons, 5}, + {0, 15}, + }; + QueueSyntheticInputSequenceLocked(auto_tap, std::size(auto_tap)); +} + +void ApplySyntheticInput(X_INPUT_STATE* input_state) { + if (!input_state) { + return; + } + + std::lock_guard lock(g_synthetic_input_mutex); + EnsureSyntheticAutoTapLocked(); + if (!g_synthetic_input_active.load(std::memory_order_relaxed) || + g_synthetic_input_step_index >= g_synthetic_input_step_count) { + g_synthetic_input_active.store(false, std::memory_order_relaxed); + return; + } + + const auto& step = g_synthetic_input_steps[g_synthetic_input_step_index]; + if (step.buttons != 0) { + input_state->gamepad.buttons = + static_cast(static_cast(input_state->gamepad.buttons) | step.buttons); + input_state->packet_number = + static_cast(static_cast(input_state->packet_number) + 1); + } + + if (g_synthetic_input_step_remaining > 0) { + --g_synthetic_input_step_remaining; + } + + while (g_synthetic_input_step_remaining == 0) { + ++g_synthetic_input_step_index; + if (g_synthetic_input_step_index >= g_synthetic_input_step_count) { + g_synthetic_input_active.store(false, std::memory_order_relaxed); + return; + } + g_synthetic_input_step_remaining = + g_synthetic_input_steps[g_synthetic_input_step_index].poll_count; + } +} + +bool PopSyntheticKeystroke(X_INPUT_KEYSTROKE* out_keystroke) { + (void)out_keystroke; + return false; +} + +} // namespace + +void QueueSyntheticInput(uint16_t buttons, uint32_t poll_count) { + SyntheticInputStep step{buttons, poll_count}; + QueueSyntheticInputSequence(&step, 1); +} + +void QueueSyntheticInputSequence(const SyntheticInputStep* steps, size_t step_count) { + if (!steps || step_count == 0) { + return; + } + + std::lock_guard lock(g_synthetic_input_mutex); + QueueSyntheticInputSequenceLocked(steps, step_count); +} + +void SetSyntheticAutoTap(uint16_t buttons, bool enabled) { + std::lock_guard lock(g_synthetic_input_mutex); + g_synthetic_auto_tap_buttons = buttons; + g_synthetic_auto_tap_enabled = enabled && buttons != 0; +} + +void ClearSyntheticInput() { + std::lock_guard lock(g_synthetic_input_mutex); + g_synthetic_input_step_count = 0; + g_synthetic_input_step_index = 0; + g_synthetic_input_step_remaining = 0; + g_synthetic_auto_tap_buttons = 0; + g_synthetic_auto_tap_enabled = false; + g_synthetic_input_active.store(false, std::memory_order_relaxed); +} + rex::input::InputSystem* input_system() { return static_cast(REX_KERNEL_STATE()->emulator()->input_system()); } @@ -112,7 +234,11 @@ u32 XamInputGetState_entry(u32 user_index, u32 flags, ppc_ptr_t i } auto* is = input_system(); - return is->GetState(actual_user_index, input_state); + const u32 result = is->GetState(actual_user_index, input_state); + if (result == X_ERROR_SUCCESS) { + ApplySyntheticInput(input_state); + } + return result; } // https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinputsetstate(v=vs.85).aspx @@ -153,6 +279,10 @@ u32 XamInputGetKeystroke_entry(u32 user_index, u32 flags, ppc_ptr_tGetKeystroke(actual_user_index, flags, keystroke); } diff --git a/src/ui/window.cpp b/src/ui/window.cpp index f45891f..784a604 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -571,6 +571,12 @@ void Window::OnFileDrop(FileDropEvent& e, WindowDestructionReceiver& destruction } void Window::OnKeyDown(KeyEvent& e, WindowDestructionReceiver& destruction_receiver) { + if (!e.prev_state() && e.virtual_key() == VirtualKey::kF10 && + (rex::cvar::Query("skate3_demo_path") || + rex::cvar::Query("skate3_demo_path_probe"))) { + REXLOG_WARN("Skate 3 demo path: raw F10 milestone marker"); + } + PropagateEventThroughInputListeners( [&e](auto listener) { listener->OnKeyDown(e); diff --git a/src/ui/window_win.cpp b/src/ui/window_win.cpp index 09c44a3..dd9c243 100644 --- a/src/ui/window_win.cpp +++ b/src/ui/window_win.cpp @@ -970,12 +970,15 @@ bool Win32Window::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam, !!(GetKeyState(VK_MENU) & 0x80), !!(GetKeyState(VK_LWIN) & 0x80)); switch (message) { case WM_KEYDOWN: + case WM_SYSKEYDOWN: OnKeyDown(e, destruction_receiver); break; case WM_KEYUP: + case WM_SYSKEYUP: OnKeyUp(e, destruction_receiver); break; case WM_CHAR: + case WM_SYSCHAR: OnKeyChar(e, destruction_receiver); break; default: