mirror of https://github.com/PCSX2/pcsx2.git
773 lines
14 KiB
C++
773 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2007 Gabest
|
|
* http://www.gabest.org
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "xpad.h"
|
|
#include <VersionHelpers.h>
|
|
|
|
#undef _WIN32_WINNT
|
|
#define _WIN32_WINNT 0x0602 // Required for XInputEnable definition
|
|
#include <xinput.h>
|
|
|
|
static HMODULE s_hModule;
|
|
static HMODULE s_xInputDll;
|
|
static decltype(&XInputEnable) pXInputEnable;
|
|
static decltype(&XInputGetState) pXInputGetState;
|
|
static decltype(&XInputSetState) pXInputSetState;
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
|
{
|
|
switch(ul_reason_for_call)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
s_hModule = hModule;
|
|
case DLL_THREAD_ATTACH:
|
|
case DLL_THREAD_DETACH:
|
|
case DLL_PROCESS_DETACH:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
|
|
static struct
|
|
{
|
|
static const UINT32 revision = 0;
|
|
static const UINT32 build = 2;
|
|
static const UINT32 minor = 0;
|
|
} s_ver;
|
|
|
|
static bool s_ps2 = false;
|
|
|
|
EXPORT_C_(UINT32) PSEgetLibType()
|
|
{
|
|
return PSE_LT_PAD;
|
|
}
|
|
|
|
EXPORT_C_(char*) PSEgetLibName()
|
|
{
|
|
return "XPad";
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PSEgetLibVersion()
|
|
{
|
|
return (s_ver.minor << 16) | (s_ver.revision << 8) | s_ver.build;
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PS2EgetLibType()
|
|
{
|
|
return PS2E_LT_PAD;
|
|
}
|
|
|
|
EXPORT_C_(char*) PS2EgetLibName()
|
|
{
|
|
return "XPad";
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PS2EgetLibVersion2(UINT32 type)
|
|
{
|
|
s_ps2 = true;
|
|
|
|
return (s_ver.build << 0) | (s_ver.revision << 8) | (PS2E_PAD_VERSION << 16) | (s_ver.minor << 24);
|
|
}
|
|
|
|
//
|
|
|
|
struct XPadButton
|
|
{
|
|
enum
|
|
{
|
|
Select = 0x0001,
|
|
L3 = 0x0002,
|
|
R3 = 0x0004,
|
|
Start = 0x0008,
|
|
Up = 0x0010,
|
|
Right = 0x0020,
|
|
Down = 0x0040,
|
|
Left = 0x0080,
|
|
L2 = 0x0100,
|
|
R2 = 0x0200,
|
|
L1 = 0x0400,
|
|
R1 = 0x0800,
|
|
Triangle = 0x1000,
|
|
Circle = 0x2000,
|
|
Cross = 0x4000,
|
|
Square = 0x8000,
|
|
};
|
|
};
|
|
|
|
class XInput
|
|
{
|
|
int m_pad;
|
|
bool m_connected;
|
|
XINPUT_STATE m_state;
|
|
XINPUT_VIBRATION m_vibration;
|
|
clock_t m_lastpoll;
|
|
|
|
public:
|
|
XInput(int pad)
|
|
: m_pad(pad)
|
|
, m_connected(false)
|
|
, m_lastpoll(0)
|
|
{
|
|
memset(&m_vibration, 0, sizeof(m_vibration));
|
|
}
|
|
|
|
bool GetState(XINPUT_STATE& state)
|
|
{
|
|
clock_t now = clock();
|
|
clock_t delay = m_connected ? 16 : 1000; // poll once per frame (16 ms is about 60 fps)
|
|
|
|
if(now > m_lastpoll + delay)
|
|
{
|
|
memset(&m_state, 0, sizeof(m_state));
|
|
|
|
m_connected = pXInputGetState(m_pad, &m_state) == S_OK; // ERROR_DEVICE_NOT_CONNECTED is not an error, SUCCEEDED(...) won't work here
|
|
|
|
m_lastpoll = now;
|
|
}
|
|
|
|
memcpy(&state, &m_state, sizeof(state));
|
|
|
|
return m_connected;
|
|
}
|
|
|
|
void SetState(XINPUT_VIBRATION& vibration)
|
|
{
|
|
if(m_vibration.wLeftMotorSpeed != vibration.wLeftMotorSpeed || m_vibration.wRightMotorSpeed != vibration.wRightMotorSpeed)
|
|
{
|
|
pXInputSetState(m_pad, &vibration);
|
|
|
|
m_vibration = vibration;
|
|
}
|
|
}
|
|
|
|
bool IsConnected()
|
|
{
|
|
return m_connected;
|
|
}
|
|
};
|
|
|
|
class XPad
|
|
{
|
|
public:
|
|
int m_pad;
|
|
XInput m_xinput;
|
|
bool m_connected;
|
|
bool m_ds2native;
|
|
bool m_analog;
|
|
bool m_locked;
|
|
bool m_vibration;
|
|
BYTE m_small;
|
|
BYTE m_large;
|
|
WORD m_buttons;
|
|
struct {BYTE x, y;} m_left;
|
|
struct {BYTE x, y;} m_right;
|
|
|
|
void SetButton(WORD buttons, WORD mask, int flag)
|
|
{
|
|
if(buttons & mask)
|
|
{
|
|
m_buttons &= ~flag;
|
|
}
|
|
else
|
|
{
|
|
m_buttons |= flag;
|
|
}
|
|
}
|
|
|
|
void SetAnalog(short src, BYTE& dst, short deadzone)
|
|
{
|
|
if(abs(src) < deadzone) src = 0;
|
|
|
|
dst = (src >> 8) + 128;
|
|
}
|
|
|
|
public:
|
|
XPad(int pad)
|
|
: m_xinput(pad)
|
|
, m_ds2native(false)
|
|
, m_analog(!s_ps2) // defaults to analog off for ps2
|
|
, m_locked(false)
|
|
, m_vibration(true)
|
|
, m_small(0)
|
|
, m_large(0)
|
|
, m_buttons(0xffff)
|
|
{
|
|
}
|
|
|
|
virtual ~XPad()
|
|
{
|
|
}
|
|
|
|
BYTE GetId()
|
|
{
|
|
return m_analog ? (m_ds2native ? 0x79 : 0x73) : 0x41;
|
|
}
|
|
|
|
BYTE ReadData(int index)
|
|
{
|
|
if(index == 0)
|
|
{
|
|
XINPUT_STATE state;
|
|
|
|
if(m_xinput.GetState(state))
|
|
{
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_BACK, XPadButton::Select);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_THUMB, XPadButton::L3);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB, XPadButton::R3);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_START, XPadButton::Start);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_UP, XPadButton::Up);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT, XPadButton::Right);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_DOWN, XPadButton::Down);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_LEFT, XPadButton::Left);
|
|
SetButton(state.Gamepad.bLeftTrigger, 0xe0, XPadButton::L2);
|
|
SetButton(state.Gamepad.bRightTrigger, 0xe0, XPadButton::R2);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER, XPadButton::L1);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER, XPadButton::R1);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_Y, XPadButton::Triangle);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_B, XPadButton::Circle);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_A, XPadButton::Cross);
|
|
SetButton(state.Gamepad.wButtons, XINPUT_GAMEPAD_X, XPadButton::Square);
|
|
|
|
SetAnalog(state.Gamepad.sThumbLX, m_left.x, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
|
SetAnalog(~state.Gamepad.sThumbLY, m_left.y, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
|
SetAnalog(state.Gamepad.sThumbRX, m_right.x, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
|
|
SetAnalog(~state.Gamepad.sThumbRY, m_right.y, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
|
|
}
|
|
else
|
|
{
|
|
m_buttons = 0xffff;
|
|
m_left.x = 128;
|
|
m_left.y = 128;
|
|
m_right.x = 128;
|
|
m_right.y = 128;
|
|
}
|
|
}
|
|
|
|
if(index == 1)
|
|
{
|
|
if(m_xinput.IsConnected())
|
|
{
|
|
XINPUT_VIBRATION vibraton;
|
|
|
|
memset(&vibraton, 0, sizeof(vibraton));
|
|
|
|
if(m_vibration && (m_small || m_large))
|
|
{
|
|
vibraton.wLeftMotorSpeed = m_large << 8;
|
|
vibraton.wRightMotorSpeed = m_small << 8;
|
|
}
|
|
|
|
m_xinput.SetState(vibraton);
|
|
}
|
|
}
|
|
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
return (BYTE)(m_buttons & 0xff);
|
|
case 1:
|
|
return (BYTE)(m_buttons >> 8);
|
|
case 2:
|
|
return m_right.x;
|
|
case 3:
|
|
return m_right.y;
|
|
case 4:
|
|
return m_left.x;
|
|
case 5:
|
|
return m_left.y;
|
|
}
|
|
|
|
return 0xff;
|
|
}
|
|
};
|
|
|
|
static class XPadPlugin
|
|
{
|
|
vector<XPad*> m_pads;
|
|
XPad* m_pad;
|
|
int m_index;
|
|
bool m_cfgreaddata;
|
|
BYTE m_unknown1;
|
|
BYTE m_unknown3;
|
|
|
|
typedef BYTE (XPadPlugin::*CommandHandler)(int, BYTE);
|
|
|
|
CommandHandler m_handlers[256];
|
|
CommandHandler m_handler;
|
|
|
|
BYTE DS2Enabler(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 2:
|
|
return 0x02;
|
|
case 5:
|
|
return 'Z';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE QueryDS2AnalogMode(int index, BYTE value)
|
|
{
|
|
if(m_pad->m_ds2native)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
return 0xff;
|
|
case 1:
|
|
return 0xff;
|
|
case 2:
|
|
return 3;
|
|
case 3:
|
|
return 0;
|
|
case 4:
|
|
return 0;
|
|
case 5:
|
|
return 'Z';
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE ReadDataAndVibrate(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
m_pad->m_small = (value&1) * 128;
|
|
break;
|
|
case 1:
|
|
m_pad->m_large = value;
|
|
break;
|
|
}
|
|
|
|
return m_pad->ReadData(index);
|
|
}
|
|
|
|
BYTE ConfigMode(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
switch(value)
|
|
{
|
|
case 0:
|
|
m_cfgreaddata = false;
|
|
break;
|
|
case 1:
|
|
m_cfgreaddata = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(m_cfgreaddata)
|
|
{
|
|
return m_pad->ReadData(index);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE SetModeAndLock(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
// if(!m_pad->m_locked) ?
|
|
m_pad->m_analog = value == 1;
|
|
break;
|
|
case 1:
|
|
m_pad->m_locked = value == 3;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE QueryModelAndMode(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
return 3;
|
|
case 1:
|
|
return 2;
|
|
case 2:
|
|
return m_pad->m_analog ? 1 : 0;
|
|
case 3:
|
|
return m_pad->m_ds2native ? 1 : 2;
|
|
case 4:
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE QueryUnknown1(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
m_unknown1 = value;
|
|
return 0;
|
|
case 1:
|
|
return 0;
|
|
case 2:
|
|
return 1;
|
|
case 3:
|
|
return m_unknown1 ? 0x01 : 0x02;
|
|
case 4:
|
|
return m_unknown1 ? 0x01 : 0x00;
|
|
case 5:
|
|
return m_unknown1 ? 0x14 : 0x0a;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE QueryUnknown2(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 2:
|
|
return 2;
|
|
case 4:
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE QueryUnknown3(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
m_unknown3 = value;
|
|
return 0;
|
|
case 3:
|
|
return m_unknown3 ? 0x07 : 0x04;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE ConfigVibration(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 0:
|
|
return value;
|
|
case 1:
|
|
m_pad->m_vibration = value == 1;
|
|
return value;
|
|
}
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
BYTE SetDS2NativeMode(int index, BYTE value)
|
|
{
|
|
switch(index)
|
|
{
|
|
case 5:
|
|
m_pad->m_ds2native = true;
|
|
return 'Z';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BYTE m_cmd;
|
|
|
|
BYTE UnknownCommand(int index, BYTE value)
|
|
{
|
|
// printf("Unknown command %02x (%d, %02x)\n", m_cmd, index, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
public:
|
|
|
|
XPadPlugin()
|
|
: m_pad(NULL)
|
|
, m_index(-1)
|
|
, m_cfgreaddata(false)
|
|
, m_handler(NULL)
|
|
{
|
|
m_pads.push_back(new XPad(0));
|
|
m_pads.push_back(new XPad(1));
|
|
|
|
for(int i = 0; i < countof(m_handlers); i++)
|
|
{
|
|
m_handlers[i] = &XPadPlugin::UnknownCommand;
|
|
}
|
|
|
|
m_handlers['@'] = &XPadPlugin::DS2Enabler;
|
|
m_handlers['A'] = &XPadPlugin::QueryDS2AnalogMode;
|
|
m_handlers['B'] = &XPadPlugin::ReadDataAndVibrate;
|
|
m_handlers['C'] = &XPadPlugin::ConfigMode;
|
|
m_handlers['D'] = &XPadPlugin::SetModeAndLock;
|
|
m_handlers['E'] = &XPadPlugin::QueryModelAndMode;
|
|
m_handlers['F'] = &XPadPlugin::QueryUnknown1;
|
|
m_handlers['G'] = &XPadPlugin::QueryUnknown2;
|
|
m_handlers['L'] = &XPadPlugin::QueryUnknown3;
|
|
m_handlers['M'] = &XPadPlugin::ConfigVibration;
|
|
m_handlers['O'] = &XPadPlugin::SetDS2NativeMode;
|
|
}
|
|
|
|
virtual ~XPadPlugin()
|
|
{
|
|
for(vector<XPad*>::iterator i = m_pads.begin(); i != m_pads.end(); i++)
|
|
{
|
|
delete *i;
|
|
}
|
|
|
|
m_pads.clear();
|
|
}
|
|
|
|
void StartPoll(int pad)
|
|
{
|
|
m_pad = m_pads[pad & 1];
|
|
m_index = 0;
|
|
}
|
|
|
|
BYTE Poll(BYTE value)
|
|
{
|
|
BYTE ret = 0;
|
|
|
|
switch(++m_index)
|
|
{
|
|
case 1:
|
|
m_cmd = value;
|
|
m_handler = m_handlers[value];
|
|
ret = (value == 'B' || value == 'C') ? m_pad->GetId() : 0xf3;
|
|
break;
|
|
case 2:
|
|
assert(value == 0);
|
|
ret = 'Z';
|
|
break;
|
|
default:
|
|
ret = (this->*m_handler)(m_index - 3, value);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
} s_padplugin;
|
|
|
|
//
|
|
|
|
static int s_nRefs = 0;
|
|
static HWND s_hWnd = NULL;
|
|
static WNDPROC s_GSWndProc = NULL;
|
|
|
|
static class CKeyEventList : protected list<KeyEvent>, protected CCritSec
|
|
{
|
|
public:
|
|
void Push(UINT32 event, UINT32 key)
|
|
{
|
|
CAutoLock cAutoLock(this);
|
|
|
|
KeyEvent e;
|
|
|
|
e.event = event;
|
|
e.key = key;
|
|
|
|
push_back(e);
|
|
}
|
|
|
|
bool Pop(KeyEvent& e)
|
|
{
|
|
CAutoLock cAutoLock(this);
|
|
|
|
if(empty()) return false;
|
|
|
|
e = front();
|
|
|
|
pop_front();
|
|
|
|
return true;
|
|
}
|
|
|
|
} s_event;
|
|
|
|
LRESULT WINAPI PADwndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch(msg)
|
|
{
|
|
case WM_KEYDOWN:
|
|
if(lParam & 0x40000000) return TRUE;
|
|
s_event.Push(KEYPRESS, wParam);
|
|
return TRUE;
|
|
case WM_KEYUP:
|
|
s_event.Push(KEYRELEASE, wParam);
|
|
return TRUE;
|
|
case WM_DESTROY:
|
|
case WM_QUIT:
|
|
s_event.Push(KEYPRESS, VK_ESCAPE);
|
|
break;
|
|
}
|
|
|
|
return CallWindowProc(s_GSWndProc, hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
//
|
|
|
|
EXPORT_C_(UINT32) PADinit(UINT32 flags)
|
|
{
|
|
DWORD loadFlags = LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32;
|
|
|
|
s_xInputDll = LoadLibraryEx(L"xinput1_3.dll", nullptr, loadFlags);
|
|
if (s_xInputDll == nullptr && IsWindows8OrGreater())
|
|
{
|
|
s_xInputDll = LoadLibraryEx(L"XInput1_4.dll", nullptr, loadFlags);
|
|
}
|
|
if (s_xInputDll == nullptr)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (!(pXInputEnable = reinterpret_cast<decltype(&XInputEnable)>(GetProcAddress(s_xInputDll, "XInputEnable")))
|
|
|| !(pXInputSetState = reinterpret_cast<decltype(&XInputSetState)>(GetProcAddress(s_xInputDll, "XInputSetState")))
|
|
|| !(pXInputGetState = reinterpret_cast<decltype(&XInputGetState)>(GetProcAddress(s_xInputDll, "XInputGetState"))))
|
|
{
|
|
FreeLibrary(s_xInputDll);
|
|
s_xInputDll = nullptr;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C PADshutdown()
|
|
{
|
|
if (s_xInputDll)
|
|
{
|
|
FreeLibrary(s_xInputDll);
|
|
s_xInputDll = nullptr;
|
|
}
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PADopen(void* pDsp)
|
|
{
|
|
pXInputEnable(TRUE);
|
|
|
|
if(s_nRefs == 0)
|
|
{
|
|
if(s_ps2)
|
|
{
|
|
s_hWnd = *(HWND*)pDsp;
|
|
s_GSWndProc = (WNDPROC)SetWindowLongPtr(s_hWnd, GWLP_WNDPROC, (LPARAM)PADwndProc);
|
|
}
|
|
}
|
|
|
|
s_nRefs++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C PADclose()
|
|
{
|
|
s_nRefs--;
|
|
|
|
if(s_nRefs == 0)
|
|
{
|
|
if(s_ps2)
|
|
{
|
|
SetWindowLongPtr(s_hWnd, GWLP_WNDPROC, (LPARAM)s_GSWndProc);
|
|
}
|
|
}
|
|
|
|
pXInputEnable(FALSE);
|
|
}
|
|
|
|
EXPORT_C_(UINT32) CALLBACK PADquery()
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
EXPORT_C_(BYTE) PADstartPoll(int pad)
|
|
{
|
|
s_padplugin.StartPoll(pad - 1);
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
EXPORT_C_(BYTE) PADpoll(BYTE value)
|
|
{
|
|
return s_padplugin.Poll(value);
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PADreadPort(int port, PadDataS* data)
|
|
{
|
|
PADstartPoll(port + 1);
|
|
|
|
data->type = PADpoll('B') >> 4;
|
|
PADpoll(0);
|
|
data->status = PADpoll(0) | (PADpoll(0) << 8);
|
|
data->rightJoyX = data->moveX = PADpoll(0);
|
|
data->rightJoyY = data->moveY = PADpoll(0);
|
|
data->leftJoyX = PADpoll(0);
|
|
data->leftJoyY = PADpoll(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PADreadPort1(PadDataS* ppds)
|
|
{
|
|
return PADreadPort(0, ppds);
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PADreadPort2(PadDataS* ppds)
|
|
{
|
|
return PADreadPort(1, ppds);
|
|
}
|
|
|
|
EXPORT_C_(KeyEvent*) PADkeyEvent()
|
|
{
|
|
static KeyEvent e;
|
|
|
|
return s_event.Pop(e) ? &e : NULL;
|
|
}
|
|
|
|
EXPORT_C PADconfigure()
|
|
{
|
|
}
|
|
|
|
EXPORT_C PADabout()
|
|
{
|
|
}
|
|
|
|
EXPORT_C_(UINT32) PADtest()
|
|
{
|
|
return 0;
|
|
}
|
|
|