2014-04-12 10:44:38 +00:00
|
|
|
/* LilyPad - Pad plugin for PS2 Emulator
|
|
|
|
* Copyright (C) 2002-2014 PCSX2 Dev Team/ChickenLiver
|
|
|
|
*
|
|
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the
|
|
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
|
|
* Software Found- ation, either version 3 of the License, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* PCSX2 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 PCSX2. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
#include "Global.h"
|
|
|
|
|
|
|
|
// For escape timer, so as not to break GSDX+DX9.
|
|
|
|
#include <time.h>
|
2016-02-14 12:29:05 +00:00
|
|
|
#include <fstream>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <sstream>
|
2010-04-24 21:37:39 +00:00
|
|
|
#include "resource.h"
|
|
|
|
#include "InputManager.h"
|
|
|
|
#include "Config.h"
|
|
|
|
|
|
|
|
#define PADdefs
|
|
|
|
|
|
|
|
#include "DeviceEnumerator.h"
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
#include "WndProcEater.h"
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
#include "KeyboardQueue.h"
|
|
|
|
#include "svnrev.h"
|
|
|
|
#include "DualShock3.h"
|
|
|
|
#include "HidDevice.h"
|
|
|
|
|
|
|
|
#define WMA_FORCE_UPDATE (WM_APP + 0x537)
|
|
|
|
#define FORCE_UPDATE_WPARAM ((WPARAM)0x74328943)
|
|
|
|
#define FORCE_UPDATE_LPARAM ((LPARAM)0x89437437)
|
|
|
|
|
|
|
|
// LilyPad version.
|
2011-11-07 09:29:36 +00:00
|
|
|
#define VERSION ((0<<8) | 11 | (0<<24))
|
2010-04-24 21:37:39 +00:00
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef __linux__
|
|
|
|
Display *GSdsp;
|
|
|
|
Window GSwin;
|
|
|
|
#else
|
2010-04-24 21:37:39 +00:00
|
|
|
HINSTANCE hInst;
|
|
|
|
HWND hWnd;
|
|
|
|
HWND hWndTop;
|
|
|
|
|
|
|
|
WndProcEater hWndGSProc;
|
|
|
|
WndProcEater hWndTopProc;
|
|
|
|
|
|
|
|
// ButtonProc is used mostly by the Config panel for eating the procedures of the
|
|
|
|
// button with keyboard focus.
|
|
|
|
WndProcEater hWndButtonProc;
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
// Keeps the various sources for Update polling (PADpoll, PADupdate, etc) from wreaking
|
|
|
|
// havoc on each other...
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef __linux__
|
|
|
|
static std::mutex updateLock;
|
|
|
|
#else
|
2010-04-24 21:37:39 +00:00
|
|
|
CRITICAL_SECTION updateLock;
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
// Used to toggle mouse listening.
|
|
|
|
u8 miceEnabled;
|
|
|
|
|
|
|
|
// 2 when both pads are initialized, 1 for one pad, etc.
|
|
|
|
int openCount = 0;
|
|
|
|
|
|
|
|
int activeWindow = 0;
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
int windowThreadId = 0;
|
|
|
|
int updateQueued = 0;
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
2015-11-07 09:01:33 +00:00
|
|
|
u32 bufSize = 0;
|
2010-04-24 21:37:39 +00:00
|
|
|
unsigned char outBuf[50];
|
|
|
|
unsigned char inBuf[50];
|
|
|
|
|
|
|
|
// windowThreadId = GetWindowThreadProcessId(hWnd, 0);
|
|
|
|
|
|
|
|
#define MODE_DIGITAL 0x41
|
|
|
|
#define MODE_ANALOG 0x73
|
|
|
|
#define MODE_DS2_NATIVE 0x79
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
int IsWindowMaximized (HWND hWnd) {
|
|
|
|
RECT rect;
|
|
|
|
if (GetWindowRect(hWnd, &rect)) {
|
|
|
|
POINT p;
|
|
|
|
p.x = rect.left;
|
|
|
|
p.y = rect.top;
|
|
|
|
MONITORINFO info;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
info.cbSize = sizeof(info);
|
|
|
|
HMONITOR hMonitor;
|
|
|
|
if ((hMonitor = MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY)) &&
|
|
|
|
GetMonitorInfo(hMonitor, &info) &&
|
|
|
|
memcmp(&info.rcMonitor, &rect, sizeof(rect)) == 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
void DEBUG_TEXT_OUT(const char *text) {
|
2016-02-14 12:29:05 +00:00
|
|
|
if (!config.debug)
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::ofstream file("logs/padLog.txt", std::ios::app);
|
|
|
|
if (!file.good())
|
|
|
|
return;
|
|
|
|
file << text;
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DEBUG_NEW_SET() {
|
2016-02-14 12:29:05 +00:00
|
|
|
if (config.debug && bufSize > 1) {
|
|
|
|
std::ofstream file("logs/padLog.txt", std::ios::app);
|
|
|
|
if (file.good()) {
|
|
|
|
std::stringstream stream;
|
|
|
|
stream.setf(std::ios::hex, std::ios::basefield);
|
|
|
|
stream.setf(std::ios::uppercase);
|
|
|
|
stream.fill('0');
|
|
|
|
|
|
|
|
unsigned char *buffer[2] = {inBuf, outBuf};
|
|
|
|
for (const auto &buf : buffer) {
|
|
|
|
// Port/FF
|
|
|
|
stream << std::setw(2) << int(buf[0]);
|
|
|
|
// Active slots/Enabled (only relevant for multitap)
|
|
|
|
stream << " (" << std::setw(2) << int(buf[1]) << ')';
|
|
|
|
|
|
|
|
// Command/Response
|
|
|
|
for (u32 n = 2; n < bufSize; ++n)
|
|
|
|
stream << ' ' << std::setw(2) << int(buf[n]);
|
|
|
|
stream << '\n';
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
2016-02-14 12:29:05 +00:00
|
|
|
stream << '\n';
|
|
|
|
file << stream.rdbuf();
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
bufSize = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void DEBUG_IN(unsigned char c) {
|
2016-02-14 12:29:05 +00:00
|
|
|
if (bufSize < sizeof(inBuf)) inBuf[bufSize] = c;
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
inline void DEBUG_OUT(unsigned char c) {
|
2016-02-14 12:29:05 +00:00
|
|
|
if (bufSize < sizeof(outBuf)) outBuf[bufSize++] = c;
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct Stick {
|
|
|
|
int horiz;
|
|
|
|
int vert;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Sum of states of all controls for a pad (Not including toggles).
|
|
|
|
struct ButtonSum {
|
|
|
|
int buttons[12];
|
|
|
|
Stick sticks[3];
|
|
|
|
};
|
|
|
|
|
2011-11-07 09:29:36 +00:00
|
|
|
#define PAD_SAVE_STATE_VERSION 3
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
// 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:
|
|
|
|
// Current pad state.
|
|
|
|
ButtonSum sum;
|
|
|
|
// State of locked buttons. Already included by sum, used
|
|
|
|
// as initial value of sum.
|
|
|
|
ButtonSum lockedSum;
|
|
|
|
|
|
|
|
// Flags for which controls (buttons or axes) are locked, if any.
|
|
|
|
DWORD lockedState;
|
|
|
|
|
|
|
|
// Used to keep track of which pads I'm running.
|
|
|
|
// Note that initialized pads *can* be disabled.
|
|
|
|
// I keep track of state of non-disabled non-initialized
|
|
|
|
// pads, but should never be asked for their state.
|
|
|
|
u8 initialized;
|
|
|
|
|
|
|
|
// Set to 1 if the state of this pad has been updated since its state
|
|
|
|
// was last queried.
|
|
|
|
char stateUpdated;
|
|
|
|
|
|
|
|
// initialized and not disabled (and mtap state for slots > 0).
|
|
|
|
u8 enabled;
|
|
|
|
} pads[2][4];
|
|
|
|
|
|
|
|
// Active slots for each port.
|
|
|
|
int slots[2];
|
|
|
|
// Which ports we're running on.
|
|
|
|
int portInitialized[2];
|
|
|
|
|
|
|
|
// Force value to be from 0 to 255.
|
|
|
|
u8 Cap (int i) {
|
|
|
|
if (i<0) return 0;
|
|
|
|
if (i>255) return 255;
|
|
|
|
return (u8) i;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void ReleaseModifierKeys() {
|
|
|
|
QueueKeyEvent(VK_SHIFT, KEYRELEASE);
|
|
|
|
QueueKeyEvent(VK_MENU, KEYRELEASE);
|
|
|
|
QueueKeyEvent(VK_CONTROL, KEYRELEASE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// RefreshEnabledDevices() enables everything that can potentially
|
|
|
|
// be bound to, as well as the "Ignore keyboard" device.
|
|
|
|
//
|
|
|
|
// This enables everything that input should be read from while the
|
|
|
|
// emulator is running. Takes into account mouse and focus state
|
|
|
|
// and which devices have bindings for enabled pads. Releases
|
|
|
|
// keyboards if window is not focused. Releases game devices if
|
|
|
|
// background monitoring is not checked.
|
|
|
|
// And releases games if not focused and config.background is not set.
|
|
|
|
void UpdateEnabledDevices(int updateList = 0) {
|
|
|
|
// Enable all devices I might want. Can ignore the rest.
|
|
|
|
RefreshEnabledDevices(updateList);
|
|
|
|
// Figure out which pads I'm getting input for.
|
|
|
|
for (int port = 0; port<2; port++) {
|
|
|
|
for (int slot = 0; slot<4; slot++) {
|
|
|
|
if (slot && !config.multitap[port]) {
|
|
|
|
pads[port][slot].enabled = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pads[port][slot].enabled = pads[port][slot].initialized && config.padConfigs[port][slot].type != DisabledPad;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i=0; i<dm->numDevices; i++) {
|
|
|
|
Device *dev = dm->devices[i];
|
|
|
|
|
|
|
|
if (!dev->enabled) continue;
|
|
|
|
if (!dev->attached) {
|
|
|
|
dm->DisableDevice(i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disable ignore keyboard if don't have focus or there are no keys to ignore.
|
|
|
|
if (dev->api == IGNORE_KEYBOARD) {
|
|
|
|
if ((!config.vistaVolume && (config.keyboardApi == NO_API || !dev->pads[0][0].numBindings)) || !activeWindow) {
|
|
|
|
dm->DisableDevice(i);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Keep for PCSX2 keyboard shotcuts, unless unfocused.
|
|
|
|
if (dev->type == KEYBOARD) {
|
|
|
|
if (!activeWindow) dm->DisableDevice(i);
|
|
|
|
}
|
|
|
|
// Keep for cursor hiding consistency, unless unfocused.
|
|
|
|
// miceEnabled tracks state of mouse enable/disable button, not if mouse API is set to disabled.
|
|
|
|
else if (dev->type == MOUSE) {
|
|
|
|
if (!miceEnabled || !activeWindow) dm->DisableDevice(i);
|
|
|
|
}
|
|
|
|
else if (!activeWindow && !config.background) dm->DisableDevice(i);
|
|
|
|
else {
|
|
|
|
int numActiveBindings = 0;
|
|
|
|
for (int port=0; port<2; port++) {
|
|
|
|
for (int slot=0; slot<4; slot++) {
|
|
|
|
if (pads[port][slot].enabled) {
|
|
|
|
numActiveBindings += dev->pads[port][slot].numBindings + dev->pads[port][slot].numFFBindings;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!numActiveBindings)
|
|
|
|
dm->DisableDevice(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, void* lpvReserved) {
|
|
|
|
hInst = hInstance;
|
|
|
|
if (fdwReason == DLL_PROCESS_ATTACH) {
|
|
|
|
InitializeCriticalSection( &updateLock );
|
|
|
|
|
|
|
|
DisableThreadLibraryCalls(hInstance);
|
|
|
|
}
|
|
|
|
else if (fdwReason == DLL_PROCESS_DETACH) {
|
|
|
|
while (openCount)
|
|
|
|
PADclose();
|
|
|
|
PADshutdown();
|
|
|
|
UninitHid();
|
|
|
|
UninitLibUsb();
|
|
|
|
DeleteCriticalSection( &updateLock );
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
void AddForce(ButtonSum *sum, u8 cmd, int delta = 255) {
|
|
|
|
if (!delta) return;
|
|
|
|
if (cmd<0x14) {
|
|
|
|
sum->buttons[cmd-0x10] += delta;
|
|
|
|
}
|
|
|
|
// D-pad. Command numbering is based on ordering of digital values.
|
|
|
|
else if (cmd < 0x18) {
|
|
|
|
if (cmd == 0x14) {
|
|
|
|
sum->sticks[0].vert -= delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x15) {
|
|
|
|
sum->sticks[0].horiz += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x16) {
|
|
|
|
sum->sticks[0].vert += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x17) {
|
|
|
|
sum->sticks[0].horiz -= delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (cmd < 0x20) {
|
|
|
|
sum->buttons[cmd-0x10-4] += delta;
|
|
|
|
}
|
|
|
|
// Left stick.
|
|
|
|
else if (cmd < 0x24) {
|
|
|
|
if (cmd == 32) {
|
|
|
|
sum->sticks[2].vert -= delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 33) {
|
|
|
|
sum->sticks[2].horiz += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 34) {
|
|
|
|
sum->sticks[2].vert += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 35) {
|
|
|
|
sum->sticks[2].horiz -= delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Right stick.
|
|
|
|
else if (cmd < 0x28) {
|
|
|
|
if (cmd == 36) {
|
|
|
|
sum->sticks[1].vert -= delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 37) {
|
|
|
|
sum->sticks[1].horiz += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 38) {
|
|
|
|
sum->sticks[1].vert += delta;
|
|
|
|
}
|
|
|
|
else if (cmd == 39) {
|
|
|
|
sum->sticks[1].horiz -= delta;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProcessButtonBinding(Binding *b, ButtonSum *sum, int value) {
|
|
|
|
if (value < b->deadZone || !value) return;
|
2011-09-11 20:18:08 +00:00
|
|
|
|
|
|
|
if ( config.turboKeyHack == 1 ){ // send a tabulator keypress to emulator
|
2011-10-09 16:20:13 +00:00
|
|
|
//printf("%x\n", b->command);
|
|
|
|
if ( b->command == 0x11 ){ // L3 button
|
2011-09-11 20:18:08 +00:00
|
|
|
static unsigned int LastCheck = 0;
|
|
|
|
unsigned int t = timeGetTime();
|
|
|
|
if (t - LastCheck < 300 ) return;
|
|
|
|
QueueKeyEvent(VK_TAB, KEYPRESS);
|
|
|
|
LastCheck = t;
|
|
|
|
}
|
|
|
|
}
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
int sensitivity = b->sensitivity;
|
|
|
|
if (sensitivity < 0) {
|
|
|
|
sensitivity = -sensitivity;
|
|
|
|
value = (1<<16)-value;
|
|
|
|
}
|
|
|
|
if (value < 0) return;
|
|
|
|
|
|
|
|
/* Note: Value ranges of FULLY_DOWN, and sensitivity of
|
|
|
|
* BASE_SENSITIVITY corresponds to an axis/button being exactly fully down.
|
|
|
|
* Math in next line takes care of those two conditions, rounding as necessary.
|
|
|
|
* Done using __int64s because overflows will occur when
|
|
|
|
* sensitivity > BASE_SENSITIVITY and/or value > FULLY_DOWN. Latter only happens
|
|
|
|
* for relative axis.
|
|
|
|
*/
|
|
|
|
int force = (int)((((sensitivity*(255*(__int64)value)) + BASE_SENSITIVITY/2)/BASE_SENSITIVITY + FULLY_DOWN/2)/FULLY_DOWN);
|
|
|
|
AddForce(sum, b->command, force);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restricts d-pad/analog stick values to be from -255 to 255 and button values to be from 0 to 255.
|
|
|
|
// With D-pad in DS2 native mode, the negative and positive ranges are both independently from 0 to 255,
|
|
|
|
// which is why I use 9 bits of all sticks. For left and right sticks, I have to remove a bit before sending.
|
|
|
|
void CapSum(ButtonSum *sum) {
|
|
|
|
int i;
|
|
|
|
for (i=0; i<3; i++) {
|
2015-02-20 22:04:27 +00:00
|
|
|
#ifdef __linux__
|
2015-02-04 21:20:58 +00:00
|
|
|
int div = std::max(abs(sum->sticks[i].horiz), abs(sum->sticks[i].vert));
|
2015-02-20 22:04:27 +00:00
|
|
|
#else
|
|
|
|
int div = max(abs(sum->sticks[i].horiz), abs(sum->sticks[i].vert));
|
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
if (div > 255) {
|
|
|
|
sum->sticks[i].horiz = sum->sticks[i].horiz * 255 / div;
|
|
|
|
sum->sticks[i].vert = sum->sticks[i].vert * 255 / div;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i=0; i<12; i++) {
|
|
|
|
sum->buttons[i] = Cap(sum->buttons[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Counter similar to stateUpdated for each pad, except used for PADkeyEvent instead.
|
|
|
|
// Only matters when GS thread updates is disabled (Just like summed pad values
|
|
|
|
// for pads beyond the first slot).
|
|
|
|
|
|
|
|
// Values, in order, correspond to PADkeyEvent, PADupdate(0), PADupdate(1), and
|
|
|
|
// WndProc(WMA_FORCE_UPDATE). Last is always 0.
|
|
|
|
char padReadKeyUpdated[4] = {0, 0, 0, 0};
|
|
|
|
|
|
|
|
#define LOCK_DIRECTION 2
|
|
|
|
#define LOCK_BUTTONS 4
|
|
|
|
#define LOCK_BOTH 1
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
struct EnterScopedSection
|
|
|
|
{
|
|
|
|
CRITICAL_SECTION& m_cs;
|
|
|
|
|
|
|
|
EnterScopedSection( CRITICAL_SECTION& cs ) : m_cs( cs ) {
|
|
|
|
EnterCriticalSection( &m_cs );
|
|
|
|
}
|
2010-04-25 00:31:27 +00:00
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
~EnterScopedSection() {
|
|
|
|
LeaveCriticalSection( &m_cs );
|
|
|
|
}
|
|
|
|
};
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
void Update(unsigned int port, unsigned int slot) {
|
|
|
|
char *stateUpdated;
|
|
|
|
if (port < 2) {
|
|
|
|
stateUpdated = &pads[port][slot].stateUpdated;
|
|
|
|
}
|
|
|
|
else if (port < 6) {
|
|
|
|
stateUpdated = padReadKeyUpdated+port-2;
|
|
|
|
}
|
|
|
|
else return;
|
|
|
|
|
|
|
|
if (*stateUpdated > 0) {
|
|
|
|
stateUpdated[0] --;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lock prior to timecheck code to avoid pesky race conditions.
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef __linux__
|
|
|
|
std::lock_guard<std::mutex> lock(updateLock);
|
|
|
|
#else
|
2010-04-24 21:37:39 +00:00
|
|
|
EnterScopedSection padlock( updateLock );
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
static unsigned int LastCheck = 0;
|
|
|
|
unsigned int t = timeGetTime();
|
|
|
|
if (t - LastCheck < 15 || !openCount) return;
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
if (windowThreadId != GetCurrentThreadId()) {
|
|
|
|
if (stateUpdated[0] < 0) {
|
|
|
|
if (!updateQueued) {
|
|
|
|
updateQueued = 1;
|
|
|
|
PostMessage(hWnd, WMA_FORCE_UPDATE, FORCE_UPDATE_WPARAM, FORCE_UPDATE_LPARAM);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
stateUpdated[0] --;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
LastCheck = t;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
ButtonSum s[2][4];
|
|
|
|
u8 lockStateChanged[2][4];
|
|
|
|
memset(lockStateChanged, 0, sizeof(lockStateChanged));
|
|
|
|
|
|
|
|
for (i=0; i<8; i++) {
|
|
|
|
s[i&1][i>>1] = pads[i&1][i>>1].lockedSum;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef __linux__
|
|
|
|
InitInfo info = {
|
|
|
|
0, 0, GSdsp, GSwin
|
|
|
|
};
|
|
|
|
#else
|
2010-04-24 21:37:39 +00:00
|
|
|
InitInfo info = {
|
|
|
|
0, 0, hWndTop, &hWndGSProc
|
|
|
|
};
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
dm->Update(&info);
|
|
|
|
static int turbo = 0;
|
|
|
|
turbo++;
|
|
|
|
for (i=0; i<dm->numDevices; i++) {
|
|
|
|
Device *dev = dm->devices[i];
|
|
|
|
// Skip both disabled devices and inactive enabled devices.
|
|
|
|
// Shouldn't be any of the latter, in general, but just in case...
|
|
|
|
if (!dev->active) continue;
|
|
|
|
for (int port=0; port<2; port++) {
|
|
|
|
for (int slot=0; slot<4; slot++) {
|
|
|
|
if (config.padConfigs[port][slot].type == DisabledPad || !pads[port][slot].initialized) continue;
|
|
|
|
for (int j=0; j<dev->pads[port][slot].numBindings; j++) {
|
|
|
|
Binding *b = dev->pads[port][slot].bindings+j;
|
|
|
|
int cmd = b->command;
|
|
|
|
int state = dev->virtualControlState[b->controlIndex];
|
|
|
|
if (!(turbo & b->turbo)) {
|
|
|
|
if (cmd > 0x0F && cmd != 0x28) {
|
|
|
|
ProcessButtonBinding(b, &s[port][slot], state);
|
|
|
|
}
|
|
|
|
else if ((state>>15) && !(dev->oldVirtualControlState[b->controlIndex]>>15)) {
|
|
|
|
if (cmd == 0x0F) {
|
|
|
|
miceEnabled = !miceEnabled;
|
|
|
|
UpdateEnabledDevices();
|
|
|
|
}
|
|
|
|
else if (cmd == 0x0C) {
|
|
|
|
lockStateChanged[port][slot] |= LOCK_BUTTONS;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x0E) {
|
|
|
|
lockStateChanged[port][slot] |= LOCK_DIRECTION;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x0D) {
|
|
|
|
lockStateChanged[port][slot] |= LOCK_BOTH;
|
|
|
|
}
|
|
|
|
else if (cmd == 0x28) {
|
|
|
|
if (!pads[port][slot].modeLock) {
|
|
|
|
if (pads[port][slot].mode != MODE_DIGITAL)
|
|
|
|
pads[port][slot].mode = MODE_DIGITAL;
|
|
|
|
else
|
|
|
|
pads[port][slot].mode = MODE_ANALOG;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dm->PostRead();
|
|
|
|
|
|
|
|
{
|
|
|
|
for (int port=0; port<2; port++) {
|
|
|
|
for (int slot=0; slot<4; slot++) {
|
|
|
|
for (int motor=0; motor<2; motor++) {
|
|
|
|
// TODO: Probably be better to send all of these at once.
|
|
|
|
if (pads[port][slot].nextVibrate[motor] | pads[port][slot].currentVibrate[motor]) {
|
|
|
|
pads[port][slot].currentVibrate[motor] = pads[port][slot].nextVibrate[motor];
|
|
|
|
dm->SetEffect(port,slot, motor, pads[port][slot].nextVibrate[motor]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int port=0; port<2; port++) {
|
|
|
|
for (int slot=0; slot<4; slot++) {
|
|
|
|
pads[port][slot].stateUpdated = 1;
|
|
|
|
if (config.padConfigs[port][slot].type == DisabledPad || !pads[port][slot].initialized) continue;
|
|
|
|
if (config.padConfigs[port][slot].type == GuitarPad) {
|
|
|
|
if (!config.GH2) {
|
|
|
|
s[port][slot].sticks[1].vert = -s[port][slot].sticks[1].vert;
|
|
|
|
}
|
|
|
|
// GH2 hack.
|
|
|
|
else if (config.GH2) {
|
|
|
|
const unsigned int oldIdList[5] = {ID_R2, ID_CIRCLE, ID_TRIANGLE, ID_CROSS, ID_SQUARE};
|
|
|
|
const unsigned int idList[5] = {ID_L2, ID_L1, ID_R1, ID_R2, ID_CROSS};
|
|
|
|
int values[5];
|
|
|
|
int i;
|
|
|
|
for (i=0; i<5; i++) {
|
|
|
|
int id = oldIdList[i] - 0x1104;
|
|
|
|
values[i] = s[port][slot].buttons[id];
|
|
|
|
s[port][slot].buttons[id] = 0;
|
|
|
|
}
|
|
|
|
s[port][slot].buttons[ID_TRIANGLE-0x1104] = values[1];
|
|
|
|
for (i=0; i<5; i++) {
|
|
|
|
int id = idList[i] - 0x1104;
|
|
|
|
s[port][slot].buttons[id] = values[i];
|
|
|
|
}
|
|
|
|
if (abs(s[port][slot].sticks[0].vert) <= 48) {
|
|
|
|
for (int i=0; i<5; i++) {
|
|
|
|
unsigned int id = idList[i] - 0x1104;
|
|
|
|
if (pads[port][slot].sum.buttons[id] < s[port][slot].buttons[id]) {
|
|
|
|
s[port][slot].buttons[id] = pads[port][slot].sum.buttons[id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (abs(pads[port][slot].sum.sticks[0].vert) <= 48) {
|
|
|
|
for (int i=0; i<5; i++) {
|
|
|
|
unsigned int id = idList[i] - 0x1104;
|
|
|
|
if (pads[port][slot].sum.buttons[id]) {
|
|
|
|
s[port][slot].buttons[id] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pads[port][slot].mode == 0x41) {
|
|
|
|
for (int i=1; i<=2; i++) {
|
|
|
|
if (abs(s[port][slot].sticks[i].horiz) >= 100)
|
|
|
|
s[port][slot].sticks[0].horiz += s[port][slot].sticks[i].horiz;
|
|
|
|
if (abs(s[port][slot].sticks[i].vert) >= 100)
|
|
|
|
s[port][slot].sticks[0].vert += s[port][slot].sticks[i].vert;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CapSum(&s[port][slot]);
|
|
|
|
if (lockStateChanged[port][slot]) {
|
|
|
|
if (lockStateChanged[port][slot] & LOCK_BOTH) {
|
|
|
|
if (pads[port][slot].lockedState != (LOCK_DIRECTION | LOCK_BUTTONS)) {
|
|
|
|
// Enable the one that's not enabled.
|
|
|
|
lockStateChanged[port][slot] ^= pads[port][slot].lockedState^(LOCK_DIRECTION | LOCK_BUTTONS);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Disable both
|
|
|
|
lockStateChanged[port][slot] ^= LOCK_DIRECTION | LOCK_BUTTONS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lockStateChanged[port][slot] & LOCK_DIRECTION) {
|
|
|
|
if (pads[port][slot].lockedState & LOCK_DIRECTION) {
|
|
|
|
memset(pads[port][slot].lockedSum.sticks, 0, sizeof(pads[port][slot].lockedSum.sticks));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
memcpy(pads[port][slot].lockedSum.sticks, s[port][slot].sticks, sizeof(pads[port][slot].lockedSum.sticks));
|
|
|
|
}
|
|
|
|
pads[port][slot].lockedState ^= LOCK_DIRECTION;
|
|
|
|
}
|
|
|
|
if (lockStateChanged[port][slot] & LOCK_BUTTONS) {
|
|
|
|
if (pads[port][slot].lockedState & LOCK_BUTTONS) {
|
|
|
|
memset(pads[port][slot].lockedSum.buttons, 0, sizeof(pads[port][slot].lockedSum.buttons));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
memcpy(pads[port][slot].lockedSum.buttons, s[port][slot].buttons, sizeof(pads[port][slot].lockedSum.buttons));
|
|
|
|
}
|
|
|
|
pads[port][slot].lockedState ^= LOCK_BUTTONS;
|
|
|
|
}
|
|
|
|
for (i=0; i<sizeof(pads[port][slot].lockedSum)/4; i++) {
|
|
|
|
if (((int*)&pads[port][slot].lockedSum)[i]) break;
|
|
|
|
}
|
|
|
|
if (i==sizeof(pads[port][slot].lockedSum)/4) {
|
|
|
|
pads[port][slot].lockedState = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i=0; i<8; i++) {
|
|
|
|
pads[i&1][i>>1].sum = s[i&1][i>>1];
|
|
|
|
}
|
|
|
|
|
|
|
|
padReadKeyUpdated[0] = padReadKeyUpdated[1] = padReadKeyUpdated[2] = 1;
|
|
|
|
|
|
|
|
if( stateUpdated[0] > 0 )
|
|
|
|
--stateUpdated[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
void CALLBACK PADupdate(int port) {
|
|
|
|
Update(port+3, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void SetVibrate(int port, int slot, int motor, u8 val) {
|
|
|
|
pads[port][slot].nextVibrate[motor] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CALLBACK PS2EgetLibType(void) {
|
|
|
|
ps2e = 1;
|
|
|
|
return PS2E_LT_PAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CALLBACK PS2EgetLibVersion2(u32 type) {
|
|
|
|
ps2e = 1;
|
|
|
|
if (type == PS2E_LT_PAD)
|
|
|
|
return (PS2E_PAD_VERSION<<16) | VERSION;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
// Used in about and config screens.
|
|
|
|
void GetNameAndVersionString(wchar_t *out) {
|
2016-02-13 01:47:43 +00:00
|
|
|
#if defined(PCSX2_DEBUG)
|
2014-03-23 14:44:43 +00:00
|
|
|
wsprintfW(out, L"LilyPad Debug %i.%i.%i (%lld)", (VERSION>>8)&0xFF, VERSION&0xFF, (VERSION>>24)&0xFF, SVN_REV);
|
2010-04-24 21:37:39 +00:00
|
|
|
#else
|
2014-03-23 14:44:43 +00:00
|
|
|
wsprintfW(out, L"LilyPad %i.%i.%i (%lld)", (VERSION>>8)&0xFF, VERSION&0xFF, (VERSION>>24)&0xFF, SVN_REV);
|
2010-04-24 21:37:39 +00:00
|
|
|
#endif
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
char* CALLBACK PSEgetLibName() {
|
2016-02-13 01:47:43 +00:00
|
|
|
#if defined(PCSX2_DEBUG)
|
2010-04-24 21:37:39 +00:00
|
|
|
static char version[50];
|
2014-03-23 14:44:43 +00:00
|
|
|
sprintf(version, "LilyPad Debug (%lld)", SVN_REV);
|
2010-04-24 21:37:39 +00:00
|
|
|
return version;
|
|
|
|
#else
|
|
|
|
static char version[50];
|
2014-03-23 14:44:43 +00:00
|
|
|
sprintf(version, "LilyPad (%lld)", SVN_REV);
|
2010-04-24 21:37:39 +00:00
|
|
|
return version;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
char* CALLBACK PS2EgetLibName(void) {
|
|
|
|
ps2e = 1;
|
|
|
|
return PSEgetLibName();
|
|
|
|
}
|
|
|
|
|
|
|
|
//void CALLBACK PADgsDriverInfo(GSdriverInfo *info) {
|
|
|
|
// info=info;
|
|
|
|
//}
|
|
|
|
|
|
|
|
void CALLBACK PADshutdown() {
|
|
|
|
DEBUG_TEXT_OUT("LilyPad shutdown.\n\n");
|
|
|
|
for (int i=0; i<8; i++)
|
|
|
|
pads[i&1][i>>1].initialized = 0;
|
|
|
|
portInitialized[0] = portInitialized[1] = 0;
|
|
|
|
UnloadConfigs();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void StopVibrate() {
|
|
|
|
for (int i=0; i<8; i++) {
|
|
|
|
SetVibrate(i&1, i>>1, 0, 0);
|
|
|
|
SetVibrate(i&1, i>>1, 1, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void ResetVibrate(int port, int slot) {
|
|
|
|
SetVibrate(port, slot, 0, 0);
|
|
|
|
SetVibrate(port, slot, 1, 0);
|
2016-02-25 22:07:56 +00:00
|
|
|
pads[port][slot].vibrate[0] = 0x5A;
|
|
|
|
for (int i = 1; i < 8; ++i)
|
|
|
|
pads[port][slot].vibrate[i] = 0xFF;
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ResetPad(int port, int slot) {
|
|
|
|
// Lines before memset currently don't do anything useful,
|
|
|
|
// but allow this function to be called at any time.
|
|
|
|
|
|
|
|
// Need to backup, so can be called at any point.
|
|
|
|
u8 enabled = pads[port][slot].enabled;
|
|
|
|
|
|
|
|
// Currently should never do anything.
|
|
|
|
SetVibrate(port, slot, 0, 0);
|
|
|
|
SetVibrate(port, slot, 1, 0);
|
|
|
|
|
|
|
|
memset(&pads[port][slot], 0, sizeof(pads[0][0]));
|
|
|
|
pads[port][slot].mode = MODE_DIGITAL;
|
|
|
|
pads[port][slot].umask[0] = pads[port][slot].umask[1] = 0xFF;
|
|
|
|
// Sets up vibrate variable.
|
|
|
|
ResetVibrate(port, slot);
|
2011-07-06 15:39:11 +00:00
|
|
|
if (config.padConfigs[port][slot].autoAnalog && !ps2e) {
|
2010-04-24 21:37:39 +00:00
|
|
|
pads[port][slot].mode = MODE_ANALOG;
|
2011-07-06 15:39:11 +00:00
|
|
|
}
|
2010-04-24 21:37:39 +00:00
|
|
|
pads[port][slot].initialized = 1;
|
|
|
|
|
|
|
|
pads[port][slot].enabled = enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct QueryInfo {
|
|
|
|
u8 port;
|
|
|
|
u8 slot;
|
|
|
|
u8 lastByte;
|
|
|
|
u8 currentCommand;
|
|
|
|
u8 numBytes;
|
|
|
|
u8 queryDone;
|
|
|
|
u8 response[42];
|
2016-02-14 14:20:37 +00:00
|
|
|
} query = {0, 0, 0, 0, 0, 0xFF, {0xF3}};
|
2010-04-24 21:37:39 +00:00
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
int saveStateIndex = 0;
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
s32 CALLBACK PADinit(u32 flags) {
|
|
|
|
// Note: Won't load settings if already loaded.
|
|
|
|
if (LoadSettings() < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
int port = (flags & 3);
|
|
|
|
if (port == 3) {
|
|
|
|
if (PADinit(1) == -1) return -1;
|
|
|
|
return PADinit(2);
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#if defined(PCSX2_DEBUG) && defined(_MSC_VER)
|
2010-04-24 21:37:39 +00:00
|
|
|
int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
|
|
|
|
tmpFlag |= _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF;
|
|
|
|
_CrtSetDbgFlag( tmpFlag );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
port --;
|
|
|
|
|
|
|
|
for (int i=0; i<4; i++) {
|
|
|
|
ResetPad(port, i);
|
|
|
|
}
|
|
|
|
slots[port] = 0;
|
|
|
|
portInitialized[port] = 1;
|
|
|
|
|
|
|
|
query.lastByte = 1;
|
|
|
|
query.numBytes = 0;
|
|
|
|
ClearKeyQueue();
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef __linux__
|
|
|
|
R_ClearKeyQueue();
|
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
// Just in case, when resuming emulation.
|
|
|
|
ReleaseModifierKeys();
|
|
|
|
|
|
|
|
DEBUG_TEXT_OUT("LilyPad initialized\n\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Note to self: Has to be a define for the sizeof() to work right.
|
|
|
|
// Note to self 2: All are the same size, anyways, except for longer full DS2 response
|
|
|
|
// and shorter digital mode response.
|
|
|
|
#define SET_RESULT(a) { \
|
|
|
|
memcpy(query.response+2, a, sizeof(a)); \
|
|
|
|
query.numBytes = 2+sizeof(a); \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SET_FINAL_RESULT(a) { \
|
|
|
|
memcpy(query.response+2, a, sizeof(a));\
|
|
|
|
query.numBytes = 2+sizeof(a); \
|
|
|
|
query.queryDone = 1; \
|
|
|
|
}
|
|
|
|
|
|
|
|
static const u8 ConfigExit[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
//static const u8 ConfigExit[7] = {0x5A, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
|
|
|
|
static const u8 noclue[7] = {0x5A, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5A};
|
|
|
|
static u8 queryMaskMode[7] = {0x5A, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x5A};
|
|
|
|
//static const u8 DSNonNativeMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
static const u8 setMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
|
|
|
|
// DS2
|
|
|
|
static const u8 queryModelDS2[7] = {0x5A, 0x03, 0x02, 0x00, 0x02, 0x01, 0x00};
|
|
|
|
// DS1
|
|
|
|
static const u8 queryModelDS1[7] = {0x5A, 0x01, 0x02, 0x00, 0x02, 0x01, 0x00};
|
|
|
|
|
|
|
|
static const u8 queryAct[2][7] = {{0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A},
|
|
|
|
{0x5A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x14}};
|
|
|
|
|
|
|
|
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};
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
// Implements a couple of the hacks that affect whatever top-level window
|
|
|
|
// the GS viewport belongs to (title, screensaver)
|
|
|
|
ExtraWndProcResult TitleHackWndProc(HWND hWndTop, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *output) {
|
|
|
|
switch (uMsg) {
|
|
|
|
case WM_SETTEXT:
|
|
|
|
if (config.saveStateTitle) {
|
|
|
|
wchar_t text[200];
|
|
|
|
int len;
|
|
|
|
if (IsWindowUnicode(hWndTop)) {
|
|
|
|
len = wcslen((wchar_t*) lParam);
|
|
|
|
if (len < sizeof(text)/sizeof(wchar_t)) wcscpy(text, (wchar_t*) lParam);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
len = MultiByteToWideChar(CP_ACP, 0, (char*) lParam, -1, text, sizeof(text)/sizeof(wchar_t));
|
|
|
|
}
|
GUI: BUGFIX: (Save)State in GS window title can get out of sync with actual current slot (after loading/saving state via the menu).
Details:
- It seems that due to legacy reasons, Lilypad is setting the current state at the GS window title, as a hack which is enabled by default (at Lilypad's main GUI tab). However, it's buggy because it only changes the display when (shift/)F2 is pressed, while the current slot actually also changes when loading/saving state via the PCSX2 main menu, so it can get out of sync (possibly with unpleasant results, especially when saving). Now it's disabled by default at Lilypad, and if enabled, displayed as "State(Lilypad)".
- PCSX2 itself now properly handles the current State slot at the GS title window.
Note: ZeroGS still changes its title independently (not via the "standard" PCSX2 mechanism) so Lilypad's title hack might still be useful for it. However, it's still buggy and can get still out of sync.
TODO: Remove this option completely from Lilypad because it's buggy? ZeroGS can still use it, but I think it can cause more harm than good. What do you think?
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4656 96395faa-99c1-11dd-bbfe-3dabce05a288
2011-05-17 11:18:45 +00:00
|
|
|
if (len > 0 && len < 150 && !wcsstr(text, L" | State(Lilypad) ")) {
|
|
|
|
wsprintfW(text+len, L" | State(Lilypad) %i", saveStateIndex);
|
2010-04-24 21:37:39 +00:00
|
|
|
SetWindowText(hWndTop, text);
|
|
|
|
return NO_WND_PROC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_SYSCOMMAND:
|
|
|
|
if ((wParam == SC_SCREENSAVE || wParam == SC_MONITORPOWER) && config.disableScreenSaver)
|
|
|
|
return NO_WND_PROC;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return CONTINUE_BLISSFULLY;
|
|
|
|
}
|
|
|
|
|
2014-04-16 15:39:07 +00:00
|
|
|
// Useful sequence before changing into active/inactive state.
|
|
|
|
// Handles hooking/unhooking of mouse and KB and also mouse cursor visibility.
|
|
|
|
// towardsActive==true indicates we're gaining activity (on focus etc), false is for losing activity (on close, kill focus, etc).
|
|
|
|
void PrepareActivityState(bool towardsActive) {
|
|
|
|
if (!towardsActive)
|
|
|
|
ReleaseModifierKeys();
|
|
|
|
activeWindow = towardsActive;
|
|
|
|
UpdateEnabledDevices();
|
|
|
|
}
|
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
// responsible for monitoring device addition/removal, focus changes, and viewport closures.
|
|
|
|
ExtraWndProcResult StatusWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *output) {
|
|
|
|
switch (uMsg) {
|
|
|
|
case WMA_FORCE_UPDATE:
|
|
|
|
if (wParam == FORCE_UPDATE_WPARAM && lParam == FORCE_UPDATE_LPARAM) {
|
|
|
|
if (updateQueued) {
|
|
|
|
updateQueued = 0;
|
|
|
|
Update(5, 0);
|
|
|
|
}
|
|
|
|
return NO_WND_PROC;
|
|
|
|
}
|
|
|
|
case WM_DEVICECHANGE:
|
|
|
|
if (wParam == DBT_DEVNODES_CHANGED) {
|
|
|
|
UpdateEnabledDevices(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_ACTIVATE:
|
|
|
|
// Release any buttons PCSX2 may think are down when
|
|
|
|
// losing/gaining focus.
|
2014-04-16 15:39:07 +00:00
|
|
|
// Note - I never managed to see this case entered, but SET/KILLFOCUS are entered. - avih 2014-04-16
|
|
|
|
PrepareActivityState(LOWORD(wParam) != WA_INACTIVE);
|
2010-04-24 21:37:39 +00:00
|
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
|
|
if (config.closeHacks & 1) {
|
|
|
|
QueueKeyEvent(VK_ESCAPE, KEYPRESS);
|
|
|
|
return NO_WND_PROC;
|
|
|
|
}
|
|
|
|
else if (config.closeHacks & 2) {
|
|
|
|
ExitProcess(0);
|
|
|
|
return NO_WND_PROC;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
|
|
QueueKeyEvent(VK_ESCAPE, KEYPRESS);
|
|
|
|
break;
|
2014-04-16 15:39:07 +00:00
|
|
|
case WM_KILLFOCUS:
|
|
|
|
PrepareActivityState(false);
|
|
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
|
|
PrepareActivityState(true);
|
|
|
|
break;
|
2010-04-24 21:37:39 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return CONTINUE_BLISSFULLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
// All that's needed to force hiding the cursor in the proper thread.
|
|
|
|
// Could have a special case elsewhere, but this make sure it's called
|
|
|
|
// only once, rather than repeatedly.
|
|
|
|
ExtraWndProcResult HideCursorProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *output) {
|
|
|
|
ShowCursor(0);
|
|
|
|
return CONTINUE_BLISSFULLY_AND_RELEASE_PROC;
|
|
|
|
}
|
|
|
|
|
|
|
|
char restoreFullScreen = 0;
|
2015-10-06 22:22:47 +00:00
|
|
|
// This hack sends ALT+ENTER to the window to toggle fullscreen.
|
|
|
|
// PCSX2 doesn't need it (it exits full screen on ESC on its own).
|
2010-04-24 21:37:39 +00:00
|
|
|
DWORD WINAPI MaximizeWindowThreadProc(void *lpParameter) {
|
|
|
|
Sleep(100);
|
|
|
|
keybd_event(VK_LMENU, MapVirtualKey(VK_LMENU, MAPVK_VK_TO_VSC), 0, 0);
|
|
|
|
keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC), 0, 0);
|
|
|
|
Sleep(10);
|
|
|
|
keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0);
|
|
|
|
keybd_event(VK_LMENU, MapVirtualKey(VK_LMENU, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
void CALLBACK PADconfigure() {
|
|
|
|
if (openCount) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Configure();
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
DWORD WINAPI RenameWindowThreadProc(void *lpParameter) {
|
|
|
|
wchar_t newTitle[200];
|
|
|
|
if (hWndTop) {
|
|
|
|
int len = GetWindowTextW(hWndTop, newTitle, 200);
|
|
|
|
if (len > 0 && len < 199) {
|
|
|
|
wchar_t *end;
|
|
|
|
if (end = wcsstr(newTitle, L" | State ")) *end = 0;
|
|
|
|
SetWindowTextW(hWndTop, newTitle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateChanged() {
|
|
|
|
if (config.saveStateTitle) {
|
|
|
|
// GSDX only checks its window's message queue at certain points or something, so
|
|
|
|
// have to do this in another thread to prevent deadlock.
|
|
|
|
HANDLE hThread = CreateThread(0, 0, RenameWindowThreadProc, 0, 0, 0);
|
|
|
|
if (hThread) CloseHandle(hThread);
|
|
|
|
}
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
s32 CALLBACK PADopen(void *pDsp) {
|
|
|
|
if (openCount++) return 0;
|
|
|
|
DEBUG_TEXT_OUT("LilyPad opened\n\n");
|
|
|
|
|
|
|
|
miceEnabled = !config.mouseUnfocus;
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
if (!hWnd) {
|
|
|
|
if (IsWindow((HWND)pDsp)) {
|
|
|
|
hWnd = (HWND) pDsp;
|
|
|
|
}
|
|
|
|
else if (pDsp && !IsBadReadPtr(pDsp, 4) && IsWindow(*(HWND*) pDsp)) {
|
|
|
|
hWnd = *(HWND*) pDsp;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
openCount = 0;
|
|
|
|
MessageBoxA(GetActiveWindow(),
|
|
|
|
"Invalid Window handle passed to LilyPad.\n"
|
|
|
|
"\n"
|
|
|
|
"Either your emulator or gs plugin is buggy,\n"
|
|
|
|
"Despite the fact the emulator is about to\n"
|
|
|
|
"blame LilyPad for failing to initialize.",
|
|
|
|
"Non-LilyPad Error", MB_OK | MB_ICONERROR);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
hWndTop = hWnd;
|
|
|
|
while (GetWindowLong (hWndTop, GWL_STYLE) & WS_CHILD)
|
|
|
|
hWndTop = GetParent (hWndTop);
|
|
|
|
|
|
|
|
if (!hWndGSProc.SetWndHandle(hWnd)) {
|
|
|
|
openCount = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements most hacks, as well as enabling/disabling mouse
|
|
|
|
// capture when focus changes.
|
|
|
|
updateQueued = 0;
|
|
|
|
hWndGSProc.Eat(StatusWndProc, 0);
|
|
|
|
|
|
|
|
if(hWnd != hWndTop) {
|
|
|
|
if (!hWndTopProc.SetWndHandle(hWndTop)) {
|
|
|
|
openCount = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
hWndTopProc.Eat(TitleHackWndProc, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
hWndGSProc.Eat(TitleHackWndProc, 0);
|
|
|
|
|
|
|
|
if (config.forceHide) {
|
|
|
|
hWndGSProc.Eat(HideCursorProc, 0);
|
|
|
|
}
|
|
|
|
SaveStateChanged();
|
2010-04-25 00:31:27 +00:00
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
windowThreadId = GetWindowThreadProcessId(hWndTop, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (restoreFullScreen) {
|
|
|
|
if (!IsWindowMaximized(hWndTop)) {
|
|
|
|
HANDLE hThread = CreateThread(0, 0, MaximizeWindowThreadProc, hWndTop, 0, 0);
|
|
|
|
if (hThread) CloseHandle(hThread);
|
|
|
|
}
|
|
|
|
restoreFullScreen = 0;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
for (int port=0; port<2; port++) {
|
|
|
|
for (int slot=0; slot<4; slot++) {
|
|
|
|
memset(&pads[port][slot].sum, 0, sizeof(pads[port][slot].sum));
|
|
|
|
memset(&pads[port][slot].lockedSum, 0, sizeof(pads[port][slot].lockedSum));
|
|
|
|
pads[port][slot].lockedState = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
// I'd really rather use this line, but GetActiveWindow() does not have complete specs.
|
|
|
|
// It *seems* to return null when no window from this thread has focus, but the
|
|
|
|
// Microsoft specs seem to imply it returns the window from this thread that would have focus,
|
|
|
|
// if any window did (topmost in this thread?). Which isn't what I want, and doesn't seem
|
|
|
|
// to be what it actually does.
|
|
|
|
// activeWindow = GetActiveWindow() == hWnd;
|
|
|
|
|
|
|
|
// activeWindow = (GetAncestor(hWnd, GA_ROOT) == GetAncestor(GetForegroundWindow(), GA_ROOT));
|
2015-02-04 21:20:58 +00:00
|
|
|
#else
|
|
|
|
// Not used so far
|
|
|
|
GSdsp = *(Display**)pDsp;
|
|
|
|
GSwin = (Window)*(((uptr*)pDsp)+1);
|
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
activeWindow = 1;
|
|
|
|
UpdateEnabledDevices();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CALLBACK PADclose() {
|
|
|
|
if (openCount && !--openCount) {
|
|
|
|
DEBUG_TEXT_OUT("LilyPad closed\n\n");
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
updateQueued = 0;
|
|
|
|
hWndGSProc.Release();
|
|
|
|
hWndTopProc.Release();
|
|
|
|
dm->ReleaseInput();
|
|
|
|
hWnd = 0;
|
|
|
|
hWndTop = 0;
|
2015-02-04 21:20:58 +00:00
|
|
|
#else
|
|
|
|
R_ClearKeyQueue();
|
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
ClearKeyQueue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 CALLBACK PADstartPoll(int port) {
|
|
|
|
DEBUG_NEW_SET();
|
|
|
|
port--;
|
|
|
|
if ((unsigned int)port <= 1 && pads[port][slots[port]].enabled) {
|
|
|
|
query.queryDone = 0;
|
|
|
|
query.port = port;
|
|
|
|
query.slot = slots[port];
|
|
|
|
query.numBytes = 2;
|
|
|
|
query.lastByte = 0;
|
|
|
|
DEBUG_IN(port);
|
|
|
|
DEBUG_OUT(0xFF);
|
|
|
|
DEBUG_IN(slots[port]);
|
|
|
|
DEBUG_OUT(pads[port][slots[port]].enabled);
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
query.queryDone = 1;
|
|
|
|
query.numBytes = 0;
|
|
|
|
query.lastByte = 1;
|
|
|
|
DEBUG_IN(0);
|
|
|
|
DEBUG_OUT(0);
|
|
|
|
DEBUG_IN(port);
|
|
|
|
DEBUG_OUT(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline int IsDualshock2(u8 port, u8 slot) {
|
|
|
|
return config.padConfigs[query.port][query.slot].type == Dualshock2Pad ||
|
|
|
|
(config.padConfigs[query.port][query.slot].type == GuitarPad && config.GH2);
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 CALLBACK PADpoll(u8 value) {
|
|
|
|
DEBUG_IN(value);
|
|
|
|
if (query.lastByte+1 >= query.numBytes) {
|
|
|
|
DEBUG_OUT(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (query.lastByte && query.queryDone) {
|
|
|
|
DEBUG_OUT(query.response[1+query.lastByte]);
|
|
|
|
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) {
|
|
|
|
// CONFIG_MODE
|
|
|
|
case 0x43:
|
|
|
|
if (pad->config) {
|
|
|
|
// In config mode. Might not actually be leaving it.
|
|
|
|
SET_RESULT(ConfigExit);
|
|
|
|
DEBUG_OUT(0xF3);
|
|
|
|
return 0xF3;
|
|
|
|
}
|
|
|
|
// READ_DATA_AND_VIBRATE
|
|
|
|
case 0x42:
|
|
|
|
query.response[2] = 0x5A;
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
if (config.padConfigs[query.port][query.slot].type == GuitarPad && !config.GH2) {
|
|
|
|
sum->sticks[0].horiz = -255;
|
|
|
|
// Not sure about this. Forces wammy to be from 0 to 0x7F.
|
|
|
|
// if (sum->sticks[2].vert > 0) sum->sticks[2].vert = 0;
|
|
|
|
}
|
|
|
|
b1 -= ((sum->sticks[0].vert < 0) << 4);
|
|
|
|
b1 -= ((sum->sticks[0].horiz > 0) << 5);
|
|
|
|
b1 -= ((sum->sticks[0].vert > 0) << 6);
|
|
|
|
b1 -= ((sum->sticks[0].horiz < 0) << 7);
|
2016-01-26 20:22:09 +00:00
|
|
|
|
2016-02-11 10:30:20 +00:00
|
|
|
//Left, Right and Down are always pressed on Pop'n Music controller.
|
2016-01-26 20:22:09 +00:00
|
|
|
if (config.padConfigs[query.port][query.slot].type == PopnPad)
|
|
|
|
b1=b1 & 0x1f;
|
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
query.response[3] = b1;
|
|
|
|
query.response[4] = b2;
|
|
|
|
|
|
|
|
query.numBytes = 5;
|
|
|
|
if (pad->mode != MODE_DIGITAL) {
|
|
|
|
query.response[5] = Cap((sum->sticks[1].horiz+255)/2);
|
|
|
|
query.response[6] = Cap((sum->sticks[1].vert+255)/2);
|
|
|
|
query.response[7] = Cap((sum->sticks[2].horiz+255)/2);
|
|
|
|
query.response[8] = Cap((sum->sticks[2].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];
|
|
|
|
|
|
|
|
// Each value is from -255 to 255, so have to use cap to convert
|
|
|
|
// negative values to 0.
|
|
|
|
query.response[9] = Cap(sum->sticks[0].horiz);
|
|
|
|
query.response[10] = Cap(-sum->sticks[0].horiz);
|
|
|
|
query.response[11] = Cap(-sum->sticks[0].vert);
|
|
|
|
query.response[12] = Cap(sum->sticks[0].vert);
|
|
|
|
|
|
|
|
// No need to cap these, already done int CapSum().
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query.lastByte=1;
|
|
|
|
DEBUG_OUT(pad->mode);
|
|
|
|
return pad->mode;
|
|
|
|
// SET_VREF_PARAM
|
|
|
|
case 0x40:
|
|
|
|
SET_FINAL_RESULT(noclue);
|
|
|
|
break;
|
|
|
|
// QUERY_DS2_ANALOG_MODE
|
|
|
|
case 0x41:
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
SET_FINAL_RESULT(queryMaskMode);
|
|
|
|
break;
|
|
|
|
// SET_MODE_AND_LOCK
|
|
|
|
case 0x44:
|
|
|
|
SET_RESULT(setMode);
|
|
|
|
ResetVibrate(query.port, query.slot);
|
|
|
|
break;
|
|
|
|
// QUERY_MODEL_AND_MODE
|
|
|
|
case 0x45:
|
|
|
|
if (IsDualshock2(query.port, query.slot)) {
|
|
|
|
SET_FINAL_RESULT(queryModelDS2)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SET_FINAL_RESULT(queryModelDS1);
|
|
|
|
}
|
|
|
|
// Not digital mode.
|
|
|
|
query.response[5] = (pad->mode & 0xF) != 1;
|
|
|
|
break;
|
|
|
|
// QUERY_ACT
|
|
|
|
case 0x46:
|
|
|
|
SET_RESULT(queryAct[0]);
|
|
|
|
break;
|
|
|
|
// QUERY_COMB
|
|
|
|
case 0x47:
|
|
|
|
SET_FINAL_RESULT(queryComb);
|
|
|
|
break;
|
|
|
|
// QUERY_MODE
|
|
|
|
case 0x4C:
|
|
|
|
SET_RESULT(queryMode);
|
|
|
|
break;
|
|
|
|
// VIBRATION_TOGGLE
|
|
|
|
case 0x4D:
|
|
|
|
memcpy(query.response+2, pad->vibrate, 7);
|
|
|
|
query.numBytes = 9;
|
|
|
|
ResetVibrate(query.port, query.slot);
|
|
|
|
break;
|
|
|
|
// SET_DS2_NATIVE_MODE
|
|
|
|
case 0x4F:
|
|
|
|
if (IsDualshock2(query.port, query.slot)) {
|
|
|
|
SET_RESULT(setNativeMode);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SET_FINAL_RESULT(setNativeMode);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
query.numBytes = 0;
|
|
|
|
query.queryDone = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DEBUG_OUT(0xF3);
|
|
|
|
return 0xF3;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
query.lastByte++;
|
|
|
|
switch (query.currentCommand) {
|
|
|
|
// READ_DATA_AND_VIBRATE
|
|
|
|
case 0x42:
|
|
|
|
if (query.lastByte == pad->vibrateI[0]) {
|
2015-11-22 22:18:02 +00:00
|
|
|
SetVibrate(query.port, query.slot, 1, 255*(value&1));
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
else if (query.lastByte == pad->vibrateI[1]) {
|
|
|
|
SetVibrate(query.port, query.slot, 0, value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// CONFIG_MODE
|
|
|
|
case 0x43:
|
|
|
|
if (query.lastByte == 3) {
|
|
|
|
query.queryDone = 1;
|
|
|
|
pad->config = value;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// SET_MODE_AND_LOCK
|
|
|
|
case 0x44:
|
|
|
|
if (query.lastByte == 3 && value < 2) {
|
|
|
|
static const u8 modes[2] = {MODE_DIGITAL, MODE_ANALOG};
|
|
|
|
pad->mode = modes[value];
|
|
|
|
}
|
|
|
|
else if (query.lastByte == 4) {
|
|
|
|
if (value == 3) {
|
|
|
|
pad->modeLock = 3;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pad->modeLock = 0;
|
2011-07-06 15:39:11 +00:00
|
|
|
if (pad->mode == MODE_DIGITAL && config.padConfigs[query.port][query.slot].autoAnalog && !ps2e) {
|
2010-04-24 21:37:39 +00:00
|
|
|
pad->mode = MODE_ANALOG;
|
2011-07-06 15:39:11 +00:00
|
|
|
}
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
query.queryDone = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// QUERY_ACT
|
|
|
|
case 0x46:
|
|
|
|
if (query.lastByte == 3) {
|
|
|
|
if (value<2) SET_RESULT(queryAct[value])
|
|
|
|
// bunch of 0's
|
|
|
|
// else SET_RESULT(setMode);
|
|
|
|
query.queryDone = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// QUERY_MODE
|
|
|
|
case 0x4C:
|
|
|
|
if (query.lastByte == 3 && value<2) {
|
|
|
|
query.response[6] = 4+value*3;
|
|
|
|
query.queryDone = 1;
|
|
|
|
}
|
|
|
|
// bunch of 0's
|
|
|
|
//else data = setMode;
|
|
|
|
break;
|
|
|
|
// VIBRATION_TOGGLE
|
|
|
|
case 0x4D:
|
|
|
|
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;
|
|
|
|
// SET_DS2_NATIVE_MODE
|
|
|
|
case 0x4F:
|
|
|
|
if (query.lastByte == 3 || query.lastByte == 4) {
|
|
|
|
pad->umask[query.lastByte-3] = value;
|
|
|
|
}
|
|
|
|
else if (query.lastByte == 5) {
|
|
|
|
if (!(value & 1)) {
|
|
|
|
pad->mode = MODE_DIGITAL;
|
|
|
|
}
|
|
|
|
else if (!(value & 2)) {
|
|
|
|
pad->mode = MODE_ANALOG;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pad->mode = MODE_DS2_NATIVE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEBUG_OUT(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DEBUG_OUT(query.response[query.lastByte]);
|
|
|
|
return query.response[query.lastByte];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns: 1 if supports pad1
|
|
|
|
// 2 if supports pad2
|
|
|
|
// 3 if both are supported
|
|
|
|
u32 CALLBACK PADquery() {
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
INT_PTR CALLBACK AboutDialogProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
|
|
if (uMsg == WM_INITDIALOG) {
|
|
|
|
wchar_t idString[100];
|
|
|
|
GetNameAndVersionString(idString);
|
|
|
|
SetDlgItemTextW(hWndDlg, IDC_VERSION, idString);
|
|
|
|
}
|
|
|
|
else if (uMsg == WM_COMMAND && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
|
|
|
|
EndDialog(hWndDlg, 0);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
void CALLBACK PADabout() {
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
2010-04-24 21:37:39 +00:00
|
|
|
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUT), 0, AboutDialogProc);
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
s32 CALLBACK PADtest() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
keyEvent* CALLBACK PADkeyEvent() {
|
|
|
|
// If running both pads, ignore every other call. So if two keys pressed in same interval...
|
|
|
|
static char eventCount = 0;
|
|
|
|
eventCount++;
|
|
|
|
if (eventCount < openCount) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
eventCount = 0;
|
|
|
|
|
|
|
|
Update(2, 0);
|
|
|
|
static keyEvent ev;
|
|
|
|
if (!GetQueuedKeyEvent(&ev)) return 0;
|
2014-04-16 15:39:07 +00:00
|
|
|
|
2015-02-04 21:20:58 +00:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
static char shiftDown = 0;
|
|
|
|
static char altDown = 0;
|
2014-04-16 15:39:07 +00:00
|
|
|
if (miceEnabled && (ev.key == VK_ESCAPE || (int)ev.key == -2) && ev.evt == KEYPRESS) {
|
|
|
|
// Disable mouse/KB hooks on escape (before going into paused mode).
|
|
|
|
// This is a hack, since PADclose (which is called on pause) should enevtually also deactivate the
|
|
|
|
// mouse/kb capture. In practice, WindowsMessagingMouse::Deactivate is called from PADclose, but doesn't
|
|
|
|
// manage to release the mouse, maybe due to the thread from which it's called or some
|
|
|
|
// state or somehow being too late.
|
|
|
|
// This explicitly triggers inactivity (releasing mouse/kb hooks) before PCSX2 starts to close the plugins.
|
|
|
|
// Regardless, the mouse/kb hooks will get re-enabled on resume if required without need for further hacks.
|
|
|
|
|
|
|
|
PrepareActivityState(false);
|
|
|
|
}
|
|
|
|
|
2010-04-24 21:37:39 +00:00
|
|
|
if ((ev.key == VK_ESCAPE || (int)ev.key == -2) && ev.evt == KEYPRESS && config.escapeFullscreenHack) {
|
|
|
|
static int t;
|
|
|
|
if ((int)ev.key != -2 && IsWindowMaximized(hWndTop)) {
|
|
|
|
t = timeGetTime();
|
|
|
|
QueueKeyEvent(-2, KEYPRESS);
|
|
|
|
HANDLE hThread = CreateThread(0, 0, MaximizeWindowThreadProc, 0, 0, 0);
|
|
|
|
if (hThread) CloseHandle(hThread);
|
|
|
|
restoreFullScreen = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (ev.key != VK_ESCAPE) {
|
|
|
|
if (timeGetTime() - t < 1000) {
|
|
|
|
QueueKeyEvent(-2, KEYPRESS);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ev.key = VK_ESCAPE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev.key == VK_F2 && ev.evt == KEYPRESS) {
|
|
|
|
saveStateIndex += 1 - 2*shiftDown;
|
|
|
|
saveStateIndex = (saveStateIndex+10)%10;
|
|
|
|
SaveStateChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
// So don't change skip mode on alt-F4.
|
|
|
|
if (ev.key == VK_F4 && altDown) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev.key == VK_LSHIFT || ev.key == VK_RSHIFT || ev.key == VK_SHIFT) {
|
|
|
|
ev.key = VK_SHIFT;
|
|
|
|
shiftDown = (ev.evt == KEYPRESS);
|
|
|
|
}
|
|
|
|
else if (ev.key == VK_LCONTROL || ev.key == VK_RCONTROL) {
|
|
|
|
ev.key = VK_CONTROL;
|
|
|
|
}
|
|
|
|
else if (ev.key == VK_LMENU || ev.key == VK_RMENU || ev.key == VK_SHIFT) {
|
|
|
|
ev.key = VK_MENU;
|
|
|
|
altDown = (ev.evt == KEYPRESS);
|
|
|
|
}
|
2015-02-04 21:20:58 +00:00
|
|
|
#endif
|
2010-04-24 21:37:39 +00:00
|
|
|
return &ev;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct PadPluginFreezeData {
|
2011-11-07 09:29:36 +00:00
|
|
|
char format[8];
|
|
|
|
// Currently all different versions are incompatible.
|
|
|
|
// May split into major/minor with some compatibility rules.
|
|
|
|
u32 version;
|
|
|
|
// So when loading, know which plugin's settings I'm loading.
|
|
|
|
// Not a big deal. Use a static variable when saving to figure it out.
|
|
|
|
u8 port;
|
|
|
|
// active slot for port
|
|
|
|
u8 slot[2];
|
|
|
|
PadFreezeData padData[2][4];
|
|
|
|
QueryInfo query;
|
2010-04-24 21:37:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
s32 CALLBACK PADfreeze(int mode, freezeData *data) {
|
2012-02-29 14:51:55 +00:00
|
|
|
if (!data)
|
|
|
|
{
|
|
|
|
printf("LilyPad savestate null pointer!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == FREEZE_SIZE) {
|
2011-11-07 09:29:36 +00:00
|
|
|
data->size = sizeof(PadPluginFreezeData);
|
|
|
|
}
|
|
|
|
else if (mode == FREEZE_LOAD) {
|
|
|
|
PadPluginFreezeData &pdata = *(PadPluginFreezeData*)(data->data);
|
|
|
|
StopVibrate();
|
|
|
|
if (data->size != sizeof(PadPluginFreezeData) ||
|
|
|
|
pdata.version != PAD_SAVE_STATE_VERSION ||
|
|
|
|
strcmp(pdata.format, "PadMode")) return 0;
|
|
|
|
|
|
|
|
if( pdata.port >= 2 ) 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not sure if the cast is strictly necessary, but feel safest with it there...
|
|
|
|
*(PadFreezeData*)&pads[port][slot] = pdata.padData[port][slot];
|
|
|
|
}
|
|
|
|
|
|
|
|
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, sizeof(pdata));
|
|
|
|
strcpy(pdata.format, "PadMode");
|
|
|
|
pdata.version = PAD_SAVE_STATE_VERSION;
|
|
|
|
pdata.port = 0;
|
|
|
|
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;
|
|
|
|
}
|
2010-04-24 21:37:39 +00:00
|
|
|
|
|
|
|
u32 CALLBACK PADreadPort1 (PadDataS* pads) {
|
|
|
|
PADstartPoll(1);
|
|
|
|
PADpoll(0x42);
|
|
|
|
memcpy(pads, query.response+1, 7);
|
|
|
|
pads->controllerType = pads[0].controllerType>>4;
|
|
|
|
memset (pads+7, 0, sizeof(PadDataS)-7);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CALLBACK PADreadPort2 (PadDataS* pads) {
|
|
|
|
PADstartPoll(2);
|
|
|
|
PADpoll(0x42);
|
|
|
|
memcpy(pads, query.response+1, 7);
|
|
|
|
pads->controllerType = pads->controllerType>>4;
|
|
|
|
memset (pads+7, 0, sizeof(PadDataS)-7);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CALLBACK PSEgetLibType() {
|
|
|
|
return 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CALLBACK PSEgetLibVersion() {
|
|
|
|
return (VERSION & 0xFFFFFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 CALLBACK PADqueryMtap(u8 port) {
|
|
|
|
port--;
|
|
|
|
if (port > 1) return 0;
|
|
|
|
return config.multitap[port];
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 CALLBACK PADsetSlot(u8 port, u8 slot) {
|
|
|
|
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;
|
|
|
|
// First slot always allowed.
|
|
|
|
// return pads[port][slot].enabled | !slot;
|
|
|
|
return 1;
|
|
|
|
}
|