Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1377,7 +1377,13 @@ if(SDL_JOYSTICK)
endif()

# DSU (DualShock UDP) client support
if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND (WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR MACOS))
# Supported on platforms with UDP socket support (BSD sockets or WinSock)
# Note: Some of these platforms haven't been fully tested yet but should work in theory
if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND
(WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR
MACOS OR IOS OR TVOS OR VISIONOS OR WATCHOS OR
QNX OR RISCOS OR
VITA OR PSP OR PS2 OR N3DS))
set(SDL_JOYSTICK_DSU 1)
sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c"
Expand All @@ -1389,6 +1395,30 @@ if(SDL_JOYSTICK)
sdl_link_dependency(dsu LIBS ws2_32)
elseif(HAIKU)
sdl_link_dependency(dsu LIBS network)
elseif(QNX)
# QNX needs socket library
sdl_link_dependency(dsu LIBS socket)
elseif(VITA)
# PlayStation Vita networking libraries
sdl_link_dependency(dsu LIBS
SceNet_stub
SceNetCtl_stub
)
elseif(PSP)
# PSP networking libraries
sdl_link_dependency(dsu LIBS
pspnet
pspnet_inet
pspnet_resolver
)
elseif(PS2)
# PlayStation 2 uses ps2sdk's lwIP
sdl_link_dependency(dsu LIBS
ps2ip
)
elseif(N3DS)
# Nintendo 3DS networking libraries (if available)
sdl_link_dependency(dsu LIBS ctru)
endif()
endif()
endif()
Expand Down
195 changes: 170 additions & 25 deletions src/joystick/dsu/SDL_dsujoystick.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,84 @@ typedef int socklen_t;

/* Platform-specific socket includes */
#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_SYS_IOCTL_H
/* iOS/tvOS/watchOS/visionOS - Same as macOS */
#if defined(__APPLE__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
/* QNX */
#elif defined(__QNXNTO__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
/* PlayStation Vita */
#elif defined(__VITA__)
#include <psp2/net/net.h>
#include <psp2/net/netctl.h>
#include <psp2/errno.h>
#define socklen_t unsigned int
#define closesocket sceNetSocketClose
#define EAGAIN SCE_NET_EAGAIN
#define EWOULDBLOCK SCE_NET_EWOULDBLOCK
/* PlayStation Portable */
#elif defined(__PSP__)
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_resolver.h>
#include <psperror.h>
#define socklen_t unsigned int
#define closesocket sceNetInetClose
/* Nintendo 3DS */
#elif defined(__3DS__)
#include <3ds.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#define closesocket closesocket /* 3DS uses closesocket like Windows */
/* PlayStation 2 */
#elif defined(__PS2__)
/* PS2 networking support - requires ps2sdk */
#include <ps2ip.h>
#include <errno.h>
#define socklen_t unsigned int
#define closesocket lwip_close
/* RISC OS */
#elif defined(__riscos__)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/* Standard Unix/Linux */
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef __sun
#include <sys/filio.h>
#endif
#define closesocket close
#endif
#ifdef __sun
#include <sys/filio.h>

/* Default closesocket if not defined */
#ifndef closesocket
#define closesocket close
#endif
#define closesocket close
#endif

#include <math.h>
Expand Down Expand Up @@ -121,21 +186,65 @@ struct DSU_Context_t *s_dsu_ctx = NULL;
void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot);
void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot);

/* Platform-specific network function wrappers */
#if defined(__VITA__)
#define DSU_sendto(sock, buf, len, flags, addr, addrlen) \
sceNetSendto((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \
sceNetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_GetLastError() sce_net_errno
#elif defined(__PSP__)
#define DSU_sendto(sock, buf, len, flags, addr, addrlen) \
sceNetInetSendto((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \
sceNetInetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_GetLastError() sceNetInetGetErrno()
#elif defined(__PS2__)
/* PS2 uses lwIP */
#define DSU_sendto(sock, buf, len, flags, addr, addrlen) \
lwip_sendto((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \
lwip_recvfrom((sock), (buf), (len), (flags), (addr), (addrlen))
#define DSU_GetLastError() errno
#else
/* Standard sendto/recvfrom */
#define DSU_sendto sendto
#define DSU_recvfrom recvfrom
#define DSU_GetLastError() errno
#endif

/* Socket helpers implementation */
int DSU_InitSockets(void)
{
#ifdef _WIN32
WSADATA wsaData;
return WSAStartup(MAKEWORD(2, 2), &wsaData);
#elif defined(__VITA__)
/* PS Vita network initialization is handled by SDL main */
return 0;
#elif defined(__PSP__)
/* PSP network initialization is handled by SDL main */
return 0;
#elif defined(__PS2__)
/* PS2 network initialization is handled by SDL main */
return 0;
#elif defined(__3DS__)
/* 3DS network initialization is handled by SDL main */
return 0;
#else
/* Unix/Linux - no initialization needed */
return 0;
#endif
}

void DSU_QuitSockets(void)
void DSU_CleanupSockets(void)
{
#ifdef _WIN32
WSACleanup();
#elif defined(__VITA__) || defined(__PSP__) || defined(__PS2__) || defined(__3DS__)
/* Console platforms handle cleanup elsewhere */
#else
/* Unix/Linux - no cleanup needed */
#endif
}

Expand All @@ -146,24 +255,59 @@ dsu_socket_t DSU_CreateSocket(Uint16 port)
int reuse = 1;
#ifdef _WIN32
u_long mode = 1;
#elif defined(__VITA__)
/* PS Vita uses different socket creation */
int nonblock = 1;
#elif defined(__PSP__)
/* PSP socket creation */
#elif defined(__PS2__)
/* PS2 socket creation */
int nb = 1;
#elif defined(__3DS__)
/* 3DS socket creation */
int nonblock = 1;
#else
int flags;
#if defined(FIONBIO)
int nonblock = 1;
#endif
#endif

#if defined(__VITA__)
sock = sceNetSocket("DSU_Socket", AF_INET, SOCK_DGRAM, 0);
#elif defined(__PSP__)
sock = sceNetInetSocket(AF_INET, SOCK_DGRAM, 0);
#elif defined(__PS2__)
sock = lwip_socket(AF_INET, SOCK_DGRAM, 0);
#elif defined(__3DS__)
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
#endif
if (sock == DSU_INVALID_SOCKET) {
return DSU_INVALID_SOCKET;
}

/* Allow address reuse */
#if !defined(__VITA__) && !defined(__PSP__) && !defined(__PS2__) /* These platforms may not support SO_REUSEADDR */
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
#endif

/* Set socket to non-blocking */
#ifdef _WIN32
ioctlsocket(sock, FIONBIO, &mode);
#elif defined(__VITA__)
sceNetSetSockOpt(sock, SCE_NET_SOL_SOCKET, SCE_NET_SO_NBIO, &nonblock, sizeof(nonblock));
#elif defined(__PSP__)
/* PSP: Set non-blocking mode differently */
sceNetInetSetNonblock(sock, 1);
#elif defined(__PS2__)
/* PS2: lwIP non-blocking mode */
int nb = 1;
lwip_ioctl(sock, FIONBIO, &nb);
#elif defined(__3DS__)
/* 3DS: Use fcntl for non-blocking */
fcntl(sock, F_SETFL, O_NONBLOCK);
#else
#if defined(FIONBIO)
if (ioctl(sock, FIONBIO, &nonblock) < 0) {
Expand Down Expand Up @@ -276,15 +420,15 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size)
server.sin_port = DSU_htons(ctx->server_port);
server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address);

result = (sendto)(ctx->socket, (const char*)packet, (int)size, 0,
(struct sockaddr *)&server, (int)sizeof(server));
result = DSU_sendto(ctx->socket, (const char*)packet, (int)size, 0,
(struct sockaddr *)&server, (int)sizeof(server));

if (result < 0) {
#ifdef _WIN32
int err = WSAGetLastError();
SDL_Log("DSU: sendto failed with error %d\n", err);
#else
SDL_Log("DSU: sendto failed with errno %d\n", errno);
SDL_Log("DSU: sendto failed with errno %d\n", DSU_GetLastError());
#endif
}

Expand Down Expand Up @@ -505,8 +649,8 @@ int SDLCALL DSU_ReceiverThread(void *data)
SDL_Log("DSU: Receiver thread exiting - mutex destroyed\n");
break;
}
received = recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0,
(struct sockaddr *)&sender, &sender_len);
received = DSU_recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0,
(struct sockaddr *)&sender, &sender_len);

if (received > (int)sizeof(DSU_Header)) {
header = (DSU_Header *)buffer;
Expand Down Expand Up @@ -581,13 +725,14 @@ int SDLCALL DSU_ReceiverThread(void *data)
SDL_Delay(100); /* Back off on errors */
}
#else
if (errno == EBADF) {
int err = DSU_GetLastError();
if (err == EBADF) {
/* Socket closed, exit gracefully */
SDL_Log("DSU: Socket closed, receiver thread exiting\n");
break;
}
if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) {
SDL_Log("DSU: recvfrom errno %d\n", errno);
if (err != EWOULDBLOCK && err != EAGAIN && err != EINTR) {
SDL_Log("DSU: recvfrom errno %d\n", err);
SDL_Delay(100);
}
#endif
Expand Down Expand Up @@ -656,7 +801,7 @@ static bool DSU_JoystickInit(void)
/* Create UDP socket */
ctx->socket = DSU_CreateSocket(ctx->client_port);
if (ctx->socket == DSU_INVALID_SOCKET) {
DSU_QuitSockets();
DSU_CleanupSockets();
SDL_free(ctx);
return false;
}
Expand All @@ -665,7 +810,7 @@ static bool DSU_JoystickInit(void)
ctx->slots_mutex = SDL_CreateMutex();
if (!ctx->slots_mutex) {
DSU_CloseSocket(ctx->socket);
DSU_QuitSockets();
DSU_CleanupSockets();
SDL_free(ctx);
SDL_OutOfMemory();
return false;
Expand All @@ -678,7 +823,7 @@ static bool DSU_JoystickInit(void)
if (!ctx->receiver_thread) {
SDL_DestroyMutex(ctx->slots_mutex);
DSU_CloseSocket(ctx->socket);
DSU_QuitSockets();
DSU_CleanupSockets();
SDL_free(ctx);
SDL_SetError("Failed to create DSU receiver thread");
return false;
Expand Down Expand Up @@ -1049,8 +1194,8 @@ static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb
server.sin_family = AF_INET;
server.sin_port = DSU_htons(ctx->server_port);
server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address);
if ((sendto)(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0,
(struct sockaddr *)&server, (int)sizeof(server)) < 0) {
if (DSU_sendto(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0,
(struct sockaddr *)&server, (int)sizeof(server)) < 0) {
SDL_SetError("Failed to send rumble packet");
return false;
}
Expand Down Expand Up @@ -1314,7 +1459,7 @@ static void DSU_JoystickQuit(void)
}

/* Clean up sockets */
DSU_QuitSockets();
DSU_CleanupSockets();

/* Clean up mutex */
if (ctx->slots_mutex) {
Expand Down
Loading