/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/hid/input.h" #include "xenia/hid/input_system.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_private.h" #include "xenia/xbox.h" namespace xe { namespace kernel { namespace xam { using xe::hid::X_INPUT_CAPABILITIES; using xe::hid::X_INPUT_KEYSTROKE; using xe::hid::X_INPUT_STATE; using xe::hid::X_INPUT_VIBRATION; constexpr uint32_t XINPUT_FLAG_GAMEPAD = 0x01; constexpr uint32_t XINPUT_FLAG_KEYBOARD = 0x02; constexpr uint32_t XINPUT_FLAG_MIC = 0x20; // Based on "karaoke" titles constexpr uint32_t XINPUT_FLAG_ANYDEVICE = 0xFF; constexpr uint32_t XINPUT_FLAG_ANY_USER = 1 << 30; dword_result_t XAutomationpUnbindController_entry(dword_t user_index) { if (user_index > 4) { return 0; } return 1; } DECLARE_XAM_EXPORT1(XAutomationpUnbindController, kInput, kStub); void XamResetInactivity_entry() { // Do we need to do anything? } DECLARE_XAM_EXPORT1(XamResetInactivity, kInput, kStub); dword_result_t XamEnableInactivityProcessing_entry(dword_t unk, dword_t enable) { return X_ERROR_SUCCESS; } DECLARE_XAM_EXPORT1(XamEnableInactivityProcessing, kInput, kStub); dword_result_t XamInputGetCapabilitiesEx_entry( dword_t unk, dword_t user_index, dword_t flags, pointer_t caps) { if (unk > 1) { return X_ERROR_NOT_SUPPORTED; } // Fail-safe check if (!caps) { return X_ERROR_BAD_ARGUMENTS; } caps.Zero(); if ((flags & XINPUT_FLAG_ANY_USER) != 0) { // should trap } if ((flags & 4) != 0) { // should trap } if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) { // Ignore any query for other types of devices. return X_ERROR_DEVICE_NOT_CONNECTED; } uint32_t actual_user_index = user_index; if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) { // Always pin user to 0. actual_user_index = 0; } auto input_system = kernel_state()->emulator()->input_system(); auto lock = input_system->lock(); return input_system->GetCapabilities(actual_user_index, flags, caps); } DECLARE_XAM_EXPORT1(XamInputGetCapabilitiesEx, kInput, kSketchy); // https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinputgetcapabilities(v=vs.85).aspx dword_result_t XamInputGetCapabilities_entry( dword_t user_index, dword_t flags, pointer_t caps) { // chrispy: actually, it appears that caps is never checked for null, it is // memset at the start regardless return XamInputGetCapabilitiesEx_entry(1, user_index, flags, caps); } DECLARE_XAM_EXPORT1(XamInputGetCapabilities, kInput, kSketchy); // https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinputgetstate(v=vs.85).aspx dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags, pointer_t input_state) { if (input_state) { memset((void*)input_state.host_address(), 0, sizeof(X_INPUT_STATE)); } if (user_index >= 4) { return X_ERROR_DEVICE_NOT_CONNECTED; } // Games call this with a NULL state ptr, probably as a query. if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) { // Ignore any query for other types of devices. return X_ERROR_DEVICE_NOT_CONNECTED; } uint32_t actual_user_index = user_index; // chrispy: change this, logic is not right if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) { // Always pin user to 0. actual_user_index = 0; } auto input_system = kernel_state()->emulator()->input_system(); auto lock = input_system->lock(); return input_system->GetState(user_index, input_state); } DECLARE_XAM_EXPORT2(XamInputGetState, kInput, kImplemented, kHighFrequency); // https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinputsetstate(v=vs.85).aspx dword_result_t XamInputSetState_entry( dword_t user_index, dword_t flags, /* flags, as far as i can see, is not used*/ pointer_t vibration) { if (user_index >= 4) { return X_E_DEVICE_NOT_CONNECTED; } if (!vibration) { return X_ERROR_BAD_ARGUMENTS; } uint32_t actual_user_index = user_index; if ((user_index & 0xFF) == 0xFF) { // Always pin user to 0. actual_user_index = 0; } auto input_system = kernel_state()->emulator()->input_system(); auto lock = input_system->lock(); return input_system->SetState(user_index, vibration); } DECLARE_XAM_EXPORT1(XamInputSetState, kInput, kImplemented); // https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinputgetkeystroke(v=vs.85).aspx dword_result_t XamInputGetKeystroke_entry( dword_t user_index, dword_t flags, pointer_t keystroke) { // https://github.com/CodeAsm/ffplay360/blob/master/Common/AtgXime.cpp // user index = index or XUSER_INDEX_ANY // flags = XINPUT_FLAG_GAMEPAD (| _ANYUSER | _ANYDEVICE) if (!keystroke) { return X_ERROR_BAD_ARGUMENTS; } if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) { // Ignore any query for other types of devices. return X_ERROR_DEVICE_NOT_CONNECTED; } uint32_t actual_user_index = user_index; if ((actual_user_index & 0xFF) == 0xFF || (flags & XINPUT_FLAG_ANY_USER)) { // Always pin user to 0. actual_user_index = 0; } auto input_system = kernel_state()->emulator()->input_system(); auto lock = input_system->lock(); return input_system->GetKeystroke(user_index, flags, keystroke); } DECLARE_XAM_EXPORT1(XamInputGetKeystroke, kInput, kImplemented); // Same as non-ex, just takes a pointer to user index. dword_result_t XamInputGetKeystrokeEx_entry( lpdword_t user_index_ptr, dword_t flags, pointer_t keystroke) { if (!keystroke) { return X_ERROR_BAD_ARGUMENTS; } if ((flags & XINPUT_FLAG_ANYDEVICE) && (flags & XINPUT_FLAG_GAMEPAD) == 0) { // Ignore any query for other types of devices. return X_ERROR_DEVICE_NOT_CONNECTED; } uint32_t user_index = *user_index_ptr; auto input_system = kernel_state()->emulator()->input_system(); auto lock = input_system->lock(); if ((user_index & 0xFF) == 0xFF) { // Always pin user to 0. user_index = 0; } if (flags & XINPUT_FLAG_ANY_USER) { // That flag means we should iterate over every connected controller and // check which one have pending request. auto result = X_ERROR_DEVICE_NOT_CONNECTED; for (uint32_t i = 0; i < 4; i++) { auto result = input_system->GetKeystroke(i, flags, keystroke); // Return result from first user that have pending request if (result == X_ERROR_SUCCESS) { *user_index_ptr = keystroke->user_index; return result; } } return result; } auto result = input_system->GetKeystroke(user_index, flags, keystroke); if (XSUCCEEDED(result)) { *user_index_ptr = keystroke->user_index; } return result; } DECLARE_XAM_EXPORT1(XamInputGetKeystrokeEx, kInput, kImplemented); X_HRESULT_result_t XamUserGetDeviceContext_entry(dword_t user_index, dword_t unk, lpdword_t out_ptr) { // Games check the result - usually with some masking. // If this function fails they assume zero, so let's fail AND // set zero just to be safe. *out_ptr = 0; if (kernel_state()->IsUserSignedIn(user_index) || (user_index & 0xFF) == 0xFF) { *out_ptr = (uint32_t)user_index; return X_E_SUCCESS; } else { return X_E_DEVICE_NOT_CONNECTED; } } DECLARE_XAM_EXPORT1(XamUserGetDeviceContext, kInput, kStub); } // namespace xam } // namespace kernel } // namespace xe DECLARE_XAM_EMPTY_REGISTER_EXPORTS(Input);