Files
vanilla/lib/gamepad/gamepad.c
T

590 lines
17 KiB
C

#define _GNU_SOURCE
#include "gamepad.h"
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include "audio.h"
#include "command.h"
#include "input.h"
#include "video.h"
#include "../pipe/def.h"
#include "util.h"
static uint32_t SERVER_ADDRESS = 0;
static const int MAX_PIPE_RETRY = 5;
char wireless_interface[128];
#define EVENT_BUFFER_SIZE 65536
#define EVENT_BUFFER_ARENA_SIZE VANILLA_MAX_EVENT_COUNT * 2
uint8_t *EVENT_BUFFER_ARENA[EVENT_BUFFER_ARENA_SIZE] = {0};
pthread_mutex_t event_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static inline int skterr()
{
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
static void create_sockaddr(struct sockaddr_in *addr, size_t *size, in_addr_t inaddr, uint16_t port)
{
memset(addr, 0, sizeof(*addr));
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
addr->sin_addr.s_addr = inaddr;
if (size) *size = sizeof(*addr);
}
void create_server_sockaddr(struct sockaddr_in *addr, size_t *size, uint16_t port)
{
// The Wii U always places itself at this address
in_addr_t ip = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? inet_addr("192.168.1.10") : SERVER_ADDRESS;
create_sockaddr(addr, size, ip, port);
}
void send_to_sockaddr(int fd, const void *data, size_t data_size, const struct sockaddr_in *sockaddr, size_t sockaddr_size)
{
ssize_t sent = sendto(fd, data, data_size, 0, (const struct sockaddr *) sockaddr, sockaddr_size);
if (sent == -1) {
int err = skterr();
if (err != 111) { // 111 is connection refused, occurs if we lose connection, but we'll already know that for other reasons so we don't need to spam the console with this error
vanilla_log("Failed to send to Wii U socket: fd: %d, errno: %i", fd, skterr());
}
}
}
void send_to_console(int fd, const void *data, size_t data_size, uint16_t port)
{
struct sockaddr_in addr;
size_t addr_size;
in_port_t console_port = port - 100;
create_server_sockaddr(&addr, &addr_size, console_port);
send_to_sockaddr(fd, data, data_size, &addr, addr_size);
}
void set_socket_rcvtimeo(int skt, uint64_t microseconds)
{
#ifdef _WIN32
DWORD millis = microseconds / 1000;
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, (const char *) &millis, sizeof(millis));
#else
struct timeval tv = {0};
tv.tv_sec = microseconds / 1000000;
tv.tv_usec = microseconds % 1000000;
setsockopt(skt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif // _WIN32
}
int create_socket(int *socket_out, in_port_t port, int pipe)
{
struct sockaddr_in addr;
size_t addr_size;
in_addr_t bind_address = (pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? htonl(INADDR_LOOPBACK) : INADDR_ANY;
create_sockaddr(&addr, &addr_size, bind_address, port);
int skt = socket(AF_INET, SOCK_DGRAM, 0);
if (skt == -1) {
vanilla_log("FAILED TO CREATE SOCKET: %i", skterr());
return VANILLA_ERR_BAD_SOCKET;
}
if (bind(skt, (const struct sockaddr *) &addr, addr_size) == -1) {
vanilla_log("FAILED TO BIND PORT %u: %i", port, skterr());
close(skt);
return VANILLA_ERR_BAD_SOCKET;
}
// vanilla_log("SUCCESSFULLY BOUND SOCKET %i ON PORT %i", skt, port);
set_socket_rcvtimeo(skt, 250000);
// int buf_sz = 16777216;
// setsockopt(skt, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz));
// setsockopt(skt, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz));
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(ANDROID)
if (!pipe && SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) {
// Bind to wireless device
setsockopt(skt, SOL_SOCKET, SO_BINDTODEVICE, wireless_interface, strlen(wireless_interface));
}
#endif
(*socket_out) = skt;
// int val = 1000; // microseconds to busy-poll per recv() before sleeping
// if (setsockopt(skt, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val)) < 0) {
// perror("setsockopt SO_BUSY_POLL");
// }
return VANILLA_SUCCESS;
}
int send_pipe_cc(int skt, vanilla_pipe_command_t *cmd, size_t cmd_size, int wait_for_reply)
{
struct sockaddr_in addr;
size_t addr_size;
in_addr_t pipe_addr = (SERVER_ADDRESS == VANILLA_ADDRESS_LOCAL) ? htonl(INADDR_LOOPBACK) : SERVER_ADDRESS;
create_sockaddr(&addr, &addr_size, pipe_addr, VANILLA_PIPE_CMD_SERVER_PORT);
ssize_t read_size;
uint8_t recv_cc;
for (int retries = 0; retries < MAX_PIPE_RETRY; retries++) {
if (sendto(skt, (const char *) cmd, cmd_size, 0, (const struct sockaddr *) &addr, addr_size) == -1) {
vanilla_log("Failed to write control code to socket: %i", skterr());
return 0;
}
if (!wait_for_reply || is_interrupted()) {
return 1;
}
read_size = recv(skt, &recv_cc, sizeof(recv_cc), 0);
if (read_size == sizeof(recv_cc)) {
if (recv_cc == VANILLA_PIPE_CC_BIND_ACK) {
return 1;
}
if (recv_cc == VANILLA_PIPE_CC_BUSY) {
if (retries == MAX_PIPE_RETRY - 1) {
return -1;
}
vanilla_log("PIPE BUSY, RETRYING");
} else {
vanilla_log("UNEXPECTED PIPE REPLY: 0x%02x", recv_cc);
}
} else if (read_size < 0) {
vanilla_log("STILL WAITING FOR REPLY: %i", skterr());
} else {
vanilla_log("UNEXPECTED PIPE REPLY SIZE: %zi", read_size);
}
sleep(1);
}
return 0;
}
int send_unbind_cc(int skt)
{
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_UNBIND;
return send_pipe_cc(skt, &cmd, sizeof(cmd.control_code), 0);
}
int connect_to_backend(int *socket, vanilla_pipe_command_t *cmd, size_t cmd_size)
{
// Try to bind with backend
int pipe_cc_skt = -1;
int ret = create_socket(&pipe_cc_skt, VANILLA_PIPE_CMD_CLIENT_PORT, 1);
if (ret != VANILLA_SUCCESS) {
return ret;
}
set_socket_rcvtimeo(pipe_cc_skt, 2000000);
int send_result = send_pipe_cc(pipe_cc_skt, cmd, cmd_size, 1);
if (send_result < 0) {
vanilla_log("PIPE IS BUSY");
close(pipe_cc_skt);
return VANILLA_ERR_BUSY;
}
if (!send_result) {
vanilla_log("FAILED TO BIND TO PIPE");
close(pipe_cc_skt);
return VANILLA_ERR_PIPE_UNRESPONSIVE;
}
*socket = pipe_cc_skt;
return VANILLA_SUCCESS;
}
void wait_for_interrupt()
{
while (!is_interrupted()) {
usleep(100000);
}
}
void sync_internal(thread_data_t *data)
{
clear_interrupt();
SERVER_ADDRESS = data->server_address;
uint16_t code = (uintptr_t) data->thread_data;
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_SYNC;
cmd.sync.code = htons(code);
int skt = -1;
vanilla_sync_event_t syncdata;
syncdata.status = connect_to_backend(&skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.sync));
if (syncdata.status == VANILLA_SUCCESS) {
// Wait for sync result from pipe
vanilla_pipe_command_t recv_cmd;
syncdata.status = VANILLA_ERR_PIPE_UNRESPONSIVE;
for (int retries = 0; retries < MAX_PIPE_RETRY; retries++) {
ssize_t read_size = recv(skt, (char *) &recv_cmd, sizeof(recv_cmd), 0);
if (recv_cmd.control_code == VANILLA_PIPE_CC_STATUS) {
syncdata.status = (int32_t) ntohl(recv_cmd.status.status);
break;
} else if (recv_cmd.control_code == VANILLA_PIPE_CC_SYNC_SUCCESS) {
syncdata.status = VANILLA_SUCCESS;
syncdata.data.bssid = recv_cmd.connection.bssid;
syncdata.data.psk = recv_cmd.connection.psk;
break;
} else if (recv_cmd.control_code == VANILLA_PIPE_CC_PING) {
// Pipe is still responsive but hasn't found anything yet
retries = -1;
}
if (is_interrupted()) {
send_unbind_cc(skt);
break;
}
}
}
if (syncdata.status == VANILLA_SUCCESS) {
push_event(data->event_loop, VANILLA_EVENT_SYNC, &syncdata, sizeof(syncdata));
} else {
push_event(data->event_loop, VANILLA_EVENT_ERROR, &syncdata.status, sizeof(syncdata.status));
}
// Wait for interrupt so frontend has a chance to receive event
wait_for_interrupt();
exit_pipe:
if (skt != -1)
close(skt);
}
int install_polkit_internal(thread_data_t *data, int install)
{
clear_interrupt();
SERVER_ADDRESS = data->server_address;
int ret = VANILLA_ERR_GENERIC;
int pipe_cc_skt = -1;
vanilla_pipe_command_t cmd;
cmd.control_code = install ? VANILLA_PIPE_CC_INSTALL_POLKIT : VANILLA_PIPE_CC_UNINSTALL_POLKIT;
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
// No interrupt is required here because VANILLA_PIPE_CC_INSTALL_POLKIT
// does not start a thread in the pipe
if (pipe_cc_skt != -1) {
// Disconnect from pipe if necessary
send_unbind_cc(pipe_cc_skt);
close(pipe_cc_skt);
}
return ret;
}
void connect_as_gamepad_internal(thread_data_t *data)
{
clear_interrupt();
SERVER_ADDRESS = data->server_address;
gamepad_context_t info;
info.event_loop = data->event_loop;
int ret = VANILLA_SUCCESS;
int pipe_cc_skt = -1;
vanilla_pipe_command_t cmd;
cmd.control_code = VANILLA_PIPE_CC_CONNECT;
cmd.connection.bssid = data->bssid;
cmd.connection.psk = data->psk;
// Connect to backend pipe
ret = connect_to_backend(&pipe_cc_skt, &cmd, sizeof(cmd.control_code) + sizeof(cmd.connection));
if (ret == VANILLA_SUCCESS) {
// Wait for backend to be available
vanilla_pipe_command_t connected_state;
ret = VANILLA_ERR_NO_CONNECTION;
while (!is_interrupted()) {
ssize_t read_size = recv(pipe_cc_skt, (char *) &connected_state, sizeof(connected_state), 0);
if (read_size < 0) {
int r = skterr();
#ifdef _WIN32
if (r != WSAETIMEDOUT) {
#else
if (r != EAGAIN) {
#endif
vanilla_log("FAILED TO GET CONNECTED STATE: %i", r);
ret = VANILLA_ERR_PIPE_UNRESPONSIVE;
break;
}
} else if (connected_state.control_code == VANILLA_PIPE_CC_CONNECTED) {
ret = VANILLA_SUCCESS;
sleep(1);
break;
}
vanilla_log("STILL WAITING FOR CONNECTED STATE");
}
}
if (ret == VANILLA_SUCCESS) {
// Open all required sockets
if (create_socket(&info.socket_vid, PORT_VID, 0) != VANILLA_SUCCESS) goto exit_pipe;
if (create_socket(&info.socket_msg, PORT_MSG, 0) != VANILLA_SUCCESS) goto exit_vid;
if (create_socket(&info.socket_hid, PORT_HID, 0) != VANILLA_SUCCESS) goto exit_msg;
if (create_socket(&info.socket_aud, PORT_AUD, 0) != VANILLA_SUCCESS) goto exit_hid;
if (create_socket(&info.socket_cmd, PORT_CMD, 0) != VANILLA_SUCCESS) goto exit_aud;
pthread_t video_thread, audio_thread, input_thread, msg_thread, cmd_thread;
int cnn = VANILLA_ERR_CONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
pthread_create(&video_thread, NULL, listen_video, &info);
pthread_create(&audio_thread, NULL, listen_audio, &info);
pthread_create(&input_thread, NULL, listen_input, &info);
pthread_create(&cmd_thread, NULL, listen_command, &info);
#ifndef __APPLE__
// macOS has a different implementation that requires this to be called
// from the thread. Since thread name is only really important for
// profiling, and that mostly happens on Linux, we just don't bother.
pthread_setname_np(video_thread, "vanilla-video");
pthread_setname_np(audio_thread, "vanilla-audio");
pthread_setname_np(input_thread, "vanilla-input");
pthread_setname_np(cmd_thread, "vanilla-cmd");
#endif
while (!is_interrupted()) {
vanilla_pipe_command_t pipe_state;
ssize_t read_size = recv(pipe_cc_skt, (char *) &pipe_state, sizeof(pipe_state), 0);
if (read_size > 0) {
switch (pipe_state.control_code) {
case VANILLA_PIPE_CC_DISCONNECTED:
cnn = VANILLA_ERR_DISCONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
break;
case VANILLA_PIPE_CC_CONNECTED:
cnn = VANILLA_ERR_CONNECTED;
push_event(data->event_loop, VANILLA_EVENT_ERROR, &cnn, sizeof(cnn));
break;
}
}
}
pthread_join(video_thread, NULL);
pthread_join(audio_thread, NULL);
pthread_join(input_thread, NULL);
pthread_join(cmd_thread, NULL);
exit_cmd:
close(info.socket_cmd);
exit_aud:
close(info.socket_aud);
exit_hid:
close(info.socket_hid);
exit_msg:
close(info.socket_msg);
exit_vid:
close(info.socket_vid);
}
exit_pipe:
if (pipe_cc_skt != -1) {
// Disconnect from pipe if necessary
send_unbind_cc(pipe_cc_skt);
close(pipe_cc_skt);
}
exit:
if (ret != VANILLA_SUCCESS) {
push_event(info.event_loop, VANILLA_EVENT_ERROR, &ret, sizeof(ret));
}
// Wait for interrupt so frontend has a chance to receive event
wait_for_interrupt();
}
int acquire_event(event_loop_t *loop, vanilla_event_t **event)
{
int ret = VANILLA_SUCCESS;
pthread_mutex_lock(&loop->mutex);
// Prevent rollover by skipping oldest event if necessary
if (loop->new_index == loop->used_index + VANILLA_MAX_EVENT_COUNT) {
vanilla_free_event(&loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT]);
vanilla_log("SKIPPED EVENT TO PREVENT ROLLOVER (%lu > %lu + %lu)", loop->new_index, loop->used_index, VANILLA_MAX_EVENT_COUNT);
loop->used_index++;
}
vanilla_event_t *ev = &loop->events[loop->new_index % VANILLA_MAX_EVENT_COUNT];
assert(!ev->data);
ev->data = get_event_buffer();
// if (!ev->data) {
// vanilla_log("OUT OF MEMORY FOR NEW EVENTS");
// ret = VANILLA_ERR_OUT_OF_MEMORY;
// }
*event = ev;
return ret;
}
int release_event(event_loop_t *loop)
{
loop->new_index++;
pthread_cond_broadcast(&loop->waitcond);
// } else {
// vanilla_log("FAILED TO PUSH EVENT: wanted %lu, only had %lu. This is a bug, please report to developers.", size, EVENT_BUFFER_SIZE);
// ret = VANILLA_ERR_INVALID_ARGUMENT;
// }
exit:
pthread_mutex_unlock(&loop->mutex);
return VANILLA_SUCCESS;
}
int push_event(event_loop_t *loop, int type, const void *data, size_t size)
{
vanilla_event_t *ev;
int ret = acquire_event(loop, &ev);
if (ret != VANILLA_SUCCESS) {
return ret;
}
ev->type = type;
memcpy(ev->data, data, size);
ev->size = size;
ret = release_event(loop);
return ret;
}
int get_event(event_loop_t *loop, vanilla_event_t *event, int wait)
{
int ret = 0;
pthread_mutex_lock(&loop->mutex);
if (loop->active) {
if (wait) {
while (loop->active && loop->used_index == loop->new_index) {
pthread_cond_wait(&loop->waitcond, &loop->mutex);
}
}
if (loop->active && loop->used_index < loop->new_index) {
// Output data to pointer
vanilla_event_t *pull_event = &loop->events[loop->used_index % VANILLA_MAX_EVENT_COUNT];
event->type = pull_event->type;
event->data = pull_event->data;
event->size = pull_event->size;
pull_event->data = NULL;
loop->used_index++;
ret = 1;
}
}
pthread_mutex_unlock(&loop->mutex);
return ret;
}
void *get_event_buffer()
{
void *buf = NULL;
pthread_mutex_lock(&event_buffer_mutex);
for (size_t i = 0; i < EVENT_BUFFER_ARENA_SIZE; i++) {
if (EVENT_BUFFER_ARENA[i]) {
buf = EVENT_BUFFER_ARENA[i];
EVENT_BUFFER_ARENA[i] = NULL;
break;
}
}
pthread_mutex_unlock(&event_buffer_mutex);
return buf;
}
void release_event_buffer(void *buffer)
{
pthread_mutex_lock(&event_buffer_mutex);
for (size_t i = 0; i < EVENT_BUFFER_ARENA_SIZE; i++) {
if (!EVENT_BUFFER_ARENA[i]) {
EVENT_BUFFER_ARENA[i] = buffer;
break;
}
}
pthread_mutex_unlock(&event_buffer_mutex);
}
void init_event_buffer_arena()
{
for (size_t i = 0; i < EVENT_BUFFER_ARENA_SIZE; i++) {
if (!EVENT_BUFFER_ARENA[i]) {
EVENT_BUFFER_ARENA[i] = malloc(EVENT_BUFFER_SIZE);
} else {
vanilla_log("CRITICAL: Buffer wasn't returned to the arena");
}
}
}
void free_event_buffer_arena()
{
for (size_t i = 0; i < EVENT_BUFFER_ARENA_SIZE; i++) {
if (EVENT_BUFFER_ARENA[i]) {
free(EVENT_BUFFER_ARENA[i]);
EVENT_BUFFER_ARENA[i] = NULL;
} else {
vanilla_log("CRITICAL: Buffer wasn't returned to the arena");
}
}
}