/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2012 - Hans-Kristian Arntzen * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 RetroArch. * If not, see . */ #include "netplay_compat.h" #include "network_cmd.h" #include "driver.h" #include "general.h" #include #include struct network_cmd { int fd; bool state[RARCH_BIND_LIST_END]; }; static bool socket_nonblock(int fd) { #ifdef _WIN32 u_long mode = 1; return ioctlsocket(fd, FIONBIO, &mode) == 0; #else return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == 0; #endif } network_cmd_t *network_cmd_new(uint16_t port) { network_cmd_t *handle = (network_cmd_t*)calloc(1, sizeof(*handle)); if (!handle) return NULL; handle->fd = -1; struct addrinfo hints, *res = NULL; memset(&hints, 0, sizeof(hints)); #if defined(_WIN32) || defined(HAVE_SOCKET_LEGACY) hints.ai_family = AF_INET; #else hints.ai_family = AF_UNSPEC; #endif hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; char port_buf[16]; int yes = 1; snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port); if (getaddrinfo(NULL, port_buf, &hints, &res) < 0) goto error; handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (handle->fd < 0) goto error; if (!socket_nonblock(handle->fd)) goto error; setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int)); if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0) { RARCH_ERR("Failed to bind socket.\n"); goto error; } freeaddrinfo(res); return handle; error: if (res) freeaddrinfo(res); network_cmd_free(handle); return NULL; } void network_cmd_free(network_cmd_t *handle) { if (handle->fd >= 0) close(handle->fd); free(handle); } struct cmd_map { const char *str; unsigned id; }; static const struct cmd_map map[] = { { "FAST_FORWARD", RARCH_FAST_FORWARD_KEY }, { "FAST_FORWARD_HOLD", RARCH_FAST_FORWARD_HOLD_KEY }, { "LOAD_STATE", RARCH_LOAD_STATE_KEY }, { "SAVE_STATE", RARCH_SAVE_STATE_KEY }, { "FULLSCREEN_TOGGLE", RARCH_FULLSCREEN_TOGGLE_KEY }, { "QUIT", RARCH_QUIT_KEY }, { "STATE_SLOT_PLUS", RARCH_STATE_SLOT_PLUS }, { "STATE_SLOT_MINUS", RARCH_STATE_SLOT_MINUS }, { "AUDIO_INPUT_RATE_PLUS", RARCH_AUDIO_INPUT_RATE_PLUS }, { "AUDIO_INPUT_RATE_MINUS", RARCH_AUDIO_INPUT_RATE_MINUS }, { "REWIND", RARCH_REWIND }, { "MOVIE_RECORD_TOGGLE", RARCH_MOVIE_RECORD_TOGGLE }, { "PAUSE_TOGGLE", RARCH_PAUSE_TOGGLE }, { "FRAMEADVANCE", RARCH_FRAMEADVANCE }, { "RESET", RARCH_RESET }, { "SHADER_NEXT", RARCH_SHADER_NEXT }, { "SHADER_PREV", RARCH_SHADER_PREV }, { "CHEAT_INDEX_PLUS", RARCH_CHEAT_INDEX_PLUS }, { "CHEAT_INDEX_MINUS", RARCH_CHEAT_INDEX_MINUS }, { "CHEAT_TOGGLE", RARCH_CHEAT_TOGGLE }, { "SCREENSHOT", RARCH_SCREENSHOT }, { "DSP_CONFIG", RARCH_DSP_CONFIG }, { "MUTE", RARCH_MUTE }, { "NETPLAY_FLIP", RARCH_NETPLAY_FLIP }, { "SLOWMOTION", RARCH_SLOWMOTION }, }; static void parse_sub_msg(network_cmd_t *handle, const char *tok) { for (unsigned i = 0; i < sizeof(map) / sizeof(map[0]); i++) { if (strcmp(tok, map[i].str) == 0) { handle->state[map[i].id] = true; return; } } RARCH_WARN("Unrecognized command \"%s\" received.\n", tok); } static void parse_msg(network_cmd_t *handle, char *buf) { const char *tok = strtok(buf, "\n"); while (tok) { parse_sub_msg(handle, tok); tok = strtok(NULL, "\n"); } } void network_cmd_set(network_cmd_t *handle, unsigned id) { if (id < RARCH_BIND_LIST_END) handle->state[id] = true; } bool network_cmd_get(network_cmd_t *handle, unsigned id) { return id < RARCH_BIND_LIST_END && handle->state[id]; } void network_cmd_pre_frame(network_cmd_t *handle) { memset(handle->state, 0, sizeof(handle->state)); fd_set fds; FD_ZERO(&fds); FD_SET(handle->fd, &fds); struct timeval tmp_tv = {0}; if (select(handle->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) return; if (!FD_ISSET(handle->fd, &fds)) return; for (;;) { char buf[1024]; ssize_t ret = recvfrom(handle->fd, buf, sizeof(buf) - 1, 0, NULL, NULL); if (ret <= 0) break; buf[ret] = '\0'; parse_msg(handle, buf); } }