Reply
28 Apr 2012

Member

Playstation Staff

PadServer: a tool+class to use a PC gamepad with the simulator

9 Replies 1,024 Views Created 28-04-2012

The simulator's input layout and limitations weren't to my taste, so I made myself a way around it:

Run a TCP server that reads PC's gamepad state and sends it back on a client request. And add a simple utility class/lib to connect to that server + decode data transparently from within the game. With a one-line change to revert-back to using the native input on real devices.

 

Uploaded here: http://dl.dropbox.com/u/1969613/openglForum/PadServer.7z

 

Code to use:

 

static ILXPAD pad1 = new ILXPAD_Net();
//static ILXPAD pad1 = new ILXPAD_Vita();
...
pad1.start();
...
pad1.update();
...
camera.roty -= pad1.RStick.X;
bool jump = pad1.Cur.CROSS;

 

Cheers. :Wink:

Please use plain text.
Reply
Message 1 of 10 (1,024 Views)
Reply
Level 4
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

Nice!

Please use plain text.
Reply
0 Kudos
Message 2 of 10 (1,015 Views)
Reply
0 Kudos
Member
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

I as well have written something to make my 360 controller work with the emulator.

It's a simple app which translates a button press on the controller to a keyboard press in the emulator, no code changes to the game required.

 

But this is a quite interesting solution as well!

Please use plain text.
Reply
0 Kudos
Message 3 of 10 (931 Views)
Reply
0 Kudos
Level 2
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

Hey guys. I modified this to work out of the box with a 360 controller for Cobalt Runner.

 

Needed some small changes to both the server binary and the C# side of things. Figured I'd drop it off here so if anyone else needs it they can use it :smileyhappy:

 

http://www.box.com/s/bac861b6e193677378ae

Please use plain text.
Reply
0 Kudos
Message 4 of 10 (892 Views)
Reply
0 Kudos
Level 4
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

Thanks to the guys who wrote this. I finally got a good Windows driver for my DualShock and had a chance to use this code.

 

I rewrote the C# side to produce the same GamePadData output as the native GetData() method. My version doesn't deal with the Pressed and Released states yet but it seems to work well.

 

I included translators for two different DS3 drivers using the original c++ padserver exe from the original poster. I'd like to include the XBox 360 at some point as well. It would be great if someone who's good with C++ could rewrite the server to compile with VS2010.

 

usage:

private GamepadReader gpadReader;
gpadReader = new GamepadReader(ControllerTypes.SonySixaxis);
gpadReader.OpenChannel();
GamePadData data = gpadReader.GetData();
gpadReader.CloseChannel();
gpadReader.Dispose();

 c# code:

using System;
using System.Net;
using System.Net.Sockets;
using Sce.Pss.Core.Input;

namespace PsmFramework.Engines.PadServer
{
	public class GamepadReader : IDisposable
	{
		#region Constructor, Dispose
		
		public GamepadReader(ControllerTypes controllerType)
		{
			Initialize(controllerType);
		}
		
		public void Dispose()
		{
			Cleanup();
		}
		
		#endregion
		
		#region Initialize, Cleanup
		
		private void Initialize(ControllerTypes controllerType)
		{
			InitializeControllerType(controllerType);
			InitializeTcpip();
			InitializeChannel();
		}
		
		private void Cleanup()
		{
			CleanupChannel();
			CleanupTcpip();
			CleanupControllerType();
		}
		
		#endregion
		
		#region Controller Type
		
		private void InitializeControllerType(ControllerTypes controllerType)
		{
			ControllerType = controllerType;
		}
		
		private void CleanupControllerType()
		{
		}
		
		public ControllerTypes ControllerType { get; set; }
		
		#endregion
		
		#region TcpIp
		
		private void InitializeTcpip()
		{
			TcpipAddress = IPAddress.Parse(TcpipAddressStr);
			TcpipEndPoint = new IPEndPoint(TcpipAddress, TcpipPort);
		}
		
		private void CleanupTcpip()
		{
			TcpipAddress = null;
			TcpipEndPoint = null;
		}
		
		public static String TcpipAddressStr = "127.0.0.1";
		private IPAddress TcpipAddress;
		public static Int32 TcpipPort = 1777;
		
		private IPEndPoint TcpipEndPoint;
		
		protected Socket TcpipSocket;
		
		#endregion
		
		#region Channel
		
		private void InitializeChannel()
		{
			ChannelState = ChannelStates.Closed;
			
			RequestMessage = new Byte[] { (Byte)'i' };
			CloseMessage = new Byte[1] { (Byte)'s' };
			
			IncomingDataBuffer = new Byte[IncomingMessageLength];
		}
		
		private void CleanupChannel()
		{
			if(ChannelState != ChannelStates.Closed)
				CloseChannel();
			
			RequestMessage = new Byte[0];
			CloseMessage = new Byte[0];
		}
		
		public void OpenChannel()
		{
			if(ChannelState != ChannelStates.Closed)
				throw new InvalidOperationException();
			
			TcpipSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			
			try
			{
				TcpipSocket.Connect(TcpipEndPoint);
				ChannelState = ChannelStates.Open;
			}
			catch(SocketException e)
			{
				TcpipSocket = null;
				throw new InvalidOperationException("Unable to access PadServer over TCP/IP.", e);
			}
		}
		
		public void CloseChannel()
		{
			if(ChannelState != ChannelStates.Open)
				throw new InvalidOperationException();
			
			if(TcpipSocket != null)
			{
				TcpipSocket.Send(CloseMessage);
				TcpipSocket.Shutdown(SocketShutdown.Both);
				TcpipSocket.Close();
				TcpipSocket.Dispose();
				TcpipSocket = null;
			}
			
			ChannelState = ChannelStates.Closed;
		}
		
		private void ReadDataFromChannel()
		{
			if(ChannelState != ChannelStates.Open)
				throw new InvalidOperationException();
			
			if(TcpipSocket == null)
				throw new InvalidOperationException();
			
			TcpipSocket.Send(RequestMessage);
			TcpipSocket.Receive(IncomingDataBuffer);
			
			Int32 incomingDataSize = BitConverter.ToInt32(IncomingDataBuffer, 0);
			if(incomingDataSize != IncomingMessageLength)
				throw new InvalidOperationException("Invalid data received from PadServer.");
		}
		
		public ChannelStates ChannelState { get; private set; }
		
		private Byte[] RequestMessage;
		
		private Byte[] IncomingDataBuffer;
		
		private Byte[] CloseMessage;
		
		private const Int32 IncomingMessageLength = 24;
		
		#endregion
		
		#region GetData
		
		public GamePadData GetData()
		{
			ReadDataFromChannel();
			
			GamePadData gpd = new GamePadData();
			
			gpd.AnalogLeftX = BitConverter.ToSingle(IncomingDataBuffer, 4);
			gpd.AnalogLeftY = BitConverter.ToSingle(IncomingDataBuffer, 8);
			gpd.AnalogRightX = BitConverter.ToSingle(IncomingDataBuffer, 12);
			gpd.AnalogRightY = BitConverter.ToSingle(IncomingDataBuffer, 16);
			
			RawButtonData = BitConverter.ToUInt32(IncomingDataBuffer, 20);
			
			//The order of button decoding is important here.
			switch(ControllerType)
			{
			case ControllerTypes.SonySixaxis:
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Triangle;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Circle;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Cross;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Square;
				if(ReadNextRawButtonValue())
				{
					//L2, do nothing.
				}
				if(ReadNextRawButtonValue())
				{
					//R2, do nothing.
					throw new InvalidOperationException();
				}
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.L;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.R;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Start;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Select;
				if(ReadNextRawButtonValue())
				{
					//L3, do nothing.
				}
				if(ReadNextRawButtonValue())
				{
					//R3, do nothing.
				}
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Left;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Right;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Up;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Down;
				break;
				
			case ControllerTypes.MotionInJoy:
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Triangle;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Circle;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Cross;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Square;
				if(ReadNextRawButtonValue())
				{
					//L2, do nothing.
				}
				if(ReadNextRawButtonValue())
				{
					//R2, do nothing.
					throw new InvalidOperationException();
				}
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.L;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.R;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Select;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Start;
				if(ReadNextRawButtonValue())
				{
					//L3, do nothing.
				}
				if(ReadNextRawButtonValue())
				{
					//R3, do nothing.
				}
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Left;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Right;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Up;
				if(ReadNextRawButtonValue())
					gpd.Buttons = GamePadButtons.Down;
				break;
			
			default:
				throw new InvalidProgramException("Unknown controller type.");
				break;
			}
			
			return gpd;
		}
		
		private Boolean ReadNextRawButtonValue()
		{
			Boolean pressed = (RawButtonData & 1) != 0;
			RawButtonData >>= 1;
			return pressed;
		}
		
		private UInt32 RawButtonData;
		
		#endregion
	}

	public enum ControllerTypes
	{
		SonySixaxis,
		MotionInJoy,
		Xbox360
	}

	public enum ChannelStates
	{
		Open,
		Closed
	}
}

 

Please use plain text.
Reply
0 Kudos
Message 5 of 10 (850 Views)
Reply
0 Kudos
Level 4
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

All of the if statements in the GetData() code above should be changed to add "gpd.Buttons |":

if(ReadNextRawButtonValue())
					gpd.Buttons = gpd.Buttons | GamePadButtons.Down;

Sorry for the confusion.

Please use plain text.
Reply
0 Kudos
Message 6 of 10 (846 Views)
Reply
0 Kudos
Level 2
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

My server build works with the 360 pad. What else do you need?

 

In any case, here's the main.cpp with some slight modifications. This should compile under VS2010.

The important stuff is the first few lines that include libs and change the order of the header file inclusion. I can't remember if this is my main.cpp that is designed to support the 360 pad or not.

 

/*

	Gamepad server for PSS

	By ultrano#ho tma il#com


	Requires a connected gamepad to the PC, and preferably it's a dualshock2 with PS2->USB converter,
	or a dualshock3 (didn't try with the latter).

	Supplies more buttons than the Vita, for more freedom of control when developing. Extend on this when you need.


	Compile with:

	g++ -c -o main.o ..\main.cpp
	g++ -o PadServer.exe main.o -lws2_32 -lwinmm

*/

#pragma comment(lib, "Winmm.lib")
#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <windows.h>
#include <stdio.h>


bool exitserver=false;
SOCKET server;


typedef unsigned char U8;
typedef unsigned int  U32;

struct vec2{
	float x,y;
};


enum ILXGPBUTTONS{
	TRIANGLE,CIRCLE,CROSS,SQUARE,L2,R2,L1,R1,SELECT,START,L3,R3,LEFT,RIGHT,UP,DOWN
};

struct PAD{
	int size;
	vec2 LStick;
	vec2 RStick;

	U32  buttons;
};


PAD ILXPad1 ={0};

static void ILXUpdateGamepad(){
	JOYINFOEX t;
	PAD j;

	memset(&t,0,sizeof(t));
	memset(&j,0,sizeof(j));
	t.dwSize=sizeof(JOYINFOEX);
	t.dwFlags=JOY_RETURNALL;

	if(JOYERR_NOERROR==joyGetPosEx(JOYSTICKID1,&t)){
		j.size = sizeof(j);

		j.LStick.x = (t.dwXpos - 32767.0f)/32767.0f;
		j.LStick.y = (t.dwYpos - 32767.0f)/32767.0f;
		j.RStick.y = (t.dwRpos - 32767.0f)/32767.0f;
		j.RStick.x = (t.dwUpos - 32767.0f)/32767.0f;

		j.buttons = t.dwButtons;

		#define fixup(what) if(what> -0.2f && what<0.2f)what=0;  else if(what<-1.0f)what= -1.0f; else if(what>1.0f)what= 1.0f;
		fixup(j.LStick.x);
		fixup(j.LStick.y);
		fixup(j.RStick.x);
		fixup(j.RStick.y);
		#undef fixup

		if(t.dwPOV==27000)j.buttons |= 1 << LEFT;
		if(t.dwPOV==9000 )j.buttons |= 1 << RIGHT;
		if(t.dwPOV==0    )j.buttons |= 1 << UP;
		if(t.dwPOV==18000)j.buttons |= 1 << DOWN;

	}


#if 0
	for(int i=0;i<16;i++){
		U8 b1,b2,b3;
		b1 = ILXPad1.buttons[i];
		b2 = j.buttons[i];
		b3 = ILXPad1.repeat[i];
		if(b1 && b2){
			if(b3<255)b3++;
			j.repeat[i]=b3;
		}
		j.newbuttons[i] = (b1^b2)&b2;
		j.buttons[i]=b2;
	}
#endif

	memcpy(&ILXPad1,&j,sizeof(j));


}




DWORD WINAPI ServerThread(LPVOID pParam)
{
	WSADATA wsaData;
	sockaddr_in local;

	int wsaret=WSAStartup(0x101,&wsaData);
	if(wsaret!=0){
        return 0;
    }

	local.sin_family=AF_INET; //Address family
    local.sin_addr.s_addr=INADDR_ANY; //Wild card IP address
    local.sin_port=htons((u_short)1777); //port to use
    server=socket(AF_INET,SOCK_STREAM,0);
    if(server==INVALID_SOCKET)
    {
        return 0;
    }
    if(bind(server,(sockaddr*)&local,sizeof(local))!=0)
    {
        return 0;
    }
    if(listen(server,10)!=0)
    {
        return 0;
    }

    SOCKET client;
    sockaddr_in from;
    int fromlen=sizeof(from);

    printf("server started\n");

    fflush(stdout);
    while(!exitserver)
    {
        char indata;

        client=accept(server, (struct sockaddr*)&from,&fromlen);
		printf("client connected. Press q to exit, s to stop\n");
		fflush(stdout);

		for(;:smileywink:{
			indata = 0;
			recv(client,&indata,1,0);
			if(!indata)break;
			if(indata=='s')break;
			if(indata=='q'){ exitserver=true; break;}
			ILXUpdateGamepad();

			send(client,(char*)&ILXPad1,sizeof(ILXPad1),0);
		}
        closesocket(client);
        printf("client gone\n");
		fflush(stdout);
    }


	return 0;
}




int main(){
	printf("Starting server at port 1777...\n");


#if 0
	DWORD tid;
	CreateThread(0,0,ServerThread,0,0,&tid);

	for(;:smileywink:{
		printf("Press q to exit:\n");
		char c = getchar();
		if(c=='q')break;
	}
#else
	ServerThread(0);
#endif

	exitserver = true;
	closesocket(server);
	WSACleanup();

	return 0;
}

 

Please use plain text.
Reply
0 Kudos
Message 7 of 10 (828 Views)
Reply
0 Kudos
Level 4
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

Hey, mainly I need a xbox controller to test it with.

 

Thanks for your code. The forum software replaced some bits with emoticons. Any chance you could fix those?

 

Hopefully Sony's going to bake gamepad support directly in the PSS in the future.

Please use plain text.
Reply
0 Kudos
Message 8 of 10 (825 Views)
Reply
0 Kudos
Level 2
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

The main changes to make it compile on VS2010 are right here:

 

#pragma comment(lib, "Winmm.lib")
#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <windows.h>
#include <stdio.h>

 

Just copy and paste that, replacing the old include directives, into the OP's source (For the old layout) or my downloadable source (For the 360 layout).

Please use plain text.
Reply
0 Kudos
Message 9 of 10 (820 Views)
Reply
0 Kudos
Member
 
Playstation Staff

Re: PadServer: a tool+class to use a PC gamepad with the simulator

that's great but.. how can i make it run with my PS Mobile studio??? what should i do?

Please use plain text.
Reply
0 Kudos
Message 10 of 10 (448 Views)
Reply
0 Kudos