add Skate 3 demo path input automation
This commit is contained in:
parent
4e468082dc
commit
8ce31c280a
5 changed files with 171 additions and 1 deletions
18
include/rex/kernel/xam/input_injection.h
Normal file
18
include/rex/kernel/xam/input_injection.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
|
@ -10,7 +10,10 @@
|
|||
*/
|
||||
|
||||
#include <rex/kernel/xam/apps/xgi_app.h>
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/ultrawide_debug.h>
|
||||
#include <rex/input/input.h>
|
||||
#include <rex/kernel/xam/input_injection.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/thread.h>
|
||||
|
||||
|
|
@ -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<bool>("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);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <rex/input/input.h>
|
||||
#include <rex/input/input_system.h>
|
||||
#include <rex/kernel/xam/input_injection.h>
|
||||
#include <rex/kernel/xam/private.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/hook.h>
|
||||
|
|
@ -18,6 +19,11 @@
|
|||
#include <rex/runtime.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
#include <rex/system/xtypes.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
|
||||
#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<bool> g_synthetic_input_active{false};
|
||||
std::mutex g_synthetic_input_mutex;
|
||||
std::array<SyntheticInputStep, kMaxSyntheticInputSteps> 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<uint16_t>(static_cast<uint16_t>(input_state->gamepad.buttons) | step.buttons);
|
||||
input_state->packet_number =
|
||||
static_cast<uint32_t>(static_cast<uint32_t>(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::input::InputSystem*>(REX_KERNEL_STATE()->emulator()->input_system());
|
||||
}
|
||||
|
|
@ -112,7 +234,11 @@ u32 XamInputGetState_entry(u32 user_index, u32 flags, ppc_ptr_t<X_INPUT_STATE> 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_t<X_INPUT_KEYS
|
|||
actual_user_index = 0;
|
||||
}
|
||||
|
||||
if (actual_user_index == 0 && PopSyntheticKeystroke(keystroke)) {
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
auto* is = input_system();
|
||||
return is->GetKeystroke(actual_user_index, flags, keystroke);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<bool>("skate3_demo_path") ||
|
||||
rex::cvar::Query<bool>("skate3_demo_path_probe"))) {
|
||||
REXLOG_WARN("Skate 3 demo path: raw F10 milestone marker");
|
||||
}
|
||||
|
||||
PropagateEventThroughInputListeners(
|
||||
[&e](auto listener) {
|
||||
listener->OnKeyDown(e);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue