mirror of https://github.com/PCSX2/pcsx2.git
Merge pull request #1476 from PCSX2/onepad-input-state
onepad 1.3: import lilypad state machine into onepad
This commit is contained in:
commit
f19da94bfd
|
@ -38,7 +38,7 @@ set(onepadGuiResources
|
|||
)
|
||||
|
||||
# plugin name
|
||||
set(Output onepad-1.2.0)
|
||||
set(Output onepad-1.3.0)
|
||||
set(onepadFinalFlags "")
|
||||
|
||||
# onepad sources
|
||||
|
@ -48,7 +48,8 @@ set(onepadSources
|
|||
SDL/joystick.cpp
|
||||
keyboard.cpp
|
||||
KeyStatus.cpp
|
||||
onepad.cpp)
|
||||
onepad.cpp
|
||||
state_management.cpp)
|
||||
|
||||
# onepad headers
|
||||
set(onepadHeaders
|
||||
|
@ -58,7 +59,8 @@ set(onepadHeaders
|
|||
SDL/joystick.h
|
||||
keyboard.h
|
||||
KeyStatus.h
|
||||
onepad.h)
|
||||
onepad.h
|
||||
state_management.h)
|
||||
|
||||
# onepad Linux sources
|
||||
set(onepadLinuxSources
|
||||
|
@ -85,14 +87,10 @@ set(onepadWindowsHeaders
|
|||
)
|
||||
|
||||
if (SDL2_API)
|
||||
set(onepadFinalLibs
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
set(onepadFinalLibs ${SDL2_LIBRARIES})
|
||||
add_definitions(-DSDL_BUILD)
|
||||
else()
|
||||
set(onepadFinalLibs
|
||||
${SDL_LIBRARY}
|
||||
)
|
||||
set(onepadFinalLibs ${SDL_LIBRARY})
|
||||
add_definitions(-DSDL_BUILD)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "GamePad.h"
|
||||
#include "onepad.h"
|
||||
#include "keyboard.h"
|
||||
#include "state_management.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
@ -188,6 +189,8 @@ EXPORT_C_(void) PADupdate(int pad)
|
|||
|
||||
key_status->commit_status(cpad);
|
||||
}
|
||||
|
||||
Pad::rumble_all();
|
||||
}
|
||||
|
||||
EXPORT_C_(void) PADconfigure()
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "onepad.h"
|
||||
#include "svnrev.h"
|
||||
#include "state_management.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
|
@ -47,79 +48,9 @@ bool toggleAutoRepeat = false;
|
|||
|
||||
const u32 version = PS2E_PAD_VERSION;
|
||||
const u32 revision = 1;
|
||||
const u32 build = 2; // increase that with each version
|
||||
const u32 build = 3; // increase that with each version
|
||||
#define PAD_SAVE_STATE_VERSION ((revision << 8) | (build << 0))
|
||||
|
||||
// Useless variable ...
|
||||
//int PadEnum[2][2] = {{0, 2}, {1, 3}};
|
||||
|
||||
u8 stdpar[2][20] = {
|
||||
{0xff, 0x5a, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00},
|
||||
{0xff, 0x5a, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00}
|
||||
};
|
||||
u8 cmd40[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5a},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5a}
|
||||
};
|
||||
u8 cmd41[2][8] = {
|
||||
{0xff, 0x5a, 0xff, 0xff, 0x03, 0x00, 0x00, 0x5a},
|
||||
{0xff, 0x5a, 0xff, 0xff, 0x03, 0x00, 0x00, 0x5a}
|
||||
};
|
||||
u8 unk46[2][8] = {
|
||||
{0xFF, 0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A},
|
||||
{0xFF, 0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A}
|
||||
};
|
||||
u8 unk47[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00}
|
||||
};
|
||||
u8 unk4c[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
};
|
||||
u8 unk4d[2][8] = {
|
||||
{0xff, 0x5a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
{0xff, 0x5a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
};
|
||||
u8 cmd4f[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a}
|
||||
};
|
||||
u8 stdcfg[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
}; // 2 & 3 = 0
|
||||
u8 stdmode[2][8] = {
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
};
|
||||
u8 stdmodel[2][8] = {
|
||||
{0xff,
|
||||
0x5a,
|
||||
0x03, // 03 - dualshock2, 01 - dualshock
|
||||
0x02, // number of modes
|
||||
0x01, // current mode: 01 - analog, 00 - digital
|
||||
0x02,
|
||||
0x01,
|
||||
0x00},
|
||||
{0xff,
|
||||
0x5a,
|
||||
0x03, // 03 - dualshock2, 01 - dualshock
|
||||
0x02, // number of modes
|
||||
0x01, // current mode: 01 - analog, 00 - digital
|
||||
0x02,
|
||||
0x01,
|
||||
0x00}
|
||||
};
|
||||
|
||||
u8 *buf;
|
||||
int padID[2];
|
||||
int padMode[2];
|
||||
int curPad, curByte, curCmd, cmdLen;
|
||||
int ds2mode = 0; // DS Mode at start
|
||||
FILE *padLog = NULL;
|
||||
|
||||
pthread_spinlock_t mutex_KeyEvent;
|
||||
|
@ -128,11 +59,6 @@ KeyStatus* key_status = NULL;
|
|||
|
||||
queue<keyEvent> ev_fifo;
|
||||
|
||||
static int padVib0[2];
|
||||
static int padVib1[2];
|
||||
//static int padVibC[2];
|
||||
static int padVibF[2][4];
|
||||
|
||||
static void InitLibraryName()
|
||||
{
|
||||
#ifdef PUBLIC
|
||||
|
@ -237,19 +163,25 @@ EXPORT_C_(s32) PADinit(u32 flags)
|
|||
|
||||
LoadConfig();
|
||||
|
||||
PADsetMode(0, 0);
|
||||
PADsetMode(1, 0);
|
||||
|
||||
key_status = new KeyStatus();
|
||||
|
||||
Pad::reset_all();
|
||||
|
||||
query.reset();
|
||||
|
||||
for (int port = 0; port < 2; port++)
|
||||
slots[port] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_C_(void) PADshutdown()
|
||||
{
|
||||
CloseLogging();
|
||||
|
||||
delete conf;
|
||||
conf = nullptr;
|
||||
|
||||
delete key_status;
|
||||
key_status = nullptr;
|
||||
}
|
||||
|
@ -298,292 +230,94 @@ EXPORT_C_(u32) PADquery()
|
|||
return 3; // both
|
||||
}
|
||||
|
||||
void PADsetMode(int pad, int mode)
|
||||
EXPORT_C_(s32) PADsetSlot(u8 port, u8 slot)
|
||||
{
|
||||
padMode[pad] = mode;
|
||||
padVib0[pad] = 0;
|
||||
padVib1[pad] = 0;
|
||||
padVibF[pad][0] = 0;
|
||||
padVibF[pad][1] = 0;
|
||||
switch (ds2mode)
|
||||
{
|
||||
case 0: // dualshock
|
||||
switch (mode)
|
||||
{
|
||||
case 0: // digital
|
||||
padID[pad] = 0x41;
|
||||
break;
|
||||
|
||||
case 1: // analog
|
||||
padID[pad] = 0x73;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // dualshock2
|
||||
switch (mode)
|
||||
{
|
||||
case 0: // digital
|
||||
padID[pad] = 0x41;
|
||||
break;
|
||||
|
||||
case 1: // analog
|
||||
padID[pad] = 0x79;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
port--;
|
||||
slot--;
|
||||
if (port > 1 || slot > 3) {
|
||||
return 0;
|
||||
}
|
||||
// Even if no pad there, record the slot, as it is the active slot regardless.
|
||||
slots[port] = slot;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
EXPORT_C_(s32) PADfreeze(int mode, freezeData *data)
|
||||
{
|
||||
if (!data)
|
||||
return -1;
|
||||
|
||||
if (mode == FREEZE_SIZE) {
|
||||
data->size = sizeof(PadPluginFreezeData);
|
||||
|
||||
} else if (mode == FREEZE_LOAD) {
|
||||
PadPluginFreezeData* pdata = (PadPluginFreezeData*)(data->data);
|
||||
|
||||
Pad::stop_vibrate_all();
|
||||
|
||||
if (data->size != sizeof(PadPluginFreezeData) || pdata->version != PAD_SAVE_STATE_VERSION ||
|
||||
strncmp(pdata->format, "OnePad", sizeof(pdata->format)))
|
||||
return 0;
|
||||
|
||||
query = pdata->query;
|
||||
if (pdata->query.slot < 4) {
|
||||
query = pdata->query;
|
||||
}
|
||||
|
||||
// Tales of the Abyss - pad fix
|
||||
// - restore data for both ports
|
||||
for (int port=0; port<2; port++) {
|
||||
for (int slot=0; slot<4; slot++) {
|
||||
u8 mode = pdata->padData[port][slot].mode;
|
||||
|
||||
if (mode != MODE_DIGITAL && mode != MODE_ANALOG && mode != MODE_DS2_NATIVE) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&pads[port][slot], &pdata->padData[port][slot], sizeof(PadFreezeData));
|
||||
}
|
||||
|
||||
if (pdata->slot[port] < 4)
|
||||
slots[port] = pdata->slot[port];
|
||||
}
|
||||
|
||||
} else if (mode == FREEZE_SAVE) {
|
||||
if (data->size != sizeof(PadPluginFreezeData)) return 0;
|
||||
|
||||
PadPluginFreezeData* pdata = (PadPluginFreezeData*)(data->data);
|
||||
|
||||
// Tales of the Abyss - pad fix
|
||||
// - PCSX2 only saves port0 (save #1), then port1 (save #2)
|
||||
|
||||
memset(pdata, 0, data->size);
|
||||
strncpy(pdata->format, "OnePad", sizeof(pdata->format));
|
||||
pdata->version = PAD_SAVE_STATE_VERSION;
|
||||
pdata->query = query;
|
||||
|
||||
for (int port=0; port<2; port++) {
|
||||
for (int slot=0; slot<4; slot++) {
|
||||
pdata->padData[port][slot] = pads[port][slot];
|
||||
}
|
||||
|
||||
pdata->slot[port] = slots[port];
|
||||
}
|
||||
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_C_(u8) PADstartPoll(int pad)
|
||||
{
|
||||
//PAD_LOG("PADstartPoll: %d\n", pad);
|
||||
|
||||
curPad = pad - 1;
|
||||
curByte = 0;
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
u8 _PADpoll(u8 value)
|
||||
{
|
||||
u8 button_check = 0;
|
||||
int vib_small;
|
||||
int vib_big;
|
||||
|
||||
if (curByte == 0)
|
||||
{
|
||||
curByte++;
|
||||
|
||||
//PAD_LOG("PADpoll: cmd: %x\n", value);
|
||||
|
||||
curCmd = value;
|
||||
switch (value)
|
||||
{
|
||||
case CMD_SET_VREF_PARAM: // DUALSHOCK2 ENABLER
|
||||
cmdLen = 8;
|
||||
buf = cmd40[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_QUERY_DS2_ANALOG_MODE: // QUERY_DS2_ANALOG_MODE
|
||||
cmdLen = 8;
|
||||
buf = cmd41[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_READ_DATA_AND_VIBRATE: // READ_DATA
|
||||
|
||||
stdpar[curPad][2] = key_status->get(curPad) >> 8;
|
||||
stdpar[curPad][3] = key_status->get(curPad) & 0xff;
|
||||
stdpar[curPad][4] = key_status->get(curPad, PAD_R_RIGHT);
|
||||
stdpar[curPad][5] = key_status->get(curPad, PAD_R_UP);
|
||||
stdpar[curPad][6] = key_status->get(curPad, PAD_L_RIGHT);
|
||||
stdpar[curPad][7] = key_status->get(curPad, PAD_L_UP);
|
||||
|
||||
if (padMode[curPad] == 1)
|
||||
cmdLen = 20;
|
||||
else
|
||||
cmdLen = 4;
|
||||
|
||||
// Square
|
||||
stdpar[curPad][15] = !test_bit(stdpar[curPad][3], 7) ? key_status->get(curPad, PAD_SQUARE) : 0;
|
||||
// X
|
||||
stdpar[curPad][14] = !test_bit(stdpar[curPad][3], 6) ? key_status->get(curPad, PAD_CROSS) : 0;
|
||||
// Circle
|
||||
stdpar[curPad][13] = !test_bit(stdpar[curPad][3], 5) ? key_status->get(curPad, PAD_CIRCLE) : 0;
|
||||
// Triangle
|
||||
stdpar[curPad][12] = !test_bit(stdpar[curPad][3], 4) ? key_status->get(curPad, PAD_TRIANGLE) : 0;
|
||||
// R1
|
||||
stdpar[curPad][17] = !test_bit(stdpar[curPad][3], 3) ? key_status->get(curPad, PAD_R1) : 0;
|
||||
// L1
|
||||
stdpar[curPad][16] = !test_bit(stdpar[curPad][3], 2) ? key_status->get(curPad, PAD_L1) : 0;
|
||||
// R2
|
||||
stdpar[curPad][19] = !test_bit(stdpar[curPad][3], 1) ? key_status->get(curPad, PAD_R2) : 0;
|
||||
// L2
|
||||
stdpar[curPad][18] = !test_bit(stdpar[curPad][3], 0) ? key_status->get(curPad, PAD_L2) : 0;
|
||||
|
||||
button_check = stdpar[curPad][2] >> 4;
|
||||
// LEFT
|
||||
stdpar[curPad][9] = !test_bit(button_check, 3) ? key_status->get(curPad, PAD_LEFT) : 0;
|
||||
// DOWN
|
||||
stdpar[curPad][11] = !test_bit(button_check, 2) ? key_status->get(curPad, PAD_DOWN) : 0;
|
||||
// RIGHT
|
||||
stdpar[curPad][8] = !test_bit(button_check, 1) ? key_status->get(curPad, PAD_RIGHT) : 0;
|
||||
// UP
|
||||
stdpar[curPad][10] = !test_bit(button_check, 0) ? key_status->get(curPad, PAD_UP) : 0;
|
||||
|
||||
buf = stdpar[curPad];
|
||||
|
||||
/* Small Motor */
|
||||
vib_small = padVibF[curPad][0] ? 2000 : 0;
|
||||
// if ((padVibF[curPad][2] != vib_small) && (padVibC[curPad] >= 0))
|
||||
if (padVibF[curPad][2] != vib_small)
|
||||
{
|
||||
padVibF[curPad][2] = vib_small;
|
||||
GamePad::DoRumble(0, curPad);
|
||||
}
|
||||
|
||||
/* Big Motor */
|
||||
vib_big = padVibF[curPad][1] ? 500 + 37*padVibF[curPad][1] : 0;
|
||||
// if ((padVibF[curPad][3] != vib_big) && (padVibC[curPad] >= 0))
|
||||
if (padVibF[curPad][3] != vib_big)
|
||||
{
|
||||
padVibF[curPad][3] = vib_big;
|
||||
GamePad::DoRumble(1, curPad);
|
||||
}
|
||||
|
||||
return padID[curPad];
|
||||
|
||||
case CMD_CONFIG_MODE: // CONFIG_MODE
|
||||
cmdLen = 8;
|
||||
buf = stdcfg[curPad];
|
||||
if (stdcfg[curPad][3] == 0xff)
|
||||
return 0xf3;
|
||||
else
|
||||
return padID[curPad];
|
||||
|
||||
case CMD_SET_MODE_AND_LOCK: // SET_MODE_AND_LOCK
|
||||
cmdLen = 8;
|
||||
buf = stdmode[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_QUERY_MODEL_AND_MODE: // QUERY_MODEL_AND_MODE
|
||||
cmdLen = 8;
|
||||
buf = stdmodel[curPad];
|
||||
buf[4] = padMode[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_QUERY_ACT: // ??
|
||||
cmdLen = 8;
|
||||
buf = unk46[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_QUERY_COMB: // ??
|
||||
cmdLen = 8;
|
||||
buf = unk47[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_QUERY_MODE: // QUERY_MODE ??
|
||||
cmdLen = 8;
|
||||
buf = unk4c[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_VIBRATION_TOGGLE:
|
||||
cmdLen = 8;
|
||||
buf = unk4d[curPad];
|
||||
return 0xf3;
|
||||
|
||||
case CMD_SET_DS2_NATIVE_MODE: // SET_DS2_NATIVE_MODE
|
||||
cmdLen = 8;
|
||||
padID[curPad] = 0x79; // setting ds2 mode
|
||||
ds2mode = 1; // Set DS2 Mode
|
||||
buf = cmd4f[curPad];
|
||||
return 0xf3;
|
||||
|
||||
default:
|
||||
PAD_LOG("*PADpoll*: unknown cmd %x\n", value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (curCmd)
|
||||
{
|
||||
case CMD_READ_DATA_AND_VIBRATE:
|
||||
|
||||
if (curByte == padVib0[curPad])
|
||||
padVibF[curPad][0] = value&1;
|
||||
if (curByte == padVib1[curPad])
|
||||
padVibF[curPad][1] = value;
|
||||
break;
|
||||
case CMD_CONFIG_MODE:
|
||||
if (curByte == 2)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
buf[2] = 0;
|
||||
buf[3] = 0;
|
||||
break;
|
||||
case 1:
|
||||
buf[2] = 0xff;
|
||||
buf[3] = 0xff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_SET_MODE_AND_LOCK:
|
||||
if (curByte == 2)
|
||||
{
|
||||
PADsetMode(curPad, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_QUERY_ACT:
|
||||
if (curByte == 2)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0: // default
|
||||
buf[5] = 0x2;
|
||||
buf[6] = 0x0;
|
||||
buf[7] = 0xA;
|
||||
break;
|
||||
case 1: // Param std conf change
|
||||
buf[5] = 0x1;
|
||||
buf[6] = 0x1;
|
||||
buf[7] = 0x14;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_QUERY_MODE:
|
||||
if (curByte == 2)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0: // mode 0 - digital mode
|
||||
buf[5] = 0x4;
|
||||
break;
|
||||
|
||||
case 1: // mode 1 - analog mode
|
||||
buf[5] = 0x7;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_VIBRATION_TOGGLE:
|
||||
|
||||
if (curByte >= 2)
|
||||
{
|
||||
if (curByte == padVib0[curPad])
|
||||
buf[curByte] = 0x00;
|
||||
if (curByte == padVib1[curPad])
|
||||
buf[curByte] = 0x01;
|
||||
if (value == 0x00)
|
||||
{
|
||||
padVib0[curPad] = curByte;
|
||||
}
|
||||
else if (value == 0x01)
|
||||
{
|
||||
padVib1[curPad] = curByte;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (curByte >= cmdLen) return 0;
|
||||
return buf[curByte++];
|
||||
return pad_start_poll(pad);
|
||||
}
|
||||
|
||||
EXPORT_C_(u8) PADpoll(u8 value)
|
||||
{
|
||||
u8 ret;
|
||||
|
||||
ret = _PADpoll(value);
|
||||
//PAD_LOG("PADpoll: %x (%d: %x)\n", value, curByte, ret);
|
||||
return ret;
|
||||
return pad_poll(value);
|
||||
}
|
||||
|
||||
// PADkeyEvent is called every vsync (return NULL if no event)
|
||||
|
|
|
@ -84,17 +84,17 @@ extern bool toggleAutoRepeat;
|
|||
|
||||
enum PadCommands
|
||||
{
|
||||
CMD_SET_VREF_PARAM = 0x40,
|
||||
CMD_SET_VREF_PARAM = 0x40,
|
||||
CMD_QUERY_DS2_ANALOG_MODE = 0x41,
|
||||
CMD_READ_DATA_AND_VIBRATE = 0x42,
|
||||
CMD_CONFIG_MODE = 0x43,
|
||||
CMD_SET_MODE_AND_LOCK = 0x44,
|
||||
CMD_QUERY_MODEL_AND_MODE = 0x45,
|
||||
CMD_QUERY_ACT = 0x46, // ??
|
||||
CMD_QUERY_COMB = 0x47, // ??
|
||||
CMD_QUERY_MODE = 0x4C, // QUERY_MODE ??
|
||||
CMD_VIBRATION_TOGGLE = 0x4D,
|
||||
CMD_SET_DS2_NATIVE_MODE = 0x4F // SET_DS2_NATIVE_MODE
|
||||
CMD_CONFIG_MODE = 0x43,
|
||||
CMD_SET_MODE_AND_LOCK = 0x44,
|
||||
CMD_QUERY_MODEL_AND_MODE = 0x45,
|
||||
CMD_QUERY_ACT = 0x46, // ??
|
||||
CMD_QUERY_COMB = 0x47, // ??
|
||||
CMD_QUERY_MODE = 0x4C, // QUERY_MODE ??
|
||||
CMD_VIBRATION_TOGGLE = 0x4D,
|
||||
CMD_SET_DS2_NATIVE_MODE = 0x4F // SET_DS2_NATIVE_MODE
|
||||
};
|
||||
|
||||
enum gamePadValues
|
||||
|
|
|
@ -0,0 +1,465 @@
|
|||
/* OnePAD
|
||||
* Copyright (C) 2016
|
||||
*
|
||||
* Based on LilyPad
|
||||
* Copyright (C) 2002-2014 PCSX2 Dev Team/ChickenLiver
|
||||
*
|
||||
* 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 of the License, 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include "state_management.h"
|
||||
|
||||
// Typical packet response on the bus
|
||||
static const u8 ConfigExit[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static const u8 noclue[7] = {0x5A, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5A};
|
||||
static const u8 setMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static const u8 queryModelDS2[7] = {0x5A, 0x03, 0x02, 0x00, 0x02, 0x01, 0x00};
|
||||
static const u8 queryModelDS1[7] = {0x5A, 0x01, 0x02, 0x00, 0x02, 0x01, 0x00};
|
||||
static const u8 queryComb[7] = {0x5A, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00};
|
||||
static const u8 queryMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static const u8 setNativeMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A};
|
||||
|
||||
static u8 queryMaskMode[7] = {0x5A, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x5A};
|
||||
|
||||
static const u8 queryAct[2][7] = {
|
||||
{0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A},
|
||||
{0x5A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x14}};
|
||||
|
||||
QueryInfo query;
|
||||
Pad pads[2][4];
|
||||
int slots[2] = {0, 0};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// QueryInfo implementation
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void QueryInfo::reset()
|
||||
{
|
||||
port = 0;
|
||||
slot = 0;
|
||||
lastByte = 1;
|
||||
currentCommand = 0;
|
||||
numBytes = 0;
|
||||
queryDone = 1;
|
||||
memset(response, 0xF3, sizeof(response));
|
||||
}
|
||||
|
||||
u8 QueryInfo::start_poll(int _port)
|
||||
{
|
||||
if (port > 1) {
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
queryDone = 0;
|
||||
port = _port;
|
||||
slot = slots[port];
|
||||
numBytes = 2;
|
||||
lastByte = 0;
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Pad implementation
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Pad::set_mode(int _mode)
|
||||
{
|
||||
mode = _mode;
|
||||
|
||||
fprintf(stdout, "OnePad: set new pad mode=");
|
||||
if (mode == MODE_DIGITAL)
|
||||
fprintf(stdout, "DIGITAL\n");
|
||||
else if (mode == MODE_ANALOG)
|
||||
fprintf(stdout, "ANALOG\n");
|
||||
else if (mode == MODE_DS2_NATIVE)
|
||||
fprintf(stdout, "DS2 NATIVE\n");
|
||||
else
|
||||
fprintf(stdout, "??? 0x%x\n", mode);
|
||||
}
|
||||
|
||||
void Pad::set_vibrate(int motor, u8 val)
|
||||
{
|
||||
nextVibrate[motor] = val;
|
||||
}
|
||||
|
||||
void Pad::reset_vibrate()
|
||||
{
|
||||
set_vibrate(0, 0);
|
||||
set_vibrate(1, 0);
|
||||
memset(vibrate, 0xFF, sizeof(vibrate));
|
||||
vibrate[0] = 0x5A;
|
||||
}
|
||||
|
||||
void Pad::reset()
|
||||
{
|
||||
memset(this, 0, sizeof(PadFreezeData));
|
||||
|
||||
set_mode(MODE_DIGITAL);
|
||||
umask[0] = umask[1] = 0xFF;
|
||||
|
||||
// Sets up vibrate variable.
|
||||
reset_vibrate();
|
||||
}
|
||||
|
||||
void Pad::rumble(int port)
|
||||
{
|
||||
for (int motor=0; motor<2; motor++) {
|
||||
// TODO: Probably be better to send all of these at once.
|
||||
if (nextVibrate[motor] | currentVibrate[motor]) {
|
||||
currentVibrate[motor] = nextVibrate[motor];
|
||||
|
||||
GamePad::DoRumble(motor, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Pad::stop_vibrate_all()
|
||||
{
|
||||
#if 0
|
||||
for (int i=0; i<8; i++) {
|
||||
SetVibrate(i&1, i>>1, 0, 0);
|
||||
SetVibrate(i&1, i>>1, 1, 0);
|
||||
}
|
||||
#endif
|
||||
// FIXME equivalent ?
|
||||
for (int port = 0; port < 2; port++)
|
||||
for (int slot = 0; slot < 4; slot++)
|
||||
pads[port][slot].reset_vibrate();
|
||||
}
|
||||
|
||||
void Pad::reset_all()
|
||||
{
|
||||
for (int port = 0; port < 2; port++)
|
||||
for (int slot = 0; slot < 4; slot++)
|
||||
pads[port][slot].reset();
|
||||
}
|
||||
|
||||
void Pad::rumble_all()
|
||||
{
|
||||
for (int port=0; port<2; port++)
|
||||
for (int slot=0; slot<4; slot++)
|
||||
pads[port][slot].rumble(port);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Pad implementation
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline bool IsDualshock2() {
|
||||
// FIXME
|
||||
#if 0
|
||||
return config.padConfigs[query.port][query.slot].type == Dualshock2Pad ||
|
||||
(config.padConfigs[query.port][query.slot].type == GuitarPad && config.GH2);
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
u8 pad_start_poll(u8 pad)
|
||||
{
|
||||
return query.start_poll(pad - 1);
|
||||
}
|
||||
|
||||
u8 pad_poll(u8 value)
|
||||
{
|
||||
if (query.lastByte+1 >= query.numBytes) {
|
||||
return 0;
|
||||
}
|
||||
if (query.lastByte && query.queryDone) {
|
||||
return query.response[++query.lastByte];
|
||||
}
|
||||
|
||||
int i;
|
||||
Pad *pad = &pads[query.port][query.slot];
|
||||
|
||||
if (query.lastByte == 0) {
|
||||
query.lastByte++;
|
||||
query.currentCommand = value;
|
||||
|
||||
switch(value) {
|
||||
case CMD_CONFIG_MODE:
|
||||
if (pad->config) {
|
||||
// In config mode. Might not actually be leaving it.
|
||||
query.set_result(ConfigExit);
|
||||
return 0xF3;
|
||||
}
|
||||
|
||||
case CMD_READ_DATA_AND_VIBRATE:
|
||||
{
|
||||
query.response[2] = 0x5A;
|
||||
#if 0
|
||||
Update(query.port, query.slot);
|
||||
ButtonSum *sum = &pad->sum;
|
||||
|
||||
u8 b1 = 0xFF, b2 = 0xFF;
|
||||
for (i = 0; i<4; i++) {
|
||||
b1 -= (sum->buttons[i] > 0) << i;
|
||||
}
|
||||
for (i = 0; i<8; i++) {
|
||||
b2 -= (sum->buttons[i+4] > 0) << i;
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME
|
||||
#if 0
|
||||
if (config.padConfigs[query.port][query.slot].type == GuitarPad && !config.GH2) {
|
||||
sum->buttons[15] = 255;
|
||||
// Not sure about this. Forces wammy to be from 0 to 0x7F.
|
||||
// if (sum->sticks[2].vert > 0) sum->sticks[2].vert = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
for (i = 4; i<8; i++) {
|
||||
b1 -= (sum->buttons[i+8] > 0) << i;
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME
|
||||
#if 0
|
||||
//Left, Right and Down are always pressed on Pop'n Music controller.
|
||||
if (config.padConfigs[query.port][query.slot].type == PopnPad)
|
||||
b1=b1 & 0x1f;
|
||||
#endif
|
||||
|
||||
uint16_t buttons = key_status->get(query.port);
|
||||
|
||||
query.numBytes = 5;
|
||||
|
||||
query.response[3] = (buttons >> 8) & 0xFF;
|
||||
query.response[4] = (buttons >> 0) & 0xFF;
|
||||
|
||||
if (pad->mode != MODE_DIGITAL) { // ANALOG || DS2 native
|
||||
query.numBytes = 9;
|
||||
|
||||
query.response[5] = key_status->get(query.port, PAD_R_RIGHT);
|
||||
query.response[6] = key_status->get(query.port, PAD_R_UP);
|
||||
query.response[7] = key_status->get(query.port, PAD_L_RIGHT);
|
||||
query.response[8] = key_status->get(query.port, PAD_L_UP);
|
||||
|
||||
if (pad->mode != MODE_ANALOG) { // DS2 native
|
||||
query.numBytes = 21;
|
||||
|
||||
query.response[9] = !test_bit(buttons, 13) ? key_status->get(query.port, PAD_RIGHT) : 0;
|
||||
query.response[10] = !test_bit(buttons, 15) ? key_status->get(query.port, PAD_LEFT) : 0;
|
||||
query.response[11] = !test_bit(buttons, 12) ? key_status->get(query.port, PAD_UP) : 0;
|
||||
query.response[12] = !test_bit(buttons, 14) ? key_status->get(query.port, PAD_DOWN) : 0;
|
||||
|
||||
query.response[13] = !test_bit(buttons, 4) ? key_status->get(query.port, PAD_TRIANGLE) : 0;
|
||||
query.response[14] = !test_bit(buttons, 5) ? key_status->get(query.port, PAD_CIRCLE) : 0;
|
||||
query.response[15] = !test_bit(buttons, 6) ? key_status->get(query.port, PAD_CROSS) : 0;
|
||||
query.response[16] = !test_bit(buttons, 7) ? key_status->get(query.port, PAD_SQUARE) : 0;
|
||||
query.response[17] = !test_bit(buttons, 2) ? key_status->get(query.port, PAD_L1) : 0;
|
||||
query.response[18] = !test_bit(buttons, 3) ? key_status->get(query.port, PAD_R1) : 0;
|
||||
query.response[19] = !test_bit(buttons, 0) ? key_status->get(query.port, PAD_L2) : 0;
|
||||
query.response[20] = !test_bit(buttons, 1) ? key_status->get(query.port, PAD_R2) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
query.response[3] = b1;
|
||||
query.response[4] = b2;
|
||||
|
||||
query.numBytes = 5;
|
||||
if (pad->mode != MODE_DIGITAL) {
|
||||
query.response[5] = Cap((sum->sticks[0].horiz+255)/2);
|
||||
query.response[6] = Cap((sum->sticks[0].vert+255)/2);
|
||||
query.response[7] = Cap((sum->sticks[1].horiz+255)/2);
|
||||
query.response[8] = Cap((sum->sticks[1].vert+255)/2);
|
||||
|
||||
query.numBytes = 9;
|
||||
if (pad->mode != MODE_ANALOG) {
|
||||
// Good idea? No clue.
|
||||
//query.response[3] &= pad->mask[0];
|
||||
//query.response[4] &= pad->mask[1];
|
||||
|
||||
// No need to cap these, already done int CapSum().
|
||||
query.response[9] = (unsigned char)sum->buttons[13]; //D-pad right
|
||||
query.response[10] = (unsigned char)sum->buttons[15]; //D-pad left
|
||||
query.response[11] = (unsigned char)sum->buttons[12]; //D-pad up
|
||||
query.response[12] = (unsigned char)sum->buttons[14]; //D-pad down
|
||||
|
||||
query.response[13] = (unsigned char) sum->buttons[8];
|
||||
query.response[14] = (unsigned char) sum->buttons[9];
|
||||
query.response[15] = (unsigned char) sum->buttons[10];
|
||||
query.response[16] = (unsigned char) sum->buttons[11];
|
||||
query.response[17] = (unsigned char) sum->buttons[6];
|
||||
query.response[18] = (unsigned char) sum->buttons[7];
|
||||
query.response[19] = (unsigned char) sum->buttons[4];
|
||||
query.response[20] = (unsigned char) sum->buttons[5];
|
||||
query.numBytes = 21;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
query.lastByte=1;
|
||||
return pad->mode;
|
||||
|
||||
case CMD_SET_VREF_PARAM:
|
||||
query.set_final_result(noclue);
|
||||
break;
|
||||
|
||||
case CMD_QUERY_DS2_ANALOG_MODE:
|
||||
// Right? Wrong? No clue.
|
||||
if (pad->mode == MODE_DIGITAL) {
|
||||
queryMaskMode[1] = queryMaskMode[2] = queryMaskMode[3] = 0;
|
||||
queryMaskMode[6] = 0x00;
|
||||
}
|
||||
else {
|
||||
queryMaskMode[1] = pad->umask[0];
|
||||
queryMaskMode[2] = pad->umask[1];
|
||||
queryMaskMode[3] = 0x03;
|
||||
// Not entirely sure about this.
|
||||
//queryMaskMode[3] = 0x01 | (pad->mode == MODE_DS2_NATIVE)*2;
|
||||
queryMaskMode[6] = 0x5A;
|
||||
}
|
||||
query.set_final_result(queryMaskMode);
|
||||
break;
|
||||
|
||||
case CMD_SET_MODE_AND_LOCK:
|
||||
query.set_result(setMode);
|
||||
pad->reset_vibrate();
|
||||
break;
|
||||
|
||||
case CMD_QUERY_MODEL_AND_MODE:
|
||||
if (IsDualshock2()) {
|
||||
query.set_final_result(queryModelDS2);
|
||||
} else {
|
||||
query.set_final_result(queryModelDS1);
|
||||
}
|
||||
// Not digital mode.
|
||||
query.response[5] = (pad->mode & 0xF) != 1;
|
||||
break;
|
||||
|
||||
case CMD_QUERY_ACT:
|
||||
query.set_result(queryAct[0]);
|
||||
break;
|
||||
|
||||
case CMD_QUERY_COMB:
|
||||
query.set_final_result(queryComb);
|
||||
break;
|
||||
|
||||
case CMD_QUERY_MODE:
|
||||
query.set_result(queryMode);
|
||||
break;
|
||||
|
||||
case CMD_VIBRATION_TOGGLE:
|
||||
memcpy(query.response+2, pad->vibrate, 7);
|
||||
query.numBytes = 9;
|
||||
//query.set_result(pad->vibrate); // warning copy 7b not 8 (but it is really important?)
|
||||
pad->reset_vibrate();
|
||||
break;
|
||||
|
||||
case CMD_SET_DS2_NATIVE_MODE:
|
||||
if (IsDualshock2()) {
|
||||
query.set_result(setNativeMode);
|
||||
} else {
|
||||
query.set_final_result(setNativeMode);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
query.numBytes = 0;
|
||||
query.queryDone = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0xF3;
|
||||
|
||||
} else {
|
||||
query.lastByte++;
|
||||
|
||||
switch (query.currentCommand) {
|
||||
case CMD_READ_DATA_AND_VIBRATE:
|
||||
if (query.lastByte == pad->vibrateI[0])
|
||||
pad->set_vibrate(1, 255*(value&1));
|
||||
else if (query.lastByte == pad->vibrateI[1])
|
||||
pad->set_vibrate(0, value);
|
||||
|
||||
break;
|
||||
|
||||
case CMD_CONFIG_MODE:
|
||||
if (query.lastByte == 3) {
|
||||
query.queryDone = 1;
|
||||
pad->config = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_SET_MODE_AND_LOCK:
|
||||
if (query.lastByte == 3 && value < 2) {
|
||||
pad->set_mode(value ? MODE_ANALOG : MODE_DIGITAL);
|
||||
} else if (query.lastByte == 4) {
|
||||
if (value == 3)
|
||||
pad->modeLock = 3;
|
||||
else
|
||||
pad->modeLock = 0;
|
||||
|
||||
query.queryDone = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_QUERY_ACT:
|
||||
if (query.lastByte == 3) {
|
||||
if (value<2) query.set_result(queryAct[value]);
|
||||
// bunch of 0's
|
||||
// else query.set_result(setMode);
|
||||
query.queryDone = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_QUERY_MODE:
|
||||
if (query.lastByte == 3 && value<2) {
|
||||
query.response[6] = 4+value*3;
|
||||
query.queryDone = 1;
|
||||
}
|
||||
// bunch of 0's
|
||||
//else data = setMode;
|
||||
break;
|
||||
|
||||
case CMD_VIBRATION_TOGGLE:
|
||||
if (query.lastByte>=3) {
|
||||
if (value == 0) {
|
||||
pad->vibrateI[0] = (u8)query.lastByte;
|
||||
}
|
||||
else if (value == 1) {
|
||||
pad->vibrateI[1] = (u8)query.lastByte;
|
||||
}
|
||||
pad->vibrate[query.lastByte-2] = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_SET_DS2_NATIVE_MODE:
|
||||
if (query.lastByte == 3 || query.lastByte == 4) {
|
||||
pad->umask[query.lastByte-3] = value;
|
||||
} else if (query.lastByte == 5) {
|
||||
if (!(value & 1))
|
||||
pad->set_mode(MODE_DIGITAL);
|
||||
else if (!(value & 2))
|
||||
pad->set_mode(MODE_ANALOG);
|
||||
else
|
||||
pad->set_mode(MODE_DS2_NATIVE);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return query.response[query.lastByte];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/* OnePAD
|
||||
* Copyright (C) 2016
|
||||
*
|
||||
* Based on LilyPad
|
||||
* Copyright (C) 2002-2014 PCSX2 Dev Team/ChickenLiver
|
||||
*
|
||||
* 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 of the License, 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include "onepad.h"
|
||||
|
||||
#define MODE_DIGITAL 0x41
|
||||
#define MODE_ANALOG 0x73
|
||||
#define MODE_DS2_NATIVE 0x79
|
||||
|
||||
// The state of the PS2 bus
|
||||
struct QueryInfo {
|
||||
u8 port;
|
||||
u8 slot;
|
||||
u8 lastByte;
|
||||
u8 currentCommand;
|
||||
u8 numBytes;
|
||||
u8 queryDone;
|
||||
u8 response[42];
|
||||
|
||||
void reset();
|
||||
u8 start_poll(int port);
|
||||
|
||||
template<size_t S>
|
||||
void set_result(const u8 (&rsp)[S])
|
||||
{
|
||||
memcpy(response+2, rsp, S);
|
||||
numBytes = 2 + S;
|
||||
}
|
||||
|
||||
template<size_t S>
|
||||
void set_final_result(const u8 (&rsp)[S])
|
||||
{
|
||||
set_result(rsp);
|
||||
queryDone = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Freeze data, for a single pad. Basically has all pad state that
|
||||
// a PS2 can set.
|
||||
struct PadFreezeData {
|
||||
// Digital / Analog / DS2 Native
|
||||
u8 mode;
|
||||
|
||||
u8 modeLock;
|
||||
|
||||
// In config mode
|
||||
u8 config;
|
||||
|
||||
u8 vibrate[8];
|
||||
u8 umask[2];
|
||||
|
||||
// Vibration indices.
|
||||
u8 vibrateI[2];
|
||||
|
||||
// Last vibration value sent to controller.
|
||||
// Only used so as not to call vibration
|
||||
// functions when old and new values are both 0.
|
||||
u8 currentVibrate[2];
|
||||
|
||||
// Next vibrate val to send to controller. If next and current are
|
||||
// both 0, nothing is sent to the controller. Otherwise, it's sent
|
||||
// on every update.
|
||||
u8 nextVibrate[2];
|
||||
};
|
||||
|
||||
class Pad : public PadFreezeData {
|
||||
public:
|
||||
// Lilypad store here the state of PC pad
|
||||
|
||||
void rumble(int port);
|
||||
void set_vibrate(int motor, u8 val);
|
||||
void reset_vibrate();
|
||||
void reset();
|
||||
|
||||
void set_mode(int mode);
|
||||
|
||||
static void reset_all();
|
||||
static void stop_vibrate_all();
|
||||
static void rumble_all();
|
||||
};
|
||||
|
||||
// Full state to manage save state
|
||||
struct PadPluginFreezeData {
|
||||
char format[8];
|
||||
u32 version;
|
||||
// active slot for port
|
||||
u8 slot[2];
|
||||
PadFreezeData padData[2][4];
|
||||
QueryInfo query;
|
||||
};
|
||||
|
||||
extern QueryInfo query;
|
||||
extern Pad pads[2][4];
|
||||
extern int slots[2];
|
||||
|
||||
extern u8 pad_start_poll(u8 pad);
|
||||
extern u8 pad_poll(u8 value);
|
Loading…
Reference in New Issue