summaryrefslogtreecommitdiff
path: root/io.c
diff options
context:
space:
mode:
authorMichael Pavone <pavone@retrodev.com>2014-12-30 19:11:34 -0800
committerMichael Pavone <pavone@retrodev.com>2014-12-30 19:11:34 -0800
commitfd85c8d7a74d44f169db4a51a600295042682ee8 (patch)
treed8052a699c4a576d5023aee7537ff0f7fee50dd5 /io.c
parentc61ca95add7b82aadef09aea8b4c48774e079069 (diff)
parent3c8d04a6b51184d9856cebd2e445791e451cb56a (diff)
Merge
Diffstat (limited to 'io.c')
-rw-r--r--io.c466
1 files changed, 412 insertions, 54 deletions
diff --git a/io.c b/io.c
index 4935321..3ec8672 100644
--- a/io.c
+++ b/io.c
@@ -3,15 +3,44 @@
This file is part of BlastEm.
BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
*/
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
#include "io.h"
#include "blastem.h"
#include "render.h"
+const char * device_type_names[] = {
+ "3-button gamepad",
+ "6-button gamepad",
+ "Mega Mouse",
+ "Menacer",
+ "Justifier",
+ "Sega multi-tap",
+ "EA 4-way Play cable A",
+ "EA 4-way Play cable B",
+ "Sega Parallel Transfer Board",
+ "Generic Device",
+ "None"
+};
+
enum {
BIND_NONE,
+ BIND_UI,
BIND_GAMEPAD1,
BIND_GAMEPAD2,
- BIND_UI
+ BIND_GAMEPAD3,
+ BIND_GAMEPAD4,
+ BIND_GAMEPAD5,
+ BIND_GAMEPAD6,
+ BIND_GAMEPAD7,
+ BIND_GAMEPAD8
};
typedef enum {
@@ -26,6 +55,7 @@ typedef enum {
} ui_action;
typedef struct {
+ io_port *port;
uint8_t bind_type;
uint8_t subtype_a;
uint8_t subtype_b;
@@ -117,7 +147,7 @@ void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t
void bind_gamepad(int keycode, int gamepadnum, int button)
{
- if (gamepadnum < 1 || gamepadnum > 2) {
+ if (gamepadnum < 1 || gamepadnum > 8) {
return;
}
uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -126,7 +156,7 @@ void bind_gamepad(int keycode, int gamepadnum, int button)
void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbutton)
{
- if (gamepadnum < 1 || gamepadnum > 2) {
+ if (gamepadnum < 1 || gamepadnum > 8) {
return;
}
uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -135,7 +165,7 @@ void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbut
void bind_dpad_gamepad(int joystick, int dpad, uint8_t direction, int gamepadnum, int button)
{
- if (gamepadnum < 1 || gamepadnum > 2) {
+ if (gamepadnum < 1 || gamepadnum > 8) {
return;
}
uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
@@ -159,17 +189,14 @@ void bind_dpad_ui(int joystick, int dpad, uint8_t direction, ui_action action, u
void handle_binding_down(keybinding * binding)
{
- switch(binding->bind_type)
+ if (binding->bind_type >= BIND_GAMEPAD1)
{
- case BIND_GAMEPAD1:
- case BIND_GAMEPAD2:
- if (binding->subtype_a <= GAMEPAD_EXTRA) {
- genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_a] |= binding->value;
+ if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
+ binding->port->input[binding->subtype_a] |= binding->value;
}
- if (binding->subtype_b <= GAMEPAD_EXTRA) {
- genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_b] |= binding->value;
+ if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
+ binding->port->input[binding->subtype_b] |= binding->value;
}
- break;
}
}
@@ -206,11 +233,11 @@ void handle_binding_up(keybinding * binding)
{
case BIND_GAMEPAD1:
case BIND_GAMEPAD2:
- if (binding->subtype_a <= GAMEPAD_EXTRA) {
- genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_a] &= ~binding->value;
+ if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
+ binding->port->input[binding->subtype_a] &= ~binding->value;
}
- if (binding->subtype_b <= GAMEPAD_EXTRA) {
- genesis->ports[binding->bind_type - BIND_GAMEPAD1].input[binding->subtype_b] &= ~binding->value;
+ if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
+ binding->port->input[binding->subtype_b] &= ~binding->value;
}
break;
case BIND_UI:
@@ -447,7 +474,169 @@ void process_speeds(tern_node * cur, char * prefix)
}
}
-void set_keybindings()
+void process_device(char * device_type, io_port * port)
+{
+ port->device_type = IO_NONE;
+ if (!device_type)
+ {
+ return;
+ }
+
+ const int gamepad_len = strlen("gamepad");
+ if (!memcmp(device_type, "gamepad", gamepad_len))
+ {
+ if (
+ (device_type[gamepad_len] != '3' && device_type[gamepad_len] != '6')
+ || device_type[gamepad_len+1] != '.' || device_type[gamepad_len+2] < '1'
+ || device_type[gamepad_len+2] > '8' || device_type[gamepad_len+3] != 0
+ )
+ {
+ fprintf(stderr, "%s is not a valid gamepad type\n", device_type);
+ } else if (device_type[gamepad_len] == '3')
+ {
+ port->device_type = IO_GAMEPAD3;
+ } else {
+ port->device_type = IO_GAMEPAD6;
+ }
+ port->device.pad.gamepad_num = device_type[gamepad_len+2] - '1';
+ } else if(!strcmp(device_type, "sega_parallel")) {
+ port->device_type = IO_SEGA_PARALLEL;
+ port->device.stream.data_fd = -1;
+ port->device.stream.listen_fd = -1;
+ } else if(!strcmp(device_type, "generic")) {
+ port->device_type = IO_GENERIC;
+ port->device.stream.data_fd = -1;
+ port->device.stream.listen_fd = -1;
+ }
+}
+
+char * io_name(int i)
+{
+ switch (i)
+ {
+ case 0:
+ return "1";
+ case 1:
+ return "2";
+ case 2:
+ return "EXT";
+ default:
+ return "invalid";
+ }
+}
+
+static char * sockfile_name;
+static void cleanup_sockfile()
+{
+ unlink(sockfile_name);
+}
+
+void setup_io_devices(tern_node * config, io_port * ports)
+{
+ tern_node *io_nodes = tern_find_prefix(config, "iodevices");
+ char * io_1 = tern_find_ptr(io_nodes, "1");
+ char * io_2 = tern_find_ptr(io_nodes, "2");
+ char * io_ext = tern_find_ptr(io_nodes, "ext");
+
+ process_device(io_1, ports);
+ process_device(io_2, ports+1);
+ process_device(io_ext, ports+2);
+
+ for (int i = 0; i < 3; i++)
+ {
+
+ if (ports[i].device_type == IO_SEGA_PARALLEL)
+ {
+ char *pipe_name = tern_find_ptr(config, "ioparallel_pipe");
+ if (!pipe_name)
+ {
+ fprintf(stderr, "IO port %s is configured to use the sega parallel board, but no paralell_pipe is set!\n", io_name(i));
+ ports[i].device_type = IO_NONE;
+ } else {
+ printf("IO port: %s connected to device '%s' with pipe name: %s\n", io_name(i), device_type_names[ports[i].device_type], pipe_name);
+ if (!strcmp("stdin", pipe_name))
+ {
+ ports[i].device.stream.data_fd = STDIN_FILENO;
+ } else {
+ if (mkfifo(pipe_name, 0666) && errno != EEXIST)
+ {
+ fprintf(stderr, "Failed to create fifo %s for Sega parallel board emulation: %d %s\n", pipe_name, errno, strerror(errno));
+ ports[i].device_type = IO_NONE;
+ } else {
+ ports[i].device.stream.data_fd = open(pipe_name, O_NONBLOCK | O_RDONLY);
+ if (ports[i].device.stream.data_fd == -1)
+ {
+ fprintf(stderr, "Failed to open fifo %s for Sega parallel board emulation: %d %s\n", pipe_name, errno, strerror(errno));
+ ports[i].device_type = IO_NONE;
+ }
+ }
+ }
+ }
+ } else if (ports[i].device_type == IO_GENERIC) {
+ char *sock_name = tern_find_ptr(config, "iosocket");
+ if (!sock_name)
+ {
+ fprintf(stderr, "IO port %s is configured to use generic IO, but no socket is set!\n", io_name(i));
+ ports[i].device_type = IO_NONE;
+ } else {
+ printf("IO port: %s connected to device '%s' with socket name: %s\n", io_name(i), device_type_names[ports[i].device_type], sock_name);
+ ports[i].device.stream.data_fd = -1;
+ ports[i].device.stream.listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ size_t pathlen = strlen(sock_name);
+ size_t addrlen = offsetof(struct sockaddr_un, sun_path) + pathlen + 1;
+ struct sockaddr_un *saddr = malloc(addrlen);
+ saddr->sun_family = AF_UNIX;
+ memcpy(saddr->sun_path, sock_name, pathlen+1);
+ if (bind(ports[i].device.stream.listen_fd, (struct sockaddr *)saddr, addrlen))
+ {
+ fprintf(stderr, "Failed to bind socket for IO Port %s to path %s: %d %s\n", io_name(i), sock_name, errno, strerror(errno));
+ goto cleanup_sock;
+ }
+ if (listen(ports[i].device.stream.listen_fd, 1))
+ {
+ fprintf(stderr, "Failed to listen on socket for IO Port %s: %d %s\n", io_name(i), errno, strerror(errno));
+ goto cleanup_sockfile;
+ }
+ sockfile_name = sock_name;
+ atexit(cleanup_sockfile);
+ continue;
+cleanup_sockfile:
+ unlink(sock_name);
+cleanup_sock:
+ close(ports[i].device.stream.listen_fd);
+ ports[i].device_type = IO_NONE;
+ }
+ } else if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6) {
+ printf("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num + 1, device_type_names[ports[i].device_type]);
+ } else {
+ printf("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]);
+ }
+ }
+}
+
+void map_bindings(io_port *ports, keybinding *bindings, int numbindings)
+{
+ for (int i = 0; i < numbindings; i++)
+ {
+ if (bindings[i].bind_type >= BIND_GAMEPAD1)
+ {
+ int num = bindings[i].bind_type - BIND_GAMEPAD1;
+ for (int j = 0; j < 3; j++)
+ {
+ if ((ports[j].device_type == IO_GAMEPAD3
+ || ports[j].device_type ==IO_GAMEPAD6)
+ && ports[j].device.pad.gamepad_num == num
+ )
+ {
+ bindings[i].port = ports + j;
+ break;
+ }
+ }
+ }
+ }
+}
+
+void set_keybindings(io_port *ports)
{
tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
special = tern_insert_int(special, "down", RENDERKEY_DOWN);
@@ -532,76 +721,245 @@ void set_keybindings()
speeds = malloc(sizeof(uint32_t));
speeds[0] = 100;
process_speeds(speed_nodes, NULL);
- for (int i = 0; i < num_speeds; i++) {
+ for (int i = 0; i < num_speeds; i++)
+ {
if (!speeds[i]) {
fprintf(stderr, "Speed index %d was not set to a valid percentage!", i);
speeds[i] = 100;
}
}
+ for (int bucket = 0; bucket < 256; bucket++)
+ {
+ if (bindings[bucket])
+ {
+ map_bindings(ports, bindings[bucket], 256);
+ }
+ }
+ for (int stick = 0; stick < MAX_JOYSTICKS; stick++)
+ {
+ if (joybindings[stick])
+ {
+ int numbuttons = render_joystick_num_buttons(stick);
+ map_bindings(ports, joybindings[stick], render_joystick_num_buttons(stick));
+ }
+ if (joydpads[stick])
+ {
+ map_bindings(ports, joydpads[stick]->bindings, 4);
+ }
+ }
}
#define TH 0x40
#define TH_TIMEOUT 8000
-void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction)
+void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction)
{
/*uint8_t control = pad->control | 0x80;
uint8_t th = control & pad->output;
if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
printf("adjust_cycles | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, current_cycle);
}*/
- if (current_cycle >= pad->timeout_cycle) {
- pad->th_counter = 0;
- } else {
- pad->timeout_cycle -= deduction;
+ if (port->device_type == IO_GAMEPAD6)
+ {
+ if (current_cycle >= port->device.pad.timeout_cycle)
+ {
+ port->device.pad.th_counter = 0;
+ } else {
+ port->device.pad.timeout_cycle -= deduction;
+ }
+ }
+}
+
+static void wait_for_connection(io_port * port)
+{
+ if (port->device.stream.data_fd == -1)
+ {
+ puts("Waiting for socket connection...");
+ port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL);
+ fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR);
}
}
-void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle)
+static void service_pipe(io_port * port)
{
- if (pad->control & TH) {
- //check if TH has changed
- if ((pad->output & TH) ^ (value & TH)) {
- if (current_cycle >= pad->timeout_cycle) {
- pad->th_counter = 0;
+ uint8_t value;
+ int numRead = read(port->device.stream.data_fd, &value, sizeof(value));
+ if (numRead > 0)
+ {
+ port->input[IO_TH0] = (value & 0xF) | 0x10;
+ port->input[IO_TH1] = (value >> 4) | 0x10;
+ } else if(numRead == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "Error reading pipe for IO port: %d %s\n", errno, strerror(errno));
+ }
+}
+
+static void service_socket(io_port *port)
+{
+ uint8_t buf[32];
+ uint8_t blocking = 0;
+ int numRead = 0;
+ while (numRead <= 0)
+ {
+ numRead = recv(port->device.stream.data_fd, buf, sizeof(buf), 0);
+ if (numRead > 0)
+ {
+ port->input[IO_TH0] = buf[numRead-1];
+ if (port->input[IO_STATE] == IO_READ_PENDING)
+ {
+ port->input[IO_STATE] = IO_READ;
+ if (blocking)
+ {
+ //pending read satisfied, back to non-blocking mode
+ fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR | O_NONBLOCK);
+ }
+ } else if (port->input[IO_STATE] == IO_WRITTEN) {
+ port->input[IO_STATE] = IO_READ;
}
- if (!(value & TH)) {
- pad->th_counter++;
+ } else if (numRead == 0) {
+ port->device.stream.data_fd = -1;
+ wait_for_connection(port);
+ } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "Error reading from socket for IO port: %d %s\n", errno, strerror(errno));
+ close(port->device.stream.data_fd);
+ wait_for_connection(port);
+ } else if (port->input[IO_STATE] == IO_READ_PENDING) {
+ //clear the nonblocking flag so the next read will block
+ if (!blocking)
+ {
+ fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+ blocking = 1;
+ }
+ } else {
+ //no new data, but that's ok
+ break;
+ }
+ }
+
+ if (port->input[IO_STATE] == IO_WRITE_PENDING)
+ {
+ uint8_t value = port->output & port->control;
+ int written = 0;
+ blocking = 0;
+ while (written <= 0)
+ {
+ send(port->device.stream.data_fd, &value, sizeof(value), 0);
+ if (written > 0)
+ {
+ port->input[IO_STATE] = IO_WRITTEN;
+ if (blocking)
+ {
+ //pending write satisfied, back to non-blocking mode
+ fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR | O_NONBLOCK);
+ }
+ } else if (written == 0) {
+ port->device.stream.data_fd = -1;
+ wait_for_connection(port);
+ } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "Error writing to socket for IO port: %d %s\n", errno, strerror(errno));
+ close(port->device.stream.data_fd);
+ wait_for_connection(port);
+ } else {
+ //clear the nonblocking flag so the next write will block
+ if (!blocking)
+ {
+ fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR);
+ blocking = 1;
+ }
}
- pad->timeout_cycle = current_cycle + TH_TIMEOUT;
}
}
- pad->output = value;
}
-uint8_t io_data_read(io_port * pad, uint32_t current_cycle)
+void io_data_write(io_port * port, uint8_t value, uint32_t current_cycle)
{
- uint8_t control = pad->control | 0x80;
- uint8_t th = control & pad->output;
+ switch (port->device_type)
+ {
+ case IO_GAMEPAD6:
+ if (port->control & TH) {
+ //check if TH has changed
+ if ((port->output & TH) ^ (value & TH)) {
+ if (current_cycle >= port->device.pad.timeout_cycle) {
+ port->device.pad.th_counter = 0;
+ }
+ if (!(value & TH)) {
+ port->device.pad.th_counter++;
+ }
+ port->device.pad.timeout_cycle = current_cycle + TH_TIMEOUT;
+ }
+ }
+ port->output = value;
+ break;
+ case IO_GENERIC:
+ wait_for_connection(port);
+ port->input[IO_STATE] = IO_WRITE_PENDING;
+ port->output = value;
+ service_socket(port);
+ break;
+ default:
+ port->output = value;
+ }
+
+}
+
+uint8_t io_data_read(io_port * port, uint32_t current_cycle)
+{
+ uint8_t control = port->control | 0x80;
+ uint8_t th = control & port->output & 0x40;
uint8_t input;
- if (current_cycle >= pad->timeout_cycle) {
- pad->th_counter = 0;
+ switch (port->device_type)
+ {
+ case IO_GAMEPAD3:
+ {
+ input = port->input[th ? GAMEPAD_TH1 : GAMEPAD_TH0];
+ break;
}
- /*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
- printf("io_data_read | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, context->current_cycle);
- }*/
- if (th) {
- if (pad->th_counter == 3) {
- input = pad->input[GAMEPAD_EXTRA];
- } else {
- input = pad->input[GAMEPAD_TH1];
+ case IO_GAMEPAD6:
+ {
+ if (current_cycle >= port->device.pad.timeout_cycle) {
+ port->device.pad.th_counter = 0;
}
- } else {
- if (pad->th_counter == 3) {
- input = pad->input[GAMEPAD_TH0] | 0xF;
- } else if(pad->th_counter == 4) {
- input = pad->input[GAMEPAD_TH0] & 0x30;
+ /*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
+ printf("io_data_read | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, port->input[GAMEPAD_TH0], port->input[GAMEPAD_TH1], port->th_counter,port->timeout_cycle, context->current_cycle);
+ }*/
+ if (th) {
+ if (port->device.pad.th_counter == 3) {
+ input = port->input[GAMEPAD_EXTRA];
+ } else {
+ input = port->input[GAMEPAD_TH1];
+ }
} else {
- input = pad->input[GAMEPAD_TH0] | 0xC;
+ if (port->device.pad.th_counter == 3) {
+ input = port->input[GAMEPAD_TH0] | 0xF;
+ } else if(port->device.pad.th_counter == 4) {
+ input = port->input[GAMEPAD_TH0] & 0x30;
+ } else {
+ input = port->input[GAMEPAD_TH0] | 0xC;
+ }
}
+ break;
+ }
+ case IO_SEGA_PARALLEL:
+ if (!th)
+ {
+ service_pipe(port);
+ }
+ input = ~port->input[th ? IO_TH1 : IO_TH0];
+ break;
+ case IO_GENERIC:
+ if (port->input[IO_TH0] & 0x80 && port->input[IO_STATE] == IO_WRITTEN)
+ {
+ //device requested a blocking read after writes
+ port->input[IO_STATE] = IO_READ_PENDING;
+ }
+ service_socket(port);
+ input = ~port->input[IO_TH0];
+ break;
+ default:
+ input = 0;
+ break;
}
- uint8_t value = ((~input) & (~control)) | (pad->output & control);
- /*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
+ uint8_t value = ((~input) & (~control)) | (port->output & control);
+ /*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
printf ("value: %X\n", value);
}*/
return value;